Files
beta_shape_pnpi/notebooks/exp_calibration.ipynb
T

326 lines
9.6 KiB
Plaintext

{
"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": [
"import matplotlib\n",
"# 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 by checking for binary pattern.\"\"\"\n",
" with open(filepath, 'rb') as f:\n",
" header = f.read(128)\n",
" # Check if first 32 bytes look like a header (small ints)\n",
" # and bytes 32-128 also look like header\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",
" # Try to detect by checking if byte 32 looks like an int\n",
" with open(filepath, 'rb') as f:\n",
" f.seek(32)\n",
" test = f.read(4)\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_xlim(0, min(200, len(raw_counts)))\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",
"\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, min(200, len(cal_counts)))\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",
"\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.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
}