diff --git a/nxtomo/application/nxtomo.py b/nxtomo/application/nxtomo.py index 06c909b7046d0dc7041c9c08a97e1bb832b3bd14..d9bcc6fd97d8738176d602f057d488a81cab4819 100644 --- a/nxtomo/application/nxtomo.py +++ b/nxtomo/application/nxtomo.py @@ -1,12 +1,13 @@ """Define NXtomo application and related functions and classes""" +from __future__ import annotations + import logging import os from copy import deepcopy from datetime import datetime from functools import partial from operator import is_not -from typing import Optional, Union import h5py import numpy @@ -34,12 +35,12 @@ class NXtomo(NXobject): Class defining an NXTomo. His final goal is to save data to disk. - :param str node_name: node_name is used by the NXobject parent to order children when dumping it to file. + :param node_name: node_name is used by the NXobject parent to order children when dumping it to file. has NXtomo is expected to be the highest object in the hierachy. node_name will only be used for saving if no `data_path` is provided when calling 'save' function. - :param Optional[NXobject] parent: parent of this NXobject. Most likely None for NXtomo + :param parent: parent of this NXobject. Most likely None for NXtomo """ - def __init__(self, node_name: str = "", parent: Optional[NXobject] = None) -> None: + def __init__(self, node_name: str = "", parent: NXobject | None = None) -> None: if node_name not in (None, ""): deprecated_warning( type_="parameter", @@ -64,11 +65,11 @@ class NXtomo(NXobject): self._set_freeze(True) @property - def start_time(self) -> Optional[Union[datetime, str]]: + def start_time(self) -> datetime | str | None: return self._start_time @start_time.setter - def start_time(self, start_time: Optional[Union[datetime, str]]): + def start_time(self, start_time: datetime | str | None): if not isinstance(start_time, (type(None), datetime, str)): raise TypeError( f"start_time is expected ot be an instance of datetime or None. Not {type(start_time)}" @@ -76,11 +77,11 @@ class NXtomo(NXobject): self._start_time = start_time @property - def end_time(self) -> Optional[Union[datetime, str]]: + def end_time(self) -> datetime | str | None: return self._end_time @end_time.setter - def end_time(self, end_time: Optional[Union[datetime, str]]): + def end_time(self, end_time: datetime | str | None): if not isinstance(end_time, (type(None), datetime, str)): raise TypeError( f"end_time is expected ot be an instance of datetime or None. Not {type(end_time)}" @@ -88,11 +89,11 @@ class NXtomo(NXobject): self._end_time = end_time @property - def title(self) -> Optional[str]: + def title(self) -> str | None: return self._title @title.setter - def title(self, title: Optional[str]): + def title(self, title: str | None): if isinstance(title, numpy.ndarray): # handle diamond use case title = str(title) @@ -103,11 +104,11 @@ class NXtomo(NXobject): self._title = title @property - def instrument(self) -> Optional[NXinstrument]: + def instrument(self) -> NXinstrument | None: return self._instrument @instrument.setter - def instrument(self, instrument: Optional[NXinstrument]) -> None: + def instrument(self, instrument: NXinstrument | None) -> None: if not isinstance(instrument, (type(None), NXinstrument)): raise TypeError( f"instrument is expected ot be an instance of {NXinstrument} or None. Not {type(instrument)}" @@ -115,11 +116,11 @@ class NXtomo(NXobject): self._instrument = instrument @property - def sample(self) -> Optional[NXsample]: + def sample(self) -> NXsample | None: return self._sample @sample.setter - def sample(self, sample: Optional[NXsample]): + def sample(self, sample: NXsample | None): if not isinstance(sample, (type(None), NXsample)): raise TypeError( f"sample is expected ot be an instance of {NXsample} or None. Not {type(sample)}" @@ -127,11 +128,11 @@ class NXtomo(NXobject): self._sample = sample @property - def control(self) -> Optional[NXmonitor]: + def control(self) -> NXmonitor | None: return self._control @control.setter - def control(self, control: Optional[NXmonitor]) -> None: + def control(self, control: NXmonitor | None) -> None: if not isinstance(control, (type(None), NXmonitor)): raise TypeError( f"control is expected ot be an instance of {NXmonitor} or None. Not {type(control)}" @@ -139,14 +140,14 @@ class NXtomo(NXobject): self._control = control @property - def energy(self) -> Optional[float]: + def energy(self) -> float | None: """ incident energy in keV """ return self._energy @energy.setter - def energy(self, energy: Optional[float]) -> None: + def energy(self, energy: float | None) -> None: if not isinstance(energy, (type(None), float)): raise TypeError( f"energy is expected ot be an instance of {float} or None. Not {type(energy)}" @@ -154,11 +155,11 @@ class NXtomo(NXobject): self._energy.value = energy @property - def group_size(self) -> Optional[int]: + def group_size(self) -> int | None: return self._group_size @group_size.setter - def group_size(self, group_size: Optional[int]): + def group_size(self, group_size: int | None): if not ( isinstance(group_size, (type(None), int)) or (numpy.isscalar(group_size) and not isinstance(group_size, (str, bytes))) @@ -169,11 +170,11 @@ class NXtomo(NXobject): self._group_size = group_size @property - def bliss_original_files(self) -> Optional[tuple]: + def bliss_original_files(self) -> tuple | None: return self._bliss_original_files @bliss_original_files.setter - def bliss_original_files(self, files: Optional[Union[tuple, numpy.ndarray]]): + def bliss_original_files(self, files: tuple | numpy.ndarray | None): if isinstance(files, numpy.ndarray): files = tuple(files) if not isinstance(files, (type(None), tuple)): @@ -185,8 +186,8 @@ class NXtomo(NXobject): @docstring(NXobject) def to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: if data_path is None: data_path = "" @@ -305,9 +306,9 @@ class NXtomo(NXobject): """ Load NXtomo instance from file_path and data_path - :param str file_path: hdf5 file path containing the NXtomo - :param str data_path: location of the NXtomo - :param str detector_data_as: how to load detector data. Can be: + :param file_path: hdf5 file path containing the NXtomo + :param data_path: location of the NXtomo + :param detector_data_as: how to load detector data. Can be: * "as_virtual_source": load it as h5py's VirtualGroup * "as_data_url": load it as silx's DataUrl * "as_numpy_array": load them as a numpy array (warning: can be memory consuming since all the data will be loaded) @@ -387,7 +388,7 @@ class NXtomo(NXobject): Ensure some key datasets have the expected number of value :param NXtomo nx_tomo: nx_tomo to check - :param bool raises_error: if True then raise ValueError when some incoherent number of value are encounter (if missing will drop a warning only). + :param raises_error: if True then raise ValueError when some incoherent number of value are encounter (if missing will drop a warning only). if False then will drop warnings only """ if not isinstance(nx_tomo, NXtomo): @@ -498,7 +499,7 @@ class NXtomo(NXobject): """ concatenate a tuple of NXobject into a single NXobject - :param tuple nx_objects: + :param nx_objects: :return: NXtomo instance which is the concatenation of the nx_objects """ nx_objects = tuple(filter(partial(is_not, None), nx_objects)) @@ -601,7 +602,7 @@ class NXtomo(NXobject): self, file_path: str, data_path: str, - nexus_path_version: Optional[float] = None, + nexus_path_version: float | None = None, overwrite: bool = False, ) -> None: # Note: we overwrite save function for NXtomo in order to force 'data_path' to be provided. @@ -624,9 +625,9 @@ class NXtomo(NXobject): Note: Darks and flat will not be affected by this sub selection - :param float start_angle: left bound to apply selection - :param float stop_angle: right bound to apply selection - :param bool copy: if True then copy the nx_tomo. Else `nx_tomo` will be affected by the modifications + :param start_angle: left bound to apply selection + :param stop_angle: right bound to apply selection + :param copy: if True then copy the nx_tomo. Else `nx_tomo` will be affected by the modifications """ nx_tomo.check_can_select_from_rotation_angle() if copy: @@ -648,7 +649,7 @@ class NXtomo(NXobject): def sub_select_from_angle_offset( nx_tomo, start_angle_offset: float, - angle_interval: Optional[float], + angle_interval: float | None, shift_angles: bool, copy=True, ): @@ -657,11 +658,11 @@ class NXtomo(NXobject): Note: Darks and flat will not be affected by this sub selection - :param float start_angle_offset: offset to apply to start the selection. Expected in degree. Must be signed. + :param start_angle_offset: offset to apply to start the selection. Expected in degree. Must be signed. **The offset is always relative to the first projection angle value** - :param float angle_interval: interval covered by the selection. If None then will select until the end... - :param bool shift_angles: should we shift the angles of `-start_angle_offset` (once the selection is done) - :param bool copy: if True then copy the nx_tomo. Else `nx_tomo` will be affected by the modifications + :param angle_interval: interval covered by the selection. If None then will select until the end... + :param shift_angles: should we shift the angles of `-start_angle_offset` (once the selection is done) + :param copy: if True then copy the nx_tomo. Else `nx_tomo` will be affected by the modifications """ nx_tomo.check_can_select_from_rotation_angle() @@ -736,9 +737,8 @@ class NXtomo(NXobject): """ return the list of 'Nxtomo' entries at the root level - :param str file_path: + :param file_path: :return: list of valid Nxtomo node (ordered alphabetically) - :rtype: tuple ..note: entries are sorted to insure consistency """ @@ -789,18 +789,18 @@ class NXtomo(NXobject): def copy_nxtomo_file( input_file: str, output_file: str, - entries: Optional[tuple], + entries: tuple | None, overwrite: bool = False, vds_resolution="update", ): """ copy one or several NXtomo from a file to another file (solving relative links) - :param str input_file: nexus file for hich NXtomo have to be copied - :param str output_file: output file - :param optional[tuple] entries: entries to be copied. If set to None then all entries will be copied - :param bool overwrite: overwrite data path if already exists - :param str vds_resolution: How to solve virtual datasets. Options are: + :param input_file: nexus file for hich NXtomo have to be copied + :param output_file: output file + :param entries: entries to be copied. If set to None then all entries will be copied + :param overwrite: overwrite data path if already exists + :param vds_resolution: How to solve virtual datasets. Options are: * update: update Virtual source (relative) paths according to the new location of the file * remove: replace the virtual data source by copying directly the resulting dataset. Warning: in this case all the dataset will be load in memory In the future next option could be: diff --git a/nxtomo/io.py b/nxtomo/io.py index b4cd5a48937efa836894b4c1372668bdcbe28ef8..55ce407ab62a170b40a92a6da3247118cf9f7bd0 100644 --- a/nxtomo/io.py +++ b/nxtomo/io.py @@ -2,8 +2,9 @@ some io utils to handle `nexus <https://manual.nexusformat.org/index.html>`_ and `hdf5 <https://www.hdfgroup.org/solutions/hdf5/>`_ with `h5py <https://www.h5py.org/>`_ """ +from __future__ import annotations + import logging -from typing import Optional import os from contextlib import contextmanager import h5py._hl.selections as selection @@ -19,7 +20,7 @@ _logger = logging.getLogger(__name__) _DEFAULT_SWMR_MODE = None -def get_swmr_mode() -> Optional[bool]: +def get_swmr_mode() -> bool | None: """ Return True if the swmr should be used in the tomoools scope """ @@ -41,10 +42,10 @@ def check_virtual_sources_exist(fname, data_path): """ Check that a virtual dataset points to actual data. - :param str fname: HDF5 file path - :param str data_path: Path within the HDF5 file + :param fname: HDF5 file path + :param data_path: Path within the HDF5 file - :return bool res: Whether the virtual dataset points to actual data. + :return res: Whether the virtual dataset points to actual data. """ with hdf5_open(fname) as f: if data_path not in f: @@ -66,13 +67,12 @@ def check_virtual_sources_exist(fname, data_path): return True -def from_data_url_to_virtual_source(url: DataUrl, target_path: Optional[str]) -> tuple: +def from_data_url_to_virtual_source(url: DataUrl, target_path: str | None) -> tuple: """ convert a DataUrl to a set (as tuple) of h5py.VirtualSource - :param DataUrl url: url to be converted to a virtual source. It must target a 2D detector + :param url: url to be converted to a virtual source. It must target a 2D detector :return: (h5py.VirtualSource, tuple(shape of the virtual source), numpy.drype: type of the dataset associated with the virtual source) - :rtype: tuple """ if not isinstance(url, DataUrl): raise TypeError( @@ -115,9 +115,8 @@ def from_virtual_source_to_data_url(vs: h5py.VirtualSource) -> DataUrl: """ convert a h5py.VirtualSource to a DataUrl - :param h5py.VirtualSource vs: virtual source to be converted to a DataUrl + :param vs: virtual source to be converted to a DataUrl :return: url - :rtype: DataUrl """ if not isinstance(vs, h5py.VirtualSource): raise TypeError( @@ -133,7 +132,7 @@ def cwd_context(new_cwd=None): create a context with 'new_cwd'. on entry update current working directory to 'new_cwd' and reset previous 'working_directory' at exit - :param Optional[str] new_cwd: current working directory to use in the context + :param new_cwd: current working directory to use in the context """ try: curdir = os.getcwd() @@ -156,10 +155,9 @@ def to_target_rel_path(file_path: str, target_path: str) -> str: cast file_path to a relative path according to target_path. This is used to deduce h5py.VirtualSource path - :param str file_path: file path to be moved to relative - :param str target_path: target used as 'reference' to get relative path + :param file_path: file path to be moved to relative + :param target_path: target used as 'reference' to get relative path :return: relative path of file_path compared to target_path - :rtype: str """ if file_path == target_path or os.path.abspath(file_path) == os.path.abspath( target_path diff --git a/nxtomo/nxobject/nxdetector.py b/nxtomo/nxobject/nxdetector.py index 783efd4f837b3fc66775742003e84199e8d41a65..9c65155a721bd723aae6060a86cecfe2042d027f 100644 --- a/nxtomo/nxobject/nxdetector.py +++ b/nxtomo/nxobject/nxdetector.py @@ -2,10 +2,12 @@ module for handling a `nxdetector <https://manual.nexusformat.org/classes/base_classes/NXdetector.html>`_ """ +from __future__ import annotations + import os +from typing import Iterable from functools import partial from operator import is_not -from typing import Iterable, Optional, Union import numpy import h5py @@ -94,18 +96,18 @@ class NXdetector(NXobject): def __init__( self, node_name="detector", - parent: Optional[NXobject] = None, - field_of_view: Optional[FOV] = None, - expected_dim: Optional[tuple] = None, + parent: NXobject | None = None, + field_of_view: FOV | None = None, + expected_dim: tuple | None = None, ) -> None: """ representation of `nexus nxdetector <https://manual.nexusformat.org/classes/base_classes/NXdetector.html>`_ Detector of the acquisition. - :param str node_name: name of the detector in the hierarchy - :param Optional[NXObject] parent: parent in the nexus hierarchy - :param Optional[FOV] field_of_view: field of view of the detector - if know. - :param Optional[tuple] expected_dim: user can provide expected dimesions as a tuple of int to be checked when data is set + :param node_name: name of the detector in the hierarchy + :param parent: parent in the nexus hierarchy + :param field_of_view: field of view of the detector - if know. + :param expected_dim: user can provide expected dimensions as a tuple of int to be checked when data is set """ super().__init__(node_name=node_name, parent=parent) self._set_freeze(False) @@ -133,7 +135,7 @@ class NXdetector(NXobject): self._set_freeze(True) @property - def data(self) -> Optional[Union[numpy.ndarray, tuple]]: + def data(self) -> numpy.ndarray | tuple | None: """ detector data (frames). can be None, a numpy array or a list of DataUrl xor h5py Virtual Source @@ -141,7 +143,7 @@ class NXdetector(NXobject): return self._data @data.setter - def data(self, data: Optional[Union[numpy.ndarray, tuple]]): + def data(self, data: numpy.ndarray | tuple | None): if isinstance(data, (tuple, list)) or ( isinstance(data, numpy.ndarray) and data.ndim == 1 @@ -172,7 +174,7 @@ class NXdetector(NXobject): self._data = data @property - def x_pixel_size(self) -> Optional[float]: + def x_pixel_size(self) -> float | None: """ x pixel size as a field with a unit (get a value and a unit - default unit is SI). Know as 'x sample pixel size' in some application @@ -180,7 +182,7 @@ class NXdetector(NXobject): return self._x_pixel_size @x_pixel_size.setter - def x_pixel_size(self, x_pixel_size: Optional[float]) -> None: + def x_pixel_size(self, x_pixel_size: float | None) -> None: if not isinstance(x_pixel_size, (type(None), float)): raise TypeError( f"x_pixel_size is expected ot be an instance of {float} or None. Not {type(x_pixel_size)}" @@ -188,7 +190,7 @@ class NXdetector(NXobject): self._x_pixel_size.value = x_pixel_size @property - def y_pixel_size(self) -> Optional[float]: + def y_pixel_size(self) -> float | None: """ y pixel size as a field with a unit (get a value and a unit - default unit is SI). Know as 'y sample pixel size' in some application @@ -196,7 +198,7 @@ class NXdetector(NXobject): return self._y_pixel_size @y_pixel_size.setter - def y_pixel_size(self, y_pixel_size: Optional[float]) -> None: + def y_pixel_size(self, y_pixel_size: float | None) -> None: if not isinstance(y_pixel_size, (type(None), float)): raise TypeError( f"y_pixel_size is expected ot be an instance of {float} or None. Not {type(y_pixel_size)}" @@ -218,7 +220,7 @@ class NXdetector(NXobject): return DetZFlipTransformation(flip=True) in self.transformations.transformations @x_flipped.setter - def x_flipped(self, flipped: Optional[bool]): + def x_flipped(self, flipped: bool | None): deprecated_warning( type_="property", name="x_flipped", @@ -228,7 +230,7 @@ class NXdetector(NXobject): ) self.set_transformation_from_x_flipped(flipped) - def set_transformation_from_x_flipped(self, flipped: Optional[bool]): + def set_transformation_from_x_flipped(self, flipped: bool | None): """Util function to set transformation from x_flipped simple bool. Used for backward compatibility and convenience. """ @@ -271,7 +273,7 @@ class NXdetector(NXobject): ) self.set_transformation_from_y_flipped(flipped=flipped) - def set_transformation_from_y_flipped(self, flipped: Optional[bool]): + def set_transformation_from_y_flipped(self, flipped: bool | None): # WARNING: moving from two simple boolean to full NXtransformations make the old API very weak. It should be removed # soon (but we want to keep the API for at least one release). This is expected to fail except if you stick to {x,y} flips if isinstance(flipped, numpy.bool_): @@ -288,14 +290,14 @@ class NXdetector(NXobject): self.transformations.add_transformation(DetYFlipTransformation(flip=flipped)) @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """ sample / detector distance as a field with unit (default SI). """ return self._distance @distance.setter - def distance(self, distance: Optional[float]) -> None: + def distance(self, distance: float | None) -> None: if not isinstance(distance, (type(None), float)): raise TypeError( f"distance is expected ot be an instance of {float} or None. Not {type(distance)}" @@ -303,40 +305,38 @@ class NXdetector(NXobject): self._distance.value = distance @property - def field_of_view(self) -> Optional[FieldOfView]: + def field_of_view(self) -> FieldOfView | None: """ detector :class:`~nxtomo.nxobject.nxdetector.FieldOfView` """ return self._field_of_view @field_of_view.setter - def field_of_view( - self, field_of_view: Optional[Union[FieldOfView, str, None]] - ) -> None: + def field_of_view(self, field_of_view: FieldOfView | str | None) -> None: if field_of_view is not None: field_of_view = FOV.from_value(field_of_view) self._field_of_view = field_of_view @property - def count_time(self) -> Optional[numpy.ndarray]: + def count_time(self) -> numpy.ndarray | None: """ count time for each frame """ return self._count_time @count_time.setter - def count_time(self, count_time: Optional[Iterable]): + def count_time(self, count_time: Iterable | None): self._count_time.value = cast_and_check_array_1D(count_time, "count_time") @property - def estimated_cor_from_motor(self) -> Optional[float]: + def estimated_cor_from_motor(self) -> float | None: """ hint of center of rotation in pixel read from motor (when possible) """ return self._estimated_cor_from_motor @estimated_cor_from_motor.setter - def estimated_cor_from_motor(self, estimated_cor_from_motor: Optional[float]): + def estimated_cor_from_motor(self, estimated_cor_from_motor: float | None): if not isinstance(estimated_cor_from_motor, (type(None), float)): raise TypeError( f"estimated_cor_from_motor is expected to be None, or an instance of float. Not {type(estimated_cor_from_motor)}" @@ -344,14 +344,14 @@ class NXdetector(NXobject): self._estimated_cor_from_motor = estimated_cor_from_motor @property - def image_key_control(self) -> Optional[numpy.ndarray]: + def image_key_control(self) -> numpy.ndarray | None: """ :class:`~nxtomo.nxobject.nxdetector.ImageKey` for each frames """ return self._image_key_control @image_key_control.setter - def image_key_control(self, control_image_key: Optional[Iterable]): + def image_key_control(self, control_image_key: Iterable | None): control_image_key = cast_and_check_array_1D( control_image_key, "control_image_key" ) @@ -364,7 +364,7 @@ class NXdetector(NXobject): ) @property - def image_key(self) -> Optional[numpy.ndarray]: + def image_key(self) -> numpy.ndarray | None: """ :class:`~nxtomo.nxobject.nxdetector.ImageKey` for each frames. Replace all :class:`~nxtomo.nxobject,nxdetector.ImageKey.ALIGNMENT` by :class:`~nxtomo.nxobject,nxdetector.ImageKey.PROJECTION` to fulfil nexus standard """ @@ -378,36 +378,36 @@ class NXdetector(NXobject): return control_image_key @property - def tomo_n(self) -> Optional[int]: + def tomo_n(self) -> int | None: """ expected number of :class:`~nxtomo.nxobject,nxdetector.ImageKey.PROJECTION` frames """ return self._tomo_n @tomo_n.setter - def tomo_n(self, tomo_n: Optional[int]): + def tomo_n(self, tomo_n: int | None): self._tomo_n = tomo_n @property - def group_size(self) -> Optional[int]: + def group_size(self) -> int | None: """ number of acquisition for the dataset """ return self._group_size @group_size.setter - def group_size(self, group_size: Optional[int]): + def group_size(self, group_size: int | None): self._group_size = group_size @property - def roi(self) -> Optional[tuple]: + def roi(self) -> tuple | None: """ detector region of interest as x0,y0,x1,y1 """ return self._roi @roi.setter - def roi(self, roi: Optional[tuple]) -> None: + def roi(self, roi: tuple | None) -> None: if roi is None: self._roi = None elif not isinstance(roi, (tuple, list, numpy.ndarray)): @@ -422,8 +422,8 @@ class NXdetector(NXobject): @docstring(NXobject) def to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: nexus_paths = get_nexus_path(nexus_path_version) nexus_detector_paths = nexus_paths.nx_detector_paths @@ -504,8 +504,8 @@ class NXdetector(NXobject): def _data_to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: nexus_paths = get_nexus_path(nexus_path_version) nexus_detector_paths = nexus_paths.nx_detector_paths @@ -920,20 +920,20 @@ class NXdetectorWithUnit(NXdetector): node_name="detector", parent=None, field_of_view=None, - expected_dim: Optional[tuple] = None, + expected_dim: tuple | None = None, ) -> None: super().__init__(node_name, parent, field_of_view, expected_dim) self._data = ElementWithUnit(default_unit=default_unit) @property @docstring(NXdetector) - def data(self) -> Union[numpy.ndarray, tuple]: + def data(self) -> numpy.ndarray | tuple: """data can be None, a numpy array or a list of DataUrl xor h5py Virtual Source""" return self._data @data.setter @docstring(NXdetector) - def data(self, data: Optional[Union[numpy.ndarray, tuple]]): + def data(self, data: numpy.ndarray | tuple | None): if isinstance(data, numpy.ndarray): if ( self._expected_dim is not None @@ -960,8 +960,8 @@ class NXdetectorWithUnit(NXdetector): def _data_to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: nexus_paths = get_nexus_path(nexus_path_version) nexus_detector_paths = nexus_paths.nx_detector_paths diff --git a/nxtomo/nxobject/nxinstrument.py b/nxtomo/nxobject/nxinstrument.py index 3ac7c61121e5629b686ebd369c3deea3155a396d..b54b8d1b40118420d90ccbbc369a5610fb450aa6 100644 --- a/nxtomo/nxobject/nxinstrument.py +++ b/nxtomo/nxobject/nxinstrument.py @@ -2,10 +2,11 @@ module for handling a `nxinstrument <https://manual.nexusformat.org/classes/base_classes/NXinstrument.html>`_ """ +from __future__ import annotations + import logging from functools import partial from operator import is_not -from typing import Optional from silx.utils.proxy import docstring from silx.io.utils import open as open_hdf5 @@ -22,15 +23,15 @@ _logger = logging.getLogger(__name__) class NXinstrument(NXobject): def __init__( - self, node_name: str = "instrument", parent: Optional[NXobject] = None + self, node_name: str = "instrument", parent: NXobject | None = None ) -> None: """ representation of `nexus NXinstrument <https://manual.nexusformat.org/classes/base_classes/NXinstrument.html>`_. Collection of the components of the instrument or beamline. - :param str node_name: name of the detector in the hierarchy - :param Optional[NXObject] parent: parent in the nexus hierarchy + :param node_name: name of the detector in the hierarchy + :param parent: parent in the nexus hierarchy """ super().__init__(node_name=node_name, parent=parent) self._set_freeze(False) @@ -51,44 +52,44 @@ class NXinstrument(NXobject): self._set_freeze(True) @property - def detector(self) -> Optional[NXdetector]: + def detector(self) -> NXdetector | None: """ :class:`~nxtomo.nxobject.nxdetector.NXdetector` """ return self._detector @detector.setter - def detector(self, detector: Optional[NXdetector]): + def detector(self, detector: NXdetector | None): if not isinstance(detector, (NXdetector, type(None))): raise TypeError( - f"detector is expected to be None or an instance of NXdetecetor. Not {type(detector)}" + f"detector is expected to be None or an instance of NXdetector. Not {type(detector)}" ) self._detector = detector @property - def diode(self) -> Optional[NXdetector]: + def diode(self) -> NXdetector | None: """ :class:`~nxtomo.nxobject.nxdetector.NXdetector` """ return self._diode @diode.setter - def diode(self, diode: Optional[NXdetector]): + def diode(self, diode: NXdetector | None): if not isinstance(diode, (NXdetector, type(None))): raise TypeError( - f"diode is expected to be None or an instance of NXdetecetor. Not {type(diode)}" + f"diode is expected to be None or an instance of NXdetector. Not {type(diode)}" ) self._diode = diode @property - def source(self) -> Optional[NXsource]: + def source(self) -> NXsource | None: """ :class:`~nxtomo.nxobject.nxdetector.NXsource` """ return self._source @source.setter - def source(self, source: Optional[NXsource]) -> None: + def source(self, source: NXsource | None) -> None: if not isinstance(source, (NXsource, type(None))): raise TypeError( f"source is expected to be None or an instance of NXsource. Not {type(source)}" @@ -96,12 +97,12 @@ class NXinstrument(NXobject): self._source = source @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """instrument name like BM00""" return self._name @name.setter - def name(self, name: Optional[str]) -> None: + def name(self, name: str | None) -> None: if not isinstance(name, (str, type(None))): raise TypeError( f"name is expected to be None or an instance of str. Not {type(name)}" @@ -111,8 +112,8 @@ class NXinstrument(NXobject): @docstring(NXobject) def to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: nexus_paths = get_nexus_paths(nexus_path_version) nexus_instrument_paths = nexus_paths.nx_instrument_paths diff --git a/nxtomo/nxobject/nxmonitor.py b/nxtomo/nxobject/nxmonitor.py index 102a783c9105a03205264cd3fef00ba150e8d6be..1d4bfb39e5138e1555903293aa0206e1d8b0ae99 100644 --- a/nxtomo/nxobject/nxmonitor.py +++ b/nxtomo/nxobject/nxmonitor.py @@ -2,9 +2,10 @@ module for handling a `nxmonitor <https://manual.nexusformat.org/classes/base_classes/NXmonitor.html>`_ """ +from __future__ import annotations + from functools import partial from operator import is_not -from typing import Optional, Union import numpy from silx.utils.proxy import docstring @@ -17,13 +18,13 @@ from pyunitsystem import ElectricCurrentSystem class NXmonitor(NXobject): - def __init__(self, node_name="control", parent: Optional[NXobject] = None) -> None: + def __init__(self, node_name="control", parent: NXobject | None = None) -> None: """ representation of `nexus NXmonitor <https://manual.nexusformat.org/classes/base_classes/NXmonitor.html>`_. A monitor of incident beam data. - :param str node_name: name of the detector in the hierarchy - :param Optional[NXObject] parent: parent in the nexus hierarchy + :param node_name: name of the detector in the hierarchy + :param parent: parent in the nexus hierarchy """ super().__init__(node_name=node_name, parent=parent) self._set_freeze(False) @@ -31,7 +32,7 @@ class NXmonitor(NXobject): self._set_freeze(True) @property - def data(self) -> Optional[numpy.ndarray]: + def data(self) -> numpy.ndarray | None: """ monitor data. In the case of NXtomo it expects to contains machine electric current for each frame @@ -39,7 +40,7 @@ class NXmonitor(NXobject): return self._data @data.setter - def data(self, data: Optional[Union[numpy.ndarray, list, tuple]]): + def data(self, data: numpy.ndarray | list | tuple | None): if isinstance(data, (tuple, list)): if len(data) == 0: data = None @@ -58,8 +59,8 @@ class NXmonitor(NXobject): @docstring(NXobject) def to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: nexus_paths = get_nexus_paths(nexus_path_version) monitor_nexus_paths = nexus_paths.nx_monitor_paths diff --git a/nxtomo/nxobject/nxobject.py b/nxtomo/nxobject/nxobject.py index 93d7388be0c245b19193502c4b8376bbfdf3976f..cd8c0cb1f3ff399fd013857028cd89ea5e407d76 100644 --- a/nxtomo/nxobject/nxobject.py +++ b/nxtomo/nxobject/nxobject.py @@ -2,9 +2,10 @@ module for handling a `nxobject <https://manual.nexusformat.org/classes/base_classes/NXobject.html>`_ """ +from __future__ import annotations + import os import logging -from typing import Optional import h5py from silx.io.dictdump import dicttonx @@ -27,7 +28,7 @@ class ElementWithUnit: """ Util class to let the user define a unit with a value - :param Unit default_unit: default unit of the element + :param default_unit: default unit of the element """ if not isinstance(default_unit, Unit): @@ -37,7 +38,7 @@ class ElementWithUnit: self._unit_type = type(default_unit) @property - def unit(self) -> Optional[float]: + def unit(self) -> float | None: """ unit as a float to cast it to SI """ @@ -91,14 +92,14 @@ class NXobject: A monitor of incident beam data. :param node_name: name of the detector in the hierarchy - :param Optional[NXObject] parent: parent in the nexus hierarchy + :param parent: parent in the nexus hierarchy """ if not isinstance(node_name, str): raise TypeError( f"name is expected to be an instance of str. Not {type(node_name)}" ) if "/" in node_name: - # make sure there is no '/' character. This is reserved to define the NXobject hierachy + # make sure there is no '/' character. This is reserved to define the NXobject hierarchy raise ValueError( "'/' found in 'node_name' parameter. This is a reserved character. Please change the name" ) @@ -110,7 +111,7 @@ class NXobject: self.__isfrozen = freeze @property - def parent(self): # -> Optional[NXobject]: + def parent(self): # -> NXobject | None: """ :class:`~nxtomo.nxobject.nxobject.NXobject` parent in the hierarchy """ @@ -167,17 +168,17 @@ class NXobject: def save( self, file_path: str, - data_path: Optional[str] = None, - nexus_path_version: Optional[float] = None, + data_path: str | None = None, + nexus_path_version: float | None = None, overwrite: bool = False, ) -> None: """ save NXtomo to disk. - :param str file_path: hdf5 file - :param str data_path: location to the NXobject. If not provided will be stored under node_name if provided (and valid) + :param file_path: hdf5 file + :param data_path: location to the NXobject. If not provided will be stored under node_name if provided (and valid) :param nexus_path_version: Optional nexus version as float. If the saving must be done **not** using the latest version - :param bool overwrite: if the data_path in file_path is already existing overwrite it. Else raise will raise an error + :param overwrite: if the data_path in file_path is already existing overwrite it. Else raise will raise an error """ if data_path == "/": _logger.warning( @@ -324,14 +325,14 @@ class NXobject: def to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: """ - convert the NXobject to an nx dict. Dictionnary that we can dump to hdf5 file + convert the NXobject to an nx dict. Dictionary that we can dump to hdf5 file - :param Optional[float] nexus_path_version: version of the nexus path version to use - :param Optional[str] data_path: can be provided to create some link in the file + :param nexus_path_version: version of the nexus path version to use + :param data_path: can be provided to create some link in the file """ raise NotImplementedError("Base class") @@ -395,6 +396,6 @@ class NXobject: """ concatenate a tuple of NXobject into a single NXobject :param Iterable Nx-objects: nx object to concatenate - :param str node_name: name of the node to create. Parent must be handled manually for now. + :param node_name: name of the node to create. Parent must be handled manually for now. """ raise NotImplementedError("Base class") diff --git a/nxtomo/nxobject/nxsample.py b/nxtomo/nxobject/nxsample.py index c7d33953cae9ee5c365138c3ba7b15c0ded6f22d..1ba6a452f0da760e5b5a8a80a7cf0db46a5f9416 100644 --- a/nxtomo/nxobject/nxsample.py +++ b/nxtomo/nxobject/nxsample.py @@ -2,10 +2,12 @@ module for handling a `nxsample <https://manual.nexusformat.org/classes/base_classes/NXsample.html>`_ """ +from __future__ import annotations + import logging from functools import partial from operator import is_not -from typing import Iterable, Optional, Tuple +from typing import Iterable import numpy from silx.utils.proxy import docstring @@ -20,13 +22,13 @@ _logger = logging.getLogger(__name__) class NXsample(NXobject): - def __init__(self, node_name="sample", parent: Optional[NXobject] = None) -> None: + def __init__(self, node_name="sample", parent: NXobject | None = None) -> None: """ representation of `nexus NXsample <https://manual.nexusformat.org/classes/base_classes/NXsample.html>`_. A monitor of incident beam data. - :param str node_name: name of the detector in the hierarchy - :param Optional[NXObject] parent: parent in the nexus hierarchy + :param node_name: name of the detector in the hierarchy + :param parent: parent in the nexus hierarchy """ super().__init__(node_name=node_name, parent=parent) self._set_freeze(False) @@ -42,65 +44,65 @@ class NXsample(NXobject): self._set_freeze(True) @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """sample name""" return self._name @name.setter - def name(self, name: Optional[str]) -> None: + def name(self, name: str | None) -> None: if not isinstance(name, (type(None), str)): raise TypeError(f"name is expected to be None or str not {type(name)}") self._name = name @property - def rotation_angle(self) -> Optional[numpy.ndarray]: + def rotation_angle(self) -> numpy.ndarray | None: """sample rotation angle. One per frame""" return self._rotation_angle @rotation_angle.setter - def rotation_angle(self, rotation_angle: Optional[Iterable]): + def rotation_angle(self, rotation_angle: Iterable | None): self._rotation_angle = cast_and_check_array_1D(rotation_angle, "rotation_angle") @property - def x_translation(self) -> Optional[numpy.ndarray]: - """sample translation along x. See `modelization at esrf <https://tomo.gitlab-pages.esrf.fr/ebs-tomo/master/modelization.html>`_ for more information""" + def x_translation(self) -> numpy.ndarray | None: + """sample translation along x. See `modelling at esrf <https://tomo.gitlab-pages.esrf.fr/ebs-tomo/master/modelization.html>`_ for more information""" return self._x_translation @x_translation.setter - def x_translation(self, x_translation: Optional[Iterable]): + def x_translation(self, x_translation: Iterable | None): self._x_translation.value = cast_and_check_array_1D( x_translation, "x_translation" ) @property - def y_translation(self) -> Optional[numpy.ndarray]: - """sample translation along y. See `modelization at esrf <https://tomo.gitlab-pages.esrf.fr/ebs-tomo/master/modelization.html>`_ for more information""" + def y_translation(self) -> numpy.ndarray | None: + """sample translation along y. See `modelling at esrf <https://tomo.gitlab-pages.esrf.fr/ebs-tomo/master/modelization.html>`_ for more information""" return self._y_translation @y_translation.setter - def y_translation(self, y_translation: Optional[Iterable]): + def y_translation(self, y_translation: Iterable | None): self._y_translation.value = cast_and_check_array_1D( y_translation, "y_translation" ) @property - def z_translation(self) -> Optional[numpy.ndarray]: - """sample translation along z. See `modelization at esrf <https://tomo.gitlab-pages.esrf.fr/ebs-tomo/master/modelization.html>`_ for more information""" + def z_translation(self) -> numpy.ndarray | None: + """sample translation along z. See `modelling at esrf <https://tomo.gitlab-pages.esrf.fr/ebs-tomo/master/modelization.html>`_ for more information""" return self._z_translation @z_translation.setter - def z_translation(self, z_translation: Optional[Iterable]): + def z_translation(self, z_translation: Iterable | None): self._z_translation.value = cast_and_check_array_1D( z_translation, "z_translation" ) @property - def transformations(self) -> Tuple[NXtransformations]: + def transformations(self) -> tuple[NXtransformations]: """detector transformations as `NXtransformations <https://manual.nexusformat.org/classes/base_classes/NXtransformations.html>`_""" return self._transformations @transformations.setter - def transformations(self, transformations: Tuple[NXtransformations]): + def transformations(self, transformations: tuple[NXtransformations]): if not isinstance(transformations, tuple): raise TypeError for transformation in transformations: @@ -110,8 +112,8 @@ class NXsample(NXobject): @docstring(NXobject) def to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: nexus_paths = get_nexus_paths(nexus_path_version) nexus_sample_paths = nexus_paths.nx_sample_paths diff --git a/nxtomo/nxobject/nxsource.py b/nxtomo/nxobject/nxsource.py index baad36acc39a2a4a8a1458ca27570baf7c289f7a..0a23c8e440aa10e3e0001adb8515f0358c2265ce 100644 --- a/nxtomo/nxobject/nxsource.py +++ b/nxtomo/nxobject/nxsource.py @@ -2,10 +2,11 @@ module for handling a `nxsource <https://manual.nexusformat.org/classes/base_classes/NXsource.html>`_ """ +from __future__ import annotations + import logging from functools import partial from operator import is_not -from typing import Optional, Union import numpy from silx.utils.enum import Enum as _Enum @@ -82,14 +83,14 @@ class NXsource(NXobject): self._set_freeze(True) @property - def name(self) -> Union[None, str]: + def name(self) -> None | str: """ source name """ return self._name @name.setter - def name(self, source_name: Union[str, None]): + def name(self, source_name: str | None): if isinstance(source_name, numpy.ndarray): # handle Diamond Dataset source_name = source_name.tostring() @@ -102,14 +103,14 @@ class NXsource(NXobject): self._name = source_name @property - def type(self) -> Optional[SourceType]: + def type(self) -> SourceType | None: """ source type as :class:`~nxtomo.nxobject.nxsource.SourceType` """ return self._type @type.setter - def type(self, type_: Union[None, str, SourceType]): + def type(self, type_: None | str | SourceType): if type_ is None: self._type = None else: @@ -117,14 +118,14 @@ class NXsource(NXobject): self._type = type_ @property - def probe(self) -> Optional[ProbeType]: + def probe(self) -> ProbeType | None: """ probe as :class:`~nxtomo.nxobject.nxsource.ProbeType` """ return self._probe @probe.setter - def probe(self, probe: Union[None, str, ProbeType]): + def probe(self, probe: None | str | ProbeType): if probe is None: self._probe = None else: @@ -136,8 +137,8 @@ class NXsource(NXobject): @docstring(NXobject) def to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: nexus_paths = get_nexus_paths(nexus_path_version) nexus_source_paths = nexus_paths.nx_source_paths diff --git a/nxtomo/nxobject/nxtransformations.py b/nxtomo/nxobject/nxtransformations.py index 123b750097200bb3c3739ec7efe387efc914bbae..337094995f18ffe02ecceb4e54cf610d373868e2 100644 --- a/nxtomo/nxobject/nxtransformations.py +++ b/nxtomo/nxobject/nxtransformations.py @@ -2,7 +2,8 @@ module for handling a `nxtransformations <https://manual.nexusformat.org/classes/base_classes/nxtransformations.html#nxtransformations>`_ """ -import typing +from __future__ import annotations + import logging import h5py @@ -31,13 +32,13 @@ class NXtransformations(NXobject): For tomotools the first usage would be to allow users to provide more metadata to tag acquisition (like 'detector has been rotate' of 90 degree...) - :param str node_name: name of the detector in the hierarchy - :param Optional[NXObject] parent: parent in the nexus hierarchy + :param node_name: name of the detector in the hierarchy + :param parent: parent in the nexus hierarchy """ super().__init__(node_name, parent) self._set_freeze(False) self._transformations = dict() - # dict with axis_name as value and Transforamtion as value. Simplify handling compared to a tuple / list / set and ensure the axis_name is unique + # dict with axis_name as value and Transformation as value. Simplify handling compared to a tuple / list / set and ensure the axis_name is unique self._set_freeze(True) @property @@ -50,7 +51,7 @@ class NXtransformations(NXobject): @transformations.setter def transformations(self, transformations: tuple): """ - :param dict transformations: dict as [str, Transformation] + :param transformations: dict as [str, Transformation] """ # check type if not isinstance(transformations, (tuple, list)): @@ -62,7 +63,7 @@ class NXtransformations(NXobject): raise TypeError( f"element are expected to be instances of {Transformation}. {type(transformation)} provided instead" ) - # convert it to a dict for conveniance + # convert it to a dict for convenience self._transformations = { transformation.axis_name: transformation for transformation in transformations @@ -78,10 +79,10 @@ class NXtransformations(NXobject): """ add a transformation to the existing one. - :param Transformation transformation: transformation to be added - :param bool overwrite: if a transformation with the same axis_name already exists then overwrite it - :param bool skip_if_exists: if a transformation with the same axis_name already exists then keep the existing one - :raises: KeyError, if a transforamtion with the same axis_name already registered + :param transformation: transformation to be added + :param overwrite: if a transformation with the same axis_name already exists then overwrite it + :param skip_if_exists: if a transformation with the same axis_name already exists then keep the existing one + :raises: KeyError, if a transformation with the same axis_name already registered """ if skip_if_exists is overwrite is True: raise ValueError( @@ -119,12 +120,12 @@ class NXtransformations(NXobject): @docstring(NXobject) def to_nx_dict( self, - nexus_path_version: typing.Optional[float] = None, - data_path: typing.Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, solve_empty_dependency: bool = False, ) -> dict: """ - :param bool append_gravity: If True all transformation without dependancy will be depending on a "gravity" Transformation which represent the gravity + :param append_gravity: If True all transformation without dependency will be depending on a "gravity" Transformation which represent the gravity """ if len(self._transformations) == 0: # if no transformation, avoid creating the group @@ -177,9 +178,7 @@ class NXtransformations(NXobject): return nx_dict @staticmethod - def load_from_file( - file_path: str, data_path: str, nexus_version: typing.Optional[float] - ): + def load_from_file(file_path: str, data_path: str, nexus_version: float | None): """ create an instance of :class:`~nxtomo.nxobject.nxtransformations,NXtransformations` and load it value from the given file and data path @@ -190,7 +189,7 @@ class NXtransformations(NXobject): ) def _load( - self, file_path: str, data_path: str, nexus_version: typing.Optional[float] + self, file_path: str, data_path: str, nexus_version: float | None ) -> NXobject: """ Create and load an NXmonitor from data on disk @@ -268,7 +267,7 @@ class NXtransformations(NXobject): return len(self.transformations) -def get_lr_flip(transformations: typing.Union[tuple, NXtransformations]) -> tuple: +def get_lr_flip(transformations: tuple | NXtransformations) -> tuple: """ check along all transformations if find Transformation matching 'LRTransformation' @@ -279,7 +278,7 @@ def get_lr_flip(transformations: typing.Union[tuple, NXtransformations]) -> tupl return _get_lr_flip(transformations) -def get_ud_flip(transformations: typing.Union[tuple, NXtransformations]) -> tuple: +def get_ud_flip(transformations: tuple | NXtransformations) -> tuple: """ check along all transformations if find Transformation matching 'UDTransformation' diff --git a/nxtomo/nxobject/test/test_nxobject.py b/nxtomo/nxobject/test/test_nxobject.py index 0a99509f1c9c45275aea028406a3963cfce02948..75687ceb7cdcf0f6ad554e7742c7a0e045cac20b 100644 --- a/nxtomo/nxobject/test/test_nxobject.py +++ b/nxtomo/nxobject/test/test_nxobject.py @@ -1,6 +1,6 @@ +from __future__ import annotations import os from tempfile import TemporaryDirectory -from typing import Optional import numpy import pytest @@ -31,8 +31,8 @@ class test_nx_object: class MyNXObject(NXobject): def to_nx_dict( self, - nexus_path_version: Optional[float] = None, - data_path: Optional[str] = None, + nexus_path_version: float | None = None, + data_path: str | None = None, ) -> dict: return { f"{self.path}/test": "toto", diff --git a/nxtomo/paths/nxtomo.py b/nxtomo/paths/nxtomo.py index 0e5cdcd1baee57b408ea0c7724e800696ca57daf..7dec28b92ee171ebbdfeab928be57ee5658a0a0d 100644 --- a/nxtomo/paths/nxtomo.py +++ b/nxtomo/paths/nxtomo.py @@ -1,7 +1,8 @@ """nexus path used to define a `NXtomo <https://manual.nexusformat.org/classes/base_classes/NXtomo.html>`_""" +from __future__ import annotations + import logging -from typing import Optional from silx.utils.deprecation import deprecated @@ -289,19 +290,19 @@ class NXtomo_PATH: return 0.02 @property - def SOURCE_NAME(self) -> Optional[str]: + def SOURCE_NAME(self) -> str | None: return None @property - def SOURCE_TYPE(self) -> Optional[str]: + def SOURCE_TYPE(self) -> str | None: return None @property - def SOURCE_PROBE(self) -> Optional[str]: + def SOURCE_PROBE(self) -> str | None: return None @property - def INSTRUMENT_NAME(self) -> Optional[str]: + def INSTRUMENT_NAME(self) -> str | None: return None @property @@ -417,7 +418,7 @@ nx_tomo_path_v_1_3 = NXtomo_PATH_v_1_3() nx_tomo_path_latest = nx_tomo_path_v_1_3 -def get_paths(version: Optional[float]) -> NXtomo_PATH: +def get_paths(version: float | None) -> NXtomo_PATH: if version is None: version = LATEST_VERSION _logger.warning( diff --git a/nxtomo/utils/detectorsplitter.py b/nxtomo/utils/detectorsplitter.py index c427e5bd4fd3ec3bd6ed0f0e1909a541e92770b1..b8e7e39d6a5e8b9d9d55457a239f2b5d5421710d 100644 --- a/nxtomo/utils/detectorsplitter.py +++ b/nxtomo/utils/detectorsplitter.py @@ -2,9 +2,10 @@ module to split a NXtomo into several """ +from __future__ import annotations + import copy import logging -from typing import Optional, Union import h5py import h5py._hl.selections as selection @@ -28,7 +29,7 @@ class NXtomoDetectorDataSplitter: In order to start the processing it requires a correctly formed NXtomo (same number of image_key, rotation_angle...) This is required for the pcotomo acquisition. - :param :class:`~nxtomo.nxobject.nxobject.NXobject` nx_tomo: nx_tomo to be splitted + :param nx_tomo: nx_tomo to be splitted """ if not isinstance(nx_tomo, NXtomo): raise TypeError( @@ -41,12 +42,12 @@ class NXtomoDetectorDataSplitter: """ nx_tomo to be splitted - :param :class:`~nxtomo.nxobject.nxobject.NXobject` nx_tomo: nx_tomo to be splitted + :param nx_tomo: nx_tomo to be splitted """ return self._nx_tomo def split( - self, data_slice: slice, nb_part: Optional[int], tomo_n: Optional[int] = None + self, data_slice: slice, nb_part: int | None, tomo_n: int | None = None ) -> tuple: """ split the dataset targetted to have a set of h5py.VirtualSource. @@ -55,8 +56,8 @@ class NXtomoDetectorDataSplitter: request seems incoherent according to the number of projection and tomo_n then it will fall back on using tomo_n for it - :param int nb_part: in how many contiguous dataset the instruement.detector.data must be splitted. - :param int tomo_n: expected number of projection per NXtomo + :param nb_part: in how many contiguous dataset the instrument.detector.data must be splitted. + :param tomo_n: expected number of projection per NXtomo :raises: ValueError if the number of frame, image_key, x_translation... is incoherent. """ if nb_part is not None and not isinstance( @@ -267,7 +268,7 @@ class NXtomoDetectorDataSplitter: if section.start == section.stop: return () - def get_elmt_shape(elmt: Union[h5py.VirtualSource, DataUrl]) -> tuple: + def get_elmt_shape(elmt: h5py.VirtualSource | DataUrl) -> tuple: if isinstance(elmt, h5py.VirtualSource): return elmt.shape elif isinstance(elmt, DataUrl): @@ -278,7 +279,7 @@ class NXtomoDetectorDataSplitter: f"elmt must be a DataUrl or h5py.VirtualSource. Not {type(elmt)}" ) - def get_elmt_nb_frame(elmt: Union[h5py.VirtualSource, DataUrl]) -> int: + def get_elmt_nb_frame(elmt: h5py.VirtualSource | DataUrl) -> int: shape = get_elmt_shape(elmt) if len(shape) == 3: return shape[0] @@ -306,8 +307,8 @@ class NXtomoDetectorDataSplitter: return slice_1.start < slice_2.stop and slice_1.stop > slice_2.start def select( - elmt: Union[h5py.VirtualSource, DataUrl], region: slice - ) -> Union[h5py.VirtualSource, DataUrl]: + elmt: h5py.VirtualSource | DataUrl, region: slice + ) -> h5py.VirtualSource | DataUrl: """select a region on the elmt. Can return at most the elmt itself or a region of it""" elmt_n_frame = get_elmt_nb_frame(elmt) @@ -420,7 +421,7 @@ class NXtomoDetectorDataSplitter: return invalid_datasets - def _get_n_frames(self) -> Optional[int]: + def _get_n_frames(self) -> int | None: dataset = self.nx_tomo.instrument.detector.data if dataset is None: return None diff --git a/nxtomo/utils/frameappender.py b/nxtomo/utils/frameappender.py index 50b17652c2c1a050829b67057d3563edf876fcdb..d2e24c2b81a84915f7c665ece859c3ecfb833d6d 100644 --- a/nxtomo/utils/frameappender.py +++ b/nxtomo/utils/frameappender.py @@ -2,8 +2,9 @@ module to append frame to an hdf5 dataset (that can be virtual) """ +from __future__ import annotations + import os -from typing import Union import h5py import h5py._hl.selections as selection @@ -24,7 +25,7 @@ from nxtomo.utils.io import DatasetReader class FrameAppender: def __init__( self, - data: Union[numpy.ndarray, DataUrl], + data: numpy.ndarray | DataUrl, file_path: str, data_path: str, where: str, @@ -34,10 +35,10 @@ class FrameAppender: Class to insert 2D frame(s) to an existing dataset :param data: data to append - :param str file_path: file path of the HDF5 dataset to extend - :param str data_path: file data_path of the HDF5 dataset to extend - :param str where: can be 'start' or 'end' to know if we should append frame at the beginning or at the end - :param logger: optioanl logger to handle logs + :param file_path: file path of the HDF5 dataset to extend + :param data_path: file data_path of the HDF5 dataset to extend + :param where: can be 'start' or 'end' to know if we should append frame at the beginning or at the end + :param logger: optional logger to handle logs """ if where not in ("start", "end"): raise ValueError("`where` should be `start` or `end`") diff --git a/nxtomo/utils/io.py b/nxtomo/utils/io.py index 5fbcf4a4dccb3a7c4185dc2ec41bc364373513a3..e58542876322d94c88ad28b6e16fe47a2027d491 100644 --- a/nxtomo/utils/io.py +++ b/nxtomo/utils/io.py @@ -80,18 +80,18 @@ def deprecated_warning( """ Function to log a deprecation warning - :param str type_: Nature of the object to be deprecated: + :param type_: Nature of the object to be deprecated: "Module", "Function", "Class" ... :param name: Object name. - :param str reason: Reason for deprecating this function + :param reason: Reason for deprecating this function (e.g. "feature no longer provided", - :param str replacement: Name of replacement function (if the reason for + :param replacement: Name of replacement function (if the reason for deprecating was to rename the function) - :param str since_version: First *silx* version for which the function was + :param since_version: First *silx* version for which the function was deprecated (e.g. "0.5.0"). - :param bool only_once: If true, the deprecation warning will only be + :param only_once: If true, the deprecation warning will only be generated one time for each different call locations. Default is true. - :param int skip_backtrace_count: Amount of last backtrace to ignore when + :param skip_backtrace_count: Amount of last backtrace to ignore when logging the backtrace """ if not depreclog.isEnabledFor(logging.WARNING): diff --git a/nxtomo/utils/transformation.py b/nxtomo/utils/transformation.py index 415016c6ec28c5dac63ef0c16de6dca87c6af7ec..740a329618ebe7679467e7298e3ebcc4b260724d 100644 --- a/nxtomo/utils/transformation.py +++ b/nxtomo/utils/transformation.py @@ -1,6 +1,7 @@ """module to provide helper classes to define transformations contained in NXtransformations""" -import typing +from __future__ import annotations + import logging import numpy @@ -39,10 +40,10 @@ class Transformation: """ Define a Transformation done on an axis - :param str axis_name: name of the transformation. - :param TransformationType transformation_type: type of the formation. As unit depends on the type of transformation this is not possible to modify it once created + :param axis_name: name of the transformation. + :param transformation_type: type of the formation. As unit depends on the type of transformation this is not possible to modify it once created :param vector: vector of the transformation. Expected as a tuple of three values that define the axis for this transformation. Can also be an instance of TransformationAxis predefining some default axis - :param Optional[str] depends_on: used to determine transformation chain. If depends on no other transformation then should be considered as if it is depending on "gravity" only. + :param depends_on: used to determine transformation chain. If depends on no other transformation then should be considered as if it is depending on "gravity" only. :warning: when convert a rotation which as 'radian' as units it will be cast to degree """ @@ -55,8 +56,8 @@ class Transformation: axis_name: str, value, transformation_type: TransformationType, - vector: typing.Union[typing.Tuple[float, float, float], TransformationAxis], - depends_on: typing.Optional[str] = None, + vector: tuple[float, float, float] | TransformationAxis, + depends_on: str | None = None, ) -> None: self._axis_name = axis_name self._transformation_values = None @@ -113,7 +114,7 @@ class Transformation: return self._units @units.setter - def units(self, units: typing.Union[str, MetricSystem]): + def units(self, units: str | MetricSystem): """ :raises ValueError: if units is invalid (depends on the transformation type). """ @@ -130,7 +131,7 @@ class Transformation: raise ValueError(f"Unrecognized unit {units}") @property - def vector(self) -> typing.Tuple[float, float, float]: + def vector(self) -> tuple[float, float, float]: return self._vector @property @@ -138,7 +139,7 @@ class Transformation: return self._offset @offset.setter - def offset(self, offset: typing.Union[tuple, list, numpy.ndarray]): + def offset(self, offset: tuple | list | numpy.ndarray): if not isinstance(offset, (tuple, list, numpy.ndarray)): raise TypeError( f"offset is expected to be a vector of three elements. {type(offset)} provided" @@ -156,7 +157,7 @@ class Transformation: @depends_on.setter def depends_on(self, depends_on): """ - :param Optional[Transformation] depends_on: + :param depends_on: """ if not (depends_on is None or isinstance(depends_on, str)): raise TypeError( @@ -165,11 +166,11 @@ class Transformation: self._depends_on = depends_on @property - def equipment_component(self) -> typing.Optional[str]: + def equipment_component(self) -> str | None: return self._equipment_component @equipment_component.setter - def equipment_component(self, equipment_component: typing.Optional[str]): + def equipment_component(self, equipment_component: str | None): if not (equipment_component is None or isinstance(equipment_component, str)): raise TypeError( f"equipment_component is expect to ne None or a str. {type(equipment_component)} provided" diff --git a/nxtomo/utils/utils.py b/nxtomo/utils/utils.py index 5860fe49a23284d187a282e405b1783a4ad7d949..a980707bad997bbfcc4937157662929847831c83 100644 --- a/nxtomo/utils/utils.py +++ b/nxtomo/utils/utils.py @@ -1,5 +1,7 @@ """general utils""" +from __future__ import annotations + from typing import Iterable import h5py import numpy @@ -12,7 +14,7 @@ def cast_and_check_array_1D(array, array_name: str): cast provided array to 1D :param array: array to be cast to 1D - :param str array_name: name of the array - used for log only + :param array_name: name of the array - used for log only """ if not isinstance(array, (type(None), numpy.ndarray, Iterable)): raise TypeError( @@ -29,8 +31,8 @@ def get_data_and_unit(file_path: str, data_path: str, default_unit): """ return for an HDF5 dataset his value and his unit. If unit cannot be found then fallback on the 'default_unit' - :param str file_path: file path location of the HDF5Dataset to read - :param str data_path: data_path location of the HDF5Dataset to read + :param file_path: file path location of the HDF5Dataset to read + :param data_path: data_path location of the HDF5Dataset to read :param default_unit: default unit to fall back if the dataset has no 'unit' or 'units' attribute """ with hdf5_open(file_path) as h5f: @@ -56,8 +58,8 @@ def get_data(file_path: str, data_path: str): proxy to h5py_read_dataset, handling use case 'data_path' not present in the file. In this case return None - :param str file_path: file path location of the HDF5Dataset to read - :param str data_path: data_path location of the HDF5Dataset to read + :param file_path: file path location of the HDF5Dataset to read + :param data_path: data_path location of the HDF5Dataset to read """ with hdf5_open(file_path) as h5f: if data_path in h5f: