Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
483 changes: 483 additions & 0 deletions scripts/production/inference_xtc_serial_pipeline/README.md

Large diffs are not rendered by default.

141 changes: 141 additions & 0 deletions scripts/production/inference_xtc_serial_pipeline/bash_launcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
"""
bash_launcher.py
Launches routine_detection_v3.sh with specified arguments.
Can be run from CDS to orchestrate the complete XTC workflow.

USAGE:
python bash_launcher.py [OPTIONS]
python bash_launcher.py --help

EXAMPLES:
# Run with defaults
python bash_launcher.py

# Run with custom parameters
python bash_launcher.py --run_number=61 --exp_number=mfx101346325 --max_events=80000

# Run with specific user
python bash_launcher.py --user=pmonteil --run_number=100
"""

import argparse
import subprocess
import sys
from pathlib import Path


def eprint(msg):
"""Write message to stderr."""
sys.stderr.write(str(msg) + "\n")


def main():
parser = argparse.ArgumentParser(
description="Launch coyote XTC detection routine via bash",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)

parser.add_argument(
"--user",
default="pmonteil",
help="Username for SSH to SDF (default: pmonteil)"
)
parser.add_argument(
"--run_number",
type=int,
default=61,
help="LCLS run number to process (default: 61)"
)
parser.add_argument(
"--exp_number",
default="mfx101346325",
help="Experiment identifier (default: mfx101346325)"
)
parser.add_argument(
"--save_normalized",
type=int,
choices=[0, 1],
default=1,
help="Save normalized images (1=yes, 0=no, default: 1)"
)
parser.add_argument(
"--max_events",
type=int,
default=10000,
help="Maximum events to process (default: 10000)"
)
parser.add_argument(
"--use_normalized",
type=int,
choices=[0, 1],
default=1,
help="Use normalized images for inference (1=yes, 0=no, default: 1)"
)
parser.add_argument(
"--camera_name",
default="inline_alvium",
help="Psana detector name for the inline camera (default: inline_alvium)"
)
parser.add_argument(
"--dry_run",
action="store_true",
help="Print command without executing"
)

args = parser.parse_args()

# Get the script path (should be in same directory)
script_path = Path(__file__).parent / "routine_detection_v3.sh"

if not script_path.exists():
eprint("[ERROR] Script not found: {}".format(script_path))
sys.exit(1)

# Build command
cmd = [
"bash",
str(script_path),
"--user={}".format(args.user),
"RUN_NUMBER={}".format(args.run_number),
"EXP_NUMBER={}".format(args.exp_number),
"SAVE_NORMALIZED={}".format(args.save_normalized),
"MAX_EVENTS={}".format(args.max_events),
"USE_NORMALIZED={}".format(args.use_normalized),
"CAMERA_NAME={}".format(args.camera_name),
]

print("[INFO] ============================================")
print("[INFO] COYOTE XTC DETECTION LAUNCHER")
print("[INFO] ============================================")
print("[INFO] User: {}".format(args.user))
print("[INFO] Run Number: {}".format(args.run_number))
print("[INFO] Experiment: {}".format(args.exp_number))
print("[INFO] Save Normalized: {}".format(args.save_normalized))
print("[INFO] Max Events: {}".format(args.max_events))
print("[INFO] Use Normalized: {}".format(args.use_normalized))
print("[INFO] Camera Name: {}".format(args.camera_name))
print("[INFO] ============================================")
print()

print("[INFO] Command: {}".format(" ".join(cmd)))
print()

if args.dry_run:
print("[INFO] DRY RUN MODE - Command not executed")
return 0

try:
result = subprocess.run(cmd, check=False)
return result.returncode
except KeyboardInterrupt:
eprint("\n[INFO] Interrupted by user")
return 130
except Exception as e:
eprint("[ERROR] Failed to execute command: {}".format(e))
return 1


