Skip to content

calcat_interface

Interfaces to calibration constant data.

AGIPD_CalibrationData

Bases: SplitConditionCalibrationData

Calibration data for the AGIPD detector.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class AGIPD_CalibrationData(SplitConditionCalibrationData):
    """Calibration data for the AGIPD detector."""

    dark_calibrations = {
        "Offset",
        "Noise",
        "ThresholdsDark",
        "BadPixelsDark",
        "BadPixelsPC",
        "SlopesPC",
        "SlopesCS",
        "BadPixelsCS",
    }
    illuminated_calibrations = {
        "BadPixelsFF",
        "SlopesFF",
    }

    dark_parameters = [
        "Sensor Bias Voltage",
        "Pixels X",
        "Pixels Y",
        "Memory cells",
        "Acquisition rate",
        "Gain setting",
        "Gain mode",
        "Integration time",
    ]
    illuminated_parameters = dark_parameters + ["Source energy"]

    def __init__(
        self,
        detector_name,
        sensor_bias_voltage,
        memory_cells,
        acquisition_rate,
        modules=None,
        client=None,
        event_at=None,
        gain_setting=None,
        gain_mode=None,
        module_naming="da",
        caldb_root=None,
        integration_time=12,
        source_energy=9.2,
        pixels_x=512,
        pixels_y=128,
    ):
        super().__init__(
            detector_name,
            modules,
            client,
            event_at,
            module_naming,
            caldb_root,
        )

        self.sensor_bias_voltage = sensor_bias_voltage
        self.memory_cells = memory_cells
        self.pixels_x = pixels_x
        self.pixels_y = pixels_y
        self.acquisition_rate = acquisition_rate
        self.gain_setting = gain_setting
        self.gain_mode = gain_mode
        self.integration_time = integration_time
        self.source_energy = source_energy

    def _build_condition(self, parameters):
        cond = super()._build_condition(parameters)

        # Fix-up some database quirks.
        if cond.get("Gain mode", None):
            cond["Gain mode"] = 1
        else:
            cond.pop("Gain mode", None)

        if cond.get("Integration time", None) == 12:
            del cond["Integration time"]

        return cond

CCVMetadata

Bases: dict

Dictionary for CCV metadata.

Identical to a regular dict, but with a custom pandas-based string representation to be easier to read.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class CCVMetadata(dict):
    """Dictionary for CCV metadata.

    Identical to a regular dict, but with a custom pandas-based
    string representation to be easier to read.
    """

    def __str__(self):
        """Pretty-print CCV metadata using pandas."""

        import pandas as pd

        res = {
            pdu_idx: {
                calibration: ccv_data["ccv_name"]
                for calibration, ccv_data in pdu_data.items()
            }
            for pdu_idx, pdu_data in self.items()
        }

        return str(pd.DataFrame.from_dict(res, orient="index"))

__str__()

Pretty-print CCV metadata using pandas.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def __str__(self):
    """Pretty-print CCV metadata using pandas."""

    import pandas as pd

    res = {
        pdu_idx: {
            calibration: ccv_data["ccv_name"]
            for calibration, ccv_data in pdu_data.items()
        }
        for pdu_idx, pdu_data in self.items()
    }

    return str(pd.DataFrame.from_dict(res, orient="index"))

CalCatApi

Internal calibration_client wrapper.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class CalCatApi(metaclass=ClientWrapper):
    """Internal calibration_client wrapper."""

    get_detector_keys = [
        "id",
        "name",
        "identifier",
        "karabo_name",
        "karabo_id_control",
        "description",
    ]
    get_pdu_keys = [
        "id",
        "physical_name",
        "karabo_da",
        "virtual_device_name",
        "detector_type_id",
        "detector_id",
        "description",
    ]

    def __init__(self, client):
        self.client = client

    @classmethod
    def format_time(cls, dt):
        """Parse different ways to specify time to CalCat."""

        if isinstance(dt, datetime):
            return dt.astimezone(timezone.utc).isoformat()
        elif isinstance(dt, date):
            return cls.format_time(datetime.combine(dt, time()))

        return dt

    def format_cond(self, condition):
        """Encode operating condition to CalCat API format.

        Args:
            condition (dict): Mapping of parameter DB name to value

        Returns:
            (dict) Operating condition for use in CalCat API.
        """

        return {
            "parameters_conditions_attributes": [
                {"parameter_name": k, "value": str(v)}
                for k, v in condition.items()
            ]
        }

    @lru_cache()
    def detector(self, detector_name):
        """Detector metadata."""

        resp_detector = Detector.get_by_identifier(self.client, detector_name)

        if not resp_detector["success"]:
            raise CalCatError(resp_detector)

        return {k: resp_detector["data"][k] for k in self.get_detector_keys}

    @lru_cache()
    def physical_detector_units(
        self,
        detector_id,
        pdu_snapshot_at,
        module_naming="da"
    ):
        """Physical detector unit metadata."""

        resp_pdus = PhysicalDetectorUnit.get_all_by_detector(
            self.client, detector_id, self.format_time(pdu_snapshot_at)
        )

        if not resp_pdus["success"]:
            raise CalCatError(resp_pdus)

        # Create dict based on requested keys: karabo_da, module number,
        # or QxMx naming convention.
        if module_naming == "da":
            return {
                pdu["karabo_da"]: {k: pdu[k] for k in self.get_pdu_keys}
                for pdu in resp_pdus["data"]
            }
        elif module_naming == "modno":
            return {
                int(pdu["karabo_da"][-2:]): {
                    k: pdu[k] for k in self.get_pdu_keys
                }
                for pdu in resp_pdus["data"]
            }
        elif module_naming == "qm":
            return {
                module_index_to_qm(int(pdu["karabo_da"][-2:])): {
                    k: pdu[k] for k in self.get_pdu_keys
                }
                for pdu in resp_pdus["data"]
            }
        else:
            raise ValueError(f"{module_naming} is unknown!. Expected da, modno, or qm")


    @lru_cache()
    def calibration_id(self, calibration_name):
        """ID for a calibration in CalCat."""

        resp_calibration = Calibration.get_by_name(
            self.client, calibration_name
        )

        if not resp_calibration["success"]:
            raise CalCatError(resp_calibration)

        return resp_calibration["data"]["id"]

    @lru_cache()
    def calibration_name(self, calibration_id):
        """Name for a calibration in CalCat."""

        resp_calibration = Calibration.get_by_id(
            self.client, calibration_id
        )

        if not resp_calibration["success"]:
            raise CalCatError(resp_calibration)

        return resp_calibration["data"]["name"]

    @lru_cache()
    def parameter_id(self, param_name):
        """ID for an operating condition parameter in CalCat."""

        resp_parameter = Parameter.get_by_name(self.client, param_name)

        if not resp_parameter["success"]:
            raise CalCatError(resp_parameter)

        return resp_parameter["data"]["id"]

    def _closest_ccv_by_time_by_condition(
            self,
            detector_name: str,
            calibration_ids: Sequence[int],
            condition: dict,
            karabo_da: Optional[str] = None,
            event_at=None,
            pdu_snapshot_at=None,
    ):
        resp = CalibrationConstantVersion.get_closest_by_time_by_detector_conditions(
            self.client,
            detector_name,
            calibration_ids,
            self.format_cond(condition),
            karabo_da=karabo_da or "",
            event_at=self.format_time(event_at),
            pdu_snapshot_at=self.format_time(pdu_snapshot_at),
        )

        if not resp["success"]:
            if resp["status_code"] == 200:
                # calibration_client turns empty response into an error
                return []
            raise CalCatError(resp)

        return resp["data"]

    def closest_ccv_by_time_by_condition(
        self,
        detector_name,
        calibrations,
        condition,
        modules=None,
        event_at=None,
        pdu_snapshot_at=None,
        metadata=None,
        module_naming="da",
    ):
        """Query bulk CCV metadata from CalCat.

        This method uses the /get_closest_version_by_detector API
        to query matching CCVs for PDUs connected to a detector instance
        in one go. In particular, it automatically includes the PDU as
        an operating condition parameter to allow for a single global
        condition rather than PDU-specific ones.

        Args:
            detector_name (str): Detector instance name.
            calibrations (Iterable of str): Calibrations to query
                metadata for.
            condition (dict): Mapping of parameter name to value.
            modules (Collection of int or None): List of module numbers
                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).
            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).
            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.
                `modno`: module index is used. Index is chosen based on last 2
                    integers in karabo_da.
                `qm`: QxMx naming convention is used. Virtual names for
                    AGIPD, DSSC, and LPD.

        Returns:
            (dict) Nested mapping of module number to calibrations to
                CCV metadata. Identical to passed metadata argument if
                passed.
        """
        event_at = self.format_time(event_at)
        pdu_snapshot_at = self.format_time(pdu_snapshot_at)

        if metadata is None:
            metadata = CCVMetadata()

        if not calibrations:
            # Make sure there are at least empty dictionaries for each
            # module.
            for mod in modules.values():
                metadata.setdefault(mod, dict())
            return metadata

        # Map calibration ID to calibration name.
        cal_id_map = {
            self.calibration_id(calibration): calibration
            for calibration in calibrations
        }
        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(da_to_modname)) if len(da_to_modname) == 1 else '',

        resp_data = self._closest_ccv_by_time_by_condition(
            detector_name,
            calibration_ids,
            condition,
            karabo_da=karabo_da,
            event_at=event_at,
            pdu_snapshot_at=pdu_snapshot_at,
        )

        for ccv in resp_data:
            try:
                mod = da_to_modname[ccv['physical_detector_unit']['karabo_da']]
            except KeyError:
                # Not included in our modules
                continue

            cc = ccv["calibration_constant"]
            metadata.setdefault(mod, dict())[
                cal_id_map[cc["calibration_id"]]
            ] = dict(
                cc_id=cc["id"],
                cc_name=cc["name"],
                condition_id=cc["condition_id"],
                ccv_id=ccv["id"],
                ccv_name=ccv["name"],
                path=Path(ccv["path_to_file"]) / ccv["file_name"],
                dataset=ccv["data_set_name"],
                begin_validity_at=ccv["begin_validity_at"],
                end_validity_at=ccv["end_validity_at"],
                raw_data_location=ccv["raw_data_location"],
                start_idx=ccv["start_idx"],
                end_idx=ccv["end_idx"],
                physical_name=ccv["physical_detector_unit"]["physical_name"],
            )
        return metadata

calibration_id(calibration_name) cached

ID for a calibration in CalCat.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
@lru_cache()
def calibration_id(self, calibration_name):
    """ID for a calibration in CalCat."""

    resp_calibration = Calibration.get_by_name(
        self.client, calibration_name
    )

    if not resp_calibration["success"]:
        raise CalCatError(resp_calibration)

    return resp_calibration["data"]["id"]

calibration_name(calibration_id) cached

Name for a calibration in CalCat.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
@lru_cache()
def calibration_name(self, calibration_id):
    """Name for a calibration in CalCat."""

    resp_calibration = Calibration.get_by_id(
        self.client, calibration_id
    )

    if not resp_calibration["success"]:
        raise CalCatError(resp_calibration)

    return resp_calibration["data"]["name"]

closest_ccv_by_time_by_condition(detector_name, calibrations, condition, modules=None, event_at=None, pdu_snapshot_at=None, metadata=None, module_naming='da')

Query bulk CCV metadata from CalCat.

This method uses the /get_closest_version_by_detector API to query matching CCVs for PDUs connected to a detector instance in one go. In particular, it automatically includes the PDU as an operating condition parameter to allow for a single global condition rather than PDU-specific ones.

Parameters:

Name Type Description Default
detector_name str

Detector instance name.

required
calibrations Iterable of str

Calibrations to query metadata for.

required
condition dict

Mapping of parameter name to value.

required
modules Collection of int or None

List of module numbers or None for all (default).

None
event_at datetime, date, str or None

Time at which the CCVs should have been valid or None for now (default).

None
pdu_snapshot_at datetime, date, str or None

Time of database state to look at or None for now (default).

None
metadata dict or None

Mapping to fill for results or None for a new dictionary (default).

None
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. modno: module index is used. Index is chosen based on last 2 integers in karabo_da. qm: QxMx naming convention is used. Virtual names for AGIPD, DSSC, and LPD.

'da'

Returns:

Type Description

(dict) Nested mapping of module number to calibrations to CCV metadata. Identical to passed metadata argument if passed.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def closest_ccv_by_time_by_condition(
    self,
    detector_name,
    calibrations,
    condition,
    modules=None,
    event_at=None,
    pdu_snapshot_at=None,
    metadata=None,
    module_naming="da",
):
    """Query bulk CCV metadata from CalCat.

    This method uses the /get_closest_version_by_detector API
    to query matching CCVs for PDUs connected to a detector instance
    in one go. In particular, it automatically includes the PDU as
    an operating condition parameter to allow for a single global
    condition rather than PDU-specific ones.

    Args:
        detector_name (str): Detector instance name.
        calibrations (Iterable of str): Calibrations to query
            metadata for.
        condition (dict): Mapping of parameter name to value.
        modules (Collection of int or None): List of module numbers
            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).
        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).
        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.
            `modno`: module index is used. Index is chosen based on last 2
                integers in karabo_da.
            `qm`: QxMx naming convention is used. Virtual names for
                AGIPD, DSSC, and LPD.

    Returns:
        (dict) Nested mapping of module number to calibrations to
            CCV metadata. Identical to passed metadata argument if
            passed.
    """
    event_at = self.format_time(event_at)
    pdu_snapshot_at = self.format_time(pdu_snapshot_at)

    if metadata is None:
        metadata = CCVMetadata()

    if not calibrations:
        # Make sure there are at least empty dictionaries for each
        # module.
        for mod in modules.values():
            metadata.setdefault(mod, dict())
        return metadata

    # Map calibration ID to calibration name.
    cal_id_map = {
        self.calibration_id(calibration): calibration
        for calibration in calibrations
    }
    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(da_to_modname)) if len(da_to_modname) == 1 else '',

    resp_data = self._closest_ccv_by_time_by_condition(
        detector_name,
        calibration_ids,
        condition,
        karabo_da=karabo_da,
        event_at=event_at,
        pdu_snapshot_at=pdu_snapshot_at,
    )

    for ccv in resp_data:
        try:
            mod = da_to_modname[ccv['physical_detector_unit']['karabo_da']]
        except KeyError:
            # Not included in our modules
            continue

        cc = ccv["calibration_constant"]
        metadata.setdefault(mod, dict())[
            cal_id_map[cc["calibration_id"]]
        ] = dict(
            cc_id=cc["id"],
            cc_name=cc["name"],
            condition_id=cc["condition_id"],
            ccv_id=ccv["id"],
            ccv_name=ccv["name"],
            path=Path(ccv["path_to_file"]) / ccv["file_name"],
            dataset=ccv["data_set_name"],
            begin_validity_at=ccv["begin_validity_at"],
            end_validity_at=ccv["end_validity_at"],
            raw_data_location=ccv["raw_data_location"],
            start_idx=ccv["start_idx"],
            end_idx=ccv["end_idx"],
            physical_name=ccv["physical_detector_unit"]["physical_name"],
        )
    return metadata

