from coseda.io import config_to_paths
from coseda.logging_utils import log_start
[docs]
def write_geomfile(
output_path,
wavelength,
adu_per_photon,
clen,
res,
data,
dim0,
dim1,
dim2,
peak_list,
peak_list_type,
detector_shift_x,
detector_shift_y,
min_ss,
max_ss,
min_fs,
max_fs,
corner_x,
corner_y,
fs_axis,
ss_axis,
mask,
mask_file,
mask_good,
mask_bad
):
"""
Write a CrystFEL-style geometry file with the specified parameters.
"""
content = f"""
wavelength = {wavelength} A
adu_per_photon = {adu_per_photon}
clen = {clen} m
res = {res}
data = {data}
dim0 = {dim0}
dim1 = {dim1}
dim2 = {dim2}
peak_list = {peak_list}
peak_list_type = {peak_list_type}
detector_shift_x = {detector_shift_x} mm
detector_shift_y = {detector_shift_y} mm
p0/min_ss = {min_ss}
p0/max_ss = {max_ss}
p0/min_fs = {min_fs}
p0/max_fs = {max_fs}
p0/corner_x = {corner_x}
p0/corner_y = {corner_y}
p0/fs = {fs_axis}
p0/ss = {ss_axis}
p0/mask = {mask}
p0/mask_file = {mask_file}
p0/mask_good = {mask_good}
p0/mask_bad = {mask_bad}
"""
with open(output_path, "w") as f:
f.write(content)
import configparser
import math
[docs]
def build_geomfile_from_ini(ini_path):
"""
Build a dictionary of geometry file parameters from an ini file.
Returns a tuple (geomfile_dict, missing_values).
"""
outputfolder, outputfolder_path, logfile, logfile_path, h5file, h5file_path = config_to_paths(ini_path)
config = configparser.ConfigParser()
config.read(ini_path)
missing_values = []
def get_config_value(section, option, cast_func=None):
try:
value = config.get(section, option)
if cast_func:
return cast_func(value)
return value
except (configparser.NoSectionError, configparser.NoOptionError, ValueError):
missing_values.append(f"{section}/{option}")
return None
# Extract values
pixels_per_meter = get_config_value('AcquisitionDetails', 'pixels_per_meter', float)
camera_length = get_config_value('AcquisitionDetails', 'camera_length', float)
camera_length_correction = get_config_value('AcquisitionDetails', 'camera_length_correction', float)
acceleration_voltage = get_config_value('AcquisitionDetails', 'acceleration_voltage', float)
resolution_width = get_config_value('AcquisitionDetails', 'resolution_width', int)
resolution_height = get_config_value('AcquisitionDetails', 'resolution_height', int)
binning_width = get_config_value('AcquisitionDetails', 'binning_width', int)
binning_height = get_config_value('AcquisitionDetails', 'binning_height', int)
det_shift_x_mm = get_config_value('entry/data', 'det_shift_x_mm', float)
det_shift_y_mm = get_config_value('entry/data', 'det_shift_y_mm', float)
# Set default values for detector_shift_x and detector_shift_y if not found
if det_shift_x_mm is None:
det_shift_x_mm = "/entry/data/det_shift_x_mm"
if det_shift_y_mm is None:
det_shift_y_mm = "/entry/data/det_shift_y_mm"
# Calculate wavelength from acceleration voltage
# electron wavelength formula: lambda = h / sqrt(2 * m * e * V) (in meters)
# Using relativistic correction:
# lambda = 12.398 / sqrt(V * (1 + V/1022000)) in Angstroms
if acceleration_voltage is not None:
try:
V = acceleration_voltage / 1000.0
wavelength = 12.398 / math.sqrt(V * (1 + V / 1022))
except Exception:
wavelength = None
missing_values.append("electron/acceleration_voltage (calculation error)")
else:
wavelength = None
# Assign values or None for missing
res = pixels_per_meter if pixels_per_meter is not None else None
if camera_length is not None and camera_length_correction is not None:
clen = camera_length * camera_length_correction
elif camera_length is not None:
clen = camera_length
log_start(logfile_path, f"Writing geometry file using camera_length without correction factor.")
else:
clen = None
missing_values.append("AcquisitionDetails/camera_length")
# Compute max_ss and max_fs
if resolution_width is not None and binning_width is not None:
max_ss = (resolution_width / binning_width) - 1
else:
max_ss = None
missing_values.append("detector/resolution_width or detector/binning_width")
if resolution_height is not None and binning_height is not None:
max_fs = (resolution_height / binning_height) - 1
else:
max_fs = None
missing_values.append("detector/resolution_height or detector/binning_height")
# Compute corner_x and corner_y
if max_fs is not None:
corner_x = -int(max_fs // 2)
else:
corner_x = None
missing_values.append("corner_x")
if max_ss is not None:
corner_y = -int(max_ss // 2)
else:
corner_y = None
missing_values.append("corner_y")
# Prepare the dictionary with constants and extracted values
geomfile_dict = {
"wavelength": wavelength,
"adu_per_photon": None,
"clen": clen,
"res": res,
"data": "/entry/data/images",
"dim0": "%",
"dim1": "ss",
"dim2": "fs",
"peak_list": "/entry/data/",
"peak_list_type": "cxi",
"detector_shift_x": det_shift_x_mm,
"detector_shift_y": det_shift_y_mm,
"min_ss": 0,
"max_ss": max_ss,
"min_fs": 0,
"max_fs": max_fs,
"corner_x": corner_x,
"corner_y": corner_y,
"fs_axis": "x",
"ss_axis": "y",
"mask": "/mask",
#"mask_file": mask_file, # removed because now writing mask directly to file
"mask_good": "0x01",
"mask_bad": "0x00",
}
# Add missing values for keys with None
for key, value in geomfile_dict.items():
if value is None and key not in missing_values:
missing_values.append(key)
return geomfile_dict, missing_values