diff --git a/src/mx_bluesky/beamlines/i24/serial/__init__.py b/src/mx_bluesky/beamlines/i24/serial/__init__.py index bb14adaf2..ca7496979 100644 --- a/src/mx_bluesky/beamlines/i24/serial/__init__.py +++ b/src/mx_bluesky/beamlines/i24/serial/__init__.py @@ -18,9 +18,6 @@ moveto, moveto_preset, pumpprobe_calc, - save_screen_map, - upload_parameters, - write_parameter_file, ) from .log import clean_up_log_config_at_end, setup_collection_logs from .setup_beamline.setup_detector import setup_detector_stage @@ -44,9 +41,6 @@ "load_lite_map", "load_stock_map", "pumpprobe_calc", - "save_screen_map", - "upload_parameters", - "write_parameter_file", "setup_collection_logs", "clean_up_log_config_at_end", ] diff --git a/src/mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py b/src/mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py index fc2cebc47..d6f5cb04c 100755 --- a/src/mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +++ b/src/mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py @@ -4,9 +4,7 @@ - March 21 added logging and Eiger functionality """ -import json import re -import shutil import sys import time from datetime import datetime @@ -40,10 +38,6 @@ log_on_entry, ) from mx_bluesky.beamlines.i24.serial.parameters import ExtruderParameters -from mx_bluesky.beamlines.i24.serial.parameters.constants import ( - PARAM_FILE_NAME, - PARAM_FILE_PATH, -) from mx_bluesky.beamlines.i24.serial.setup_beamline import Pilatus, caget, caput, pv from mx_bluesky.beamlines.i24.serial.setup_beamline import setup_beamline as sup from mx_bluesky.beamlines.i24.serial.setup_beamline.setup_detector import ( @@ -141,12 +135,20 @@ def enter_hutch( @log_on_entry -def write_parameter_file( - detector_stage: DetectorMotion, attenuator: ReadOnlyAttenuator -): - """Writes a json parameter file that can later be parsed by the model.""" - param_file: Path = PARAM_FILE_PATH / PARAM_FILE_NAME - SSX_LOGGER.debug(f"Writing Parameter File to: {param_file}\n") +def read_parameters(detector_stage: DetectorMotion, attenuator: ReadOnlyAttenuator): + """ Read the parameters from user input and create the parameter model for an \ + extruder collection. + + Args: + detector_stage (DetectorMotion): The detector stage device. + attenuator (ReadOnlyAttenuator): A read-only attenuator device to get the \ + transmission value. + + Returns: + ExtruderParameters: Parameter model for extruder collections + + """ + SSX_LOGGER.info("Creating parameter model from input.") det_type = yield from get_detector_type(detector_stage) SSX_LOGGER.warning(f"DETECTOR TYPE: {det_type}") @@ -182,12 +184,11 @@ def write_parameter_file( "laser_dwell_s": pump_exp, "laser_delay_s": pump_delay, } - with open(param_file, "w") as f: - json.dump(params_dict, f, indent=4) SSX_LOGGER.info("Parameters \n") SSX_LOGGER.info(pformat(params_dict)) yield from bps.null() + return ExtruderParameters(**params_dict) @log_on_entry @@ -467,11 +468,6 @@ def collection_complete_plan( dcid.collection_complete(end_time, aborted=False) SSX_LOGGER.info(f"End Time = {end_time.ctime()}") - # Copy parameter file - shutil.copy2( - PARAM_FILE_PATH / PARAM_FILE_NAME, - collection_directory / PARAM_FILE_NAME, - ) yield from bps.null() @@ -489,8 +485,9 @@ def run_extruder_plan( start_time = datetime.now() SSX_LOGGER.info(f"Collection start time: {start_time.ctime()}") - yield from write_parameter_file(detector_stage, attenuator) - parameters = ExtruderParameters.from_file(PARAM_FILE_PATH / PARAM_FILE_NAME) + parameters: ExtruderParameters = yield from read_parameters( + detector_stage, attenuator + ) beam_center_device = sup.get_beam_center_device(parameters.detector_name) diff --git a/src/mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl b/src/mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl index ff7812f6b..f6df47910 100644 --- a/src/mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +++ b/src/mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl @@ -3,8 +3,8 @@ beginScreenProperties major 4 minor 0 release 1 -x 1062 -y 369 +x 308 +y 339 w 650 h 400 font "arial-medium-r-18.0" @@ -179,50 +179,6 @@ font "arial-medium-r-18.0" colorPv "ME14E-MO-IOC-01:GP67" endObjectProperties -# (Shell Command) -object shellCmdClass -beginObjectProperties -major 4 -minor 3 -release 0 -x 40 -y 290 -w 194 -h 40 -fgColor index 0 -bgColor index 23 -topShadowColor index 1 -botShadowColor index 11 -font "arial-medium-r-18.0" -buttonLabel "Save Screen Map" -numCmds 1 -command { - 0 "blueapi -c CONFIG_LOCATION controller run save_screen_map" -} -endObjectProperties - -# (Shell Command) -object shellCmdClass -beginObjectProperties -major 4 -minor 3 -release 0 -x 12 -y 337 -w 244 -h 53 -fgColor index 13 -bgColor index 49 -topShadowColor index 1 -botShadowColor index 11 -font "arial-medium-r-18.0" -buttonLabel "Upload Parameters" -numCmds 1 -command { - 0 "blueapi -c CONFIG_LOCATION controller run upload_parameters '\{\"pmac\":\"pmac\"\}'" -} -endObjectProperties - # (Message Button) object activeMessageButtonClass beginObjectProperties diff --git a/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py b/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py index dcd32eb9c..664035e6a 100755 --- a/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +++ b/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py @@ -2,14 +2,12 @@ Fixed target data collection """ -import shutil from datetime import datetime from pathlib import Path from time import sleep import bluesky.plan_stubs as bps import bluesky.preprocessors as bpp -import numpy as np from bluesky.utils import MsgGenerator from dodal.common import inject from dodal.devices.attenuator import ReadOnlyAttenuator @@ -35,18 +33,11 @@ PumpProbeSetting, ) from mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1 import ( - write_parameter_file, + read_parameters, + upload_chip_map_to_geobrick, ) from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER, log_on_entry -from mx_bluesky.beamlines.i24.serial.parameters import ( - ChipDescription, - FixedTargetParameters, -) -from mx_bluesky.beamlines.i24.serial.parameters.constants import ( - LITEMAP_PATH, - PARAM_FILE_NAME, - PARAM_FILE_PATH_FT, -) +from mx_bluesky.beamlines.i24.serial.parameters import FixedTargetParameters from mx_bluesky.beamlines.i24.serial.setup_beamline import caget, cagetstring, caput, pv from mx_bluesky.beamlines.i24.serial.setup_beamline import setup_beamline as sup from mx_bluesky.beamlines.i24.serial.setup_beamline.setup_zebra_plans import ( @@ -107,19 +98,6 @@ def calculate_collection_timeout(parameters: FixedTargetParameters) -> float: return timeout -def copy_files_to_data_location( - dest_dir: Path | str, - param_path: Path = PARAM_FILE_PATH_FT, - map_file: Path = LITEMAP_PATH, - map_type: MappingType = MappingType.Lite, -): - if not isinstance(dest_dir, Path): - dest_dir = Path(dest_dir) - shutil.copy2(param_path / "parameters.txt", dest_dir / "parameters.txt") - if map_type == MappingType.Lite: - shutil.copy2(map_file / "currentchip.map", dest_dir / "currentchip.map") - - def write_userlog( parameters: FixedTargetParameters, filename: str, @@ -308,50 +286,18 @@ def get_prog_num( @log_on_entry -def datasetsizei24( - n_exposures: int, - chip_params: ChipDescription, - map_type: MappingType, -) -> int: - # Calculates how many images will be collected based on map type and N repeats - SSX_LOGGER.info("Calculate total number of images expected in data collection.") - - if map_type == MappingType.NoMap: - if chip_params.chip_type == ChipType.Custom: - total_numb_imgs = chip_params.x_num_steps * chip_params.y_num_steps - SSX_LOGGER.info( - f"Map type: None \tCustom chip \tNumber of images {total_numb_imgs}" - ) - else: - chip_format = chip_params.chip_format[:4] - total_numb_imgs = int(np.prod(chip_format)) - SSX_LOGGER.info( - f"""Map type: None \tOxford chip {chip_params.chip_type} \t \ - Number of images {total_numb_imgs}""" - ) - - elif map_type == MappingType.Lite: - SSX_LOGGER.info(f"Using Mapping Lite on chip type {chip_params.chip_type}") - chip_format = chip_params.chip_format[2:4] - block_count = 0 - with open(LITEMAP_PATH / "currentchip.map") as f: - for line in f.readlines(): - entry = line.split() - if entry[2] == "1": - block_count += 1 - - SSX_LOGGER.info(f"Block count={block_count}") - SSX_LOGGER.info(f"Chip format={chip_format}") - - SSX_LOGGER.info(f"Number of exposures={n_exposures}") - - total_numb_imgs = int(np.prod(chip_format) * block_count * n_exposures) - SSX_LOGGER.info(f"Calculated number of images: {total_numb_imgs}") +def set_datasize( + parameters: FixedTargetParameters, +): + SSX_LOGGER.info("Setting PV to calculated total number of images") - SSX_LOGGER.info("Set PV to calculated number of images.") - caput(pv.me14e_gp10, int(total_numb_imgs)) + SSX_LOGGER.debug(f"Map type: {parameters.map_type}") + SSX_LOGGER.debug(f"Chip type: {parameters.chip.chip_type}") + if parameters.map_type == MappingType.Lite: + SSX_LOGGER.debug(f"Num exposures: {parameters.num_exposures}") + SSX_LOGGER.debug(f"Block count: {len(parameters.chip_map)}") - return int(total_numb_imgs) + caput(pv.me14e_gp10, parameters.total_num_images) @log_on_entry @@ -622,9 +568,7 @@ def main_fixed_target_plan( parameters.checker_pattern, ) - parameters.total_num_images = datasetsizei24( - parameters.num_exposures, parameters.chip, parameters.map_type - ) + set_datasize(parameters) start_time = yield from start_i24( zebra, @@ -695,8 +639,7 @@ def collection_complete_plan( SSX_LOGGER.debug(f"Collection end time {end_time}") dcid.collection_complete(end_time, aborted=False) - # Copy parameter file and eventual chip map to collection directory - copy_files_to_data_location(collection_directory, map_type=map_type) + # NOTE no files to copy anymore but shoud write userlog here yield from bps.null() @@ -749,29 +692,13 @@ def run_fixed_target_plan( mirrors: FocusMirrorsMode = inject("focus_mirrors"), attenuator: ReadOnlyAttenuator = inject("attenuator"), ) -> MsgGenerator: - # in the first instance, write params here - yield from write_parameter_file(detector_stage, attenuator) - - SSX_LOGGER.info("Getting parameters from file.") - parameters = FixedTargetParameters.from_file(PARAM_FILE_PATH_FT / PARAM_FILE_NAME) - - log_msg = f""" - Parameters for I24 serial collection: \n - Chip name is {parameters.filename} - visit = {parameters.visit} - sub_dir = {parameters.directory} - n_exposures = {parameters.num_exposures} - chip_type = {str(parameters.chip.chip_type)} - map_type = {str(parameters.map_type)} - dcdetdist = {parameters.detector_distance_mm} - exptime = {parameters.exposure_time_s} - det_type = {parameters.detector_name} - pump_repeat = {str(parameters.pump_repeat)} - pumpexptime = {parameters.laser_dwell_s} - pumpdelay = {parameters.laser_delay_s} - prepumpexptime = {parameters.pre_pump_exposure_s} - """ - SSX_LOGGER.info(log_msg) + # Read the parameters + parameters: FixedTargetParameters = yield from read_parameters( + detector_stage, attenuator + ) + + if parameters.chip_map: + upload_chip_map_to_geobrick(pmac, parameters.chip_map) beam_center_device = sup.get_beam_center_device(parameters.detector_name) diff --git a/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py b/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py index 7c4413ce7..9c4545f0a 100755 --- a/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +++ b/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py @@ -21,17 +21,24 @@ from dodal.devices.i24.i24_detector_motion import DetectorMotion from dodal.devices.i24.pmac import PMAC, EncReset, LaserSettings -from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ChipType, Fiducials +from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ( + ChipType, + Fiducials, + MappingType, +) from mx_bluesky.beamlines.i24.serial.log import ( SSX_LOGGER, _read_visit_directory_from_file, log_on_entry, ) -from mx_bluesky.beamlines.i24.serial.parameters import get_chip_format +from mx_bluesky.beamlines.i24.serial.parameters import ( + FixedTargetParameters, + get_chip_format, + get_chip_map, +) from mx_bluesky.beamlines.i24.serial.parameters.constants import ( CS_FILES_PATH, LITEMAP_PATH, - PARAM_FILE_NAME, PARAM_FILE_PATH_FT, PVAR_FILE_PATH, ) @@ -47,6 +54,8 @@ ChipType.Custom: 25.40, ChipType.Minichip: 25.40, } +OXFORD_CHIP_WIDTH = 8 +PVAR_TEMPLATE = f"P3%0{2}d1" CHIPTYPE_PV = pv.me14e_gp1 MAPTYPE_PV = pv.me14e_gp2 NUM_EXPOSURES_PV = pv.me14e_gp3 @@ -107,22 +116,35 @@ def initialise_stages( @log_on_entry -def write_parameter_file( +def read_parameters( detector_stage: DetectorMotion, attenuator: ReadOnlyAttenuator, ) -> MsgGenerator: - param_path: Path = PARAM_FILE_PATH_FT - # Create directory if it doesn't yet exist. - param_path.mkdir(parents=True, exist_ok=True) + """ Read the parameters from user input and create the parameter model for a fixed \ + target collection. - SSX_LOGGER.info( - f"Writing Parameter File: {(param_path / PARAM_FILE_NAME).as_posix()}" - ) + Args: + detector_stage (DetectorMotion): The detector stage device. + attenuator (ReadOnlyAttenuator): A read-only attenuator device to get the \ + transmission value. + + Returns: + FixedTargetParameters: Parameter model for fixed target collections + + """ + SSX_LOGGER.info("Creating parameter model from input.") filename = caget(pv.me14e_chip_name) det_type = yield from get_detector_type(detector_stage) chip_params = get_chip_format(ChipType(int(caget(CHIPTYPE_PV)))) map_type = int(caget(MAPTYPE_PV)) + if map_type == MappingType.Lite and chip_params.chip_type in [ + ChipType.Oxford, + ChipType.OxfordInner, + ]: + chip_map = get_chip_map() + else: + chip_map = [] pump_repeat = int(caget(PUMP_REPEAT_PV)) # If file name ends in a digit this causes processing/pilatus pain. @@ -153,6 +175,7 @@ def write_parameter_file( "map_type": map_type, "pump_repeat": pump_repeat, "checker_pattern": bool(caget(pv.me14e_gp111)), + "chip_map": chip_map, "laser_dwell_s": float(caget(pv.me14e_gp103)) if pump_repeat != 0 else 0.0, "laser_delay_s": float(caget(pv.me14e_gp110)) if pump_repeat != 0 else 0.0, "pre_pump_exposure_s": float(caget(pv.me14e_gp109)) @@ -160,13 +183,11 @@ def write_parameter_file( else None, } - with open(param_path / PARAM_FILE_NAME, "w") as f: - json.dump(params_dict, f, indent=4) - - SSX_LOGGER.info("Information written to file \n") + SSX_LOGGER.info("Parameters for I24 serial collection: \n") SSX_LOGGER.info(pformat(params_dict)) yield from bps.null() + return FixedTargetParameters(**params_dict) def scrape_pvar_file(fid: str, pvar_dir: Path = PVAR_FILE_PATH): @@ -221,59 +242,25 @@ def define_current_chip( @log_on_entry -def save_screen_map() -> MsgGenerator: - litemap_path: Path = LITEMAP_PATH - litemap_path.mkdir(parents=True, exist_ok=True) - - SSX_LOGGER.info(f"Saving {litemap_path.as_posix()} currentchip.map") - with open(litemap_path / "currentchip.map", "w") as f: - SSX_LOGGER.debug("Printing only blocks with block_val == 1") - for x in range(1, 82): - block_str = f"ME14E-MO-IOC-01:GP{x + 10:d}" - block_val = int(caget(block_str)) - if block_val == 1: - SSX_LOGGER.info(f"{block_str} {block_val:d}") - line = f"{x:02d}status P3{x:02d}1 \t{block_val}\n" - f.write(line) - yield from bps.null() +def upload_chip_map_to_geobrick(pmac: PMAC, chip_map: list[int]) -> MsgGenerator: + """Upload the map parameters for an Oxford-type chip (width=8) to the geobrick. + Args: + pmac (PMAC): The PMAC device. + chip_map (list[int]): A list of selected blocks to be collected. -@log_on_entry -def upload_parameters(pmac: PMAC = inject("pmac")) -> MsgGenerator: + """ SSX_LOGGER.info("Uploading Parameters for Oxford Chip to the GeoBrick") - caput(CHIPTYPE_PV, 0) - width = 8 - - map_file: Path = LITEMAP_PATH / "currentchip.map" - if not map_file.exists(): - raise FileNotFoundError(f"The file {map_file} has not yet been created") - - with open(map_file) as f: - SSX_LOGGER.info(f"Chipid {ChipType.Oxford}") - SSX_LOGGER.info(f"width {width}") - x = 1 - for line in f.readlines()[: width**2]: - cols = line.split() - pvar = cols[1] - value = cols[2] - s = pvar + "=" + value - if value != "1": - s2 = pvar + " " - sys.stdout.write(s2) - else: - sys.stdout.write(s + " ") - sys.stdout.flush() - if x == width: - print() - x = 1 - else: - x += 1 - yield from bps.abs_set(pmac.pmac_string, s, wait=True) - sleep(0.02) - - SSX_LOGGER.warning("Automatic Setting Mapping Type to Lite has been disabled") + SSX_LOGGER.info(f"Chipid {ChipType.Oxford}, width {OXFORD_CHIP_WIDTH}") + for block in range(1, 65): + value = 1 if block in chip_map else 0 + pvar = PVAR_TEMPLATE % block + pvar_str = f"{pvar}={value}" + SSX_LOGGER.debug(f"Set {pvar_str} for block {block}") + yield from bps.abs_set(pmac.pmac_string, pvar_str, wait=True) + # Wait for PMAC to be done processing PVAR string + sleep(0.02) SSX_LOGGER.debug("Upload parameters done.") - yield from bps.null() @log_on_entry diff --git a/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py b/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py index 9a68e5dfd..eb0dc6d21 100755 --- a/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +++ b/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py @@ -3,28 +3,10 @@ """ import string -from pathlib import Path from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ChipType from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER, log_on_entry -from mx_bluesky.beamlines.i24.serial.parameters import ( - FixedTargetParameters, - get_chip_format, -) -from mx_bluesky.beamlines.i24.serial.parameters.constants import ( - PARAM_FILE_NAME, - PARAM_FILE_PATH_FT, -) - - -def read_parameter_file( - param_path: Path | str = PARAM_FILE_PATH_FT, -) -> FixedTargetParameters: - if not isinstance(param_path, Path): - param_path = Path(param_path) - params_file = param_path / PARAM_FILE_NAME - params = FixedTargetParameters.from_file(params_file) - return params +from mx_bluesky.beamlines.i24.serial.parameters import get_chip_format @log_on_entry diff --git a/src/mx_bluesky/beamlines/i24/serial/parameters/__init__.py b/src/mx_bluesky/beamlines/i24/serial/parameters/__init__.py index 220b20e1d..4e4ee00b4 100644 --- a/src/mx_bluesky/beamlines/i24/serial/parameters/__init__.py +++ b/src/mx_bluesky/beamlines/i24/serial/parameters/__init__.py @@ -6,7 +6,10 @@ FixedTargetParameters, SerialAndLaserExperiment, ) -from mx_bluesky.beamlines.i24.serial.parameters.utils import get_chip_format +from mx_bluesky.beamlines.i24.serial.parameters.utils import ( + get_chip_format, + get_chip_map, +) __all__ = [ "SSXType", @@ -16,4 +19,5 @@ "FixedTargetParameters", "SerialAndLaserExperiment", "get_chip_format", + "get_chip_map", ] diff --git a/src/mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py b/src/mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py index d1593a17c..f3bfd5943 100644 --- a/src/mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +++ b/src/mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py @@ -3,7 +3,8 @@ from pathlib import Path from typing import Literal -from pydantic import BaseModel, ConfigDict, field_validator +import numpy as np +from pydantic import BaseModel, ConfigDict, computed_field, field_validator from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ( ChipType, @@ -56,6 +57,11 @@ def from_file(cls, filename: str | Path): def nexgen_experiment_type(self) -> str: pass + @property + @abstractmethod + def ispyb_experiment_type(self) -> SSXType: + pass + class ExtruderParameters(SerialAndLaserExperiment): """Extruder parameter model.""" @@ -103,6 +109,10 @@ def y_block_size(self) -> float: else: return ((self.y_num_steps - 1) * self.y_step_size) + self.b2b_vert + @property + def tot_num_blocks(self) -> int: + return self.x_blocks * self.y_blocks + class FixedTargetParameters(SerialAndLaserExperiment): """Fixed target parameter model.""" @@ -112,7 +122,7 @@ class FixedTargetParameters(SerialAndLaserExperiment): map_type: MappingType pump_repeat: PumpProbeSetting checker_pattern: bool = False - total_num_images: int = 0 # Calculated in the code for now + chip_map: list[int] @property def nexgen_experiment_type(self) -> str: @@ -122,6 +132,28 @@ def nexgen_experiment_type(self) -> str: def ispyb_experiment_type(self) -> SSXType: return SSXType.FIXED + @computed_field # type: ignore # Mypy doesn't like it + @property + def total_num_images(self) -> int: + match self.map_type: + case MappingType.NoMap: + if self.chip.chip_type is ChipType.Custom: + num_images = ( + self.chip.x_num_steps + * self.chip.y_num_steps + * self.num_exposures + ) + else: + chip_format = self.chip.chip_format[:4] + num_images = int(np.prod(chip_format) * self.num_exposures) + case MappingType.Lite: + chip_format = self.chip.chip_format[2:4] + block_count = len(self.chip_map) # type: ignore + num_images = int( + np.prod(chip_format) * block_count * self.num_exposures + ) + return num_images + class BeamSettings(BaseModel): model_config = ConfigDict(frozen=True) diff --git a/src/mx_bluesky/beamlines/i24/serial/parameters/utils.py b/src/mx_bluesky/beamlines/i24/serial/parameters/utils.py index 807784929..137339d1b 100644 --- a/src/mx_bluesky/beamlines/i24/serial/parameters/utils.py +++ b/src/mx_bluesky/beamlines/i24/serial/parameters/utils.py @@ -6,6 +6,12 @@ ) from mx_bluesky.beamlines.i24.serial.setup_beamline import caget, pv +OXFORD_BLOCKS_PVS = [f"ME14E-MO-IOC-01:GP{i}" for i in range(11, 75)] + + +class EmptyMapError(Exception): + pass + def get_chip_format(chip_type: ChipType) -> ChipDescription: """Default parameter values.""" @@ -40,3 +46,16 @@ def get_chip_format(chip_type: ChipType) -> ChipDescription: defaults["b2b_horz"] = defaults["b2b_vert"] = 0.0 chip_params: dict[str, Any] = {"chip_type": chip_type, **defaults} return ChipDescription(**chip_params) + + +def get_chip_map() -> list[int]: + """Return a list of blocks (the 'chip map') to be collected on an Oxford type chip \ + when using lite mapping.""" + chipmap = [] + for n, block_pv in enumerate(OXFORD_BLOCKS_PVS): + block_val = int(caget(block_pv)) + if block_val == 1: + chipmap.append(n + 1) + if len(chipmap) == 0: + raise EmptyMapError("No blocks selected for Lite map.") + return chipmap diff --git a/tests/unit_tests/beamlines/i24/serial/conftest.py b/tests/unit_tests/beamlines/i24/serial/conftest.py index ed9910d13..4dbf0f079 100644 --- a/tests/unit_tests/beamlines/i24/serial/conftest.py +++ b/tests/unit_tests/beamlines/i24/serial/conftest.py @@ -43,6 +43,7 @@ def dummy_params_without_pp(): "map_type": 1, "pump_repeat": 0, "checker_pattern": False, + "chip_map": [1], } return FixedTargetParameters(**params) diff --git a/tests/unit_tests/beamlines/i24/serial/extruder/test_extruder_collect.py b/tests/unit_tests/beamlines/i24/serial/extruder/test_extruder_collect.py index 895b796e0..de833865b 100644 --- a/tests/unit_tests/beamlines/i24/serial/extruder/test_extruder_collect.py +++ b/tests/unit_tests/beamlines/i24/serial/extruder/test_extruder_collect.py @@ -1,4 +1,4 @@ -from unittest.mock import ANY, MagicMock, call, mock_open, patch +from unittest.mock import ANY, MagicMock, call, patch import bluesky.plan_stubs as bps import pytest @@ -14,8 +14,8 @@ initialise_extruder, laser_check, main_extruder_plan, + read_parameters, tidy_up_at_collection_end_plan, - write_parameter_file, ) from mx_bluesky.beamlines.i24.serial.parameters import BeamSettings, ExtruderParameters from mx_bluesky.beamlines.i24.serial.setup_beamline import Eiger, Pilatus @@ -71,7 +71,6 @@ def fake_generator(value): "mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.get_detector_type" ) @patch("mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.caget") -@patch("mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.json") @patch( "mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2._read_visit_directory_from_file" ) @@ -79,11 +78,10 @@ def fake_generator(value): "mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.SSX_LOGGER" ) @patch("mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.bps.rd") -def test_write_parameter_file( +def test_read_parameters( fake_rd, fake_log, mock_read_visit, - mock_json, fake_caget, fake_det, detector_stage, @@ -93,16 +91,13 @@ def test_write_parameter_file( fake_det.side_effect = [fake_generator(Eiger())] fake_rd.side_effect = [fake_generator(0.3)] with patch( - "mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.open", - mock_open(), + "mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.ExtruderParameters", ): - RE(write_parameter_file(detector_stage, mock_attenuator)) + RE(read_parameters(detector_stage, mock_attenuator)) assert fake_caget.call_count == 8 - mock_json.dump.assert_called_once() - fake_log.debug.assert_called_once() fake_log.warning.assert_called_once() - assert fake_log.info.call_count == 2 + assert fake_log.info.call_count == 3 @patch("mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.caget") @@ -360,12 +355,11 @@ def test_aborted_plan_with_pilatus( fake_dcid.collection_complete.assert_called_once_with(ANY, aborted=True) -@patch("mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.shutil") @patch("mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.DCID") @patch("mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.sleep") @patch("mx_bluesky.beamlines.i24.serial.extruder.i24ssx_Extruder_Collect_py3v2.caput") def test_collection_complete_plan_with_eiger( - fake_caput, fake_sleep, fake_dcid, fake_shutil, dummy_params, RE + fake_caput, fake_sleep, fake_dcid, dummy_params, RE ): RE( collection_complete_plan( diff --git a/tests/unit_tests/beamlines/i24/serial/fixed_target/conftest.py b/tests/unit_tests/beamlines/i24/serial/fixed_target/conftest.py index 847a941b0..d20ffb475 100644 --- a/tests/unit_tests/beamlines/i24/serial/fixed_target/conftest.py +++ b/tests/unit_tests/beamlines/i24/serial/fixed_target/conftest.py @@ -23,6 +23,7 @@ def dummy_params_with_pp(): "map_type": 1, "pump_repeat": 3, "checker_pattern": False, + "chip_map": [1], "laser_dwell_s": 0.02, "laser_delay_s": 0.05, } diff --git a/tests/unit_tests/beamlines/i24/serial/fixed_target/test_chip_manager.py b/tests/unit_tests/beamlines/i24/serial/fixed_target/test_chip_manager.py index 33c9a6b6a..f00b87ea8 100644 --- a/tests/unit_tests/beamlines/i24/serial/fixed_target/test_chip_manager.py +++ b/tests/unit_tests/beamlines/i24/serial/fixed_target/test_chip_manager.py @@ -18,11 +18,11 @@ moveto, moveto_preset, pumpprobe_calc, + read_parameters, scrape_mtr_directions, scrape_mtr_fiducials, set_pmac_strings_for_cs, - upload_parameters, - write_parameter_file, + upload_chip_map_to_geobrick, ) from mx_bluesky.beamlines.i24.serial.setup_beamline import Eiger @@ -51,23 +51,17 @@ "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.get_chip_format" ) @patch("mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.caget") -@patch("mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.json") @patch( "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1._read_visit_directory_from_file" ) @patch( "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.SSX_LOGGER" ) -@patch( - "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.Path.mkdir" -) @patch("mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.bps.rd") -def test_write_parameter_file( +def test_read_parameters( fake_rd, - fake_mkdir, fake_log, mock_read_visit, - mock_json, fake_caget, fake_chip, fake_det, @@ -82,14 +76,11 @@ def fake_generator(value): fake_det.side_effect = [fake_generator(Eiger())] fake_rd.side_effect = [fake_generator(0.3)] with patch( - "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.open", - mock_open(), + "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.FixedTargetParameters", ): - RE(write_parameter_file(detector_stage, mock_attenuator)) + RE(read_parameters(detector_stage, mock_attenuator)) - fake_mkdir.assert_called_once() assert fake_caget.call_count == 12 - mock_json.dump.assert_called_once() assert fake_log.info.call_count == 3 @@ -123,48 +114,31 @@ async def test_initialise( ) -@patch( - "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.Path.exists" -) -@patch( - "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.sys.stdout" +@pytest.mark.parametrize( + "fake_chip_map", + [[10], [1, 2, 15, 16], list(range(33, 65))], # 1 block, 1 corner, half chip ) -@patch("mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.caput") -def test_upload_parameters( - fake_caput: MagicMock, - fake_stdout: MagicMock, - fake_file_exists: MagicMock, - pmac: PMAC, - RE, +@patch("mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.sleep") +def test_upload_chip_map_to_geobrick( + fake_sleep: MagicMock, fake_chip_map: list[int], pmac: PMAC, RE ): - fake_file_exists.return_value = True - with patch( - "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.open", - mock_open(read_data=chipmap_str), - ): - RE(upload_parameters(pmac)) + tot_blocks = 64 + RE(upload_chip_map_to_geobrick(pmac, fake_chip_map)) mock_pmac_str = get_mock_put(pmac.pmac_string) - mock_pmac_str.assert_has_calls( - [ - call("P3011=1", wait=True), - call("P3021=0", wait=True), - call("P3031=0", wait=True), - call("P3041=0", wait=True), - ] - ) + assert mock_pmac_str.call_count == tot_blocks + pvar_zero_calls = [ + call(f"P3{i:02d}1=0", wait=True) for i in range(1, 65) if i not in fake_chip_map + ] -@patch( - "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.Path.exists" -) -@patch("mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.caput") -def test_upload_parameters_fails_if_no_file( - fake_caput: MagicMock, fake_file_exists: MagicMock, pmac: PMAC, RE -): - fake_file_exists.return_value = False - with pytest.raises(FileNotFoundError): - RE(upload_parameters(pmac)) + pvar_one_calls = [call(f"P3{i:02d}1=1", wait=True) for i in fake_chip_map] + + assert len(pvar_zero_calls) == tot_blocks - len(fake_chip_map) + assert len(pvar_one_calls) == len(fake_chip_map) + + mock_pmac_str.assert_has_calls(pvar_one_calls, any_order=True) + mock_pmac_str.assert_has_calls(pvar_zero_calls, any_order=True) @patch("mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Manager_py3v1.caget") diff --git a/tests/unit_tests/beamlines/i24/serial/fixed_target/test_ft_collect.py b/tests/unit_tests/beamlines/i24/serial/fixed_target/test_ft_collect.py index 7f1b7cdc7..b57942a3a 100644 --- a/tests/unit_tests/beamlines/i24/serial/fixed_target/test_ft_collect.py +++ b/tests/unit_tests/beamlines/i24/serial/fixed_target/test_ft_collect.py @@ -17,7 +17,6 @@ from mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1 import ( PMAC_MOVE_TIME, calculate_collection_timeout, - datasetsizei24, finish_i24, get_chip_prog_values, get_prog_num, @@ -25,6 +24,7 @@ load_motion_program_data, main_fixed_target_plan, run_aborted_plan, + set_datasize, start_i24, tidy_up_after_collection_plan, write_userlog, @@ -42,7 +42,7 @@ def fake_generator(value): def test_calculate_collection_timeout(dummy_params_without_pp): - dummy_params_without_pp.total_num_images = 400 + assert dummy_params_without_pp.total_num_images == 400 expected_collection_time = ( dummy_params_without_pp.total_num_images * dummy_params_without_pp.exposure_time_s @@ -54,7 +54,7 @@ def test_calculate_collection_timeout(dummy_params_without_pp): def test_calculate_collection_timeout_for_eava(dummy_params_with_pp): - dummy_params_with_pp.total_num_images = 400 + assert dummy_params_with_pp.total_num_images == 400 buffer = dummy_params_with_pp.total_num_images * PMAC_MOVE_TIME + 600 expected_pump_and_probe_time = 12.05 timeout = calculate_collection_timeout(dummy_params_with_pp) @@ -78,17 +78,20 @@ def test_write_userlog(fake_mkdir, fake_log, dummy_params_without_pp): fake_log.debug.assert_called_once() +@patch( + "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.SSX_LOGGER" +) @patch("mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.caput") -def test_datasetsizei24_for_one_block_and_two_exposures( - fake_caput, dummy_params_without_pp +def test_set_datasize_for_one_block_and_two_exposures( + fake_caput, fake_log, dummy_params_without_pp ): - with patch( - "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.open", - mock_open(read_data=chipmap_str), - ): - tot_num_imgs = datasetsizei24(2, dummy_params_without_pp.chip, MappingType.Lite) - assert tot_num_imgs == 800 + dummy_params_without_pp.num_exposures = 2 + dummy_params_without_pp.chip_map = [20] + assert dummy_params_without_pp.total_num_images == 800 + set_datasize(dummy_params_without_pp) fake_caput.assert_called_once_with("ME14E-MO-IOC-01:GP10", 800) + assert fake_log.info.call_count == 1 + assert fake_log.debug.call_count == 4 def test_get_chip_prog_values(dummy_params_without_pp): @@ -192,7 +195,8 @@ def test_start_i24_with_eiger( eiger_beam_center, dummy_params_without_pp, ): - dummy_params_without_pp.total_num_images = 800 + dummy_params_without_pp.chip_map = [1, 2] + assert dummy_params_without_pp.total_num_images == 800 set_mock_value(dcm.wavelength_in_a, 0.6) RE( start_i24( @@ -362,7 +366,7 @@ async def test_kickoff_and_complete_fails_if_scan_status_pv_does_not_change( "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.start_i24" ) @patch( - "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.datasetsizei24" + "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.set_datasize" ) @patch( "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.load_motion_program_data" diff --git a/tests/unit_tests/beamlines/i24/serial/parameters/test_parameter_model.py b/tests/unit_tests/beamlines/i24/serial/parameters/test_parameter_model.py new file mode 100644 index 000000000..79455db33 --- /dev/null +++ b/tests/unit_tests/beamlines/i24/serial/parameters/test_parameter_model.py @@ -0,0 +1,95 @@ +from pathlib import Path + +# import pytest +from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ( + ChipType, + MappingType, + PumpProbeSetting, +) +from mx_bluesky.beamlines.i24.serial.parameters.experiment_parameters import ( + ChipDescription, + ExtruderParameters, + FixedTargetParameters, + SSXType, +) +from mx_bluesky.beamlines.i24.serial.parameters.utils import get_chip_format + + +def test_extruder_params(dummy_params_ex): + assert isinstance(dummy_params_ex, ExtruderParameters) + assert dummy_params_ex.collection_directory == Path("foo/bar") + assert dummy_params_ex.nexgen_experiment_type == "extruder" + assert dummy_params_ex.ispyb_experiment_type is SSXType.EXTRUDER + + +def test_chip_params_with_mapping_lite_and_one_block(dummy_params_without_pp): + assert isinstance(dummy_params_without_pp, FixedTargetParameters) + assert dummy_params_without_pp.total_num_images == 400 + assert dummy_params_without_pp.chip.chip_type is ChipType.Oxford + assert dummy_params_without_pp.nexgen_experiment_type == "fixed-target" + assert dummy_params_without_pp.ispyb_experiment_type is SSXType.FIXED + + +def test_chip_params_with_multiple_blocks(dummy_params_without_pp): + dummy_params_without_pp.chip_map = [1, 2, 3] + assert dummy_params_without_pp.total_num_images == 1200 + assert dummy_params_without_pp.map_type is MappingType.Lite + assert dummy_params_without_pp.pump_repeat is PumpProbeSetting.NoPP + + +def test_chip_params_with_multiple_exposures(dummy_params_without_pp): + dummy_params_without_pp.num_exposures = 2 + dummy_params_without_pp.chip_map = [1, 16] + assert dummy_params_without_pp.total_num_images == 1600 + + +def test_chip_params_with_no_mapping_for_oxford_chip(): + oxford_defaults = get_chip_format(ChipType.Oxford) + oxford_chip = { + "visit": "foo", + "directory": "bar", + "filename": "chip", + "exposure_time_s": 0.01, + "detector_distance_mm": 100, + "detector_name": "eiger", + "transmission": 1.0, + "num_exposures": 1, + "chip": oxford_defaults.model_dump(), + "map_type": 0, + "pump_repeat": 0, + "checker_pattern": False, + "chip_map": [], + } + params = FixedTargetParameters(**oxford_chip) + assert params.total_num_images == 25600 + + +def test_chip_params_with_no_mapping_for_custom_chip(): + custom_defaults = { + "chip_type": ChipType.Custom, + "x_num_steps": 5, + "y_num_steps": 3, + "x_step_size": 0.1, + "y_step_size": 0.1, + "x_blocks": 1, + "y_blocks": 1, + "b2b_horz": 0.0, + "b2b_vert": 0.0, + } + custom_chip = { + "visit": "foo", + "directory": "bar", + "filename": "chip", + "exposure_time_s": 0.01, + "detector_distance_mm": 100, + "detector_name": "eiger", + "transmission": 1.0, + "num_exposures": 2, + "chip": ChipDescription(**custom_defaults), + "map_type": 0, + "pump_repeat": 0, + "checker_pattern": False, + "chip_map": [], + } + params = FixedTargetParameters(**custom_chip) + assert params.total_num_images == 30 diff --git a/tests/unit_tests/beamlines/i24/serial/parameters/test_utils.py b/tests/unit_tests/beamlines/i24/serial/parameters/test_utils.py index 463bc8d3c..f4a517cc2 100644 --- a/tests/unit_tests/beamlines/i24/serial/parameters/test_utils.py +++ b/tests/unit_tests/beamlines/i24/serial/parameters/test_utils.py @@ -3,7 +3,8 @@ import pytest from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ChipType -from mx_bluesky.beamlines.i24.serial.parameters import get_chip_format +from mx_bluesky.beamlines.i24.serial.parameters import get_chip_format, get_chip_map +from mx_bluesky.beamlines.i24.serial.parameters.utils import EmptyMapError @pytest.mark.parametrize( @@ -36,3 +37,28 @@ def test_get_chip_format_for_custom_chips(fake_caget: MagicMock): assert test_defaults["y_num_steps"] == 2 assert test_defaults["x_step_size"] == 0.2 and test_defaults["y_step_size"] == 0.2 assert test_defaults["y_blocks"] == 1 + + +@patch( + "mx_bluesky.beamlines.i24.serial.parameters.utils.OXFORD_BLOCKS_PVS", + new=["block1", "block2", "block3"], +) +@patch("mx_bluesky.beamlines.i24.serial.parameters.utils.caget") +def test_get_chip_map_raises_error_for_empty_map(fake_caget: MagicMock): + fake_caget.side_effect = [0, 0, 0] + with pytest.raises(EmptyMapError): + get_chip_map() + + +@patch( + "mx_bluesky.beamlines.i24.serial.parameters.utils.OXFORD_BLOCKS_PVS", + new=["block1", "block2", "block3"], +) +@patch("mx_bluesky.beamlines.i24.serial.parameters.utils.caget") +def test_get_chip_map(fake_caget: MagicMock): + fake_caget.side_effect = ["1", "0", "1"] + + chip_map = get_chip_map() + + assert len(chip_map) == 2 + assert chip_map == [1, 3]