From 13b2eb32d9e31e9b2f2cb4b5e3c65278d0ef0adc Mon Sep 17 00:00:00 2001 From: x10sa Date: Wed, 28 Oct 2026 20:18:25 +0100 Subject: [PATCH] Integrate latest PXII production calibrations and fixes --- pxii_bec/device_configs/pxii-devices.yaml | 10 +- pxii_bec/macros/daily.py | 37 ++- pxii_bec/macros/katscripts.py | 361 ++++++++++------------ pxii_bec/macros/pxii_parameters.py | 7 +- pxii_bec/macros/pxii_setup.py | 2 +- pxii_bec/macros/weekly.py | 44 ++- 6 files changed, 236 insertions(+), 225 deletions(-) diff --git a/pxii_bec/device_configs/pxii-devices.yaml b/pxii_bec/device_configs/pxii-devices.yaml index 99e95ee..4342d08 100644 --- a/pxii_bec/device_configs/pxii-devices.yaml +++ b/pxii_bec/device_configs/pxii-devices.yaml @@ -65,15 +65,15 @@ aerotech: mount: 0 work: 0 x: - in: 0.0599968 + in: 0.06 out: -10 safe: -50 y: mount: -0.0 - work: -0.03699256 + work: -0.035 z: mount: 0 - work: -0.00900414 + work: -0.009 bcu_bpm1: description: BCU BPM Channel 1 deviceClass: ophyd.EpicsSignalRO @@ -302,7 +302,7 @@ bs_x: onFailure: buffer readoutPriority: baseline userParameter: - in: 0.44000227 + in: 0.34999599 bs_y: description: Beamstop Y deviceClass: ophyd_devices.EpicsMotor @@ -314,7 +314,7 @@ bs_y: onFailure: buffer readoutPriority: baseline userParameter: - in: -3.3000017 + in: -3.5 bs_z: description: Beamstop Z deviceClass: ophyd_devices.EpicsMotor diff --git a/pxii_bec/macros/daily.py b/pxii_bec/macros/daily.py index 3198779..40820c7 100644 --- a/pxii_bec/macros/daily.py +++ b/pxii_bec/macros/daily.py @@ -1182,7 +1182,7 @@ def daily_beamstop(): result["bs_x_pos"] = x_result["centre"] result["bs_y_pos"] = y_result["centre"] - result["intensity after alignment"] = read_i2() + result['intensity after alignment'] = read_i2() # message = ( # "Intensity after Y scan: " @@ -1324,8 +1324,8 @@ def daily_collimator(): get_value(dev.i1) - result["intensity_coll_out"] = get_value(dev.i1) - print(f"Starting intensity is {result["intensity_coll_out"]:.3e}") + result['intensity_coll_out'] = get_value(dev.i1) + print(f"Starting intensity is {result['intensity_coll_out']:.3e}") bl.d["coll_y"].move("in") @@ -1448,7 +1448,7 @@ def read_bcu_beampos_safe(): """Read calibrated BCU beam position without failing beam alignment.""" try: result = get_bcu_beampos() - return result["bcu_x_um"], result["bcu_y_um"], None + return result['bcu_x_um'], result['bcu_y_um'], None except Exception as exc: warning = f"BCU BPM position unavailable: {exc}" @@ -1930,11 +1930,34 @@ def log_daily_setup_to_csv(results): get_value(dev.hfm_pitch), ".5f" ), } - + fieldnames = list(row.keys()) file_exists = filename.exists() + # Check whether the existing CSV has the current schema + if file_exists: + with open(filename, "r", newline="", encoding="utf-8") as f: + reader = csv.reader(f) + existing_header = next(reader, None) + + if existing_header != fieldnames: + # Archive the old CSV rather than writing incompatible rows to it + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + archive_filename = filename.with_name( + f"{filename.stem}_archive_{timestamp}{filename.suffix}" + ) + + filename.rename(archive_filename) + + print( + "⚠ Daily beamline CSV schema has changed.\n" + f" Previous history archived as: {archive_filename.name}\n" + f" Starting new history file: {filename.name}" + ) + + file_exists = False + with open(filename, "a", newline="", encoding="utf-8") as f: - writer = csv.DictWriter(f, fieldnames=row.keys()) + writer = csv.DictWriter(f, fieldnames=fieldnames) if not file_exists: writer.writeheader() @@ -2478,4 +2501,4 @@ def create_daily_html_report(results, total_duration=None, messages=None): """ - return html \ No newline at end of file + return html diff --git a/pxii_bec/macros/katscripts.py b/pxii_bec/macros/katscripts.py index 5670025..57751ee 100755 --- a/pxii_bec/macros/katscripts.py +++ b/pxii_bec/macros/katscripts.py @@ -190,7 +190,6 @@ def energy_checks(start_energy=8000, end_energy=20000): f.close() - # def scan_gap(gomax = False, close = True, fine = False): # """Scan the gap and monitor on lu_bpmsum or bcu_bpmsum""" # close_fe_slits(close) @@ -352,14 +351,13 @@ def beam_centre_from_ss(): def generate_dcm_lut( - start_energy=6000, - end_energy=30000, - step=1000, - scan_id_gap=False, - scint=True, - prefix = "dcm_calibration", + start_energy=6000, + end_energy=30000, + step=1000, + scan_id_gap=False, + scint=True, + prefix="dcm_calibration", ): - """Generate a DCM calibration lookup table. Parameters @@ -393,13 +391,19 @@ def generate_dcm_lut( if scan_id_gap: with open(filename, "w", encoding="utf-8") as f: if scint: - f.write(f"energy,harmonic,gap,pitch,lu_Xnorm,lu_Ynorm,ss_Xnorm,ss_Ynorm,bcu_x,bcu_y,samcam_x,samcam_y,zoom={zoom}\n") + f.write( + f"energy,harmonic,gap,pitch,lu_Xnorm,lu_Ynorm,ss_Xnorm,ss_Ynorm,bcu_x,bcu_y,samcam_x,samcam_y,zoom={zoom}\n" + ) else: - f.write("energy,harmonic,gap,pitch,lu_Xnorm,lu_Ynorm,ss_Xnorm,ss_Ynorm,bcu_x,bcu_y,samcam_x,samcam_y,zoom={zoom}\n") + f.write( + "energy,harmonic,gap,pitch,lu_Xnorm,lu_Ynorm,ss_Xnorm,ss_Ynorm,bcu_x,bcu_y,samcam_x,samcam_y,zoom={zoom}\n" + ) else: with open(filename, "w", encoding="utf-8") as f: if scint: - f.write(f"energy,pitch,lu_Xnorm,lu_Ynorm,ss_Xnorm,ss_Ynorm,bcu_x,bcu_y,samcam_x,samcam_y,zoom={zoom}\n") + f.write( + f"energy,pitch,lu_Xnorm,lu_Ynorm,ss_Xnorm,ss_Ynorm,bcu_x,bcu_y,samcam_x,samcam_y,zoom={zoom}\n" + ) else: f.write("energy,pitch,lu_Xnorm,lu_Ynorm,ss_Xnorm,ss_Ynorm,bcu_x,bcu_y\n") @@ -500,15 +504,14 @@ def generate_dcm_lut( # ------------------------------------------------------------ res = get_bcu_beampos() - bcu_x = res['bcu_x_um'] - bcu_y = res['bcu_y_um'] + bcu_x = res["bcu_x_um"] + bcu_y = res["bcu_y_um"] ss_res = compute_norm_ss() - ss_Xnorm = ss_res['ss_Xnorm'] - ss_Ynorm = ss_res['ss_Ynorm'] + ss_Xnorm = ss_res["ss_Xnorm"] + ss_Ynorm = ss_res["ss_Ynorm"] lu_res = compute_norm_lu() - lu_Xnorm = lu_res['lu_Xnorm'] - lu_Ynorm = lu_res['lu_Ynorm'] - + lu_Xnorm = lu_res["lu_Xnorm"] + lu_Ynorm = lu_res["lu_Ynorm"] pitch = get_value(dev.dcm_pitch) @@ -547,7 +550,7 @@ def generate_dcm_lut( f.write( f"{energy:.0f},{h},{gap_value:.5g},{pitch:.5g}," f"{ss_Xnorm:.5g},{ss_Ynorm:.5g},{bcu_x:.2f},{bcu_y:.2f}\n" - ) + ) else: @@ -555,12 +558,13 @@ def generate_dcm_lut( f.write( f"{energy:.0f},{pitch:.5g},{lu_Xnorm:.5g},{lu_Ynorm:.5g}," f"{ss_Xnorm:.5g},{ss_Ynorm:.5g},{bcu_x:.2f},{bcu_y:.2f},{x:.4g},{y:.4g}\n" - ) + ) else: f.write( - f"{energy:.0f}," f"{pitch:.5g},{lu_Xnorm:.5g},{lu_Ynorm:.5g}," + f"{energy:.0f}," + f"{pitch:.5g},{lu_Xnorm:.5g},{lu_Ynorm:.5g}," f"{ss_Xnorm:.5g},{ss_Ynorm:.5g},{bcu_x:.2f},{bcu_y:.2f}\n" - ) + ) finally: print("\nReturning beamline to safe conditions...") @@ -600,45 +604,24 @@ def generate_dcm_lut( print(f"\nCalibration data written to:\n" f"{filename}") - return { - "filename": filename, - "failure_filename": failure_filename, - "failures": failures, - } + return {"filename": filename, "failure_filename": failure_filename, "failures": failures} -def calibrate_dcm_pitch( - start_energy=6000, - end_energy=30000, - step=2000, - fit_order=3, -): + +def calibrate_dcm_pitch(start_energy=6000, end_energy=30000, step=2000, fit_order=3): """Quickly recalibrate the DCM pitch lookup table.""" print("\nRunning DCM pitch calibration") - print( - f"{start_energy:.0f}–{end_energy:.0f} eV " - f"in {step:.0f} eV steps" - ) + print(f"{start_energy:.0f}–{end_energy:.0f} eV " f"in {step:.0f} eV steps") failures = generate_dcm_lut( - start_energy=start_energy, - end_energy=end_energy, - step=step, - scan_id_gap=False, - scint=False, + start_energy=start_energy, end_energy=end_energy, step=step, scan_id_gap=False, scint=False ) print("\nAnalysing DCM pitch calibration...") - fit = pitch_analysis( - filedate=filenow(date_only=True), - fit_order=fit_order, - ) + fit = pitch_analysis(filedate=filenow(date_only=True), fit_order=fit_order) - return { - "fit": fit, - "failures": failures, - } + return {"fit": fit, "failures": failures} def write_lut(): @@ -657,8 +640,6 @@ def write_lut(): f.write(f"{energy:.0f},{gap:.5g},{pitch:.5g},{roll:.4g},{perp:.5g},{x:.4g},{y:.4g}\n") - - def write_beamsize(): """Write beam size and flux to file""" filename = "luts/beamsizes.csv" @@ -677,8 +658,6 @@ def write_beamsize(): f.write(f"{lens_config},{sx:.4g},{sy:.4g},{i1_reading:.4g}\n") - - def cal_samcam_fixed_x(): # lat = dev.hfm_x.read()["hfm_x"]["value"] # pitch = dev.hfm_pitch.read()["hfm_pitch"]["value"] @@ -1072,8 +1051,6 @@ from epics import caget, caput import time - - # def get_new_beamsizes(mirror): # filename = f"luts/{mirror}_size.csv" # if mirror == "hfm": @@ -1300,7 +1277,6 @@ def mirror_pitch(mirror="vfm"): results = np.array(results) - def vfm_yaw_check(): slit_size = 0.02 slit_point1 = -1.2 @@ -1370,6 +1346,7 @@ def vfm_yaw_check(): plt.tight_layout() plt.show() + # @dataclass # class SlitAxis: # name: str @@ -1495,11 +1472,6 @@ def vfm_yaw_check(): # print(f"=== Finished {slit.name} ===") - - - - - # def define_slits(): # slits = [ # Slit( @@ -1728,9 +1700,9 @@ import matplotlib.pyplot as plt from epics import caget -def scan_scin_focus(planner, type="fine"): +def scan_scin_focus(type="fine"): dev.bcu_shutter.put(0) - planner.move_to(BeamlineState.BEAM_VISUALISATION) + # bl.planner.move_to(BeamlineState.BEAM_VISUALISATION) dev.bcu_shutter.put(1) z0 = dev.diag_z.position @@ -1746,11 +1718,12 @@ def scan_scin_focus(planner, type="fine"): umv(dev.scam_zoom, 1000) dev.transm.put(0.1) + time.sleep(3) auto_exposure("samcam", target=200, max_iter=20) time.sleep(0.2) - go_to_peak(dev.diag_z, dev.scam, min, max, steps, relative=True, gomax=True, confirm=False) + go_to_peak(dev.diag_z, dev.scam, min, max, steps, relative=False, gomax=True, confirm=False) dev.bcu_shutter.put(0) @@ -1829,7 +1802,6 @@ def centre_bcu_bpm_y(): return centre - def centre_ss_bpm_x(): positions = [] errors = [] @@ -1866,7 +1838,7 @@ def centre_ss_bpm_y(): ch1 = get_value(dev.ss_bpm1) ch2 = get_value(dev.ss_bpm2) ch3 = get_value(dev.ss_bpm3) - ch4 = get_value(dev.ss_bpm4) + ch4 = get_value(dev.ss_bpm4) _, y_error = bpm_errors(ch1, ch2, ch3, ch4) print(f"BPM_Y = {float(y)}, Yerror = {y_error}") @@ -1879,6 +1851,7 @@ def centre_ss_bpm_y(): umv(dev.ss_bpm_y, centre) return centre + def centre_lu_bpm_x(): positions = [] errors = [] @@ -1915,7 +1888,7 @@ def centre_lu_bpm_y(): ch1 = get_value(dev.lu_bpm1) ch2 = get_value(dev.lu_bpm2) ch3 = get_value(dev.lu_bpm3) - ch4 = get_value(dev.lu_bpm4) + ch4 = get_value(dev.lu_bpm4) _, y_error = bpm_errors(ch1, ch2, ch3, ch4) print(f"BPM_Y = {float(y)}, Yerror = {y_error}") @@ -1928,6 +1901,8 @@ def centre_lu_bpm_y(): umv(dev.lu_bpm_y, centre) return centre + + def read_i1(): return dev.i1.read()["i1"]["value"] @@ -2159,10 +2134,11 @@ def measure_omega_runout( # def dcm_pitch_calibration(start_energy=6000, end_energy=31000, step=3000): def dcm_pitch_calibration(): """Quick calibration of DCM Pitch against energy""" - energies = [5500, 8000, 10000, 12000, 14000, 16000, 18000, - 20000, 22000, 24000, 27000, 30000] + # energies = [5500, 8000, 10000, 12000, 14000, 16000, 18000, + # 20000, 22000, 24000, 27000, 30000] + energies = [16000, 18000, 20000, 22000, 24000, 27000, 30000] filename = bl.env.lut_dir / f"dcm_pitch_{filenow(date_only=True)}.csv" - with open(filename, "w", encoding="utf-8") as f: + with open(filename, "a", encoding="utf-8") as f: f.write("energy,bragg,pitch\n") # for energy in range(start_energy, end_energy, step): try: @@ -2199,9 +2175,7 @@ def dcm_pitch_calibration(): # import matplotlib.pyplot as plt -def analyse_roll_calibration( - energies=(6000, 9000, 12000, 15000, 18000, 21000), -): +def analyse_roll_calibration(energies=(6000, 9000, 12000, 15000, 18000, 21000)): """ Analyse roll calibration scans at several energies. @@ -2237,20 +2211,13 @@ def analyse_roll_calibration( fits[energy] = (m, b) # Plot measured points - ax.scatter( - roll, - y, - label=f"{energy / 1000:g} keV", - ) + ax.scatter(roll, y, label=f"{energy / 1000:g} keV") # Plot fitted line xx = np.linspace(roll.min(), roll.max(), 100) ax.plot(xx, m * xx + b, "--") - print( - f"{energy / 1000:4.1f} keV: " - f"Y = {m:8.3f} × roll + {b:8.3f}" - ) + print(f"{energy / 1000:4.1f} keV: " f"Y = {m:8.3f} × roll + {b:8.3f}") # ------------------------------------------------------------ # Find roll where fitted lines have minimum spread @@ -2265,12 +2232,7 @@ def analyse_roll_calibration( best_roll = -np.sum(dm * db) / np.sum(dm**2) # Predicted Y position at best roll - predicted_y = np.array( - [ - m * best_roll + b - for m, b in fits.values() - ] - ) + predicted_y = np.array([m * best_roll + b for m, b in fits.values()]) mean_y = predicted_y.mean() std_y = predicted_y.std() @@ -2285,33 +2247,17 @@ def analyse_roll_calibration( print("\nPredicted Y at best roll:") for energy, y in zip(energies, predicted_y): - print( - f" {energy / 1000:4.1f} keV : " - f"{y:8.2f} px" - ) + print(f" {energy / 1000:4.1f} keV : " f"{y:8.2f} px") - print( - f"\nMean Y = {mean_y:.2f} px" - f"\nSpread = {std_y:.2f} px" - ) + print(f"\nMean Y = {mean_y:.2f} px" f"\nSpread = {std_y:.2f} px") # ------------------------------------------------------------ # Plot optimum # ------------------------------------------------------------ - ax.axvline( - best_roll, - color="black", - linestyle=":", - label=f"Best roll = {best_roll:.4f}", - ) + ax.axvline(best_roll, color="black", linestyle=":", label=f"Best roll = {best_roll:.4f}") - ax.axhline( - mean_y, - color="grey", - linestyle=":", - alpha=0.5, - ) + ax.axhline(mean_y, color="grey", linestyle=":", alpha=0.5) ax.set_xlabel("Roll") ax.set_ylabel("Scintillator Y position (px)") @@ -2329,6 +2275,7 @@ def analyse_roll_calibration( "predicted_y": dict(zip(energies, predicted_y)), } + def analyse_roll(low=6000, high=25000, low_file=None, high_file=None): """Find the DCM fine-roll value where low/high-energy beam positions intersect.""" @@ -2417,36 +2364,79 @@ def scan_roll(energy, start, stop, steps=9, settle=0.2, filename=None, overwrite results = read_scintXY() if not results["ok"]: raise RuntimeError( - f"Beam position measurement failed at " - f"{energy / 1000:.1f} keV, roll={roll:.3f}: " - f"{results['message']}" + f"Beam position measurement failed at " + f"{energy / 1000:.1f} keV, roll={roll:.3f}: " + f"{results['message']}" + ) + + print( + f"{energy / 1000:.1f} keV | " + f"roll = {roll:.3f} | " + f"Y = {results['Y_pixels']:.2f}" ) - print(f"{energy / 1000:.1f} keV | " f"roll = {roll:.3f} | " f"Y = {results["Y_pixels"]:.2f}") - - f.write(f"{roll:.6f},{results["Y_pixels"]}\n") + f.write(f"{roll:.6f},{results['Y_pixels']}\n") finally: finish_scint_measurement() return filename -def measure_rolls(): + +def scan_roll_bcu(energy, start, stop, steps=9, settle=0.2, filename=None, overwrite=False): + """Measure scintillator Y position as a function of DCM fine roll.""" + + scanpoints = np.linspace(start, stop, steps) + print(f"Scanning roll from {start} to {stop}") + + if filename is None: + filename = bl.env.lut_dir / f"roll_{filenow(date_only=True)}_{energy:.0f}.csv" + + if filename.exists() and not overwrite: + print(f"{filename.name} already exists.") + print("Use overwrite=True if you want to repeat this energy.") + return filename + + bl_energy(energy) + + with open(filename, "w", encoding="utf-8") as f: + f.write("Roll,Y pos\n") + + for roll in scanpoints: + umv(dev.dcm_froll, roll) + time.sleep(settle) + + result = get_bcu_beampos() + + print( + f"{energy / 1000:.1f} keV | " + f"roll = {roll:.3f} | " + f"Y = {result['bcu_y_um']:.2f}" + ) + + f.write(f"{roll:.6f},{result['bcu_y_um']}\n") + + return filename + + +def measure_rolls(monitor="scint"): energies = [6000, 9000, 12000, 15000, 18000, 21000] - try: - for energy in energies: - scan_roll(energy, start=2, stop=5, overwrite=True) - finally: - print("done") - # bl_energy(12400) + if monitor == "scint": + try: + for energy in energies: + scan_roll(energy, start=2.0, stop=4, overwrite=True) + finally: + print("done") + # bl_energy(12400) + else: + try: + for energy in energies: + scan_roll_bcu(energy, start=2.8, stop=4, overwrite=True) + finally: + print("done") + # bl_energy(12400) -def find_best_roll( - low=6000, - high=25000, - low_range=(2.0, 4.0), - high_range=(1.0, 8.0), - steps=9, -): +def find_best_roll(low=6000, high=25000, low_range=(2.0, 4.0), high_range=(1.0, 8.0), steps=9): """Collect missing roll scans and calculate optimum roll.""" filename_prefix = filenow(date_only=True) @@ -2454,17 +2444,12 @@ def find_best_roll( high_file = bl.env.lut_dir / f"roll_{filename_prefix}_{high:.0f}.csv" # Define the two scans - scans = [ - (low, low_range, low_file), - (high, high_range, high_file), - ] + scans = [(low, low_range, low_file), (high, high_range, high_file)] # Start with whichever energy is closest to the current energy current_energy = get_current_energy() - scans.sort( - key=lambda scan: abs(current_energy - scan[0]) - ) + scans.sort(key=lambda scan: abs(current_energy - scan[0])) print(f"Current energy: {current_energy:.0f} eV") print(f"Scanning {scans[0][0]:.0f} eV first") @@ -2472,12 +2457,7 @@ def find_best_roll( # Run scans in the chosen order for energy, scan_range, filename in scans: if not filename.exists(): - scan_roll( - energy, - *scan_range, - steps=steps, - filename=filename, - ) + scan_roll(energy, *scan_range, steps=steps, filename=filename) else: print(f"Using existing {filename.name}") @@ -2577,6 +2557,7 @@ def test_vfm3(dmove, offset=0.002): f"Position error = {(final-target)*1000:+.3f} µm" ) + def beamstop_check_energies(): bs_in = 0.904 @@ -2588,9 +2569,9 @@ def beamstop_check_energies(): filename = bl.env.lut_dir / f"bs_checks_{filenow(date_only=True)}.csv" with open(filename, "w", encoding="utf-8") as f: f.write("Energy,I2_in,I2_out\n") - + try: - for energy in range (start_energy, end_energy, -2000): + for energy in range(start_energy, end_energy, -2000): print(f"Moving to energy {energy}") bl_energy(energy) dev.transm.put(0.1) @@ -2614,6 +2595,7 @@ def beamstop_check_energies(): umv(dev.bs_y, bs_in) bl_energy(12400) + def check_perp(): Xref = -0.0059957 Yref = -0.000993 @@ -2622,9 +2604,9 @@ def check_perp(): start_perp = get_value(dev.dcm_perp) - perps = [start_perp, start_perp-0.1, start_perp, start_perp+0.1, start_perp] + perps = [start_perp, start_perp - 0.1, start_perp, start_perp + 0.1, start_perp] filename = bl.env.lut_dir / f"perp_check.csv" - with open(filename, 'w', encoding="utf-8") as f: + with open(filename, "w", encoding="utf-8") as f: f.write("Delta perp, Pitch, Xnorm, Delta X") for perp in perps: @@ -2633,9 +2615,9 @@ def check_perp(): mono_pitch_scan() pitch = get_value(dev.dcm_pitch) Xnorm, Ynorm = compute_norm_ss() - Xdelta = -0.247*(Xnorm-Xref) - Ydelta = 0.0420*(Ynorm-Yref) - with open(filename, 'a', encoding="utf-8") as f: + Xdelta = -0.247 * (Xnorm - Xref) + Ydelta = 0.0420 * (Ynorm - Yref) + with open(filename, "a", encoding="utf-8") as f: f.write(f"{delta_perp},{pitch:.5g},{Xnorm:.5g},{Xdelta:.5g}") print( f"Delta perp: {delta_perp}, " @@ -2644,6 +2626,7 @@ def check_perp(): f"delta X: {Xdelta:.5g}" ) + def calibrate_dcm_perp(): """ Determine the DCM perp position required to maintain a fixed beam @@ -2679,16 +2662,7 @@ def calibrate_dcm_perp(): # beam displacement (mm) / perp displacement (mm) beam_per_perp = 1.46 - energies = [ - 6000, - 8000, - 10000, - 12000, - 16000, - 20000, - 24000, - 30000, - ] + energies = [6000, 8000, 10000, 12000, 16000, 20000, 24000, 30000] filename = bl.env.lut_dir / "dcm_perp_calibration.csv" @@ -2800,6 +2774,8 @@ def calibrate_dcm_perp(): print(filename) finally: bl_energy(12400) + + from datetime import datetime @@ -2833,10 +2809,7 @@ def scan_dcm_exit_vs_energy(): 30000, ] - filename = ( - bl.env.lut_dir - / f"dcm_exit_scan_{datetime.now():%Y%m%d_%H%M%S}.csv" - ) + filename = bl.env.lut_dir / f"dcm_exit_scan_{datetime.now():%Y%m%d_%H%M%S}.csv" # -------------------------------------------------------------- # Create CSV @@ -2869,7 +2842,6 @@ def scan_dcm_exit_vs_energy(): # Move to energy using current calibration bl_energy(energy) - # Read actual DCM positions after optimisation perp = get_value(dev.dcm_perp) pitch = get_value(dev.dcm_pitch) @@ -2923,13 +2895,16 @@ def scan_dcm_exit_vs_energy(): finally: bl_energy(12400) + def perp_again(): energies = [8000, 10000, 12400, 15000, 18000, 20000, 22000, 25000] perp_offsets = [-0.05, -0.025, 0.0, 0.025, 0.05, 0.0] filename = bl.env.lut_dir / f"perp_{filenow()}.csv" with open(filename, "w", encoding="utf-8") as f: - f.write("energy,dcm_pitch,dcm_perp,lu_bpmsum,lu_Xnorm,lu_Ynorm,bcu_bpmsum,bcu_x,bcu_y,samcam_x,samcam_y\n") + f.write( + "energy,dcm_pitch,dcm_perp,lu_bpmsum,lu_Xnorm,lu_Ynorm,bcu_bpmsum,bcu_x,bcu_y,samcam_x,samcam_y\n" + ) try: for energy in energies: bl_energy(energy) @@ -2941,13 +2916,12 @@ def perp_again(): for offset in perp_offsets: perp = perp_value + offset - umv(dev.dcm_perp,perp) + umv(dev.dcm_perp, perp) mono_pitch_scan() dcm_pitch = get_value(dev.dcm_pitch) dcm_perp = get_value(dev.dcm_perp) - lu_bpmsum = get_value(dev.lu_bpmsum) lu = compute_norm_lu() lu_Xnorm = lu["lu_Xnorm"] @@ -2961,7 +2935,7 @@ def perp_again(): scint = read_scintXY(image=False) samcam_x = scint["X_diff_microns"] samcam_y = scint["Y_diff_microns"] - + with open(filename, "a", encoding="utf-8") as f: f.write( f"{energy:.0f},{dcm_pitch:.5f},{dcm_perp:.5f},{lu_bpmsum:.1f},{lu_Xnorm:.3f},{lu_Ynorm:.3f}," @@ -2971,6 +2945,7 @@ def perp_again(): finish_scint_measurement() bl_energy(12400) + def perp_ss_xi(): # energies = [8000, 10000, 12400, 15000, 18000, 20000, 22000, 25000] energies = [25000] @@ -2978,11 +2953,11 @@ def perp_ss_xi(): filename = bl.env.lut_dir / f"perp_xi_{filenow()}.csv" with open(filename, "w", encoding="utf-8") as f: - f.write("energy,dcm_pitch,dcm_perp,lu_bpms,lu_Xnorm,lu_Ynorm,xi_cam_x,xi_cam_y\n") + f.write("energy,dcm_pitch,dcm_perp,lu_bpms,lu_Xnorm,lu_Ynorm,xi_cam_x,xi_cam_y\n") try: for energy in energies: bl_energy(energy) - + prepare_ss_xi_measurement() perp_value = get_value(dev.dcm_perp) @@ -2990,23 +2965,21 @@ def perp_ss_xi(): for offset in perp_offsets: perp = perp_value + offset - umv(dev.dcm_perp,perp) + umv(dev.dcm_perp, perp) mono_pitch_scan() dcm_pitch = get_value(dev.dcm_pitch) dcm_perp = get_value(dev.dcm_perp) - lu_bpmsum = get_value(dev.lu_bpmsum) lu = compute_norm_lu() lu_Xnorm = lu["lu_Xnorm"] lu_Ynorm = lu["lu_Ynorm"] - xi_read = read_ss_xicamXY() xicam_x = xi_read["X_pixels"] xicam_y = xi_read["Y_pixels"] - + with open(filename, "a", encoding="utf-8") as f: f.write( f"{energy:.0f},{dcm_pitch:.5f},{dcm_perp:.5f},{lu_bpmsum:.1f},{lu_Xnorm:.3f},{lu_Ynorm:.3f}," @@ -3016,6 +2989,7 @@ def perp_ss_xi(): finish_ss_xi_measurement() # bl_energy(12400) + def gap_beam(): # energies = [8000, 10000, 12400, 15000, 18000, 20000, 22000, 25000] energies = [22000] @@ -3023,13 +2997,13 @@ def gap_beam(): filename = bl.env.lut_dir / f"gap_xi_{filenow()}.csv" filename2 = bl.env.lut_dir / f"gap_scint_{filenow()}.csv" with open(filename, "w", encoding="utf-8") as f: - f.write("energy,gap,lu_bpmsum,lu_Xnorm,lu_Ynorm,xi_cam_x,xi_cam_y\n") + f.write("energy,gap,lu_bpmsum,lu_Xnorm,lu_Ynorm,xi_cam_x,xi_cam_y\n") with open(filename2, "w", encoding="utf-8") as f: - f.write("energy,gap,lu_bpmsum,lu_Xnorm,lu_Ynorm,samcam_x,samcam_y\n") + f.write("energy,gap,lu_bpmsum,lu_Xnorm,lu_Ynorm,samcam_x,samcam_y\n") try: # for energy in energies: # bl_energy(energy) - + # prepare_ss_xi_measurement() # scanpoints=np.linspace(4.535,4.555,20) @@ -3047,7 +3021,7 @@ def gap_beam(): # xi_read = read_ss_xicamXY() # xicam_x = xi_read["X_pixels"] # xicam_y = xi_read["Y_pixels"] - + # with open(filename, "a", encoding="utf-8") as f: # f.write( # f"{energy:.0f},{gap:.4f},{lu_bpmsum:.1f},{lu_Xnorm:.3f},{lu_Ynorm:.3f}," @@ -3059,7 +3033,7 @@ def gap_beam(): finish_ss_xi_measurement() prepare_scint_measurement() - scanpoints=np.linspace(4.535,4.555,20) + scanpoints = np.linspace(4.535, 4.555, 20) for i in scanpoints: umv(dev.id_gap, i) time.sleep(0.2) @@ -3071,33 +3045,30 @@ def gap_beam(): lu_Xnorm = lu["lu_Xnorm"] lu_Ynorm = lu["lu_Ynorm"] - scint_read = read_scintXY(image=False) samcam_x = scint_read["X_diff_microns"] samcam_y = scint_read["Y_diff_microns"] - + with open(filename2, "a", encoding="utf-8") as f: f.write( f"{energy:.0f},{gap:.4f},{lu_bpmsum:.1f},{lu_Xnorm:.3f},{lu_Ynorm:.3f}," f"{samcam_x:.1f},{samcam_y:.1f}\n" - ) + ) finally: finish_ss_xi_measurement() finish_scint_measurement() # bl_energy(12400) + def gap_test(): for energy in range(6000, 30001, 500): try: harmonic, gap = Gap.calculate_gap(energy) - print( - f"{energy:5d} eV " - f"H{harmonic:2d} " - f"{gap:.4f} mm" - ) + print(f"{energy:5d} eV " f"H{harmonic:2d} " f"{gap:.4f} mm") except ValueError as exc: print(energy, exc) + def energy_beam_position(): """ Measure the beam trajectory as a function of energy. @@ -3112,17 +3083,7 @@ def energy_beam_position(): No DCM perp correction or beam steering is applied. """ - energies = [ - 6000, - 8000, - 10000, - 12400, - 15000, - 18000, - 20000, - 22000, - 25000, - ] + energies = [6000, 8000, 10000, 12400, 15000, 18000, 20000, 22000, 25000] filename = bl.env.lut_dir / f"energy_beam_position_{filenow()}.csv" @@ -3208,7 +3169,7 @@ def energy_beam_position(): samcam_y = scint["Y_diff_microns"] # Flux - _,flux = bpm2flux() + _, flux = bpm2flux() # ---------------------------------------------------------- # Save immediately @@ -3255,4 +3216,4 @@ def energy_beam_position(): print("\nReturning to 12400 eV") bl_energy(12400) - print(f"\nResults saved to:\n{filename}") \ No newline at end of file + print(f"\nResults saved to:\n{filename}") diff --git a/pxii_bec/macros/pxii_parameters.py b/pxii_bec/macros/pxii_parameters.py index d3f9e44..9b08a3e 100755 --- a/pxii_bec/macros/pxii_parameters.py +++ b/pxii_bec/macros/pxii_parameters.py @@ -20,7 +20,7 @@ class EnergyDefaults: mono_fpitch = dev.dcm_fpitch mono_perp = dev.dcm_perp mono_roll = dev.dcm_froll - mono_roll_value = 3.88 + mono_roll_value = 3.186 stripe_thresholds = {"silicon": 9000, "rhodium": 20000, "platinum": 40000} pitch_scan = {"halfwidth": 0.075, "steps": 20} @@ -29,9 +29,8 @@ class EnergyDefaults: class Calibration: """Calibration parameters for PXII optics""" - # pitch_calib = [-7.60082220e-19, 6.09893342e-14, -1.81702221e-09, 2.45457520e-05, -5.61275367e00] - - pitch_calib = [-1.84031410e-07,-6.76777668e-05, -5.59684124] + # pitch_calib = [-1.84031410e-07,-6.76777668e-05, -5.59684124] + pitch_calib = [-1.75794156e-07, -6.68437530e-05, -5.66961048e+00] # roll = np.array([2.28291039e-03, -2.41928101e01]) diff --git a/pxii_bec/macros/pxii_setup.py b/pxii_bec/macros/pxii_setup.py index a361a80..59dfb5d 100644 --- a/pxii_bec/macros/pxii_setup.py +++ b/pxii_bec/macros/pxii_setup.py @@ -263,7 +263,7 @@ def scan_ge(edge=11103.1, mode="coarse"): if mode == "coarse": scan_range, scanpoints = 100, 41 if mode == "medium": - scan_range, scanpoints = 40, 41 + scan_range, scanpoints = 40, 61 elif mode == "fine": scan_range, scanpoints = 15, 61 else: diff --git a/pxii_bec/macros/weekly.py b/pxii_bec/macros/weekly.py index 27f938b..85c76fb 100644 --- a/pxii_bec/macros/weekly.py +++ b/pxii_bec/macros/weekly.py @@ -105,15 +105,43 @@ def save_weekly_results(results): "energy_offset_eV": format_value(edge_result.get("energy_offset"), ".2f") } - exists = filename.exists() - with open(filename, "a", newline="") as csvfile: - writer = csv.DictWriter(csvfile, fieldnames=list(row)) - if not exists: - writer.writeheader() - writer.writerow(row) - print(f"\nWeekly results saved to:\n{filename}") - return filename + fieldnames = list(row.keys()) + file_exists = filename.exists() + + # Check whether the existing CSV has the current schema + if file_exists: + with open(filename, "r", newline="", encoding="utf-8") as f: + reader = csv.reader(f) + existing_header = next(reader, None) + + if existing_header != fieldnames: + # Archive the old CSV rather than writing incompatible rows to it + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + archive_filename = filename.with_name( + f"{filename.stem}_archive_{timestamp}{filename.suffix}" + ) + + filename.rename(archive_filename) + + print( + "⚠ Weekly beamline CSV schema has changed.\n" + f" Previous history archived as: {archive_filename.name}\n" + f" Starting new history file: {filename.name}" + ) + + file_exists = False + + with open(filename, "a", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + + if not file_exists: + writer.writeheader() + + writer.writerow(row) + + print(f"✓ Weekly beamline history saved to {filename}") + return filename def send_week_to_scilog(filename=None): if filename is None: