from enum import Enum
from typing import Any, Dict, List
from malcolm.core import Info, VMeta
[docs]class DatasetType(Enum):
"""NeXus type of a produced dataset"""
#: Detector data, like the 2D data from an imaging detector
PRIMARY = "primary"
#: Calculated from detector data, like the sum of each frame
SECONDARY = "secondary"
#: Raw data sets (used when linking to raw odin datasets)
RAW = "raw"
#: Data that only makes sense when considered with detector data, like a
#: measure of beam current with an ion chamber
MONITOR = "monitor"
#: The demand positions of an axis as specified by the generator
POSITION_SET = "position_set"
#: The readback positions of an axis that moves during the scan
POSITION_VALUE = "position_value"
#: The minimum value of the readback position of an axis in each frame
POSITION_MIN = "position_min"
#: The maximum value of the readback position of an axis in each frame
POSITION_MAX = "position_max"
[docs]class ParameterTweakInfo(Info):
"""Info about a configure() parameter that needs to be tweaked
Args:
parameter: Parameter name, e.g. "generator"
value: The value it should be changed to
"""
def __init__(self, parameter: str, value: Any) -> None:
self.parameter = parameter
self.value = value
[docs]class RunProgressInfo(Info):
"""Info about how far the current run has progressed
Args:
steps: The number of completed steps
"""
def __init__(self, steps: int) -> None:
self.steps = steps
[docs]class MinTurnaroundInfo(Info):
"""Info about the minimum time gap that should be left between points
that are not joined together
Args:
gap: The minimum time gap in seconds
interval: the minimum interval between two turnaround points
"""
def __init__(self, gap: float, interval: float) -> None:
self.gap = gap
self.interval = interval
[docs]class DatasetProducedInfo(Info):
"""Declare that we will write the following dataset to file
Args:
name: Dataset name
filename: Filename relative to the fileDir we were given
type: What NeXuS dataset type it produces
rank: The rank of the dataset including generator dims
path: The path of the dataset within the file
uniqueid: The path of the UniqueID dataset within the file
"""
def __init__(
self,
name: str,
filename: str,
type: DatasetType,
rank: int,
path: str,
uniqueid: str,
) -> None:
self.name = name
self.filename = filename
self.type = type
self.rank = rank
self.path = path
self.uniqueid = uniqueid
[docs]class MotionTrigger(Enum):
"""Request from a trigger source to the motion controller of what triggers
it needs"""
NONE = 0 #: No Triggers required
ROW_GATE = 1 #: Trigger that spans each continuous joined section
EVERY_POINT = 2 #: One trigger for each point
[docs]class MotionTriggerInfo(Info):
"""Declare that we need triggers of a certain sort from the motor controller
Args:
trigger: What type is required
"""
def __init__(self, trigger: MotionTrigger) -> None:
self.trigger = trigger
[docs]class DetectorMutiframeInfo(Info):
"""Declare that we are able to produce mutiple frames per point for this
detector
Args:
mri: The mri of the detector in the DetectorTable
"""
def __init__(self, mri: str) -> None:
self.mri = mri
[docs]class ExposureDeadtimeInfo(Info):
"""Detector exposure time should be generator.duration - deadtime
Args:
readout_time: The per frame readout time of the detector
frequency_accuracy: The crystal accuracy in ppm
min_exposure: The minimum exposure time this detector supports
"""
def __init__(
self, readout_time: float, frequency_accuracy: float, min_exposure: float
) -> None:
self.readout_time = readout_time
self.frequency_accuracy = frequency_accuracy
self.min_exposure = min_exposure
[docs] def calculate_exposure(self, duration: float, exposure: float = 0.0) -> float:
"""Calculate the exposure to set the detector to given the duration of
the frame and the readout_time and frequency_accuracy"""
# If duration and exposure time are zero, return the minimum exposure time
if exposure == 0.0 and duration == 0.0:
return self.min_exposure
max_exposure = self.calculate_maximum_exposure(duration)
# If exposure time is 0, then use the max_exposure for this duration
if exposure <= 0.0 or exposure > max_exposure:
exposure = max_exposure
elif exposure < self.min_exposure:
exposure = self.min_exposure
return exposure
[docs] def calculate_minimum_duration(self, exposure: float) -> float:
"""Calculate the minimum frame duration required for the frame"""
denominator = 1 - (self.frequency_accuracy / 1000000.0)
min_duration = (exposure + self.readout_time) / denominator
return min_duration
[docs] def calculate_maximum_exposure(self, duration: float) -> float:
"""Calculate the maximum possible exposure based on a duration"""
exposure = (
duration
- self.readout_time
- (self.frequency_accuracy * duration / 1000000.0)
)
if exposure < self.min_exposure:
exposure = self.min_exposure
return exposure