diff --git a/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb b/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb index 5201f84d61c2837f4254de7a761152fd012fabb0..eee1cf0950b0f5ce15fcf87ec7fca01279081e50 100644 --- a/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb +++ b/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb @@ -164,7 +164,6 @@ ")\n", "from cal_tools.ana_tools import get_range\n", "from cal_tools.enums import AgipdGainMode, BadPixels\n", - "from cal_tools.restful_config import restful_config\n", "from cal_tools.step_timing import StepTimer\n", "from cal_tools.tools import (\n", " CalibrationMetadata,\n", diff --git a/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb b/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb index dc6bf84a22374919cf26560c34116f024ae26ded..31d42f0087a46c8b1002c0591351219f22980ed1 100644 --- a/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb +++ b/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb @@ -93,7 +93,6 @@ "from calibration_client import CalibrationClient\n", "from cal_tools.agipdlib import AgipdCtrl, SnowResolution\n", "from cal_tools.calcat_interface import AGIPD_CalibrationData, CalCatError\n", - "from cal_tools.restful_config import restful_config\n", "from cal_tools.step_timing import StepTimer\n", "from cal_tools.tools import CalibrationMetadata, calcat_creation_time, module_index_to_qm\n", "from cal_tools.enums import AgipdGainMode" @@ -307,10 +306,12 @@ "for cname in constants:\n", " if cname in gain_constants:\n", " agipd_cal.gain_mode = None\n", + " else:\n", + " agipd_cal.gain_mode = gain_mode\n", " try:\n", " a = agipd_cal.metadata([cname])\n", " for mod, ccv_entry in a.items():\n", - " agipd_metadata[mod].update(a[mod])\n", + " agipd_metadata[mod].update(ccv_entry)\n", " except CalCatError as e:\n", " pass # a validation of missing constants is done later.\n", "\n", diff --git a/src/cal_tools/agipdlib.py b/src/cal_tools/agipdlib.py index 13781f7cd78cfb9197bfbf0f5352d18aea735dc0..0225bafa65d56dc4f54431f5f639224f2d442083 100644 --- a/src/cal_tools/agipdlib.py +++ b/src/cal_tools/agipdlib.py @@ -1409,7 +1409,10 @@ class AgipdCorrections: * Constants.AGIPD.SlopesFF * Constants.AGIPD.BadPixelsFF """ - from cal_tools.calcat_interface import AGIPD_CalibrationData + from cal_tools.calcat_interface import ( + AGIPD_CalibrationData, + CalCatError, + ) when = {} cons_data = {} @@ -1445,24 +1448,33 @@ class AgipdCorrections: gain_constants += ["SlopesPC", "BadPixelsPC"] if self.corr_bools.get("xray_corr"): gain_constants += ["SlopesFF", "BadPixelsFF"] - + metadata = {karabo_da: {}} constants = dark_constants constants += gain_constants - metadata = agipd_cal.metadata(constants) + for cname in constants: + if cname in gain_constants: + agipd_cal.gain_mode = None + else: + agipd_cal.gain_mode = gain_mode + try: + metadata[karabo_da].update( + agipd_cal.metadata([cname])[karabo_da]) + except CalCatError as e: + pass # a validation of missing constants is done later. + # Validate the constants availability and raise/warn correspondingly. - for mod, ccv_dict in metadata.items(): - missing_dark_constants = set( - c for c in dark_constants if c not in ccv_dict.keys()) - missing_gain_constants = set( - c for c in gain_constants if c not in ccv_dict.keys()) - if missing_dark_constants: - raise KeyError( - f"Dark constants {missing_dark_constants} are not available" - f" for correction. Module: {mod}") - if missing_gain_constants: - warning( - f"Gain constants {missing_gain_constants} were" - f" not retrieved. Module: {mod}") + missing_dark_constants = set( + c for c in dark_constants if c not in metadata[karabo_da].keys()) + missing_gain_constants = set( + c for c in gain_constants if c not in metadata[karabo_da].keys()) + if missing_dark_constants: + warning( + f"Dark constants {missing_dark_constants} are not available" + f" for correction. Module: {karabo_da}") + if missing_gain_constants: + warning( + f"Gain constants {missing_gain_constants} were" + f" not retrieved. Module: {karabo_da}") for cname in constants: if metadata[karabo_da].get(cname): diff --git a/src/cal_tools/calcat_interface.py b/src/cal_tools/calcat_interface.py index b51df9aa6c5734c8275ba83d0bf854ca282f4d37..b4843d4df6257b5f015cfc93e859c6f9bca2d8d1 100644 --- a/src/cal_tools/calcat_interface.py +++ b/src/cal_tools/calcat_interface.py @@ -106,7 +106,7 @@ class CalCatApi(metaclass=ClientWrapper): """Parse different ways to specify time to CalCat.""" if isinstance(dt, datetime): - return dt.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%S%Z") + return dt.astimezone(timezone.utc).isoformat() elif isinstance(dt, date): return cls.format_time(datetime.combine(dt, time())) @@ -145,13 +145,13 @@ class CalCatApi(metaclass=ClientWrapper): def physical_detector_units( self, detector_id, - snapshot_at, - module_naming="da", + pdu_snapshot_at, + module_naming="da" ): """Physical detector unit metadata.""" resp_pdus = PhysicalDetectorUnit.get_all_by_detector( - self.client, detector_id, self.format_time(snapshot_at) + self.client, detector_id, self.format_time(pdu_snapshot_at) ) if not resp_pdus["success"]: @@ -181,6 +181,7 @@ class CalCatApi(metaclass=ClientWrapper): else: raise ValueError(f"{module_naming} is unknown!") + @lru_cache() def calibration_id(self, calibration_name): """ID for a calibration in CalCat.""" @@ -212,7 +213,7 @@ class CalCatApi(metaclass=ClientWrapper): condition, modules=None, event_at=None, - snapshot_at=None, + pdu_snapshot_at=None, metadata=None, module_naming="da", ): @@ -233,7 +234,7 @@ class CalCatApi(metaclass=ClientWrapper): or None for all (default). event_at (datetime, date, str or None): Time at which the CCVs should have been valid or None for now (default). - snapshot_at (datetime, date, str or None): Time of database + pdu_snapshot_at (datetime, date, str or None): Time of database state to look at or None for now (default). metadata (dict or None): Mapping to fill for results or None for a new dictionary (default). @@ -251,7 +252,7 @@ class CalCatApi(metaclass=ClientWrapper): passed. """ event_at = self.format_time(event_at) - snapshot_at = self.format_time(snapshot_at) + pdu_snapshot_at = self.format_time(pdu_snapshot_at) if metadata is None: metadata = CCVMetadata() @@ -270,15 +271,22 @@ class CalCatApi(metaclass=ClientWrapper): } calibration_ids = list(cal_id_map.keys()) + # Map aggregator to the selected module name. + da_to_modname = { + data['karabo_da']: mod_name for mod_name, data in + self.physical_detector_units( + self.detector(detector_name)['id'], + pdu_snapshot_at, + module_naming=module_naming + ).items() + if not modules or mod_name in modules + } # The API call supports a single module or all modules, as the # performance increase is only minor in between. Hence, all # modules are queried if more than one is selected and filtered # afterwards, if necessary. - karabo_da = ( - next(iter(modules)) - if modules is not None and len(modules) == 1 - else "", - ) # noqa + karabo_da = next(iter(da_to_modname)) if len(da_to_modname) == 1 else '', + resp_versions = CalibrationConstantVersion.get_closest_by_time_by_detector_conditions( # noqa self.client, detector_name, @@ -286,7 +294,7 @@ class CalCatApi(metaclass=ClientWrapper): self.format_cond(condition), karabo_da=karabo_da, event_at=event_at, - snapshot_at=snapshot_at, + pdu_snapshot_at=pdu_snapshot_at, ) if not resp_versions["success"]: @@ -294,17 +302,7 @@ class CalCatApi(metaclass=ClientWrapper): for ccv in resp_versions["data"]: try: - if module_naming == "da": - mod = ccv["physical_detector_unit"]["karabo_da"] - # Can be used for AGIPD, LPD, and DSSC. - elif module_naming == "qm": - mod = module_index_to_qm( - int(ccv["physical_detector_unit"]["karabo_da"][-2:]) - ) - elif module_naming == "modno": - mod = int(ccv["physical_detector_unit"]["karabo_da"][-2:]) - else: - raise ValueError(f"{module_naming} is unknown!") + mod = da_to_modname[ccv['physical_detector_unit']['karabo_da']] except KeyError: # Not included in our modules continue @@ -376,7 +374,6 @@ class CalibrationData: modules=None, client=None, event_at=None, - snapshot_at=None, module_naming="da", ): """Initialize a new CalibrationData object. @@ -393,8 +390,6 @@ class CalibrationData: communication, global one by default. event_at (datetime, date, str or None): Default time at which the CCVs should have been valid, now if omitted - snapshot_at (datetime, date, str or None): Default time of - database state to look at, now if omitted. module_naming (str or None): Expected module name convention to be used as metadata dict keys. Expected values are: `da`: data aggregator name is used. Default. @@ -409,10 +404,11 @@ class CalibrationData: self.detector_name = detector_name self.modules = modules self.event_at = event_at - self.snapshot_at = snapshot_at + self.pdu_snapshot_at = event_at self.module_naming = module_naming if client is None: + client = ( self.__class__.default_client or self.__class__.new_anonymous_client() @@ -525,7 +521,7 @@ class CalibrationData: @property def physical_detector_units(self): return self._api.physical_detector_units( - self.detector["id"], self.snapshot_at, self.module_naming + self.detector["id"], self.pdu_snapshot_at, self.module_naming ) @property @@ -540,7 +536,7 @@ class CalibrationData: "modules", "client", "event_at", - "snapshot_at", + "pdu_snapshot_at", } | {self._simplify_parameter_name(name) for name in self.parameters} kwargs = {key: getattr(self, key) for key in keys} @@ -552,7 +548,7 @@ class CalibrationData: self, calibrations=None, event_at=None, - snapshot_at=None, + pdu_snapshot_at=None, ): """Query CCV metadata for calibrations, conditions and time. @@ -562,7 +558,7 @@ class CalibrationData: event_at (datetime, date, str or None): Time at which the CCVs should have been valid, now or default value passed at initialization time if omitted. - snapshot_at (datetime, date, str or None): Time of database + pdu_snapshot_at (datetime, date, str or None): Time of database state to look at, now or default value passed at initialization time if omitted. @@ -577,7 +573,7 @@ class CalibrationData: self.condition, self.modules, event_at or self.event_at, - snapshot_at or self.snapshot_at, + pdu_snapshot_at or self.pdu_snapshot_at, metadata, module_naming=self.module_naming, ) @@ -587,7 +583,7 @@ class CalibrationData: self, module, calibration, - metadata, + metadata=None, ): """Load CCV data as ndarray. @@ -600,6 +596,8 @@ class CalibrationData: Returns: (ndarray): CCV data """ + import numpy as np + if self.caldb_root is None: raise RuntimeError("calibration database store unavailable") @@ -609,9 +607,12 @@ class CalibrationData: if metadata is None: metadata = self.metadata([calibration]) - return self._load_ccv_data(metadata, module, calibration) + row = metadata[module][calibration] + + with h5py.File(self.caldb_root / row['path'], 'r') as f: + return np.asarray(f[row['dataset'] + '/data']) - def _allocate_const_arrays(self, metadata, const_load_mp, const_data): + def _allocate_constant_arrays(self, metadata, const_load_mp, const_data): for mod, ccv_entry in metadata.items(): const_data[mod] = {} @@ -625,8 +626,23 @@ class CalibrationData: shape=shape, dtype=dtype ) - def load_constants_data(self, metadata): - def load_constant_dataset(wid, index, mod): + def load_constants_from_metadata(self, metadata): + """Load the data for all constants in metadata object. + + Args: + metadata (CCVMetadata, optional): CCV metadata to load + constant data for, may be None to query metadata. + Returns: + (Dict): A dictionary of constant data. + {module: {calibration: ndarray}}. + """ + def _load_constant_dataset(wid, index, mod): + """Load constant dataset from the CCVMetadata `metadata` into + a shared allocated array. + + Args: + mod (str): module key in `metadata` object + """ for cname, mdata in metadata[mod].items(): with h5py.File(self.caldb_root / mdata["path"], "r") as cf: cf[f"{mdata['dataset']}/data"].read_direct( @@ -635,8 +651,8 @@ class CalibrationData: const_data = dict() const_load_mp = psh.ProcessContext(num_workers=24) - self._allocate_const_arrays(metadata, const_load_mp, const_data) - const_load_mp.map(load_constant_dataset, list(metadata.keys())) + self._allocate_constant_arrays(metadata, const_load_mp, const_data) + const_load_mp.map(_load_constant_dataset, list(metadata.keys())) return const_data @@ -663,42 +679,15 @@ class CalibrationData: if metadata is None: metadata = self.metadata(calibrations) - return self.load_constants_data(metadata) - - def data_map( - self, - calibrations=None, - metadata=None, - ): - """Load all CCV data in a nested map of ndarrays. - - Args: - calibrations (Iterable of str, optional): Calibration constants - or None for all available (default). - metadata (CCVMetadata, optional): CCV metadata to load constant - for or None to query metadata automatically (default). - Returns: - (dict of dict of ndarray): CCV data by module number and - calibration constant name. - {module: {calibration: ndarray}} - """ - if self.caldb_root is None: - raise RuntimeError("calibration database store unavailable") - - if metadata is None: - metadata = self.metadata(calibrations) - - return self.load_constants_data(metadata) + return self.load_constants_from_metadata(metadata) def _build_condition(self, parameters): cond = dict() for db_name in parameters: value = getattr(self, self._simplify_parameter_name(db_name), None) - if value is not None: cond[db_name] = value - return cond @classmethod @@ -842,7 +831,7 @@ class SplitConditionCalibrationData(CalibrationData): self, calibrations=None, event_at=None, - snapshot_at=None, + pdu_snapshot_at=None, ): """Query CCV metadata for calibrations, conditions and time. @@ -852,7 +841,7 @@ class SplitConditionCalibrationData(CalibrationData): event_at (datetime, date, str or None): Time at which the CCVs should have been valid, now or default value passed at initialization time if omitted. - snapshot_at (datetime, date, str or None): Time of database + pdu_snapshot_at (datetime, date, str or None): Time of database state to look at, now or default value passed at initialization time if omitted. @@ -878,7 +867,7 @@ class SplitConditionCalibrationData(CalibrationData): self.dark_condition, self.modules, event_at or self.event_at, - snapshot_at or self.snapshot_at, + pdu_snapshot_at or self.pdu_snapshot_at, metadata, module_naming=self.module_naming, ) @@ -892,7 +881,7 @@ class SplitConditionCalibrationData(CalibrationData): self.illuminated_condition, self.modules, event_at or self.event_at, - snapshot_at or self.snapshot_at, + pdu_snapshot_at or self.pdu_snapshot_at, metadata, module_naming=self.module_naming, ) @@ -937,7 +926,6 @@ class AGIPD_CalibrationData(SplitConditionCalibrationData): modules=None, client=None, event_at=None, - snapshot_at=None, gain_setting=None, gain_mode=None, module_naming="da", @@ -951,7 +939,6 @@ class AGIPD_CalibrationData(SplitConditionCalibrationData): modules, client, event_at, - snapshot_at, module_naming, ) @@ -967,12 +954,15 @@ class AGIPD_CalibrationData(SplitConditionCalibrationData): def _build_condition(self, parameters): cond = super()._build_condition(parameters) - # Fix-up some database quirks. - if int(cond.get("Gain mode", -1)) == 0: + gain_mode = cond.get("Gain mode", None) + + if gain_mode is not None and int(gain_mode) == 0: del cond["Gain mode"] + elif gain_mode is not None: + cond["Gain mode"] = 1 - if int(cond.get("Integration time", -1)) == 12: + if int(cond.get("Integration time", None)) == 12: del cond["Integration time"] return cond @@ -1015,7 +1005,6 @@ class LPD_CalibrationData(SplitConditionCalibrationData): modules=None, client=None, event_at=None, - snapshot_at=None, module_naming="da", ): super().__init__( @@ -1023,7 +1012,6 @@ class LPD_CalibrationData(SplitConditionCalibrationData): modules, client, event_at, - snapshot_at, module_naming, ) @@ -1068,7 +1056,6 @@ class DSSC_CalibrationData(CalibrationData): modules=None, client=None, event_at=None, - snapshot_at=None, module_naming="da", ): super().__init__( @@ -1076,7 +1063,6 @@ class DSSC_CalibrationData(CalibrationData): modules, client, event_at, - snapshot_at, module_naming, ) @@ -1124,7 +1110,6 @@ class JUNGFRAU_CalibrationData(CalibrationData): modules=None, client=None, event_at=None, - snapshot_at=None, module_naming="da", ): super().__init__( @@ -1132,7 +1117,6 @@ class JUNGFRAU_CalibrationData(CalibrationData): modules, client, event_at, - snapshot_at, module_naming, ) @@ -1155,15 +1139,20 @@ class JUNGFRAU_CalibrationData(CalibrationData): return cond -class PNCCD_CalibrationData(CalibrationData): - calibrations = { +class PNCCD_CalibrationData(SplitConditionCalibrationData): + """Calibration data for the pnCCD detector.""" + + dark_calibrations = { "OffsetCCD", "BadPixelsDarkCCD", "NoiseCCD", + } + illuminated_calibrations = { "RelativeGainCCD", "CTECCD", } - parameters = [ + + dark_parameters = [ "Sensor Bias Voltage", "Memory cells", "Pixels X", @@ -1173,6 +1162,8 @@ class PNCCD_CalibrationData(CalibrationData): "Gain Setting", ] + illuminated_parameters = dark_parameters + ["Source energy"] + def __init__( self, detector_name, @@ -1180,20 +1171,20 @@ class PNCCD_CalibrationData(CalibrationData): integration_time, sensor_temperature, gain_setting, + source_energy=9.2, pixels_x=1024, pixels_y=1024, + modules=None, client=None, event_at=None, - snapshot_at=None, module_naming="da", ): # Ignore modules for this detector. super().__init__( detector_name, - None, + modules, client, event_at, - snapshot_at, module_naming, ) @@ -1204,9 +1195,11 @@ class PNCCD_CalibrationData(CalibrationData): self.integration_time = integration_time self.sensor_temperature = sensor_temperature self.gain_setting = gain_setting - + self.source_energy = source_energy class EPIX100_CalibrationData(SplitConditionCalibrationData): + """Calibration data for the ePix100 detector.""" + dark_calibrations = { "OffsetEPix100", "NoiseEPix100", @@ -1237,18 +1230,17 @@ class EPIX100_CalibrationData(SplitConditionCalibrationData): pixels_x=708, pixels_y=768, source_energy=9.2, + modules=None, client=None, event_at=None, - snapshot_at=None, module_naming="da", ): # Ignore modules for this detector. super().__init__( detector_name, - None, + modules, client, event_at, - snapshot_at, module_naming, ) @@ -1263,6 +1255,8 @@ class EPIX100_CalibrationData(SplitConditionCalibrationData): class GOTTHARD2_CalibrationData(CalibrationData): + """Calibration data for the Gotthard II detector.""" + calibrations = { "LUTGotthard2" "OffsetGotthard2", "NoiseGotthard2", @@ -1286,18 +1280,17 @@ class GOTTHARD2_CalibrationData(CalibrationData): exposure_period, acquisition_rate, single_photon, + modules=None, client=None, event_at=None, - snapshot_at=None, module_naming="da", ): # Ignore modules for this detector. super().__init__( detector_name, - None, + modules, client, event_at, - snapshot_at, module_naming, )