refactor: remove exp_data experimental data package
Detector data handling, energy calibration, peak fitting and instrumental corrections leave the project scope (theory-only spectrum shape calculation). Removes the package, its tests, the exp pipeline integration test, the calibration notebook, and the ExpSpectrum fixtures from the shared conftest.
This commit is contained in:
@@ -1,321 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Experimental Data Calibration Pipeline\n",
|
||||
"\n",
|
||||
"Interactive workflow for processing raw CAMAC MCA binary spectra:\n",
|
||||
"read -> subtract -> calibrate -> livetime -> validate -> export\n",
|
||||
"\n",
|
||||
"## Prerequisites\n",
|
||||
"- Raw binary spectrum files (A*.DAT format)\n",
|
||||
"- Am-241 calibration file (CAM1.DAT)\n",
|
||||
"- Pulser frequency and real time (from calibration run metadata)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Use non-interactive backend only when no display is available (e.g. jupyter execute)\n",
|
||||
"import matplotlib as mpl\n",
|
||||
"try:\n",
|
||||
" import matplotlib.pyplot as plt\n",
|
||||
" # Check if we're in a headless environment\n",
|
||||
" import os\n",
|
||||
" if not os.environ.get(\"DISPLAY\") and not os.environ.get(\"WAYLAND_DISPLAY\"):\n",
|
||||
" mpl.use(\"Agg\")\n",
|
||||
"except ImportError:\n",
|
||||
" mpl.use(\"Agg\")\n",
|
||||
" import matplotlib.pyplot as plt\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"from exp_data.pipeline import process_spectrum, process_run_sequence\n",
|
||||
"from exp_data.root_io import write_spectrum, read_spectrum\n",
|
||||
"from exp_data.livetime import LivetimeDetermination\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Configuration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Paths\n",
|
||||
"RAW_FILE = \"../data/raw/A1\"\n",
|
||||
"CALIBRATION_FILE = \"../data/raw/CAM1.DAT\"\n",
|
||||
"RUN_DIR = \"../data/raw/\"\n",
|
||||
"\n",
|
||||
"# Pulser parameters (from calibration run metadata)\n",
|
||||
"PULSER_FREQUENCY_HZ = 100.0\n",
|
||||
"REAL_TIME_SEC = 3600.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Raw Spectrum Visualization\n",
|
||||
"Visualize raw spectra before calibration to identify peaks and verify calibration source."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Visualize raw spectra before calibration\n",
|
||||
"# This helps identify what calibration source is actually present\n",
|
||||
"\n",
|
||||
"# Detect header size from file\n",
|
||||
"def detect_header_size(filepath):\n",
|
||||
" \"\"\"Detect MCA header size from the file size.\"\"\"\n",
|
||||
" # Simple heuristic: if file is exactly 32 + 4096*4 = 16412, header=32\n",
|
||||
" # If file is 128 + 4096*4 = 16512, header=128\n",
|
||||
" import os\n",
|
||||
" size = os.path.getsize(filepath)\n",
|
||||
" expected_32 = 32 + 4096 * 4\n",
|
||||
" expected_128 = 128 + 4096 * 4\n",
|
||||
" if size == expected_32:\n",
|
||||
" return 32\n",
|
||||
" elif size == expected_128:\n",
|
||||
" return 128\n",
|
||||
" else:\n",
|
||||
" return 32 # default\n",
|
||||
"\n",
|
||||
"# --- Raw run spectrum ---\n",
|
||||
"header_size = detect_header_size(RAW_FILE)\n",
|
||||
"with open(RAW_FILE, 'rb') as f:\n",
|
||||
" f.read(header_size)\n",
|
||||
" raw_counts = np.fromfile(f, dtype=np.int32, count=4096)\n",
|
||||
"\n",
|
||||
"fig, axes = plt.subplots(2, 2, figsize=(14, 8))\n",
|
||||
"\n",
|
||||
"ax = axes[0, 0]\n",
|
||||
"ax.plot(raw_counts, 'b-', linewidth=0.5)\n",
|
||||
"ax.set_title('Raw Run Spectrum (counts vs channel)')\n",
|
||||
"ax.set_xlabel('Channel')\n",
|
||||
"ax.set_ylabel('Counts')\n",
|
||||
"ax.set_yscale('log')\n",
|
||||
"ax.set_xlim(0, 600)\n",
|
||||
"\n",
|
||||
"ax = axes[0, 1]\n",
|
||||
"ax.plot(raw_counts, 'b-', linewidth=0.5)\n",
|
||||
"ax.set_title('Raw Run Spectrum (full range)')\n",
|
||||
"ax.set_xlabel('Channel')\n",
|
||||
"ax.set_ylabel('Counts')\n",
|
||||
"ax.set_yscale('log')\n",
|
||||
"\n",
|
||||
"# --- Calibration spectrum ---\n",
|
||||
"with open(CALIBRATION_FILE, 'rb') as f:\n",
|
||||
" f.read(128) # CAM files have 128-byte header\n",
|
||||
" cal_counts = np.fromfile(f, dtype=np.int32, count=4096)\n",
|
||||
"\n",
|
||||
"ax = axes[1, 0]\n",
|
||||
"ax.plot(cal_counts, 'r-', linewidth=0.5)\n",
|
||||
"ax.set_title('Calibration Spectrum (counts vs channel)')\n",
|
||||
"ax.set_xlabel('Channel')\n",
|
||||
"ax.set_ylabel('Counts')\n",
|
||||
"ax.set_xlim(0, 600)\n",
|
||||
"ax.set_yscale('log')\n",
|
||||
"\n",
|
||||
"ax = axes[1, 1]\n",
|
||||
"ax.plot(cal_counts, 'r-', linewidth=0.5)\n",
|
||||
"ax.set_title('Calibration Spectrum (full range)')\n",
|
||||
"ax.set_xlabel('Channel')\n",
|
||||
"ax.set_ylabel('Counts')\n",
|
||||
"ax.set_yscale('log')\n",
|
||||
"\n",
|
||||
"plt.tight_layout()\n",
|
||||
"plt.show()\n",
|
||||
"\n",
|
||||
"# Print some stats\n",
|
||||
"print(f'Raw run: {len(raw_counts)} channels, max={raw_counts.max()} at ch {np.argmax(raw_counts)}')\n",
|
||||
"print(f'Calibration: {len(cal_counts)} channels, max={cal_counts.max()} at ch {np.argmax(cal_counts)}')\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Process Single Spectrum"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result = process_spectrum(\n",
|
||||
" raw_path=RAW_FILE,\n",
|
||||
" calibration_path=CALIBRATION_FILE,\n",
|
||||
" pulser_frequency_hz=PULSER_FREQUENCY_HZ,\n",
|
||||
" real_time_sec=REAL_TIME_SEC,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(f\"Validation: {'PASSED' if result.validation_passed else 'FAILED'}\")\n",
|
||||
"print(f\"Calibration chi2/dof: {result.calibration.chi2_per_dof:.4f}\")\n",
|
||||
"for msg in result.validation_messages:\n",
|
||||
" print(f\" {msg}\")\n",
|
||||
"if result.livetime:\n",
|
||||
" print(f\"Live-time fraction: {result.livetime.live_time_fraction:.4f}\")\n",
|
||||
" print(f\"Pulser peak: {result.livetime.pulser_peak_center_keV:.1f} keV\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Plot Calibrated Spectrum"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.figure(figsize=(10, 6))\n",
|
||||
"plt.plot(result.spectrum.energies, result.spectrum.counts, 'b-', label='Counts')\n",
|
||||
"plt.xlabel('Energy (keV)')\n",
|
||||
"plt.ylabel('Counts')\n",
|
||||
"plt.yscale('log')\n",
|
||||
"plt.title('Calibrated Beta Spectrum')\n",
|
||||
"plt.legend()\n",
|
||||
"plt.tight_layout()\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Process Run Sequence"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"results = process_run_sequence(\n",
|
||||
" run_dir=RUN_DIR,\n",
|
||||
" calibration_path=CALIBRATION_FILE,\n",
|
||||
" prefix=\"A\",\n",
|
||||
" pulser_frequency_hz=PULSER_FREQUENCY_HZ,\n",
|
||||
" real_time_sec=REAL_TIME_SEC,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(f\"Processed {len(results)} runs\")\n",
|
||||
"for i, r in enumerate(results):\n",
|
||||
" print(f\" Run {i+1}: {'PASSED' if r.validation_passed else 'FAILED'} \"\n",
|
||||
" f\"(chi2/dof={r.calibration.chi2_per_dof:.4f})\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Export to ROOT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"write_spectrum(\"output.root\", result.spectrum)\n",
|
||||
"print(\"Exported to output.root\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Read Back from ROOT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"read_back = read_spectrum(\"output.root\")\n",
|
||||
"print(f\"Read {len(read_back.energies)} channels\")\n",
|
||||
"print(f\"Source: {read_back.source}\")\n",
|
||||
"print(f\"Run ID: {read_back.run_id}\")\n",
|
||||
"print(f\"Calibration: {read_back.metadata.get('calibration', {})}\")\n",
|
||||
"\n",
|
||||
"# Verify round-trip\n",
|
||||
"assert np.allclose(read_back.energies, result.spectrum.energies)\n",
|
||||
"assert np.allclose(read_back.counts, result.spectrum.counts)\n",
|
||||
"print(\"Round-trip verified\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Livetime Determination"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Direct livetime determination from calibration file\n",
|
||||
"livetime_result = LivetimeDetermination.from_file(\n",
|
||||
" filepath=CALIBRATION_FILE,\n",
|
||||
" pulser_frequency_hz=PULSER_FREQUENCY_HZ,\n",
|
||||
" real_time_sec=REAL_TIME_SEC,\n",
|
||||
" source=\"pulser\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(f\"Live-time fraction: {livetime_result.live_time_fraction:.4f}\")\n",
|
||||
"print(f\"Observed pulser rate: {livetime_result.observed_pulser_rate:.1f} Hz\")\n",
|
||||
"print(f\"Pulser peak: {livetime_result.pulser_peak_center_keV:.1f} keV \"\n",
|
||||
" f\"(sigma={livetime_result.pulser_peak_sigma_keV:.2f} keV, \"\n",
|
||||
" f\"chi2/dof={livetime_result.pulser_peak_chi2:.2f})\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.14.4"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
Reference in New Issue
Block a user