detector(detector_name) cached

Detector metadata.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
@lru_cache()
def detector(self, detector_name):
    """Detector metadata."""

    resp_detector = Detector.get_by_identifier(self.client, detector_name)

    if not resp_detector["success"]:
        raise CalCatError(resp_detector)

    return {k: resp_detector["data"][k] for k in self.get_detector_keys}

format_cond(condition)

Encode operating condition to CalCat API format.

Parameters:

Name Type Description Default
condition dict

Mapping of parameter DB name to value

required

Returns:

Type Description

(dict) Operating condition for use in CalCat API.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def format_cond(self, condition):
    """Encode operating condition to CalCat API format.

    Args:
        condition (dict): Mapping of parameter DB name to value

    Returns:
        (dict) Operating condition for use in CalCat API.
    """

    return {
        "parameters_conditions_attributes": [
            {"parameter_name": k, "value": str(v)}
            for k, v in condition.items()
        ]
    }

format_time(dt) classmethod

Parse different ways to specify time to CalCat.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
@classmethod
def format_time(cls, dt):
    """Parse different ways to specify time to CalCat."""

    if isinstance(dt, datetime):
        return dt.astimezone(timezone.utc).isoformat()
    elif isinstance(dt, date):
        return cls.format_time(datetime.combine(dt, time()))

    return dt

parameter_id(param_name) cached

ID for an operating condition parameter in CalCat.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
@lru_cache()
def parameter_id(self, param_name):
    """ID for an operating condition parameter in CalCat."""

    resp_parameter = Parameter.get_by_name(self.client, param_name)

    if not resp_parameter["success"]:
        raise CalCatError(resp_parameter)

    return resp_parameter["data"]["id"]

physical_detector_units(detector_id, pdu_snapshot_at, module_naming='da') cached

Physical detector unit metadata.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
@lru_cache()
def physical_detector_units(
    self,
    detector_id,
    pdu_snapshot_at,
    module_naming="da"
):
    """Physical detector unit metadata."""

    resp_pdus = PhysicalDetectorUnit.get_all_by_detector(
        self.client, detector_id, self.format_time(pdu_snapshot_at)
    )

    if not resp_pdus["success"]:
        raise CalCatError(resp_pdus)

    # Create dict based on requested keys: karabo_da, module number,
    # or QxMx naming convention.
    if module_naming == "da":
        return {
            pdu["karabo_da"]: {k: pdu[k] for k in self.get_pdu_keys}
            for pdu in resp_pdus["data"]
        }
    elif module_naming == "modno":
        return {
            int(pdu["karabo_da"][-2:]): {
                k: pdu[k] for k in self.get_pdu_keys
            }
            for pdu in resp_pdus["data"]
        }
    elif module_naming == "qm":
        return {
            module_index_to_qm(int(pdu["karabo_da"][-2:])): {
                k: pdu[k] for k in self.get_pdu_keys
            }
            for pdu in resp_pdus["data"]
        }
    else:
        raise ValueError(f"{module_naming} is unknown!. Expected da, modno, or qm")

CalCatError

Bases: Exception

CalCat API error.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class CalCatError(Exception):
    """CalCat API error."""

    def __init__(self, response):
        super().__init__(response["info"])

CalibrationData

Calibration constants data for detectors.

European XFEL uses a web app and database to store records about the characterization of detectors and the data necessary to their correction and analysis, collectively called CalCat. The default installation is available at https://in.xfel.eu/calibration.

A detector is identified by a name (e.g. SPB_DET_AGIPD1M-1) and consists of one or more detector modules. The modules are a virtual concept and may be identified by their number (e.g. 3), the Karabo data aggregator in EuXFEL's DAQ system they're connected to (e.g. AGIPD05) or a virtual device name describing their relative location (e.g. Q3M2).

A detector module is mapped to an actual physical detector unit (PDU), which may be changed in case of a physical replacement. When characterization data is inserted into the database, it is attached to the PDU currently mapped to a module and not the virtual module itself.

Characterization data is organized by its type just called calibration (e.g. Offset or SlopesFF) and the operating condition it was taken in, which is a mapping of parameter keys to their values (e.g. Sensor bias voltage or integration time). Any unique combination of calibration (type) and operating condition is a calibration constant (CC). Any individual measurement of a CC is called a calibration constant version (CCV). There may be many CCVs for any given CC.

