diff --git a/cdiutils/__init__.py b/cdiutils/__init__.py index e8014125..9ef091fc 100755 --- a/cdiutils/__init__.py +++ b/cdiutils/__init__.py @@ -1,7 +1,11 @@ from . import load, plot, process +from .geometry import Geometry +from .converter import SpaceConverter __all__ = [ "load", "plot", - "process" + "process", + "Geometry", + "SpaceConverter" ] \ No newline at end of file diff --git a/cdiutils/converter.py b/cdiutils/converter.py index a76cbe1f..83dc19d6 100644 --- a/cdiutils/converter.py +++ b/cdiutils/converter.py @@ -366,7 +366,7 @@ def _check_shape(self, shape: tuple) -> None: def init_interpolator( self, detector_data: np.ndarray, - direct_space_data_shape: tuple = None, + direct_space_data_shape: tuple | np.ndarray | list = None, direct_space_voxel_size: tuple | np.ndarray | list | float = None, space: str = "direct", shift_voxel: tuple = None @@ -414,6 +414,7 @@ def init_interpolator( "if space is 'direct' direct_space_data_shape must be " "provided." ) + direct_space_data_shape = tuple(direct_space_data_shape) if shape != direct_space_data_shape: raise ValueError( "The cropped_raw_data should have the same shape as the " diff --git a/cdiutils/geometry.py b/cdiutils/geometry.py index 8d846a69..f59de453 100644 --- a/cdiutils/geometry.py +++ b/cdiutils/geometry.py @@ -39,33 +39,50 @@ def from_setup(cls, beamline_setup: str) -> None: """Create a Geometry instance using a beamline name.""" # Note that we use CXI convention here - if beamline_setup in ("ID01", "ID01SPEC", "ID01BLISS"): + if beamline_setup.lower() in ("id01", "id01spec", "id01bliss"): return cls( sample_circles=["x-", "y-"], # eta, phi - detector_circles=["y-", "x-"], # delta, nu + detector_circles=["y-", "x-"], # nu, delta detector_vertical_orientation="y-", detector_horizontal_orientation="x+", beam_direction=[1, 0, 0] ) - if "P10" in beamline_setup: + if "p10" in beamline_setup.lower(): return cls( sample_circles=["x-", "y-"], # om (or samth), phi - detector_circles=["y+", "x-"], # del (or e2_t02), gam + detector_circles=["y+", "x-"], # gam, del (or e2_t02) detector_vertical_orientation="y-", detector_horizontal_orientation="x+", beam_direction=[1, 0, 0] ) - if beamline_setup == "SIXS2022": + if beamline_setup.lower() == "sixs2022": return cls( sample_circles=["x-", "y+"], # mu, omega - detector_circles=["y+", "x-"], # gamma, delta + detector_circles=["y+", "x-"], # gamma, delta NOT SURE OF THE COMMENT + detector_vertical_orientation="y-", + detector_horizontal_orientation="x+", + beam_direction=[1, 0, 0] + ) + if beamline_setup.lower() == "nanomax": + return cls( + sample_circles=["x-", "y-"], # gontheta, gonphi + detector_circles=["y-", "x-"], # gamma, delta + detector_vertical_orientation="y+", + detector_horizontal_orientation="x-", + beam_direction=[1, 0, 0] + ) + if beamline_setup.lower() == "cristal": + return cls( + sample_circles=["x-", "y+"], # omega, phi + detector_circles=["y+", "x-"], # gamma, delta OK FOR omega/delta but not for the two others detector_vertical_orientation="y-", detector_horizontal_orientation="x+", beam_direction=[1, 0, 0] ) raise NotImplementedError( f"The beamline_setup {beamline_setup} is not valid. Available:\n" - "'ID01', 'ID01SPEC', 'ID01BLISS', 'P10' , 'SIXS2022'" + "'ID01', 'ID01SPEC', 'ID01BLISS', 'P10', 'P10EH2', 'SIXS2022' " + "and NanoMAX." ) def cxi_to_xu(self) -> None: diff --git a/cdiutils/load/bliss.py b/cdiutils/load/bliss.py index 3a15aa7d..de8464c3 100644 --- a/cdiutils/load/bliss.py +++ b/cdiutils/load/bliss.py @@ -4,7 +4,6 @@ import hdf5plugin import silx.io.h5py_utils -from cdiutils.utils import CroppingHandler from cdiutils.load import Loader @@ -23,10 +22,10 @@ class BlissLoader(Loader): """ angle_names = { - "sample_outofplane_angle": "eta", - "sample_inplane_angle": "phi", - "detector_outofplane_angle": "delta", - "detector_inplane_angle": "nu" + "sample_outofplane_angle": "eta", + "sample_inplane_angle": "phi", + "detector_outofplane_angle": "delta", + "detector_inplane_angle": "nu" } def __init__( @@ -79,12 +78,7 @@ def load_detector_data( + f".1/measurement/{self.detector_name}" ) - if roi is None: - roi = tuple(slice(None) for i in range(3)) - elif len(roi) == 2: - roi = tuple([slice(None), roi[0], roi[1]]) - elif all(isinstance(e, int) for e in roi): - roi = CroppingHandler.roi_list_to_slices(roi) + roi = self._check_roi(roi) try: if binning_along_axis0: diff --git a/cdiutils/load/loader.py b/cdiutils/load/loader.py index 3e59ebe8..a1d8159f 100644 --- a/cdiutils/load/loader.py +++ b/cdiutils/load/loader.py @@ -2,6 +2,8 @@ import numpy as np +from cdiutils.utils import CroppingHandler + class Loader: """A generic class for loaders.""" @@ -86,6 +88,39 @@ def _check_load(data_or_path: np.ndarray | str) -> np.ndarray: "parameter provide a path, np.ndarray or leave it to None" ) + @staticmethod + def _check_roi(roi: tuple = None) -> tuple[slice]: + """ + Utility function to check if a region of interest (roi) was + parsed correctly. + + Args: + roi (tuple, optional): the roi, a tuple of slices. + len = 2 or len = 3 if tuple of slices. len = 4 or + len = 6 if tuple of int. Defaults to None. + + Raises: + ValueError: if roi does not correspond to tuple of slices + with len 2 or 3 or tuple of int wit len 4 or 6. + + Returns: + tuple[slice]: the prepared roi. + """ + usage_text = ( + "Wrong value for roi (roi={}), roi should be:\n" + "\t - either a tuple of slices with len = 2 or len = 3" + "\t - either a tuple of int with len = 4 or len = 6" + ) + if roi is None: + return tuple(slice(None) for _ in range(3)) + if len(roi) == 2 or len(roi) == 3: + if all(isinstance(e, slice) for e in roi): + return (slice(None), roi[0], roi[1]) + if len(roi) == 4 or len(roi) == 6: + if all(isinstance(e, int) for e in roi): + return CroppingHandler.roi_list_to_slices(roi) + raise ValueError(usage_text.format(roi)) + @staticmethod def get_mask( channel: int = None, diff --git a/cdiutils/load/nanomax.py b/cdiutils/load/nanomax.py new file mode 100644 index 00000000..9104fb4c --- /dev/null +++ b/cdiutils/load/nanomax.py @@ -0,0 +1,106 @@ +""" +Loader for the Nanomax beamlien at MAXIV. +See: +https://www.maxiv.lu.se/beamlines-accelerators/beamlines/nanomax/ +""" + +import numpy as np + +from cdiutils.load import Loader +from cdiutils.load.bliss import safe + + +class NanoMaxLoader(Loader): + """ + A class to handle loading/reading .h5 files that were created at the + NanoMax beamline. + + Args: + experiment_file_path (str): path to the master file + used for the experiment. + detector_name (str): name of the detector. + sample_name (str, optional): name of the sample. Defaults + to None. + flat_field (np.ndarray | str, optional): flat field to + account for the non homogeneous counting of the + detector. Defaults to None. + alien_mask (np.ndarray | str, optional): array to mask the + aliens. Defaults to None. + """ + + angle_names = { + "sample_outofplane_angle": "gontheta", + "sample_inplane_angle": "gonphi", + "detector_outofplane_angle": "delta", + "detector_inplane_angle": "nu" + } + + def __init__( + self, + experiment_file_path: str, + detector_name: str = "eiger500k", + sample_name: str = None, + flat_field: np.ndarray | str = None, + alien_mask: np.ndarray | str = None, + **kwargs + ) -> None: + """ + Initialise NanoMaxLoader with experiment data file path and + detector information. + + Args: + experiment_file_path (str): path to the master file + used for the experiment. + detector_name (str): name of the detector. + sample_name (str, optional): name of the sample. Defaults + to None. + flat_field (np.ndarray | str, optional): flat field to + account for the non homogeneous counting of the + detector. Defaults to None. + alien_mask (np.ndarray | str, optional): array to mask the + aliens. Defaults to None. + """ + super(NanoMaxLoader, self).__init__(flat_field, alien_mask) + self.experiment_file_path = experiment_file_path + self.detector_name = detector_name + self.sample_name = sample_name + + @safe + def load_detector_data( + self, + scan: int, + sample_name: str = None, + roi: tuple[slice] = None, + binning_along_axis0: int = None, + binnig_method: str = "sum" + ) -> np.ndarray: + """ + Main method to load the detector data (collected intensity). + + Args: + scan (int): the scan number you want to load the data from. + sample_name (str, optional): the sample name for this scan. + Only used if self.sample_name is None. Defaults to None. + roi (tuple[slice], optional): the region of interest of the + detector to load. Defaults to None. + binning_along_axis0 (int, optional): whether to bin the data + along the rocking curve axis. Defaults to None. + binnig_method (str, optional): the method employed for the + binning. It can be sum or "mean". Defaults to "sum". + + Returns: + np.ndarray: the detector data. + """ + # # The self.h5file is initialised by the @safe decorator. + # h5file = self.h5file + # if sample_name is None: + # sample_name = self.sample_name + + # # Where to find the data. + # key_path = ( + # "_".join((sample_name, str(scan))) + # + f"/entry/measurement/{self.detector_name}" + # ) + + # roi = self._check_roi(roi) + pass diff --git a/cdiutils/load/p10.py b/cdiutils/load/p10.py index 544255d8..9f5e0417 100644 --- a/cdiutils/load/p10.py +++ b/cdiutils/load/p10.py @@ -127,10 +127,7 @@ def load_detector_data( path = self._get_file_path(scan, sample_name) key_path = "entry/data/data_000001" - if roi is None: - roi = tuple(slice(None) for i in range(3)) - elif len(roi) == 2: - roi = tuple([slice(None), roi[0], roi[1]]) + roi = self._check_roi(roi) with silx.io.h5py_utils.File(path) as h5file: if binning_along_axis0: diff --git a/cdiutils/load/sixs.py b/cdiutils/load/sixs.py index 0a47d327..51fcffc4 100644 --- a/cdiutils/load/sixs.py +++ b/cdiutils/load/sixs.py @@ -107,10 +107,7 @@ def load_detector_data( path = self._get_file_path(scan, sample_name) key_path = "com/scan_data/test_image" - if roi is None: - roi = tuple(slice(None) for i in range(3)) - elif len(roi) == 2: - roi = tuple([slice(None), roi[0], roi[1]]) + roi = self._check_roi(roi) with silx.io.h5py_utils.File(path) as h5file: if binning_along_axis0: diff --git a/cdiutils/load/spec.py b/cdiutils/load/spec.py index 3c7145ab..a8b0d98d 100644 --- a/cdiutils/load/spec.py +++ b/cdiutils/load/spec.py @@ -66,10 +66,7 @@ def load_detector_data( roi: tuple[slice] = None, binning_along_axis0=None ): - if roi is None: - roi = tuple(slice(None) for i in range(3)) - elif len(roi) == 2: - roi = tuple([slice(None), roi[0], roi[1]]) + roi = self._check_roi(roi) # TODO: implement flat_field consideration and binning_along_axis0 frame_ids = specfile[f"{scan}.1/measurement/{self.detector_name}"][...] diff --git a/cdiutils/plot/formatting.py b/cdiutils/plot/formatting.py index d9f8fc20..d5405c0d 100755 --- a/cdiutils/plot/formatting.py +++ b/cdiutils/plot/formatting.py @@ -38,7 +38,7 @@ def get_extents( to the y-axis extent in the matshow/imshow plot. """ absolute_extents = [ - voxel_size[i] * shape[i] // (2 if zero_centered else 1) + voxel_size[i] * shape[i] / (2 if zero_centered else 1) for i in range(3) ] return ( diff --git a/cdiutils/process/parameters.py b/cdiutils/process/parameters.py index ff0c7af6..ade23c6b 100644 --- a/cdiutils/process/parameters.py +++ b/cdiutils/process/parameters.py @@ -62,7 +62,7 @@ "support_update_period": 20, "support_smooth_width_begin": 2, "support_smooth_width_end": 1, - "support_post_expand": None, + "support_post_expand": None, # (-1, 1) "psf": "pseudo-voigt,0.5,0.1,10", "nb_raar": 500, "nb_hio": 300, diff --git a/cdiutils/process/phaser.py b/cdiutils/process/phaser.py index 325dec13..6ede6177 100644 --- a/cdiutils/process/phaser.py +++ b/cdiutils/process/phaser.py @@ -47,8 +47,8 @@ # support-related params "support_threshold": (0.15, 0.40), "smooth_width": (2, 0.5, 600), + "post_expand": None, # (-1, 1) "support_update_period": 50, - "post_expand": (1, -2, 1), "method": "rms", "force_shrink": False, "update_border_n": 0,