if __name__ == "__main__":
sys.exit(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Must source this env before running:
# source /sdf/group/lcls/ds/ana/sw/conda2/manage/bin/psconda.sh

import os
import sys
import csv
from pathlib import Path
import cv2
import numpy as np
from psana import DataSource


def parse_bool(s: str) -> bool:
"""Accept 1/0, true/false, yes/no (case-insensitive)."""
v = s.strip().lower()
if v in ("1", "true", "t", "yes", "y", "on"):
return True
if v in ("0", "false", "f", "no", "n", "off"):
return False
raise ValueError(f"Could not parse boolean from: {s!r}")


# -------------------------
# Argument parsing
# -------------------------
DEFAULT_RUN = 61
DEFAULT_EXP = "mfx "
DEFAULT_SAVE_NORM = True
DEFAULT_MAX_EVENTS = 10000
DEFAULT_CAMERA = "inline_alvium"

argc = len(sys.argv)

if argc == 1:
run_number = DEFAULT_RUN
exp = DEFAULT_EXP
SAVE_NORMALIZED = DEFAULT_SAVE_NORM
max_events = DEFAULT_MAX_EVENTS
camera_name = DEFAULT_CAMERA
elif argc in (2, 3, 4, 5, 6):
run_number = int(sys.argv[1])
exp = sys.argv[2] if argc >= 3 else DEFAULT_EXP
SAVE_NORMALIZED = parse_bool(sys.argv[3]) if argc >= 4 else DEFAULT_SAVE_NORM
max_events = int(sys.argv[4]) if argc >= 5 else DEFAULT_MAX_EVENTS
camera_name = sys.argv[5] if argc >= 6 else DEFAULT_CAMERA
else:
print(
"Usage:\n"
" python export_xtc_normalized.py\n"
" python export_xtc_normalized.py <run_number>\n"
" python export_xtc_normalized.py <run_number> <exp_number>\n"
" python export_xtc_normalized.py <run_number> <exp_number> <save_normalized>\n"
" python export_xtc_normalized.py <run_number> <exp_number> <save_normalized> <max_events>\n"
" python export_xtc_normalized.py <run_number> <exp_number> <save_normalized> <max_events> <camera_name>\n"
)
sys.exit(1)

print(f"[CONFIG] exp={exp} run={run_number} save_normalized={SAVE_NORMALIZED} max_events={max_events} camera_name={camera_name}")

# -------------------------
# psana setup
# -------------------------
#print(run_number, exp, max_events, SAVE_NORMALIZED)
ds = DataSource(exp=exp, run=[run_number], max_events=max_events)

myrun = next(ds.runs())

cam = myrun.Detector(camera_name)
xtraj = myrun.Detector("XTRAJ")
ytraj = myrun.Detector("YTRAJ")

# -------------------------
# Output dirs
# -------------------------
out_dir = Path(f"run_{run_number}_png")
out_dir.mkdir(exist_ok=True)

norm_dir = Path(f"run_{run_number}_png_norm")
if SAVE_NORMALIZED:
norm_dir.mkdir(exist_ok=True)

# -------------------------
# CSV (manual open/close — SAME as second script)
# -------------------------
out_dir_csv = Path("results_csv")
out_dir_csv.mkdir(exist_ok=True)
csv_path = out_dir_csv / "event_data.csv"
csv_file = open(csv_path, "w", newline="")
writer = csv.writer(csv_file)
writer.writerow(["event_id", "png_file", "xtraj", "ytraj"])

# -------------------------
# Event loop
# -------------------------
eid = 0

for evt in myrun.events():
if eid % 3 == 0 :
img = cam.raw.value(evt)
if img is None:
eid += 1
continue

x = xtraj(evt)
y = ytraj(evt)

# Save raw
fname = out_dir / f"event_{eid:06d}.png"
#cv2.imwrite(str(fname), img)

# Save normalized
if SAVE_NORMALIZED:
img8 = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
fname_norm = norm_dir / f"event_{eid:06d}.png"
cv2.imwrite(str(fname_norm), img8)

# CSV entry (same as your second script)
writer.writerow([eid, fname.name, x[eid], y[eid]])

print(f"[EVENT {eid}] Saved {fname.name} | X={x[eid]}, Y={y[eid]}")

eid += 1

csv_file.close()

print(f"\n[INFO] Saved {eid} raw images → {out_dir}")
if SAVE_NORMALIZED:
print(f"[INFO] Saved {eid} normalized images → {norm_dir}")
print(f"[INFO] CSV saved → {csv_path}")
Loading