Note that while an authenticated connection to CalCat is possible from anywhere, the actual calibration data referred to is only available on the European XFEL computing infrastructure. If no explicit credentials are supplied, an anonymous read-only connection is established that is also only available from there.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class CalibrationData:
    """Calibration constants data for detectors.

    European XFEL uses a web app and database to store records about the
    characterization of detectors and the data necessary to their
    correction and analysis, collectively called CalCat. The default
    installation is available at https://in.xfel.eu/calibration.

    A detector is identified by a name (e.g. SPB_DET_AGIPD1M-1) and
    consists of one or more detector modules. The modules are a virtual
    concept and may be identified by their number (e.g. 3), the Karabo
    data aggregator in EuXFEL's DAQ system they're connected to
    (e.g. AGIPD05) or a virtual device name describing their relative
    location (e.g. Q3M2).

    A detector module is mapped to an actual physical detector unit
    (PDU), which may be changed in case of a physical replacement. When
    characterization data is inserted into the database, it is attached
    to the PDU currently mapped to a module and not the virtual module
    itself.

    Characterization data is organized by its type just called
    calibration (e.g. Offset or SlopesFF) and the operating condition it
    was taken in, which is a mapping of parameter keys to their values
    (e.g. Sensor bias voltage or integration time). Any unique
    combination of calibration (type) and operating condition is a
    calibration constant (CC). Any individual measurement of a CC is
    called a calibration constant version (CCV). There may be many CCVs
    for any given CC.

    Note that while an authenticated connection to CalCat is possible
    from anywhere, the actual calibration data referred to is only
    available on the European XFEL computing infrastructure. If no
    explicit credentials are supplied, an anonymous read-only connection
    is established that is also only available from there.
    """

    calibrations = set()
    default_client = None
    _default_caldb_root = ...

    def __init__(
        self,
        detector_name,
        modules=None,
        client=None,
        event_at=None,
        module_naming="da",
        caldb_root=None,
    ):
        """Initialize a new CalibrationData object.

        If no calibration-client object is passed or has been created
        using Calibration.new_client, an anonymous read-only connection
        is established automatically.

        Args:
            detector_name (str): Name of detector in CalCat.
            modules (Iterable of int, optional): Module numbers to
                query for or None for all available (default).
            client (CalibrationClient, optional): Client for CalCat
                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
            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.
                `modno`: module index is used. Index is chosen based on last 2
                    integers in karabo_da.
                `qm`: QxMx naming convention is used. Virtual names for
                    AGIPD, DSSC, and LPD.
            caldb_root (str or None): Path to the root directory for caldb
                files, finds folder for production caldb by default.
            **condition_params: Operating condition parameters defined
                on an instance level.
        """

        self.detector_name = detector_name
        self.modules = modules
        self.event_at = event_at
        self.pdu_snapshot_at = event_at
        self.module_naming = module_naming
        if caldb_root is None:
            self.caldb_root = self._get_default_caldb_root()
        else:
            self.caldb_root = Path(caldb_root)

        if client is None:

            client = (
                self.__class__.default_client
                or self.__class__.new_anonymous_client()
            )

        self._api = CalCatApi(client)

    @staticmethod
    def new_anonymous_client():
        """Create an anonymous calibration-client object.

        This connection allows read-only access to CalCat using a
        facility-provided OAuth reverse proxy. This is only accessible
        on the European XFEL computing infrastructure.
        """

        print(
            "Access to CalCat via the XFEL OAuth proxy is currently "
            "considered in testing, please report any issues to "
            "da-support@xfel.eu"
        )
        return CalibrationData.new_client(
            None,
            None,
            None,
            use_oauth2=False,
            base_url="http://exflcalproxy:8080/",
        )

    @staticmethod
    def new_client(
        client_id,
        client_secret,
        user_email,
        installation="",
        base_url="https://in.xfel.eu/{}calibration",
        **kwargs,
    ):
        """Create a new calibration-client object.

        The client object is saved as a class property and is
        automatically to any future CalibrationData objects created, if
        no other client is passed explicitly.

        Arguments:
            client_id (str): Client ID.
            client_secret (str): Client secret.
            user_email (str): LDAP user email.
            installation (str, optional): Prefix for CalCat
                installation, production system by default.
            base_url (str, optional): URL template for CalCat
                installation, public European XFEL by default.
            Any further keyword arguments are passed on to
            CalibrationClient.__init__().

        Returns:
            (CalibrationClient) CalCat client.
        """

        base_url = base_url.format(f"{installation}_" if installation else "")

        # Note this is not a classmethod and we're modifying
        # CalibrationData directly to use the same object across all
        # detector-specific implementations.
        CalibrationData.default_client = CalibrationClient(
            client_id=client_id,
            client_secret=client_secret,
            user_email=user_email,
            base_api_url=f"{base_url}/api/",
            token_url=f"{base_url}/oauth/token",
            refresh_url=f"{base_url}/oauth/token",
            auth_url=f"{base_url}/oauth/authorize",
            scope="",
            **kwargs,
        )
        return CalibrationData.default_client

    @staticmethod
    def _get_default_caldb_root():
        if CalibrationData._default_caldb_root is ...:
            onc_path = Path("/common/cal/caldb_store")
            maxwell_path = Path("/gpfs/exfel/d/cal/caldb_store")
            if onc_path.is_dir():
                CalibrationData._default_caldb_root = onc_path
            elif maxwell_path.is_dir():
                CalibrationData._default_caldb_root = maxwell_path
            else:
                CalibrationData._default_caldb_root = None

        return CalibrationData._default_caldb_root

    @property
    def client(self):
        return self._api.client

    @property
    def detector(self):
        return self._api.detector(self.detector_name)

    @property
    def physical_detector_units(self):
        return self._api.physical_detector_units(
            self.detector["id"], self.pdu_snapshot_at, self.module_naming
        )

    @property
    def mod_to_pdu(self):
        """Get the physical detector units and create a dictionary
        mapping each module name to physical name (physical detector unit).

        Returns:
            DICT: mapping module to physical detector unit name.
        """
        return {
            mod: pdu_md["physical_name"] for mod, pdu_md in self._api.physical_detector_units(  # noqa
            self.detector["id"],
            self.pdu_snapshot_at,
            self.module_naming,
            ).items()
        }

    @property
    def condition(self):
        return self._build_condition(self.parameters)

    def replace(self, **new_kwargs):
        """Create a new CalibrationData object with altered values."""

        keys = {
            "detector_name",
            "modules",
            "client",
            "event_at",
            "pdu_snapshot_at",
        } | {self._simplify_parameter_name(name) for name in self.parameters}

        kwargs = {key: getattr(self, key) for key in keys}
        kwargs.update(new_kwargs)

        return self.__class__(**kwargs)

    def metadata(
        self,
        calibrations=None,
        event_at=None,
        pdu_snapshot_at=None,
    ):
        """Query CCV metadata for calibrations, conditions and time.

        Args:
            calibrations (Iterable of str, optional): Calibrations to
                query metadata for, may be None to retrieve all.
            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.
            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.

        Returns:
            (CCVMetadata) CCV metadata result.
        """

        metadata = CCVMetadata()
        self._api.closest_ccv_by_time_by_condition(
            self.detector_name,
            calibrations or self.calibrations,
            self.condition,
            self.modules,
            event_at or self.event_at,
            pdu_snapshot_at or self.pdu_snapshot_at,
            metadata,
            module_naming=self.module_naming,
        )
        return metadata

    def ndarray(
        self,
        module,
        calibration,
        metadata=None,
    ):
        """Load CCV data as ndarray.

        Args:
            module (int): Module number
            calibration (str): Calibration constant.
            metadata (CCVMetadata, optional): CCV metadata to load
                constant data for, may be None to query metadata.

        Returns:
            (ndarray): CCV data
        """
        import numpy as np

        if self.caldb_root is None:
            raise RuntimeError("calibration database store unavailable")

        if self.modules and module not in self.modules:
            raise ValueError("module not part of this calibration data")

        if metadata is None:
            metadata = self.metadata([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_constant_arrays(self, metadata, const_load_mp, const_data):

        for mod, ccv_entry in metadata.items():
            const_data[mod] = {}
            for cname, mdata in ccv_entry.items():
                dataset = mdata["dataset"]
                with h5py.File(self.caldb_root / mdata["path"], "r") as cf:
                    shape = cf[f"{dataset}/data"].shape
                    dtype = cf[f"{dataset}/data"].dtype

                const_data[mod][cname] = const_load_mp.alloc(
                    shape=shape, dtype=dtype
                )

    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(
                        const_data[mod][cname]
                    )

        const_data = dict()
        const_load_mp = psh.ProcessContext(num_workers=24)
        self._allocate_constant_arrays(metadata, const_load_mp, const_data)
        const_load_mp.map(_load_constant_dataset, list(metadata.keys()))

        return const_data

    def ndarray_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_from_metadata(metadata)

    def display_markdown_retrieved_constants(
        self,
        metadata=None,
        ccvs_url="https://in.xfel.eu/calibration/calibration_constant_versions/"  # noqa
    ):
        """
        Display markdown table with reference links for the
        retrieved constants. Tables are split into groups of a
        maximum of 4 modules.

        Args:
            metadata (dict, optional): Metadata for calibration constants.
                Defaults to None.
            ccvs_url (str, optional): URL for calibration constant versions.
                Defaults to
                "https://in.xfel.eu/calibration/calibration_constant_versions/".
        """
        from IPython.display import Markdown, display
        from tabulate import tabulate

        if metadata is None:
            metadata = self.metadata()

        calibrations = set()
        # Get all calibrations available in the metadata for all modules.
        for c in list(metadata.values()):
            calibrations |= c.keys()

        cal_groups = [
            list(calibrations)[x:x+4] for x in range(0, len(calibrations), 4)]

        # Loop over groups of calibrations.
        for cal_group in cal_groups:
            table = [["Modules"] + cal_group]

            # Loop over calibrations and modules to form the next rows.
            for mod in metadata:
                mod_consts = []

                for cname in cal_group:
                    c_mdata = metadata[mod].get(cname)
                    # A calibration that is available in given metadata.
                    if c_mdata is not None:
                        # Have the creation time a reference
                        # link to the CCV on CALCAT.
                        c_time = datetime.fromisoformat(
                            c_mdata["begin_validity_at"]).strftime(
                                "%Y-%m-%d %H:%M")
                        mod_consts.append(
                            f"[{c_time}]({ccvs_url}/{c_mdata['ccv_id']})")
                    else:
                        # Constant is not available for this module.
                        mod_consts.append("___")

                table.append([mod] + mod_consts)

            display(
                Markdown(
                    tabulate(
                        table,
                        tablefmt="pipe",
                        headers="firstrow",
                        )
                    )
                )

    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
    def _from_multimod_detector_data(
        cls,
        component_cls,
        data,
        detector,
        modules,
        client,
    ):
        if isinstance(detector, component_cls):
            detector_name = detector.detector_name
        elif detector is None:
            detector_name = component_cls._find_detector_name(data)
        elif isinstance(detector, str):
            detector_name = detector
        else:
            raise ValueError(
                f"detector may be an object of type "
                f"{type(cls)}, a string or None"
            )

        source_to_modno = dict(
            component_cls._source_matches(data, detector_name)
        )
        detector_sources = [data[source] for source in source_to_modno.keys()]

        if modules is None:
            modules = sorted(source_to_modno.values())

        creation_date = cls._determine_data_creation_date(data)

        # Create new CalibrationData object.
        caldata = cls(
            detector_name,
            modules,
            client,
            creation_date,
            creation_date,
        )

        caldata.memory_cells = component_cls._get_memory_cell_count(
            detector_sources[0]
        )
        caldata.pixels_x = component_cls.module_shape[1]
        caldata.pixels_y = component_cls.module_shape[0]

        return caldata, detector_sources

    @staticmethod
    def _simplify_parameter_name(name):
        """Convert parameter names to valid Python symbols."""

        return name.lower().replace(" ", "_")

    @staticmethod
    def _determine_data_creation_date(data):
        """Determine data creation date."""

        assert data.files, "data contains no files"

        try:
            creation_date = data.files[0].metadata()["creationDate"]
        except KeyError:
            from warnings import warn

            warn(
                "Last file modification time used as creation date for old "
                "DAQ file format may be unreliable"
            )

            return datetime.fromtimestamp(
                Path(data.files[0].filename).lstat().st_mtime
            )
        else:
            if not data.is_single_run:
                from warnings import warn

                warn(
                    "Sample file used to determine creation date for multi "
                    "run data"
                )

            return creation_date

mod_to_pdu property

Get the physical detector units and create a dictionary mapping each module name to physical name (physical detector unit).

Returns:

Name Type Description
DICT

mapping module to physical detector unit name.

__init__(detector_name, modules=None, client=None, event_at=None, module_naming='da', caldb_root=None)

Initialize a new CalibrationData object.

If no calibration-client object is passed or has been created using Calibration.new_client, an anonymous read-only connection is established automatically.

Parameters:

Name Type Description Default
detector_name str

Name of detector in CalCat.

required
modules Iterable of int

Module numbers to query for or None for all available (default).

None
client CalibrationClient

Client for CalCat communication, global one by default.

None
event_at datetime, date, str or None

Default time at which the CCVs should have been valid, now if omitted

None
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. modno: module index is used. Index is chosen based on last 2 integers in karabo_da. qm: QxMx naming convention is used. Virtual names for AGIPD, DSSC, and LPD.

'da'
caldb_root str or None

Path to the root directory for caldb files, finds folder for production caldb by default.

None
**condition_params

Operating condition parameters defined on an instance level.

required
Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def __init__(
    self,
    detector_name,
    modules=None,
    client=None,
    event_at=None,
    module_naming="da",
    caldb_root=None,
):
    """Initialize a new CalibrationData object.

    If no calibration-client object is passed or has been created
    using Calibration.new_client, an anonymous read-only connection
    is established automatically.

    Args:
        detector_name (str): Name of detector in CalCat.
        modules (Iterable of int, optional): Module numbers to
            query for or None for all available (default).
        client (CalibrationClient, optional): Client for CalCat
            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
        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.
            `modno`: module index is used. Index is chosen based on last 2
                integers in karabo_da.
            `qm`: QxMx naming convention is used. Virtual names for
                AGIPD, DSSC, and LPD.
        caldb_root (str or None): Path to the root directory for caldb
            files, finds folder for production caldb by default.
        **condition_params: Operating condition parameters defined
            on an instance level.
    """

    self.detector_name = detector_name
    self.modules = modules
    self.event_at = event_at
    self.pdu_snapshot_at = event_at
    self.module_naming = module_naming
    if caldb_root is None:
        self.caldb_root = self._get_default_caldb_root()
    else:
        self.caldb_root = Path(caldb_root)

    if client is None:

        client = (
            self.__class__.default_client
            or self.__class__.new_anonymous_client()
        )

    self._api = CalCatApi(client)

display_markdown_retrieved_constants(metadata=None, ccvs_url='https://in.xfel.eu/calibration/calibration_constant_versions/')

Display markdown table with reference links for the retrieved constants. Tables are split into groups of a maximum of 4 modules.

Parameters:

Name Type Description Default
metadata dict

Metadata for calibration constants. Defaults to None.

None
ccvs_url str

URL for calibration constant versions. Defaults to "https://in.xfel.eu/calibration/calibration_constant_versions/".

'https://in.xfel.eu/calibration/calibration_constant_versions/'
Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def display_markdown_retrieved_constants(
    self,
    metadata=None,
    ccvs_url="https://in.xfel.eu/calibration/calibration_constant_versions/"  # noqa
):
    """
    Display markdown table with reference links for the
    retrieved constants. Tables are split into groups of a
    maximum of 4 modules.

    Args:
        metadata (dict, optional): Metadata for calibration constants.
            Defaults to None.
        ccvs_url (str, optional): URL for calibration constant versions.
            Defaults to
            "https://in.xfel.eu/calibration/calibration_constant_versions/".
    """
    from IPython.display import Markdown, display
    from tabulate import tabulate

    if metadata is None:
        metadata = self.metadata()

    calibrations = set()
    # Get all calibrations available in the metadata for all modules.
    for c in list(metadata.values()):
        calibrations |= c.keys()

    cal_groups = [
        list(calibrations)[x:x+4] for x in range(0, len(calibrations), 4)]

    # Loop over groups of calibrations.
    for cal_group in cal_groups:
        table = [["Modules"] + cal_group]

        # Loop over calibrations and modules to form the next rows.
        for mod in metadata:
            mod_consts = []

            for cname in cal_group:
                c_mdata = metadata[mod].get(cname)
                # A calibration that is available in given metadata.
                if c_mdata is not None:
                    # Have the creation time a reference
                    # link to the CCV on CALCAT.
                    c_time = datetime.fromisoformat(
                        c_mdata["begin_validity_at"]).strftime(
                            "%Y-%m-%d %H:%M")
                    mod_consts.append(
                        f"[{c_time}]({ccvs_url}/{c_mdata['ccv_id']})")
                else:
                    # Constant is not available for this module.
                    mod_consts.append("___")

            table.append([mod] + mod_consts)

        display(
            Markdown(
                tabulate(
                    table,
                    tablefmt="pipe",
                    headers="firstrow",
                    )
                )
            )

load_constants_from_metadata(metadata)

Load the data for all constants in metadata object.

Parameters:

Name Type Description Default
metadata CCVMetadata

CCV metadata to load constant data for, may be None to query metadata.

required

Returns:

Type Description
Dict

A dictionary of constant data. {module: {calibration: ndarray}}.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
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(
                    const_data[mod][cname]
                )

    const_data = dict()
    const_load_mp = psh.ProcessContext(num_workers=24)
    self._allocate_constant_arrays(metadata, const_load_mp, const_data)
    const_load_mp.map(_load_constant_dataset, list(metadata.keys()))

    return const_data

metadata(calibrations=None, event_at=None, pdu_snapshot_at=None)

Query CCV metadata for calibrations, conditions and time.

Parameters:

Name Type Description Default
calibrations Iterable of str

Calibrations to query metadata for, may be None to retrieve all.

None
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.

None
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.

None

Returns:

Type Description

(CCVMetadata) CCV metadata result.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def metadata(
    self,
    calibrations=None,
    event_at=None,
    pdu_snapshot_at=None,
):
    """Query CCV metadata for calibrations, conditions and time.

    Args:
        calibrations (Iterable of str, optional): Calibrations to
            query metadata for, may be None to retrieve all.
        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.
        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.

    Returns:
        (CCVMetadata) CCV metadata result.
    """

    metadata = CCVMetadata()
    self._api.closest_ccv_by_time_by_condition(
        self.detector_name,
        calibrations or self.calibrations,
        self.condition,
        self.modules,
        event_at or self.event_at,
        pdu_snapshot_at or self.pdu_snapshot_at,
        metadata,
        module_naming=self.module_naming,
    )
    return metadata

ndarray(module, calibration, metadata=None)

Load CCV data as ndarray.

Parameters:

Name Type Description Default
module int

Module number

required
calibration str

Calibration constant.

required
metadata CCVMetadata

CCV metadata to load constant data for, may be None to query metadata.

None

Returns:

Type Description
ndarray

CCV data

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def ndarray(
    self,
    module,
    calibration,
    metadata=None,
):
    """Load CCV data as ndarray.

    Args:
        module (int): Module number
        calibration (str): Calibration constant.
        metadata (CCVMetadata, optional): CCV metadata to load
            constant data for, may be None to query metadata.

    Returns:
        (ndarray): CCV data
    """
    import numpy as np

    if self.caldb_root is None:
        raise RuntimeError("calibration database store unavailable")

    if self.modules and module not in self.modules:
        raise ValueError("module not part of this calibration data")

    if metadata is None:
        metadata = self.metadata([calibration])

    row = metadata[module][calibration]

    with h5py.File(self.caldb_root / row['path'], 'r') as f:
        return np.asarray(f[row['dataset'] + '/data'])

ndarray_map(calibrations=None, metadata=None)

Load all CCV data in a nested map of ndarrays.

Parameters:

Name Type Description Default
calibrations Iterable of str

Calibration constants or None for all available (default).

None
metadata CCVMetadata

CCV metadata to load constant for or None to query metadata automatically (default).

None

Returns:

Type Description
dict of dict of ndarray

CCV data by module number and calibration constant name. {module: {calibration: ndarray}}

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def ndarray_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_from_metadata(metadata)

new_anonymous_client() staticmethod

Create an anonymous calibration-client object.

This connection allows read-only access to CalCat using a facility-provided OAuth reverse proxy. This is only accessible on the European XFEL computing infrastructure.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
@staticmethod
def new_anonymous_client():
    """Create an anonymous calibration-client object.

    This connection allows read-only access to CalCat using a
    facility-provided OAuth reverse proxy. This is only accessible
    on the European XFEL computing infrastructure.
    """

    print(
        "Access to CalCat via the XFEL OAuth proxy is currently "
        "considered in testing, please report any issues to "
        "da-support@xfel.eu"
    )
    return CalibrationData.new_client(
        None,
        None,
        None,
        use_oauth2=False,
        base_url="http://exflcalproxy:8080/",
    )

new_client(client_id, client_secret, user_email, installation='', base_url='https://in.xfel.eu/{}calibration', **kwargs) staticmethod

Create a new calibration-client object.

The client object is saved as a class property and is automatically to any future CalibrationData objects created, if no other client is passed explicitly.

Parameters:

Name Type Description Default
client_id str

Client ID.

required
client_secret str

Client secret.

required
user_email str

LDAP user email.

required
installation str

Prefix for CalCat installation, production system by default.

''
base_url str

URL template for CalCat installation, public European XFEL by default.

'https://in.xfel.eu/{}calibration'

Returns:

Type Description

(CalibrationClient) CalCat client.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
@staticmethod
def new_client(
    client_id,
    client_secret,
    user_email,
    installation="",
    base_url="https://in.xfel.eu/{}calibration",
    **kwargs,
):
    """Create a new calibration-client object.

    The client object is saved as a class property and is
    automatically to any future CalibrationData objects created, if
    no other client is passed explicitly.

    Arguments:
        client_id (str): Client ID.
        client_secret (str): Client secret.
        user_email (str): LDAP user email.
        installation (str, optional): Prefix for CalCat
            installation, production system by default.
        base_url (str, optional): URL template for CalCat
            installation, public European XFEL by default.
        Any further keyword arguments are passed on to
        CalibrationClient.__init__().

    Returns:
        (CalibrationClient) CalCat client.
    """

    base_url = base_url.format(f"{installation}_" if installation else "")

    # Note this is not a classmethod and we're modifying
    # CalibrationData directly to use the same object across all
    # detector-specific implementations.
    CalibrationData.default_client = CalibrationClient(
        client_id=client_id,
        client_secret=client_secret,
        user_email=user_email,
        base_api_url=f"{base_url}/api/",
        token_url=f"{base_url}/oauth/token",
        refresh_url=f"{base_url}/oauth/token",
        auth_url=f"{base_url}/oauth/authorize",
        scope="",
        **kwargs,
    )
    return CalibrationData.default_client

replace(**new_kwargs)

Create a new CalibrationData object with altered values.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def replace(self, **new_kwargs):
    """Create a new CalibrationData object with altered values."""

    keys = {
        "detector_name",
        "modules",
        "client",
        "event_at",
        "pdu_snapshot_at",
    } | {self._simplify_parameter_name(name) for name in self.parameters}

    kwargs = {key: getattr(self, key) for key in keys}
    kwargs.update(new_kwargs)

    return self.__class__(**kwargs)

ClientWrapper

Bases: type

Metaclass to wrap each calibration_client exactly once.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class ClientWrapper(type):
    """Metaclass to wrap each calibration_client exactly once."""

    _clients = WeakKeyDictionary()

    def __call__(cls, client):
        instance = cls._clients.get(client, None)

        if instance is None:
            instance = cls._clients[client] = type.__call__(cls, client)

        return instance

DSSC_CalibrationData

Bases: CalibrationData

Calibration data for the DSSC detetor.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class DSSC_CalibrationData(CalibrationData):
    """Calibration data for the DSSC detetor."""

    calibrations = {
        "Offset",
        "Noise",
    }
    parameters = [
        "Sensor Bias Voltage",
        "Memory cells",
        "Pixels X",
        "Pixels Y",
        "Pulse id checksum",
        "Acquisition rate",
        "Target gain",
        "Encoded gain",
    ]

    def __init__(
        self,
        detector_name,
        sensor_bias_voltage,
        memory_cells,
        pulse_id_checksum=None,
        acquisition_rate=None,
        target_gain=None,
        encoded_gain=None,
        pixels_x=512,
        pixels_y=128,
        modules=None,
        client=None,
        event_at=None,
        module_naming="da",
        caldb_root=None,
    ):
        super().__init__(
            detector_name,
            modules,
            client,
            event_at,
            module_naming,
            caldb_root,
        )

        self.sensor_bias_voltage = sensor_bias_voltage
        self.memory_cells = memory_cells
        self.pixels_x = pixels_x
        self.pixels_y = pixels_y
        self.pulse_id_checksum = pulse_id_checksum
        self.acquisition_rate = acquisition_rate
        self.target_gain = target_gain
        self.encoded_gain = encoded_gain

EPIX100_CalibrationData

Bases: SplitConditionCalibrationData

Calibration data for the ePix100 detector.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class EPIX100_CalibrationData(SplitConditionCalibrationData):
    """Calibration data for the ePix100 detector."""

    dark_calibrations = {
        "OffsetEPix100",
        "NoiseEPix100",
        "BadPixelsDarkEPix100",
    }
    illuminated_calibrations = {
        "RelativeGainEPix100",
        # 'BadPixelsFFEPix100',
    }
    dark_parameters = [
        "Sensor Bias Voltage",
        "Memory cells",
        "Pixels X",
        "Pixels Y",
        "Integration time",
        "Sensor temperature",
        "In vacuum",
    ]
    illuminated_parameters = dark_parameters + ["Source energy"]

    def __init__(
        self,
        detector_name,
        sensor_bias_voltage,
        integration_time,
        in_vacuum=0,
        sensor_temperature=288,
        pixels_x=708,
        pixels_y=768,
        source_energy=9.2,
        modules=None,
        client=None,
        event_at=None,
        module_naming="da",
        caldb_root=None,
    ):
        # Ignore modules for this detector.
        super().__init__(
            detector_name,
            modules,
            client,
            event_at,
            module_naming,
            caldb_root,
        )

        self.sensor_bias_voltage = sensor_bias_voltage
        self.integration_time = integration_time
        self.memory_cells = 1  # Ignore memory_cells for this detector
        self.pixels_x = pixels_x
        self.pixels_y = pixels_y
        self.in_vacuum = in_vacuum
        self.sensor_temperature = sensor_temperature
        self.source_energy = source_energy

GOTTHARD2_CalibrationData

Bases: CalibrationData

Calibration data for the Gotthard II detector.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class GOTTHARD2_CalibrationData(CalibrationData):
    """Calibration data for the Gotthard II detector."""

    calibrations = {
        "LUTGotthard2",
        "OffsetGotthard2",
        "NoiseGotthard2",
        "BadPixelsDarkGotthard2",
        "RelativeGainGotthard2",
        "BadPixelsFFGotthard2",
    }
    parameters = [
        "Sensor Bias Voltage",
        "Exposure time",
        "Exposure period",
        "Acquisition rate",
        "Single photon",
    ]

    def __init__(
        self,
        detector_name,
        sensor_bias_voltage,
        exposure_time,
        exposure_period,
        acquisition_rate,
        single_photon,
        modules=None,
        client=None,
        event_at=None,
        module_naming="da",
        caldb_root=None,
    ):
        # Ignore modules for this detector.
        super().__init__(
            detector_name,
            modules,
            client,
            event_at,
            module_naming,
            caldb_root,
        )

        self.sensor_bias_voltage = sensor_bias_voltage
        self.exposure_time = exposure_time
        self.exposure_period = exposure_period
        self.acquisition_rate = acquisition_rate
        self.single_photon = single_photon

JUNGFRAU_CalibrationData

Bases: CalibrationData

Calibration data for the JUNGFRAU detector.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class JUNGFRAU_CalibrationData(CalibrationData):
    """Calibration data for the JUNGFRAU detector."""

    calibrations = {
        "Offset10Hz",
        "Noise10Hz",
        "BadPixelsDark10Hz",
        "RelativeGain10Hz",
        "BadPixelsFF10Hz",
    }
    parameters = [
        "Sensor Bias Voltage",
        "Memory Cells",
        "Pixels X",
        "Pixels Y",
        "Integration Time",
        "Sensor temperature",
        "Gain Setting",
        "Gain mode",
    ]

    def __init__(
        self,
        detector_name,
        sensor_bias_voltage,
        memory_cells,
        integration_time,
        gain_setting,
        gain_mode=None,
        sensor_temperature=291,
        pixels_x=1024,
        pixels_y=512,
        modules=None,
        client=None,
        event_at=None,
        module_naming="da",
        caldb_root=None,
    ):
        super().__init__(
            detector_name,
            modules,
            client,
            event_at,
            module_naming,
            caldb_root,
        )

        self.sensor_bias_voltage = sensor_bias_voltage
        self.memory_cells = memory_cells
        self.pixels_x = pixels_x
        self.pixels_y = pixels_y
        self.integration_time = integration_time
        self.sensor_temperature = sensor_temperature
        self.gain_setting = gain_setting
        self.gain_mode = gain_mode

    def _build_condition(self, parameters):
        cond = super()._build_condition(parameters)

        # Fix-up some database quirks.
        if int(cond.get("Gain mode", -1)) == 0:
            del cond["Gain mode"]

        return cond

LPD_CalibrationData

Bases: SplitConditionCalibrationData

Calibration data for the LPD detector.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class LPD_CalibrationData(SplitConditionCalibrationData):
    """Calibration data for the LPD detector."""

    dark_calibrations = {
        "Offset",
        "Noise",
        "BadPixelsDark",
    }
    illuminated_calibrations = {
        "RelativeGain",
        "GainAmpMap",
        "FFMap",
        "BadPixelsFF",
    }

    dark_parameters = [
        "Sensor Bias Voltage",
        "Memory cells",
        "Pixels X",
        "Pixels Y",
        "Feedback capacitor",
        "Memory cell order",
    ]

    illuminated_parameters = [
        "Sensor Bias Voltage",
        "Memory cells",
        "Pixels X",
        "Pixels Y",
        "Feedback capacitor",
        "Source Energy",
        "category"
    ]

    def __init__(
        self,
        detector_name,
        sensor_bias_voltage,
        memory_cells,
        feedback_capacitor=5.0,
        pixels_x=256,
        pixels_y=256,
        source_energy=9.2,
        memory_cell_order=None,
        category=1,
        modules=None,
        client=None,
        event_at=None,
        module_naming="da",
        caldb_root=None,
    ):
        super().__init__(
            detector_name,
            modules,
            client,
            event_at,
            module_naming,
            caldb_root,
        )

        self.sensor_bias_voltage = sensor_bias_voltage
        self.memory_cells = memory_cells
        self.pixels_x = pixels_x
        self.pixels_y = pixels_y
        self.feedback_capacitor = feedback_capacitor
        self.memory_cell_order = memory_cell_order
        self.source_energy = source_energy
        self.category = category

PNCCD_CalibrationData

Bases: SplitConditionCalibrationData

Calibration data for the pnCCD detector.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class PNCCD_CalibrationData(SplitConditionCalibrationData):
    """Calibration data for the pnCCD detector."""

    dark_calibrations = {
        "OffsetCCD",
        "BadPixelsDarkCCD",
        "NoiseCCD",
    }
    illuminated_calibrations = {
        "RelativeGainCCD",
        "CTECCD",
    }

    dark_parameters = [
        "Sensor Bias Voltage",
        "Memory cells",
        "Pixels X",
        "Pixels Y",
        "Integration Time",
        "Sensor Temperature",
        "Gain Setting",
    ]

    illuminated_parameters = dark_parameters + ["Source energy"]

    def __init__(
        self,
        detector_name,
        sensor_bias_voltage,
        integration_time,
        sensor_temperature,
        gain_setting,
        source_energy=9.2,
        pixels_x=1024,
        pixels_y=1024,
        modules=None,
        client=None,
        event_at=None,
        module_naming="da",
        caldb_root=None,
    ):
        # Ignore modules for this detector.
        super().__init__(
            detector_name,
            modules,
            client,
            event_at,
            module_naming,
            caldb_root,
        )

        self.sensor_bias_voltage = sensor_bias_voltage
        self.memory_cells = 1  # Ignore memory_cells for this detector
        self.pixels_x = pixels_x
        self.pixels_y = pixels_y
        self.integration_time = integration_time
        self.sensor_temperature = sensor_temperature
        self.gain_setting = gain_setting
        self.source_energy = source_energy

SplitConditionCalibrationData

Bases: CalibrationData

Calibration data with dark and illuminated conditions.

Some detectors of this kind distinguish between two different operating conditions depending on whether photons illuminate the detector or not, correspondingly called the illuminated and dark conditions. Typically the illuminated condition is a superset of the dark condition.

Not all implementations for semiconductor detectors inherit from this type, but only those that make this distinction such as AGIPD and LPD.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
class SplitConditionCalibrationData(CalibrationData):
    """Calibration data with dark and illuminated conditions.

    Some detectors of this kind distinguish between two different
    operating conditions depending on whether photons illuminate the
    detector or not, correspondingly called the illuminated and dark
    conditions. Typically the illuminated condition is a superset of the
    dark condition.

    Not all implementations for semiconductor detectors inherit from
    this type, but only those that make this distinction such as AGIPD
    and LPD.
    """

    dark_calibrations = set()
    illuminated_calibrations = set()
    dark_parameters = list()
    illuminated_parameters = list()

    @property
    def calibrations(self):
        """Compatibility with CalibrationData."""

        return self.dark_calibrations | self.illuminated_calibrations

    @property
    def parameters(self):
        """Compatibility with CalibrationData."""

        # Removes likely duplicates while preserving order.
        return list(
            dict.fromkeys(self.dark_parameters + self.illuminated_parameters)
        )

    @property
    def condition(self):
        """Compatibility with CalibrationData."""

        cond = dict()
        cond.update(self.dark_condition)
        cond.update(self.illuminated_condition)

        return cond

    @property
    def dark_condition(self):
        return self._build_condition(self.dark_parameters)

    @property
    def illuminated_condition(self):
        return self._build_condition(self.illuminated_parameters)

    def metadata(
        self,
        calibrations=None,
        event_at=None,
        pdu_snapshot_at=None,
    ):
        """Query CCV metadata for calibrations, conditions and time.

        Args:
            calibrations (Iterable of str, optional): Calibrations to
                query metadata for, may be None to retrieve all.
            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.
            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.

        Returns:
            (CCVMetadata) CCV metadata result.
        """

        if calibrations is None:
            calibrations = (
                self.dark_calibrations | self.illuminated_calibrations
            )

        metadata = CCVMetadata()

        # Calibrations are sorted to ensure using exactly the same query
        # for multiple configuration. e.g. This is essential for calparrot.
        dark_calibrations = sorted(
            self.dark_calibrations & set(calibrations))
        if dark_calibrations:
            self._api.closest_ccv_by_time_by_condition(
                self.detector_name,
                dark_calibrations,
                self.dark_condition,
                self.modules,
                event_at or self.event_at,
                pdu_snapshot_at or self.pdu_snapshot_at,
                metadata,
                module_naming=self.module_naming,
            )

        illum_calibrations = sorted(
            self.illuminated_calibrations & set(calibrations))
        if illum_calibrations:
            self._api.closest_ccv_by_time_by_condition(
                self.detector_name,
                illum_calibrations,
                self.illuminated_condition,
                self.modules,
                event_at or self.event_at,
                pdu_snapshot_at or self.pdu_snapshot_at,
                metadata,
                module_naming=self.module_naming,
            )

        return metadata

calibrations property

Compatibility with CalibrationData.

condition property

Compatibility with CalibrationData.

parameters property

Compatibility with CalibrationData.

metadata(calibrations=None, event_at=None, pdu_snapshot_at=None)

Query CCV metadata for calibrations, conditions and time.

Parameters:

Name Type Description Default
calibrations Iterable of str

Calibrations to query metadata for, may be None to retrieve all.

None
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.

None
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.

None

Returns:

Type Description

(CCVMetadata) CCV metadata result.

Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/calcat_interface.py
def metadata(
    self,
    calibrations=None,
    event_at=None,
    pdu_snapshot_at=None,
):
    """Query CCV metadata for calibrations, conditions and time.

    Args:
        calibrations (Iterable of str, optional): Calibrations to
            query metadata for, may be None to retrieve all.
        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.
        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.

    Returns:
        (CCVMetadata) CCV metadata result.
    """

    if calibrations is None:
        calibrations = (
            self.dark_calibrations | self.illuminated_calibrations
        )

    metadata = CCVMetadata()

    # Calibrations are sorted to ensure using exactly the same query
    # for multiple configuration. e.g. This is essential for calparrot.
    dark_calibrations = sorted(
        self.dark_calibrations & set(calibrations))
    if dark_calibrations:
        self._api.closest_ccv_by_time_by_condition(
            self.detector_name,
            dark_calibrations,
            self.dark_condition,
            self.modules,
            event_at or self.event_at,
            pdu_snapshot_at or self.pdu_snapshot_at,
            metadata,
            module_naming=self.module_naming,
        )

    illum_calibrations = sorted(
        self.illuminated_calibrations & set(calibrations))
    if illum_calibrations:
        self._api.closest_ccv_by_time_by_condition(
            self.detector_name,
            illum_calibrations,
            self.illuminated_condition,
            self.modules,
            event_at or self.event_at,
            pdu_snapshot_at or self.pdu_snapshot_at,
            metadata,
            module_naming=self.module_naming,
        )

    return metadata