nxtomomill 2.2.0.dev1__py3-none-any.whl → 2.2.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nxtomomill/app/h52nx.py +35 -1
- nxtomomill/converter/hdf5/acquisition/baseacquisition.py +122 -102
- nxtomomill/converter/hdf5/acquisition/standardacquisition.py +17 -1
- nxtomomill/converter/hdf5/acquisition/tests/test_acquisition.py +75 -6
- nxtomomill/converter/hdf5/acquisition/utils/_context.py +1 -0
- nxtomomill/converter/hdf5/acquisition/utils/_keys.py +19 -0
- nxtomomill/converter/hdf5/tests/test_err_handling.py +48 -0
- nxtomomill/converter/hdf5/tests/test_h52nx_utils.py +9 -5
- nxtomomill/converter/hdf5/tests/test_hdf5converter.py +132 -0
- nxtomomill/io/config/confighandler.py +1 -1
- nxtomomill/models/dx2nx.py +9 -0
- nxtomomill/models/edf2nx/general_section.py +6 -3
- nxtomomill/models/edf2nx/unit_section.py +3 -0
- nxtomomill/models/h52nx/_context.py +2 -0
- nxtomomill/models/h52nx/general_section.py +5 -1
- nxtomomill/models/h52nx/keys_section.py +3 -0
- nxtomomill/models/tests/test_edf2nx_model.py +12 -0
- nxtomomill/models/tests/test_h52nx_model.py +15 -2
- nxtomomill/utils/flat_reducer.py +1 -1
- {nxtomomill-2.2.0.dev1.dist-info → nxtomomill-2.2.0.dev2.dist-info}/METADATA +1 -1
- {nxtomomill-2.2.0.dev1.dist-info → nxtomomill-2.2.0.dev2.dist-info}/RECORD +25 -24
- {nxtomomill-2.2.0.dev1.dist-info → nxtomomill-2.2.0.dev2.dist-info}/WHEEL +1 -1
- {nxtomomill-2.2.0.dev1.dist-info → nxtomomill-2.2.0.dev2.dist-info}/entry_points.txt +0 -0
- {nxtomomill-2.2.0.dev1.dist-info → nxtomomill-2.2.0.dev2.dist-info}/licenses/LICENSE +0 -0
- {nxtomomill-2.2.0.dev1.dist-info → nxtomomill-2.2.0.dev2.dist-info}/top_level.txt +0 -0
nxtomomill/app/h52nx.py
CHANGED
|
@@ -10,7 +10,9 @@ For a complete tutorial you can have a look at: :ref:`Tomoh52nx`
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import argparse
|
|
13
|
+
import cProfile
|
|
13
14
|
import logging
|
|
15
|
+
import os
|
|
14
16
|
|
|
15
17
|
from tqdm import tqdm
|
|
16
18
|
|
|
@@ -331,12 +333,35 @@ def main(argv):
|
|
|
331
333
|
"links to the raw data. Defaults to the 'NXTOMOMILL_RESOLVE_VDS' "
|
|
332
334
|
"environment variable if set, else resolving is enabled.",
|
|
333
335
|
)
|
|
336
|
+
parser.add_argument(
|
|
337
|
+
"--profiling",
|
|
338
|
+
"--profile",
|
|
339
|
+
help="Profile the conversion with 'cProfile' and dump the statistics "
|
|
340
|
+
"to a file (see '--profiling-output-file').",
|
|
341
|
+
action="store_true",
|
|
342
|
+
default=False,
|
|
343
|
+
)
|
|
344
|
+
parser.add_argument(
|
|
345
|
+
"--profiling-output-file",
|
|
346
|
+
"--profiling_output_file",
|
|
347
|
+
default="profiling.out",
|
|
348
|
+
help="Output file to dump the 'cProfile' statistics to when "
|
|
349
|
+
"'--profiling' is provided. If this file already exists the "
|
|
350
|
+
"conversion won't be launched (default: 'profiling.out').",
|
|
351
|
+
)
|
|
334
352
|
options = parser.parse_args(argv[1:])
|
|
335
353
|
if options.request_input:
|
|
336
354
|
callback_det_sel = _ask_for_selecting_detector
|
|
337
355
|
else:
|
|
338
356
|
callback_det_sel = None
|
|
339
357
|
|
|
358
|
+
if options.profiling and os.path.exists(options.profiling_output_file):
|
|
359
|
+
_logger.error(
|
|
360
|
+
f"Profiling output file already exists: '{options.profiling_output_file}'. "
|
|
361
|
+
"Remove it or provide another one with '--profiling-output-file'."
|
|
362
|
+
)
|
|
363
|
+
return 1
|
|
364
|
+
|
|
340
365
|
try:
|
|
341
366
|
configuration_handler = TomoHDF5ConfigHandler(options)
|
|
342
367
|
except Exception:
|
|
@@ -355,6 +380,15 @@ def main(argv):
|
|
|
355
380
|
input_callback=None,
|
|
356
381
|
detector_sel_callback=callback_det_sel,
|
|
357
382
|
)
|
|
358
|
-
|
|
383
|
+
if options.profiling:
|
|
384
|
+
profiler = cProfile.Profile()
|
|
385
|
+
profiler.enable()
|
|
386
|
+
try:
|
|
387
|
+
converter.convert()
|
|
388
|
+
finally:
|
|
389
|
+
profiler.disable()
|
|
390
|
+
profiler.dump_stats(options.profiling_output_file)
|
|
391
|
+
else:
|
|
392
|
+
converter.convert()
|
|
359
393
|
if converter.has_failed_acquisitions:
|
|
360
394
|
return 1
|
|
@@ -12,7 +12,9 @@ import copy
|
|
|
12
12
|
|
|
13
13
|
import h5py
|
|
14
14
|
|
|
15
|
+
import nxtomomill
|
|
15
16
|
from nxtomo.application.nxtomo import NXtomo
|
|
17
|
+
from nxtomo.io import HDF5File
|
|
16
18
|
from nxtomomill.utils.h5pyutils import from_data_url_to_virtual_source
|
|
17
19
|
from nxtomomill.utils.hdf5 import EntryReader
|
|
18
20
|
from nxtomomill.utils.utils import embed_url # noqa F401
|
|
@@ -30,7 +32,8 @@ from silx.io.url import DataUrl
|
|
|
30
32
|
from silx.io.utils import h5py_read_dataset
|
|
31
33
|
|
|
32
34
|
from nxtomomill.models.h52nx.h52nx import H52nxModel
|
|
33
|
-
from .
|
|
35
|
+
from nxtomomill.models.h52nx._context import _Key
|
|
36
|
+
from .utils._keys import keys_to_str_tuple, split_keys_by_priority
|
|
34
37
|
from .utils._context import read_key, MissingValue, InvalidValue
|
|
35
38
|
|
|
36
39
|
_ureg = pint.get_application_registry()
|
|
@@ -302,6 +305,12 @@ class BaseAcquisition:
|
|
|
302
305
|
use_abs_path_for_vds=self.configuration.use_abs_path_for_vds,
|
|
303
306
|
overwrite=self.configuration.overwrite,
|
|
304
307
|
)
|
|
308
|
+
# record the nxtomomill version used to create this NXtomo as attributes
|
|
309
|
+
# on the NXtomo entry group itself
|
|
310
|
+
with HDF5File(output_file, mode="a") as h5f:
|
|
311
|
+
entry_grp = h5f[data_path]
|
|
312
|
+
entry_grp.attrs["creator"] = "nxtomomill"
|
|
313
|
+
entry_grp.attrs["creator_version"] = nxtomomill.__version__
|
|
305
314
|
# check rotation angle
|
|
306
315
|
if nx_tomo.sample.rotation_angle is not None:
|
|
307
316
|
unique_angles = numpy.unique(
|
|
@@ -410,10 +419,11 @@ class BaseAcquisition:
|
|
|
410
419
|
# skip values containing '*DIS*'
|
|
411
420
|
continue
|
|
412
421
|
except InvalidValue:
|
|
413
|
-
# dataset reports an 'ERR' value:
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
422
|
+
# dataset reports an 'ERR' value: skip it and try the next key
|
|
423
|
+
_logger.warning(
|
|
424
|
+
f"{info_retrieve} reports an 'ERR' value for {node.name} - {key}. This key will be skipped."
|
|
425
|
+
)
|
|
426
|
+
continue
|
|
417
427
|
except KeyError as e:
|
|
418
428
|
_logger.info(
|
|
419
429
|
f"Cannot solve key {key}. Most likely one dataset is not there. Error is {e}"
|
|
@@ -619,6 +629,55 @@ class BaseAcquisition:
|
|
|
619
629
|
|
|
620
630
|
return None
|
|
621
631
|
|
|
632
|
+
@staticmethod
|
|
633
|
+
def _get_values_from_nodes(
|
|
634
|
+
nodes: tuple[h5py.Group | None, ...],
|
|
635
|
+
n_frame: int | None,
|
|
636
|
+
keys: tuple[_Key, ...],
|
|
637
|
+
info_retrieve: str,
|
|
638
|
+
expected_unit: pint.Unit,
|
|
639
|
+
) -> pint.Quantity | None:
|
|
640
|
+
"""
|
|
641
|
+
Look for the values of 'keys' in 'nodes'.
|
|
642
|
+
|
|
643
|
+
Keys resolved from the bliss-tomo 'tomoconfig' are looked at first (on all nodes, arrays
|
|
644
|
+
first then scalars). The other keys (default keys) are only used as a fallback if none of
|
|
645
|
+
the 'tomoconfig' keys could be found.
|
|
646
|
+
|
|
647
|
+
:return: values as a pint.Quantity or None if no key could be found.
|
|
648
|
+
"""
|
|
649
|
+
tomo_config_keys, fallback_keys = split_keys_by_priority(keys)
|
|
650
|
+
|
|
651
|
+
def look_for_keys(keys_to_check: tuple[str, ...]) -> pint.Quantity | None:
|
|
652
|
+
for node in nodes:
|
|
653
|
+
if node is None:
|
|
654
|
+
continue
|
|
655
|
+
try:
|
|
656
|
+
values, unit = BaseAcquisition._get_node_values_for_frame_array(
|
|
657
|
+
node=node,
|
|
658
|
+
n_frame=n_frame,
|
|
659
|
+
keys=keys_to_check,
|
|
660
|
+
info_retrieve=info_retrieve,
|
|
661
|
+
expected_unit=expected_unit,
|
|
662
|
+
)
|
|
663
|
+
except (ValueError, KeyError):
|
|
664
|
+
pass
|
|
665
|
+
else:
|
|
666
|
+
return values * unit
|
|
667
|
+
return None
|
|
668
|
+
|
|
669
|
+
if len(tomo_config_keys) > 0:
|
|
670
|
+
result = look_for_keys(tomo_config_keys)
|
|
671
|
+
if result is not None:
|
|
672
|
+
return result
|
|
673
|
+
_logger.warning(
|
|
674
|
+
f"Unable to find {info_retrieve} from the bliss-tomo 'tomoconfig' datasets {tomo_config_keys}. "
|
|
675
|
+
f"Will look at the fallback datasets {fallback_keys}."
|
|
676
|
+
)
|
|
677
|
+
if len(fallback_keys) > 0:
|
|
678
|
+
return look_for_keys(fallback_keys)
|
|
679
|
+
return None
|
|
680
|
+
|
|
622
681
|
def _get_rotation_angle(self, root_node: h5py.Group, n_frame: int) -> pint.Quantity:
|
|
623
682
|
"""
|
|
624
683
|
return the list of rotation angle for each frame.
|
|
@@ -629,23 +688,19 @@ class BaseAcquisition:
|
|
|
629
688
|
if not isinstance(root_node, h5py.Group):
|
|
630
689
|
raise TypeError("root_node is expected to be a h5py.Group")
|
|
631
690
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
except (ValueError, KeyError):
|
|
646
|
-
pass
|
|
647
|
-
else:
|
|
648
|
-
return angles * unit
|
|
691
|
+
angles = self._get_values_from_nodes(
|
|
692
|
+
nodes=(
|
|
693
|
+
self._get_positioners_node(root_node),
|
|
694
|
+
root_node,
|
|
695
|
+
self._get_measurement_node(root_node),
|
|
696
|
+
),
|
|
697
|
+
n_frame=n_frame,
|
|
698
|
+
keys=self.configuration.rotation_angle_keys,
|
|
699
|
+
info_retrieve="rotation angle",
|
|
700
|
+
expected_unit=_ureg.degree,
|
|
701
|
+
)
|
|
702
|
+
if angles is not None:
|
|
703
|
+
return angles
|
|
649
704
|
|
|
650
705
|
mess = f"Unable to find rotation angle for {root_node.name}"
|
|
651
706
|
if self.raise_error_if_issue:
|
|
@@ -657,19 +712,15 @@ class BaseAcquisition:
|
|
|
657
712
|
|
|
658
713
|
def _get_sample_x(self, root_node, n_frame) -> pint.Quantity:
|
|
659
714
|
"""return the list of translation for each frame"""
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
except (ValueError, KeyError):
|
|
670
|
-
pass
|
|
671
|
-
else:
|
|
672
|
-
return x_tr * unit
|
|
715
|
+
translation = self._get_values_from_nodes(
|
|
716
|
+
nodes=(self._get_positioners_node(root_node), root_node),
|
|
717
|
+
n_frame=n_frame,
|
|
718
|
+
keys=self.configuration.sample_x_keys,
|
|
719
|
+
info_retrieve="sample x translation",
|
|
720
|
+
expected_unit=_ureg.mm,
|
|
721
|
+
)
|
|
722
|
+
if translation is not None:
|
|
723
|
+
return translation
|
|
673
724
|
|
|
674
725
|
mess = f"Unable to find sample x for {root_node.name}"
|
|
675
726
|
if self.raise_error_if_issue:
|
|
@@ -681,19 +732,15 @@ class BaseAcquisition:
|
|
|
681
732
|
|
|
682
733
|
def _get_sample_y(self, root_node, n_frame) -> pint.Quantity:
|
|
683
734
|
"""return the list of translation for each frame"""
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
except (ValueError, KeyError):
|
|
694
|
-
pass
|
|
695
|
-
else:
|
|
696
|
-
return y_tr * unit
|
|
735
|
+
translation = self._get_values_from_nodes(
|
|
736
|
+
nodes=(self._get_positioners_node(root_node), root_node),
|
|
737
|
+
n_frame=n_frame,
|
|
738
|
+
keys=self.configuration.sample_y_keys,
|
|
739
|
+
info_retrieve="sample y translation",
|
|
740
|
+
expected_unit=_ureg.mm,
|
|
741
|
+
)
|
|
742
|
+
if translation is not None:
|
|
743
|
+
return translation
|
|
697
744
|
|
|
698
745
|
mess = f"Unable to find sample y for {root_node.name}"
|
|
699
746
|
if self.raise_error_if_issue:
|
|
@@ -705,24 +752,15 @@ class BaseAcquisition:
|
|
|
705
752
|
|
|
706
753
|
def _get_translation_x(self, root_node, n_frame) -> pint.Quantity | None:
|
|
707
754
|
"""return the list of translation for each frame"""
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
except InvalidValue:
|
|
718
|
-
_logger.error(
|
|
719
|
-
f"translation x reports an 'ERR' value for {root_node.name}. Translation x will be set to None."
|
|
720
|
-
)
|
|
721
|
-
return None
|
|
722
|
-
except (ValueError, KeyError):
|
|
723
|
-
pass
|
|
724
|
-
else:
|
|
725
|
-
return x_tr * unit
|
|
755
|
+
translation = self._get_values_from_nodes(
|
|
756
|
+
nodes=(self._get_positioners_node(root_node), root_node),
|
|
757
|
+
n_frame=n_frame,
|
|
758
|
+
keys=self.configuration.translation_x_keys,
|
|
759
|
+
info_retrieve="x base translation",
|
|
760
|
+
expected_unit=_ureg.mm,
|
|
761
|
+
)
|
|
762
|
+
if translation is not None:
|
|
763
|
+
return translation
|
|
726
764
|
|
|
727
765
|
mess = f"Unable to find translation x for {root_node.name}"
|
|
728
766
|
if self.raise_error_if_issue:
|
|
@@ -734,24 +772,15 @@ class BaseAcquisition:
|
|
|
734
772
|
|
|
735
773
|
def _get_translation_y(self, root_node, n_frame) -> pint.Quantity | None:
|
|
736
774
|
"""return the list of translation for each frame"""
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
except InvalidValue:
|
|
747
|
-
_logger.error(
|
|
748
|
-
f"translation y reports an 'ERR' value for {root_node.name}. Translation y will be set to None."
|
|
749
|
-
)
|
|
750
|
-
return None
|
|
751
|
-
except (ValueError, KeyError):
|
|
752
|
-
pass
|
|
753
|
-
else:
|
|
754
|
-
return y_tr * unit
|
|
775
|
+
translation = self._get_values_from_nodes(
|
|
776
|
+
nodes=(self._get_positioners_node(root_node), root_node),
|
|
777
|
+
n_frame=n_frame,
|
|
778
|
+
keys=self.configuration.translation_y_keys,
|
|
779
|
+
info_retrieve="y base translation",
|
|
780
|
+
expected_unit=_ureg.mm,
|
|
781
|
+
)
|
|
782
|
+
if translation is not None:
|
|
783
|
+
return translation
|
|
755
784
|
|
|
756
785
|
mess = f"Unable to find translation y for {root_node.name}"
|
|
757
786
|
if self.raise_error_if_issue:
|
|
@@ -765,24 +794,15 @@ class BaseAcquisition:
|
|
|
765
794
|
def get_translation_z_frm(
|
|
766
795
|
root_node, n_frame: int, configuration: H52nxModel
|
|
767
796
|
) -> pint.Quantity | None:
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
except InvalidValue:
|
|
778
|
-
_logger.error(
|
|
779
|
-
f"translation z reports an 'ERR' value for {root_node.name}. Translation z will be set to None."
|
|
780
|
-
)
|
|
781
|
-
return None
|
|
782
|
-
except (ValueError, KeyError):
|
|
783
|
-
pass
|
|
784
|
-
else:
|
|
785
|
-
return z_tr * unit
|
|
797
|
+
translation = BaseAcquisition._get_values_from_nodes(
|
|
798
|
+
nodes=(BaseAcquisition._get_positioners_node(root_node), root_node),
|
|
799
|
+
n_frame=n_frame,
|
|
800
|
+
keys=configuration.translation_z_keys,
|
|
801
|
+
info_retrieve="z translation",
|
|
802
|
+
expected_unit=_ureg.mm,
|
|
803
|
+
)
|
|
804
|
+
if translation is not None:
|
|
805
|
+
return translation
|
|
786
806
|
|
|
787
807
|
mess = f"Unable to find translation z on node {root_node.name}"
|
|
788
808
|
if configuration.raises_error:
|
|
@@ -65,6 +65,10 @@ from .utils._keys import keys_to_str_tuple
|
|
|
65
65
|
_ureg = pint.get_application_registry()
|
|
66
66
|
_logger = logging.getLogger(__name__)
|
|
67
67
|
|
|
68
|
+
_MAX_VDS_SOURCES_TO_RESOLVE = 100
|
|
69
|
+
"""If a camera dataset virtual source relies on that many (or more) distinct files, skip
|
|
70
|
+
resolving external links / virtual dataset sources for it."""
|
|
71
|
+
|
|
68
72
|
|
|
69
73
|
__all__ = ["StandardAcquisition"]
|
|
70
74
|
|
|
@@ -346,7 +350,19 @@ class StandardAcquisition(BaseAcquisition):
|
|
|
346
350
|
image_key_control = frame_type.to_image_key_control()
|
|
347
351
|
self._image_key_control.extend([image_key_control.value] * n_frame)
|
|
348
352
|
|
|
349
|
-
|
|
353
|
+
n_vds_files = (
|
|
354
|
+
len({vs_info.file_name for vs_info in data_dataset.virtual_sources()})
|
|
355
|
+
if data_dataset.is_virtual
|
|
356
|
+
else 0
|
|
357
|
+
)
|
|
358
|
+
if n_vds_files >= _MAX_VDS_SOURCES_TO_RESOLVE:
|
|
359
|
+
_logger.warning(
|
|
360
|
+
f"{camera_dataset_url.path()} relies on {n_vds_files} files "
|
|
361
|
+
f"(>= {_MAX_VDS_SOURCES_TO_RESOLVE}). Skip external link / VDS "
|
|
362
|
+
"resolution to avoid opening too many files."
|
|
363
|
+
)
|
|
364
|
+
solved_camera_dataset_urls = (camera_dataset_url,)
|
|
365
|
+
elif self.configuration.resolve_vds:
|
|
350
366
|
try:
|
|
351
367
|
solved_camera_dataset_urls = resolve_link(
|
|
352
368
|
dataset_url=camera_dataset_url,
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import tempfile
|
|
5
5
|
|
|
6
|
+
import logging
|
|
7
|
+
|
|
6
8
|
import h5py
|
|
9
|
+
import numpy
|
|
7
10
|
import pytest
|
|
8
11
|
from silx.io.url import DataUrl
|
|
9
12
|
|
|
@@ -13,6 +16,7 @@ from nxtomomill.converter.hdf5.acquisition.baseacquisition import (
|
|
|
13
16
|
get_dataset_name_from_motor,
|
|
14
17
|
)
|
|
15
18
|
from nxtomomill.io.config import TomoHDF5Config
|
|
19
|
+
from nxtomomill.models.h52nx._context import _Key
|
|
16
20
|
from nxtomomill.tests.datasets import GitlabDataset
|
|
17
21
|
from nxtomomill import converter
|
|
18
22
|
from nxtomo.application.nxtomo import NXtomo
|
|
@@ -56,20 +60,80 @@ def test_get_dataset_name_from_motor():
|
|
|
56
60
|
@pytest.mark.parametrize("raises_error", (True, False))
|
|
57
61
|
def test_translation_with_err_value(tmp_path, raises_error):
|
|
58
62
|
"""
|
|
59
|
-
Test that translation
|
|
60
|
-
|
|
63
|
+
Test that translation datasets reporting an 'ERR' value are skipped:
|
|
64
|
+
the next valid key is used and, if none is valid, it behaves as if the
|
|
65
|
+
translation was not found (raises or default to 0).
|
|
61
66
|
"""
|
|
62
67
|
file_path = os.path.join(tmp_path, "test.h5")
|
|
63
68
|
with HDF5File(file_path, mode="w") as h5f:
|
|
64
69
|
h5f["/entry/instrument/positioners/tx"] = "ERR"
|
|
65
70
|
h5f["/entry/instrument/positioners/ty"] = "ERR"
|
|
66
71
|
h5f["/entry/instrument/positioners/tz"] = "ERR"
|
|
72
|
+
h5f["/entry/instrument/positioners/sz"] = 2.0
|
|
73
|
+
h5f["/entry/instrument/positioners/sz"].attrs["units"] = "mm"
|
|
67
74
|
|
|
68
75
|
config = TomoHDF5Config()
|
|
69
76
|
config.raises_error = raises_error
|
|
70
77
|
config.translation_x_keys = ("tx",)
|
|
71
78
|
config.translation_y_keys = ("ty",)
|
|
72
|
-
config.translation_z_keys = ("tz",)
|
|
79
|
+
config.translation_z_keys = ("tz", "sz")
|
|
80
|
+
|
|
81
|
+
url = DataUrl(file_path=file_path, data_path="/entry", scheme="silx")
|
|
82
|
+
std_acq = BaseAcquisition(
|
|
83
|
+
root_url=url,
|
|
84
|
+
configuration=config,
|
|
85
|
+
detector_sel_callback=None,
|
|
86
|
+
start_index=0,
|
|
87
|
+
)
|
|
88
|
+
with h5py.File(file_path, mode="r") as h5f:
|
|
89
|
+
root_node = h5f["/entry"]
|
|
90
|
+
# 'tz' reports 'ERR': fall back to 'sz'
|
|
91
|
+
z_tr = std_acq._get_translation_z(root_node=root_node, n_frame=1)
|
|
92
|
+
assert z_tr.to("mm").magnitude.tolist() == [2.0]
|
|
93
|
+
|
|
94
|
+
# no valid key: same behavior as a missing translation
|
|
95
|
+
if raises_error:
|
|
96
|
+
with pytest.raises(ValueError):
|
|
97
|
+
std_acq._get_translation_x(root_node=root_node, n_frame=1)
|
|
98
|
+
with pytest.raises(ValueError):
|
|
99
|
+
std_acq._get_translation_y(root_node=root_node, n_frame=1)
|
|
100
|
+
else:
|
|
101
|
+
x_tr = std_acq._get_translation_x(root_node=root_node, n_frame=1)
|
|
102
|
+
assert x_tr.magnitude.tolist() == [0]
|
|
103
|
+
y_tr = std_acq._get_translation_y(root_node=root_node, n_frame=1)
|
|
104
|
+
assert y_tr.magnitude.tolist() == [0]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_tomo_config_keys_priority(tmp_path, caplog):
|
|
108
|
+
"""
|
|
109
|
+
Test that keys resolved from the bliss-tomo 'tomoconfig' are looked at first (array then scalar)
|
|
110
|
+
and that the default keys are only used as fallback.
|
|
111
|
+
"""
|
|
112
|
+
n_frame = 3
|
|
113
|
+
file_path = os.path.join(tmp_path, "test.h5")
|
|
114
|
+
with HDF5File(file_path, mode="w") as h5f:
|
|
115
|
+
# tomoconfig motor is static (scalar) while a default key records an array
|
|
116
|
+
h5f["/entry/instrument/positioners/real_sx"] = 1.0
|
|
117
|
+
h5f["/entry/instrument/positioners/samx"] = numpy.array([5.0, 6.0, 7.0])
|
|
118
|
+
# tomoconfig motor only exists in root_node while a default key exists in positioners
|
|
119
|
+
h5f["/entry/real_sy"] = numpy.array([1.0, 2.0, 3.0])
|
|
120
|
+
h5f["/entry/instrument/positioners/samy"] = numpy.array([5.0, 6.0, 7.0])
|
|
121
|
+
# tomoconfig motor doesn't exist: fallback on the default key
|
|
122
|
+
h5f["/entry/instrument/positioners/sz"] = numpy.array([5.0, 6.0, 7.0])
|
|
123
|
+
|
|
124
|
+
config = TomoHDF5Config()
|
|
125
|
+
config.sample_x_keys = (
|
|
126
|
+
_Key(dataset="real_sx", contexts=set(), resolved_from_tomo_config=True),
|
|
127
|
+
_Key(dataset="samx", contexts=set()),
|
|
128
|
+
)
|
|
129
|
+
config.sample_y_keys = (
|
|
130
|
+
_Key(dataset="real_sy", contexts=set(), resolved_from_tomo_config=True),
|
|
131
|
+
_Key(dataset="samy", contexts=set()),
|
|
132
|
+
)
|
|
133
|
+
config.translation_z_keys = (
|
|
134
|
+
_Key(dataset="real_tz", contexts=set(), resolved_from_tomo_config=True),
|
|
135
|
+
_Key(dataset="sz", contexts=set()),
|
|
136
|
+
)
|
|
73
137
|
|
|
74
138
|
url = DataUrl(file_path=file_path, data_path="/entry", scheme="silx")
|
|
75
139
|
std_acq = BaseAcquisition(
|
|
@@ -80,9 +144,14 @@ def test_translation_with_err_value(tmp_path, raises_error):
|
|
|
80
144
|
)
|
|
81
145
|
with h5py.File(file_path, mode="r") as h5f:
|
|
82
146
|
root_node = h5f["/entry"]
|
|
83
|
-
|
|
84
|
-
assert
|
|
85
|
-
|
|
147
|
+
sample_x = std_acq._get_sample_x(root_node=root_node, n_frame=n_frame)
|
|
148
|
+
assert sample_x.magnitude.tolist() == [1.0] * n_frame
|
|
149
|
+
sample_y = std_acq._get_sample_y(root_node=root_node, n_frame=n_frame)
|
|
150
|
+
assert sample_y.magnitude.tolist() == [1.0, 2.0, 3.0]
|
|
151
|
+
with caplog.at_level(logging.WARNING):
|
|
152
|
+
z_tr = std_acq._get_translation_z(root_node=root_node, n_frame=n_frame)
|
|
153
|
+
assert z_tr.magnitude.tolist() == [5.0, 6.0, 7.0]
|
|
154
|
+
assert "Will look at the fallback datasets" in caplog.text
|
|
86
155
|
|
|
87
156
|
|
|
88
157
|
def test_frame_flips(tmp_path, monkeypatch):
|
|
@@ -8,3 +8,22 @@ def keys_to_str_tuple(keys: tuple[_Key]) -> tuple[str]:
|
|
|
8
8
|
if not isinstance(key, _Key):
|
|
9
9
|
raise TypeError(f"key should be an instance of {_Key}. Got {type(key)}")
|
|
10
10
|
return tuple([key.dataset for key in keys])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def split_keys_by_priority(
|
|
14
|
+
keys: tuple[_Key, ...],
|
|
15
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
16
|
+
"""
|
|
17
|
+
Split keys between the ones resolved from the bliss-tomo 'tomoconfig' (to be looked at first)
|
|
18
|
+
and the fallback ones (default keys).
|
|
19
|
+
"""
|
|
20
|
+
for key in keys:
|
|
21
|
+
if not isinstance(key, _Key):
|
|
22
|
+
raise TypeError(f"key should be an instance of {_Key}. Got {type(key)}")
|
|
23
|
+
tomo_config_keys = tuple(
|
|
24
|
+
key.dataset for key in keys if key.resolved_from_tomo_config
|
|
25
|
+
)
|
|
26
|
+
fallback_keys = tuple(
|
|
27
|
+
key.dataset for key in keys if not key.resolved_from_tomo_config
|
|
28
|
+
)
|
|
29
|
+
return tomo_config_keys, fallback_keys
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import h5py
|
|
6
|
+
import numpy
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from nxtomo import NXtomo
|
|
10
|
+
from nxtomomill.models.h52nx import H52nxModel
|
|
11
|
+
from nxtomomill.converter.hdf5.hdf5converter import from_h5_to_nx
|
|
12
|
+
from nxtomomill.tests.datasets import GitlabDataset
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture()
|
|
16
|
+
def dataset_with_err():
|
|
17
|
+
dataset_path = (
|
|
18
|
+
"h5_datasets/handling_ERR/15.12um_A327_lung_overview__TA2_REF_B_000_0001.h5"
|
|
19
|
+
)
|
|
20
|
+
source_file = GitlabDataset.get_dataset(dataset_path)
|
|
21
|
+
if source_file is None:
|
|
22
|
+
raise FileNotFoundError(f"Unable to download {dataset_path}")
|
|
23
|
+
return source_file
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_err_translation_fallback(tmp_path, dataset_with_err):
|
|
27
|
+
"""
|
|
28
|
+
Make sure that if a translation candidate reports an 'ERR' value
|
|
29
|
+
('mrsz') the next valid candidate ('sz') is used.
|
|
30
|
+
"""
|
|
31
|
+
with h5py.File(dataset_with_err, mode="r") as h5f:
|
|
32
|
+
assert h5f["4.1/instrument/positioners/mrsz"][()] == b"ERR"
|
|
33
|
+
expected_z = h5f["4.1/instrument/positioners/sz"][()]
|
|
34
|
+
|
|
35
|
+
output_file = os.path.join(tmp_path, "output.nx")
|
|
36
|
+
model = H52nxModel(
|
|
37
|
+
input_file=dataset_with_err,
|
|
38
|
+
output_file=output_file,
|
|
39
|
+
single_file=True,
|
|
40
|
+
)
|
|
41
|
+
from_h5_to_nx(configuration=model)
|
|
42
|
+
assert os.path.exists(output_file)
|
|
43
|
+
|
|
44
|
+
nxtomo = NXtomo().load(file_path=output_file, data_path="entry0000")
|
|
45
|
+
# ESRF z axis is stored as the NXtomo (McStas) y axis
|
|
46
|
+
y_translation = nxtomo.sample.y_translation
|
|
47
|
+
assert y_translation is not None
|
|
48
|
+
numpy.testing.assert_allclose(y_translation.to("mm").magnitude, expected_z)
|
|
@@ -97,15 +97,19 @@ def test_update_config_keys_from_tomo_config_context():
|
|
|
97
97
|
# the real (per-frame) motor name is tried first, the bliss alias (static / scalar value) is
|
|
98
98
|
# kept as a fallback in case the real motor name doesn't exist in a given (sub-)scan.
|
|
99
99
|
assert model.sample_x_keys == (
|
|
100
|
-
_Key(dataset="real_sx", contexts=set()),
|
|
101
|
-
_Key(dataset="alias_sx", contexts=set()),
|
|
100
|
+
_Key(dataset="real_sx", contexts=set(), resolved_from_tomo_config=True),
|
|
101
|
+
_Key(dataset="alias_sx", contexts=set(), resolved_from_tomo_config=True),
|
|
102
102
|
)
|
|
103
103
|
assert model.sample_y_keys == (
|
|
104
|
-
_Key(dataset="real_sy+real_ty", contexts=set()),
|
|
105
|
-
_Key(
|
|
104
|
+
_Key(dataset="real_sy+real_ty", contexts=set(), resolved_from_tomo_config=True),
|
|
105
|
+
_Key(
|
|
106
|
+
dataset="alias_sy+alias_ty", contexts=set(), resolved_from_tomo_config=True
|
|
107
|
+
),
|
|
106
108
|
)
|
|
107
109
|
# when tomo_config only registered a single name there is nothing to fall back to: no duplicate
|
|
108
|
-
assert model.valid_camera_names == (
|
|
110
|
+
assert model.valid_camera_names == (
|
|
111
|
+
_Key(dataset="single_su", contexts=set(), resolved_from_tomo_config=True),
|
|
112
|
+
)
|
|
109
113
|
|
|
110
114
|
|
|
111
115
|
def test_update_config_keys_from_beamline_context():
|
|
@@ -27,6 +27,7 @@ from nxtomomill.converter.hdf5.utils import (
|
|
|
27
27
|
get_default_output_file,
|
|
28
28
|
)
|
|
29
29
|
from nxtomomill.models.h52nx import H52nxModel
|
|
30
|
+
import nxtomomill.converter.hdf5.acquisition.standardacquisition as standardacquisition_module
|
|
30
31
|
|
|
31
32
|
from tomoscan.scans.nxtomoscan import NXtomoScan
|
|
32
33
|
|
|
@@ -1415,6 +1416,49 @@ def test_id16a_use_case(tmp_path):
|
|
|
1415
1416
|
transformation.transformation_values.magnitude == numpy.array([0.0])
|
|
1416
1417
|
|
|
1417
1418
|
|
|
1419
|
+
def test_creator_and_creator_version_are_saved(tmp_path):
|
|
1420
|
+
"""
|
|
1421
|
+
Test that each NXtomo entry created by h52nx records the nxtomomill
|
|
1422
|
+
version used to create it, as 'creator' / 'creator_version' attributes
|
|
1423
|
+
on the NXtomo entry group itself.
|
|
1424
|
+
"""
|
|
1425
|
+
import nxtomomill
|
|
1426
|
+
|
|
1427
|
+
folder = tmp_path / "output_test_creator_and_creator_version_are_saved"
|
|
1428
|
+
folder.mkdir()
|
|
1429
|
+
bliss_mock = MockBlissAcquisition(
|
|
1430
|
+
n_sample=1,
|
|
1431
|
+
n_sequence=1,
|
|
1432
|
+
n_scan_per_sequence=2,
|
|
1433
|
+
n_darks=1,
|
|
1434
|
+
n_flats=1,
|
|
1435
|
+
with_nx_detector_attr=True,
|
|
1436
|
+
output_dir=folder,
|
|
1437
|
+
detector_name="pcolinux",
|
|
1438
|
+
create_tomo_config=False,
|
|
1439
|
+
)
|
|
1440
|
+
sample = bliss_mock.samples[0]
|
|
1441
|
+
|
|
1442
|
+
config = TomoHDF5Config()
|
|
1443
|
+
config.input_file = sample.sample_file
|
|
1444
|
+
config.output_file = str(tmp_path / "output.nx")
|
|
1445
|
+
config.single_file = True
|
|
1446
|
+
config.sample_x_keys = ("sx",)
|
|
1447
|
+
config.sample_y_keys = ("sy",)
|
|
1448
|
+
config.translation_x_keys = ("tx",)
|
|
1449
|
+
|
|
1450
|
+
converter.from_h5_to_nx(configuration=config)
|
|
1451
|
+
assert os.path.exists(config.output_file)
|
|
1452
|
+
|
|
1453
|
+
with HDF5File(config.output_file, mode="r") as h5f:
|
|
1454
|
+
entries = [name for name in h5f.keys() if "creator" in h5f[name].attrs]
|
|
1455
|
+
assert len(entries) > 0
|
|
1456
|
+
for entry in entries:
|
|
1457
|
+
entry_grp = h5f[entry]
|
|
1458
|
+
assert entry_grp.attrs["creator"] == "nxtomomill"
|
|
1459
|
+
assert entry_grp.attrs["creator_version"] == nxtomomill.__version__
|
|
1460
|
+
|
|
1461
|
+
|
|
1418
1462
|
def test_resolve_vds_option(tmp_path, monkeypatch):
|
|
1419
1463
|
"""
|
|
1420
1464
|
Test that the 'resolve_vds' configuration option correctly enables /
|
|
@@ -1472,6 +1516,94 @@ def test_resolve_vds_option(tmp_path, monkeypatch):
|
|
|
1472
1516
|
assert os.path.exists(config.output_file)
|
|
1473
1517
|
|
|
1474
1518
|
|
|
1519
|
+
def test_skip_vds_resolution_when_too_many_files(tmp_path, monkeypatch):
|
|
1520
|
+
"""
|
|
1521
|
+
Test that VDS / external link resolution is skipped entirely - even with
|
|
1522
|
+
'resolve_vds' enabled - when the camera dataset virtual source spans at
|
|
1523
|
+
least '_MAX_VDS_SOURCES_TO_RESOLVE' distinct files.
|
|
1524
|
+
"""
|
|
1525
|
+
|
|
1526
|
+
n_chunks = 3
|
|
1527
|
+
monkeypatch.setattr(
|
|
1528
|
+
standardacquisition_module, "_MAX_VDS_SOURCES_TO_RESOLVE", n_chunks
|
|
1529
|
+
)
|
|
1530
|
+
|
|
1531
|
+
folder = tmp_path / "output_test_skip_vds_resolution_when_too_many_files"
|
|
1532
|
+
folder.mkdir()
|
|
1533
|
+
bliss_mock = MockBlissAcquisition(
|
|
1534
|
+
n_sample=1,
|
|
1535
|
+
n_sequence=1,
|
|
1536
|
+
n_scan_per_sequence=2,
|
|
1537
|
+
n_darks=1,
|
|
1538
|
+
n_flats=1,
|
|
1539
|
+
with_nx_detector_attr=True,
|
|
1540
|
+
output_dir=folder,
|
|
1541
|
+
detector_name="pcolinux",
|
|
1542
|
+
create_tomo_config=False,
|
|
1543
|
+
)
|
|
1544
|
+
sample = bliss_mock.samples[0]
|
|
1545
|
+
sample_dir = os.path.dirname(sample.sample_file)
|
|
1546
|
+
|
|
1547
|
+
# turn every (plain) detector dataset into a virtual dataset spanning
|
|
1548
|
+
# 'n_chunks' distinct files, like a real bliss acquisition chunked
|
|
1549
|
+
# across many small files.
|
|
1550
|
+
with h5py.File(sample.sample_file, "a") as h5f:
|
|
1551
|
+
target_entries = [
|
|
1552
|
+
entry_name
|
|
1553
|
+
for entry_name, entry_grp in h5f.items()
|
|
1554
|
+
if (det_grp := entry_grp.get("instrument/pcolinux")) is not None
|
|
1555
|
+
and "data" in det_grp
|
|
1556
|
+
and det_grp["data"].shape[0] >= n_chunks
|
|
1557
|
+
]
|
|
1558
|
+
assert len(target_entries) > 0, "no suitable entry found"
|
|
1559
|
+
|
|
1560
|
+
for entry_name in target_entries:
|
|
1561
|
+
det_grp = h5f[entry_name]["instrument/pcolinux"]
|
|
1562
|
+
original_data = det_grp["data"][()]
|
|
1563
|
+
del det_grp["data"]
|
|
1564
|
+
n_frames = original_data.shape[0]
|
|
1565
|
+
boundaries = numpy.linspace(0, n_frames, n_chunks + 1, dtype=int)
|
|
1566
|
+
layout = h5py.VirtualLayout(
|
|
1567
|
+
shape=original_data.shape, dtype=original_data.dtype
|
|
1568
|
+
)
|
|
1569
|
+
for i in range(n_chunks):
|
|
1570
|
+
start, stop = boundaries[i], boundaries[i + 1]
|
|
1571
|
+
chunk_file = os.path.join(sample_dir, f"{entry_name}_det_chunk_{i}.h5")
|
|
1572
|
+
with h5py.File(chunk_file, "w") as chunk_h5:
|
|
1573
|
+
chunk_h5.create_dataset("data", data=original_data[start:stop])
|
|
1574
|
+
with h5py.File(chunk_file, "r") as chunk_h5:
|
|
1575
|
+
layout[start:stop] = h5py.VirtualSource(
|
|
1576
|
+
path_or_dataset=chunk_h5["data"]
|
|
1577
|
+
)
|
|
1578
|
+
det_grp.create_virtual_dataset("data", layout)
|
|
1579
|
+
|
|
1580
|
+
call_counter = {"n": 0}
|
|
1581
|
+
original_resolve_link = standardacquisition_module.resolve_link
|
|
1582
|
+
|
|
1583
|
+
def _counting_resolve_link(*args, **kwargs):
|
|
1584
|
+
call_counter["n"] += 1
|
|
1585
|
+
return original_resolve_link(*args, **kwargs)
|
|
1586
|
+
|
|
1587
|
+
monkeypatch.setattr(
|
|
1588
|
+
standardacquisition_module, "resolve_link", _counting_resolve_link
|
|
1589
|
+
)
|
|
1590
|
+
|
|
1591
|
+
config = TomoHDF5Config()
|
|
1592
|
+
config.input_file = sample.sample_file
|
|
1593
|
+
config.output_file = str(tmp_path / "output.nx")
|
|
1594
|
+
config.single_file = True
|
|
1595
|
+
# explicitly enabled: the file-count threshold must override it
|
|
1596
|
+
config.resolve_vds = True
|
|
1597
|
+
config.sample_x_keys = ("sx",)
|
|
1598
|
+
config.sample_y_keys = ("sy",)
|
|
1599
|
+
config.translation_x_keys = ("tx",)
|
|
1600
|
+
|
|
1601
|
+
converter.from_h5_to_nx(configuration=config)
|
|
1602
|
+
|
|
1603
|
+
assert call_counter["n"] == 0
|
|
1604
|
+
assert os.path.exists(config.output_file)
|
|
1605
|
+
|
|
1606
|
+
|
|
1475
1607
|
def test_single_file_and_overwrite_from_cli(tmp_path):
|
|
1476
1608
|
"""
|
|
1477
1609
|
Test that the `--single-file` and `--overwrite` options provided from
|
|
@@ -8,11 +8,11 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
|
-
from nxtomomill.utils._os import abspath
|
|
12
11
|
|
|
13
12
|
from nxtomomill.converter.hdf5.utils import get_default_output_file
|
|
14
13
|
|
|
15
14
|
from nxtomomill.models.h52nx.h52nx import H52nxModel
|
|
15
|
+
from nxtomomill.utils._os import abspath
|
|
16
16
|
|
|
17
17
|
_logger = logging.getLogger(__name__)
|
|
18
18
|
|
nxtomomill/models/dx2nx.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from nxtomo.nxobject.nxdetector import FieldOfView
|
|
6
|
+
from nxtomomill.utils._os import abspath
|
|
6
7
|
from .base.general_section import GeneralSection
|
|
7
8
|
from pydantic import (
|
|
8
9
|
Field,
|
|
@@ -57,6 +58,14 @@ class DX2nxModel(GeneralSection):
|
|
|
57
58
|
)
|
|
58
59
|
energy: float | None = Field(default=None, description="Energy in keV")
|
|
59
60
|
|
|
61
|
+
@field_validator(
|
|
62
|
+
"input_file",
|
|
63
|
+
mode="before",
|
|
64
|
+
)
|
|
65
|
+
@classmethod
|
|
66
|
+
def cast_input_file_to_abspath(cls, value: str) -> str:
|
|
67
|
+
return abspath(value)
|
|
68
|
+
|
|
60
69
|
@field_validator(
|
|
61
70
|
"field_of_view",
|
|
62
71
|
)
|
|
@@ -83,9 +83,12 @@ class GeneralSection(_GeneralSectionBase):
|
|
|
83
83
|
mode="plain",
|
|
84
84
|
)
|
|
85
85
|
@classmethod
|
|
86
|
-
def cast_to_tuple(
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
def cast_to_tuple(
|
|
87
|
+
cls, value: tuple[str, ...] | list[str] | str | None
|
|
88
|
+
) -> tuple[str]:
|
|
89
|
+
if isinstance(value, (tuple, list)):
|
|
90
|
+
# a list is obtained if the configuration went through a json serialization for example
|
|
91
|
+
return tuple(value)
|
|
89
92
|
value = filter_str_def(value)
|
|
90
93
|
return convert_str_to_tuple(value) or ()
|
|
91
94
|
|
|
@@ -63,6 +63,9 @@ class UnitSection(BaseModel):
|
|
|
63
63
|
)
|
|
64
64
|
@classmethod
|
|
65
65
|
def cast_to_pint_unit(cls, value: str | FieldOfView) -> pint.Unit | None:
|
|
66
|
+
if value is None:
|
|
67
|
+
# pint >= 0.26.2 casts None to 'dimensionless'
|
|
68
|
+
raise TypeError("unit cannot be None")
|
|
66
69
|
if value in ("kiloelectron_volt", "kev"):
|
|
67
70
|
return _ureg.keV
|
|
68
71
|
if value == "milliampere":
|
|
@@ -53,6 +53,8 @@ _CONTEXT_CLASSES = {Proxy, Beamline}
|
|
|
53
53
|
class _Key(BaseModel):
|
|
54
54
|
dataset: str
|
|
55
55
|
contexts: set[_CONTEXT]
|
|
56
|
+
resolved_from_tomo_config: bool = False
|
|
57
|
+
"""True if the dataset name has been resolved from the bliss-tomo 'tomoconfig'. Those keys have priority over the other (fallback) keys."""
|
|
56
58
|
|
|
57
59
|
@field_validator(
|
|
58
60
|
"dataset",
|
|
@@ -12,6 +12,7 @@ from nxtomomill.models.utils import (
|
|
|
12
12
|
convert_str_to_bool,
|
|
13
13
|
convert_str_to_tuple,
|
|
14
14
|
)
|
|
15
|
+
from nxtomomill.utils._os import abspath
|
|
15
16
|
from ..base.general_section import GeneralSection as _GeneralSectionBase
|
|
16
17
|
|
|
17
18
|
|
|
@@ -159,7 +160,10 @@ class GeneralSection(_GeneralSectionBase):
|
|
|
159
160
|
)
|
|
160
161
|
@classmethod
|
|
161
162
|
def cast_general_section_to_str(cls, value: str | None) -> str | None:
|
|
162
|
-
|
|
163
|
+
value = filter_str_def(value)
|
|
164
|
+
if not value:
|
|
165
|
+
return value
|
|
166
|
+
return abspath(value)
|
|
163
167
|
|
|
164
168
|
@field_validator(
|
|
165
169
|
"no_input",
|
|
@@ -17,6 +17,9 @@ def _cast_keys_section_to_tuple(value):
|
|
|
17
17
|
if isinstance(value, str):
|
|
18
18
|
# convert "sa, id16a:sx" to ("sa", "id16a:sx")
|
|
19
19
|
value: tuple = convert_str_to_tuple(value)
|
|
20
|
+
elif isinstance(value, list):
|
|
21
|
+
# a list is obtained if the configuration went through a json serialization for example
|
|
22
|
+
value = tuple(value)
|
|
20
23
|
|
|
21
24
|
def invalid_type():
|
|
22
25
|
raise TypeError(
|
|
@@ -289,3 +289,15 @@ def test_TomoEDFConfig_setters():
|
|
|
289
289
|
|
|
290
290
|
detector_section_dict = config_dict["DETECTOR_SECTION"]
|
|
291
291
|
assert detector_section_dict["field_of_view"] == FieldOfView.HALF.value
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def test_edf2nx_model_json_round_trip():
|
|
295
|
+
"""
|
|
296
|
+
Make sure the configuration can be loaded back once dumped to json (tuples are converted to lists).
|
|
297
|
+
"""
|
|
298
|
+
import json
|
|
299
|
+
|
|
300
|
+
config = EDF2nxModel()
|
|
301
|
+
as_json = json.loads(json.dumps(config.to_nested_model().model_dump(), default=str))
|
|
302
|
+
assert isinstance(as_json["general_section"]["patterns_to_ignores"], list)
|
|
303
|
+
assert EDF2nxModel.from_dict(as_json) == config
|
|
@@ -26,6 +26,7 @@ from nxtomomill.models.h52nx.extra_params_section import (
|
|
|
26
26
|
ExtraParamsSection,
|
|
27
27
|
)
|
|
28
28
|
from nxtomomill.converter.hdf5.acquisition.utils._keys import _Key
|
|
29
|
+
from nxtomomill.utils._os import abspath
|
|
29
30
|
|
|
30
31
|
from nxtomomill.tests.resources import h52nx_config_files as _h52nx_config_files
|
|
31
32
|
from configparser import ConfigParser
|
|
@@ -67,7 +68,7 @@ def test_moving_to_pydantic_modified_config_file():
|
|
|
67
68
|
|
|
68
69
|
config = H52nxModel.from_cfg_file(ref_file)
|
|
69
70
|
# general section
|
|
70
|
-
assert config.input_file == "input_file.h5"
|
|
71
|
+
assert config.input_file == abspath("input_file.h5")
|
|
71
72
|
assert config.output_file == "output_file.nx"
|
|
72
73
|
assert config.overwrite is True
|
|
73
74
|
assert config.file_extension is FileExtension.H5
|
|
@@ -226,7 +227,7 @@ def test_generate_default_hdf5_config():
|
|
|
226
227
|
|
|
227
228
|
# check input and output file
|
|
228
229
|
general_information = output["GENERAL_SECTION"]
|
|
229
|
-
assert general_information["input_file"] == "toto.h5"
|
|
230
|
+
assert general_information["input_file"] == abspath("toto.h5")
|
|
230
231
|
assert general_information["output_file"] == "toto.nx"
|
|
231
232
|
|
|
232
233
|
|
|
@@ -333,3 +334,15 @@ def test_hdf5config_to_and_from_cfg_file(tmp_path):
|
|
|
333
334
|
assert os.path.exists(file_path)
|
|
334
335
|
loaded_config = H52nxModel.from_cfg_file(file_path=file_path)
|
|
335
336
|
assert isinstance(loaded_config, H52nxModel)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def test_h52nx_model_json_round_trip():
|
|
340
|
+
"""
|
|
341
|
+
Make sure the configuration can be loaded back once dumped to json (tuples are converted to lists).
|
|
342
|
+
"""
|
|
343
|
+
import json
|
|
344
|
+
|
|
345
|
+
config = H52nxModel()
|
|
346
|
+
as_json = json.loads(json.dumps(config.to_nested_model().model_dump(), default=str))
|
|
347
|
+
assert isinstance(as_json["keys_section"]["valid_camera_names"], list)
|
|
348
|
+
assert H52nxModel.from_dict(as_json) == config
|
nxtomomill/utils/flat_reducer.py
CHANGED
|
@@ -135,9 +135,9 @@ class __RefsDarks:
|
|
|
135
135
|
):
|
|
136
136
|
self.dict_or_file_name = dict_or_file_name
|
|
137
137
|
self.entry_name = entry_name
|
|
138
|
+
self._open_mode_for_reading = open_mode_for_reading
|
|
138
139
|
self.flat_image, self.flat_current = self._take_image_and_meta("flat")
|
|
139
140
|
self.dark_image, self.dark_current = self._take_image_and_meta("dark")
|
|
140
|
-
self._open_mode_for_reading = open_mode_for_reading
|
|
141
141
|
|
|
142
142
|
def _take_image_and_meta(self, what) -> tuple:
|
|
143
143
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nxtomomill
|
|
3
|
-
Version: 2.2.0.
|
|
3
|
+
Version: 2.2.0.dev2
|
|
4
4
|
Summary: applications and library to convert raw format to NXtomo format
|
|
5
5
|
Author-email: Henri Payno <henri.payno@esrf.fr>, Pierre Paleo <pierre.paleo@esrf.fr>, Pierre-Olivier Autran <pierre-olivier.autran@esrf.fr>, Jérôme Lesaint <jerome.lesaint@esrf.fr>, Alessandro Mirone <mirone@esrf.fr>
|
|
6
6
|
License:
|
|
@@ -13,7 +13,7 @@ nxtomomill/app/edfconfig.py,sha256=TJDQfkeIBX1Rk13HBl1otdKB8uJwqAyKuFvhmtti3Kg,1
|
|
|
13
13
|
nxtomomill/app/fluo2nx.py,sha256=mI8m18rQ9qOuY6CPIKoWJRaIuu4jgI2Z65Fi1P03FeI,4524
|
|
14
14
|
nxtomomill/app/fluoconfig.py,sha256=M2b6pd8HmtcLx2wC0NcQbjXB2l550JuQap16JWhVQss,948
|
|
15
15
|
nxtomomill/app/fscan2nx.py,sha256=uejEHirz6TDAvh-blkrORsMlx8ZW1MRGoxj8dtLwhlw,2363
|
|
16
|
-
nxtomomill/app/h52nx.py,sha256=
|
|
16
|
+
nxtomomill/app/h52nx.py,sha256=qHLrT9SgkBtYtRogUdKWw8mvdpy1aI24bA9ZBujyWNM,13193
|
|
17
17
|
nxtomomill/app/h5config.py,sha256=OpRLjwGlDbnHmh8W_fCU-JXAjts2GOcrfHu5I4oLCMk,1527
|
|
18
18
|
nxtomomill/app/nxcopy.py,sha256=KknTJ0r61erBzQ83i7OJkFgTT1bH8FHo0llfbj0TzPk,2477
|
|
19
19
|
nxtomomill/app/patch_nx.py,sha256=oXMevqyYe1ZS3ylYz6w8jYfu8_YfdNJLkeAMv7OaT9E,9839
|
|
@@ -58,14 +58,14 @@ nxtomomill/converter/hdf5/utils.py,sha256=uS8HymNSwMEWYvhD1yUxkco7xi41LAeG1pNXFs
|
|
|
58
58
|
nxtomomill/converter/hdf5/acquisition/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
59
|
nxtomomill/converter/hdf5/acquisition/_bliss_activeconfig.py,sha256=mgC-apxxu6PuVmLnve3W619LMkKnu9weWPTVbgvQrRQ,943
|
|
60
60
|
nxtomomill/converter/hdf5/acquisition/backandforth.py,sha256=PbhoELpvfHRYvl25GFnhyGjf6L42UV8uxYcrXHZ4Wxk,677
|
|
61
|
-
nxtomomill/converter/hdf5/acquisition/baseacquisition.py,sha256=
|
|
61
|
+
nxtomomill/converter/hdf5/acquisition/baseacquisition.py,sha256=mclCgRPxUbi5bme3SdDu8bnlNcI05ZKSZRBYLqvF3H0,35041
|
|
62
62
|
nxtomomill/converter/hdf5/acquisition/blisstomoconfig.py,sha256=9-LxXeyNPDUPLybwJ48o7XHx8ggqvy-K-FnhJ0qie-c,6953
|
|
63
63
|
nxtomomill/converter/hdf5/acquisition/multitomo.py,sha256=N5fHVwKSqqHPDEycRhJ0mtXLijvxCHwY_IiDtQ5SFq4,19280
|
|
64
64
|
nxtomomill/converter/hdf5/acquisition/pcotomoacquisition.py,sha256=1CgX1rFzekIckUlT8u2eb-xw5wCIF3TOoesmIg1y1oc,619
|
|
65
|
-
nxtomomill/converter/hdf5/acquisition/standardacquisition.py,sha256=
|
|
65
|
+
nxtomomill/converter/hdf5/acquisition/standardacquisition.py,sha256=pcDoq8HlC-HGwUeRQC3cwJRO7FdQ9gIjnt6nNMaiLcs,63260
|
|
66
66
|
nxtomomill/converter/hdf5/acquisition/zseriesacquisition.py,sha256=15VZvPMqp6pluGXo2rRh5IVfbAegGMy2ByHo5doCeDM,10634
|
|
67
67
|
nxtomomill/converter/hdf5/acquisition/tests/bliss_tomo_datasets.py,sha256=pcusczTBMttXRJGeEag4Uel6y8riWd981g-OVxW2M_o,874
|
|
68
|
-
nxtomomill/converter/hdf5/acquisition/tests/test_acquisition.py,sha256=
|
|
68
|
+
nxtomomill/converter/hdf5/acquisition/tests/test_acquisition.py,sha256=_g0PHHcReRT2gD3IrMoLhiBP8G25IZm3uvW6qkYpBZI,7480
|
|
69
69
|
nxtomomill/converter/hdf5/acquisition/tests/test_acquisition_utils.py,sha256=UTAq1J_c_cYNtfZdhBTqwCzu4h1gIdGwDWlyJNXQg3M,9271
|
|
70
70
|
nxtomomill/converter/hdf5/acquisition/tests/test_back_and_forth.py,sha256=l2NbPcM21kdN0gGEC5xUHVsYE_tsS5AeZfa0idV3x8w,4573
|
|
71
71
|
nxtomomill/converter/hdf5/acquisition/tests/test_bliss_tomo_config.py,sha256=sukeUc6P2Plg30hUeq26v15WcXN4zv2KfIX5LTF7RVo,3256
|
|
@@ -74,8 +74,8 @@ nxtomomill/converter/hdf5/acquisition/tests/test_legacy.py,sha256=EVj1s8NBVXQoAg
|
|
|
74
74
|
nxtomomill/converter/hdf5/acquisition/tests/test_multitomo_zseries.py,sha256=wPUhG3VihCND_Uoq-yHec4qwpNQyzyb3qvzHPOOhfzQ,3654
|
|
75
75
|
nxtomomill/converter/hdf5/acquisition/tests/test_multitomoacquisition.py,sha256=PCfpLhXvm8x3WU1d_pc57Ny4QpavNEPGy47BYaQ3oXc,7651
|
|
76
76
|
nxtomomill/converter/hdf5/acquisition/utils/__init__.py,sha256=tc3O9HTgBxoG2hr2qIy41d9ARQBN1rPMb8Zo44eamHs,443
|
|
77
|
-
nxtomomill/converter/hdf5/acquisition/utils/_context.py,sha256=
|
|
78
|
-
nxtomomill/converter/hdf5/acquisition/utils/_keys.py,sha256=
|
|
77
|
+
nxtomomill/converter/hdf5/acquisition/utils/_context.py,sha256=Wfd5nM1RKA-5kgYdW_mMO7f611PPQtHObgzmOnHpETc,9301
|
|
78
|
+
nxtomomill/converter/hdf5/acquisition/utils/_keys.py,sha256=vGbVJcW4qPKsJOtc91AYvSFaRNJR3wwLMjLQcJuhSS8,1001
|
|
79
79
|
nxtomomill/converter/hdf5/acquisition/utils/_scan_type_finder.py,sha256=n3Vf3aCMwi6bD7LJlyojqk2JZTYgxDgaP5x9AB0ijv8,3695
|
|
80
80
|
nxtomomill/converter/hdf5/acquisition/utils/bliss_scan_type.py,sha256=s72IPhbpU-lv79VshxDPzJMjRyaTIQPUFYbDIzol0R0,1378
|
|
81
81
|
nxtomomill/converter/hdf5/acquisition/utils/detector.py,sha256=WHaHw7_082NL8p0J5ReI1yJ5faGmh_7hpHqbrD9bCR0,1926
|
|
@@ -84,13 +84,14 @@ nxtomomill/converter/hdf5/acquisition/utils/series.py,sha256=nIUcBSEIIsGAEHK11-D
|
|
|
84
84
|
nxtomomill/converter/hdf5/acquisition/utils/timestamps.py,sha256=cvTi4Ol5DwA1u6st3sY8DNxq95jnivMrFF2VGxf7agc,461
|
|
85
85
|
nxtomomill/converter/hdf5/post_processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
86
86
|
nxtomomill/converter/hdf5/post_processing/dark_flat_copy.py,sha256=nBbXT9r0mxlGI1la3pqT-g7pmpmvMSVCj0uBoZMenb8,9751
|
|
87
|
+
nxtomomill/converter/hdf5/tests/test_err_handling.py,sha256=rSVSp5lL581t9M5CSGgMNOUJHpNucTJ_SgKUttkSjXY,1558
|
|
87
88
|
nxtomomill/converter/hdf5/tests/test_frame_flip.py,sha256=0S0FmMM9RVj1UQNwCsXQyI_bwQ8hx6ORdl4-EsWUjf8,2330
|
|
88
|
-
nxtomomill/converter/hdf5/tests/test_h52nx_utils.py,sha256=
|
|
89
|
-
nxtomomill/converter/hdf5/tests/test_hdf5converter.py,sha256=
|
|
89
|
+
nxtomomill/converter/hdf5/tests/test_h52nx_utils.py,sha256=QGAmTeaf9feqJvlWPXBbw_wjOChX0hWr8Kl5I2OfyoU,12002
|
|
90
|
+
nxtomomill/converter/hdf5/tests/test_hdf5converter.py,sha256=0iSp-pDRElp3168Jm27lNG5mMrZ8q0Y9jq0cIS85PMU,64022
|
|
90
91
|
nxtomomill/io/__init__.py,sha256=gxUEBT0A0o8kJJ19N4pIIYVTA-7TuvuJq2BEepqbrNQ,91
|
|
91
92
|
nxtomomill/io/config/__init__.py,sha256=q1RV3iBYSqpV-r8H794YBXl_1DiA0KNZVWEmWpF04x4,348
|
|
92
93
|
nxtomomill/io/config/configbase.py,sha256=pLPGV91Vwxuqidh7UnkWscXauPzsrNbAI9T7DJQHMEo,554
|
|
93
|
-
nxtomomill/io/config/confighandler.py,sha256=
|
|
94
|
+
nxtomomill/io/config/confighandler.py,sha256=55bvQJNtlM1DIeDpfCjMrrofNxDP-h5zqQf0f0RC5V8,6475
|
|
94
95
|
nxtomomill/io/config/dxconfig.py,sha256=33gYIWsr0R7qL8uY7IDoE6kUNuWHtWp3_tfaY6TSGJk,590
|
|
95
96
|
nxtomomill/io/config/edfconfig.py,sha256=J04CZoDYkV3XZ-ZowNdyx5Lf8W1-QbAXKPnSPksqSLA,911
|
|
96
97
|
nxtomomill/io/config/fluoconfig.py,sha256=R-Cy1pYROzqS9CDj3T9kO7m17oCgyazIPz2d8-BRZwY,933
|
|
@@ -100,7 +101,7 @@ nxtomomill/io/config/tests/test_confighandler.py,sha256=-O7tbbKI3n7lSbXfDxa-mwt1
|
|
|
100
101
|
nxtomomill/io/tests/test_frame_group.py,sha256=P0weUFFwlNLhIDk4gpJsCrFSZ_mzF54SNNqjMVr_lhw,5719
|
|
101
102
|
nxtomomill/io/tests/utils.py,sha256=z_-AVtX26c6gsmpDgol1RqXmbdzdaj2jMXY3wfx2UyI,1950
|
|
102
103
|
nxtomomill/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
103
|
-
nxtomomill/models/dx2nx.py,sha256=
|
|
104
|
+
nxtomomill/models/dx2nx.py,sha256=QAP32N2BsAqDLHgmhRDgeoL_OP3Oi5t2HT26zVUHpck,3358
|
|
104
105
|
nxtomomill/models/utils.py,sha256=VB34e3AXfcFka7OL-eeP6byKBiSP_b4dNiqBuclpgyU,3096
|
|
105
106
|
nxtomomill/models/base/ConfigBase.py,sha256=6QZBVAdKIoarm0oMvmYFLL5AmldA-F76QP1p-Pp64j4,971
|
|
106
107
|
nxtomomill/models/base/FrmFlatToNestedBase.py,sha256=HvvMSmZrCfH4HqTCJgr4Bv3_K8IQSRRoriT6tdHIWFk,1053
|
|
@@ -116,28 +117,28 @@ nxtomomill/models/edf2nx/__init__.py,sha256=btz-4hb-LOIodmLSSihq1FhbF0W7vGTcwt6j
|
|
|
116
117
|
nxtomomill/models/edf2nx/dark_and_flat_section.py,sha256=AvgAxdMQ_EWlnR7h5nUeEYr1LyI7KjE0YaZsUf3YOL8,1711
|
|
117
118
|
nxtomomill/models/edf2nx/detector_section.py,sha256=54K2Ezb1ktjs46kkfktcfRP9ptJaN1_VRyBMCWyD7bk,1372
|
|
118
119
|
nxtomomill/models/edf2nx/edf2nx.py,sha256=RCTgD4Y_zbmiAjED8I1PqXymcYwZbd7EP416RNRMo7w,6696
|
|
119
|
-
nxtomomill/models/edf2nx/general_section.py,sha256=
|
|
120
|
+
nxtomomill/models/edf2nx/general_section.py,sha256=wCPxjCxeuy517QL2600BLqRm95sfW0swlKYrRDCFA0Y,5214
|
|
120
121
|
nxtomomill/models/edf2nx/keys_section.py,sha256=H04pODXv0Wtun3Qbd3JFB7xXq58PIL_5wtUVPVkc4XY,2056
|
|
121
122
|
nxtomomill/models/edf2nx/sample_section.py,sha256=wEPBuKIDwXTn84-oFahUOKZ-Em3Kpb6wmZJl7VaPNlk,1640
|
|
122
|
-
nxtomomill/models/edf2nx/unit_section.py,sha256=
|
|
123
|
+
nxtomomill/models/edf2nx/unit_section.py,sha256=220wL2YT6XSw5M9kmp7IIMkU93_CawdxoA4gkSm03Gs,4039
|
|
123
124
|
nxtomomill/models/fluo2nx/__init__.py,sha256=E0KmuFqL10lxFF2K7IBnsv8VpQYmNh-FQFESwNcN_4o,120
|
|
124
125
|
nxtomomill/models/fluo2nx/fluo2nx.py,sha256=gWVNsytqyd6hIj6TffBRTObdj2Elvqf4ECVFhsHjr2o,3613
|
|
125
126
|
nxtomomill/models/fluo2nx/general_section.py,sha256=JmfAmnWZJWsCbzR-cNOLi1gIUrKTb_hqWNDjnQdVcAk,2621
|
|
126
127
|
nxtomomill/models/h52nx/FrameGroup.py,sha256=mjw-pQfNm_WVR10JkktVfK0-NK05Hf3cct3mKtCCyOI,9363
|
|
127
128
|
nxtomomill/models/h52nx/__init__.py,sha256=F2B5npgvXjYWw3npIJsA_Jt0IyTeM8rsl_HBQYu8zJI,112
|
|
128
129
|
nxtomomill/models/h52nx/_acquisitionstep.py,sha256=YFA0kmcKUUKv6Qi3GMdm26RlT00BJ_afBy-v97VAnOU,2206
|
|
129
|
-
nxtomomill/models/h52nx/_context.py,sha256=
|
|
130
|
+
nxtomomill/models/h52nx/_context.py,sha256=OVVugpf1eRAt5hJDic2hWTDyeOTHr_iqJaCR46ITF08,3737
|
|
130
131
|
nxtomomill/models/h52nx/entries_and_title_section.py,sha256=qEs2VGRf6Nciq3FupJUZCKpNA2ZGBeK694O_kDOWo1Y,4703
|
|
131
132
|
nxtomomill/models/h52nx/extra_params_section.py,sha256=ZnxzwJVOjUqOd6ngmmrIb2xZVABfomBvKSELiT00Nmc,1547
|
|
132
133
|
nxtomomill/models/h52nx/frame_type_section.py,sha256=U-akT0f8lOvzjOQ6ZuUSwy3-pYAYkiVXeY7bBtZ0o44,3716
|
|
133
|
-
nxtomomill/models/h52nx/general_section.py,sha256=
|
|
134
|
+
nxtomomill/models/h52nx/general_section.py,sha256=z69qLCEIFKa7xFdt1zcVmed_jZSRqLFzB7-T6CpCnPc,7010
|
|
134
135
|
nxtomomill/models/h52nx/h52nx.py,sha256=4EwwMBQZBPQQ-um6rCgk5uhv-S01zjDzDVo1us2WT88,8603
|
|
135
|
-
nxtomomill/models/h52nx/keys_section.py,sha256=
|
|
136
|
+
nxtomomill/models/h52nx/keys_section.py,sha256=FFGb39DWlLaN-XOcAEOOgIBc64RdETjugtH8X68ACcs,6082
|
|
136
137
|
nxtomomill/models/h52nx/multitomo_section.py,sha256=aOM3oxopLjlBFzs0UXInxbkZ_NOw0QXS0BGI_2uhzLk,1819
|
|
137
138
|
nxtomomill/models/tests/test_context.py,sha256=w0A02DXL5hEhw-imMleSeue7ejqotuu_qr0aUJrEhxg,1711
|
|
138
|
-
nxtomomill/models/tests/test_edf2nx_model.py,sha256=
|
|
139
|
+
nxtomomill/models/tests/test_edf2nx_model.py,sha256=Tf8GaRkvVettIY_FO_br8KJ30LvTBWTkxV-6vfaQw_c,11812
|
|
139
140
|
nxtomomill/models/tests/test_fluo2nx_model.py,sha256=v-_B0-2ZIt4wapKWjtTjNUEDBCbU2x-WtVDQGvvYms0,5247
|
|
140
|
-
nxtomomill/models/tests/test_h52nx_model.py,sha256=
|
|
141
|
+
nxtomomill/models/tests/test_h52nx_model.py,sha256=K1o0q5QQpCbNwxTmi5G988nXnB9gY502SZS5MRYOugY,13215
|
|
141
142
|
nxtomomill/models/tests/test_io_utils.py,sha256=ZvHx5XpVAx3C-3phFWwy0RFamtf98idt-bWTcpLljkU,1576
|
|
142
143
|
nxtomomill/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
143
144
|
nxtomomill/tests/datasets.py,sha256=hjEAtM2F6GNIJoE8VaX5cnAikWM5Wt12J-nRyTi14kY,344
|
|
@@ -151,7 +152,7 @@ nxtomomill/tests/utils/bliss.py,sha256=fslMDgGqssY_3KjlmkpaVhfqMpjqukI0QdOrH3-bE
|
|
|
151
152
|
nxtomomill/tests/utils/dxfile.py,sha256=LSoBQtBBs8dOFvoJw0t0QpWsWVF_FrPeAKTTfAPyD6k,1892
|
|
152
153
|
nxtomomill/utils/__init__.py,sha256=HiwjKOWdwZ9wV0F16HQcE1qUWCuk_rL_N2bsSIBZVwo,568
|
|
153
154
|
nxtomomill/utils/_os.py,sha256=Mzz2_d4Inz7fmxZWl0wI03GQUb0oVo1soiX52Dkq49Y,368
|
|
154
|
-
nxtomomill/utils/flat_reducer.py,sha256=
|
|
155
|
+
nxtomomill/utils/flat_reducer.py,sha256=Ul-Gi3CYR0JU6tbWaFaZntJ7T-oRtjzYusQbwg0Zrps,12416
|
|
155
156
|
nxtomomill/utils/h5pyutils.py,sha256=tLSnUfFp-A3RaL3YXu4b_deiToQKFi8xn45Cp0r_1Dw,8400
|
|
156
157
|
nxtomomill/utils/hdf5.py,sha256=KuJdr3LnvA8eX17ECdwlPgVARZAf_8Z5xIJZx3Otplw,4983
|
|
157
158
|
nxtomomill/utils/io.py,sha256=f3pdEqH2Fca5vl7xPry1cPp5yP4Tnas8DGbfPqz-94Q,3158
|
|
@@ -162,9 +163,9 @@ nxtomomill/utils/tests/test_flat_reducer.py,sha256=kMatWooQ5vRcsf03rkMDEEdgkmjv9
|
|
|
162
163
|
nxtomomill/utils/tests/test_h5pyutils.py,sha256=L9h0-m68rvLCWIFzK71K3Hg_9YH-_PynEsPghSkz5qY,8890
|
|
163
164
|
nxtomomill/utils/tests/test_nexus_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
164
165
|
nxtomomill/utils/tests/test_utils.py,sha256=D4I_r2Xux4pAc8E4lhi4BHA0-0kyiOqlU9JNnJGS3Es,53064
|
|
165
|
-
nxtomomill-2.2.0.
|
|
166
|
-
nxtomomill-2.2.0.
|
|
167
|
-
nxtomomill-2.2.0.
|
|
168
|
-
nxtomomill-2.2.0.
|
|
169
|
-
nxtomomill-2.2.0.
|
|
170
|
-
nxtomomill-2.2.0.
|
|
166
|
+
nxtomomill-2.2.0.dev2.dist-info/licenses/LICENSE,sha256=3-51vily0513UmUtHb-gWYFOhge5Ldp8HTjIOPtIdBo,1257
|
|
167
|
+
nxtomomill-2.2.0.dev2.dist-info/METADATA,sha256=1J8Des6wS0p8z_lMZB2-UXA9RBsW1C6S2LISFKo1IVY,5318
|
|
168
|
+
nxtomomill-2.2.0.dev2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
169
|
+
nxtomomill-2.2.0.dev2.dist-info/entry_points.txt,sha256=kZUAunNCjlk8gda1r07SPNRO9TR5rP0wVPa8-jVx5ts,56
|
|
170
|
+
nxtomomill-2.2.0.dev2.dist-info/top_level.txt,sha256=FxntqOpZgeTW3KHuyJ7gjFwOcTSdNNIGex2gPf07-Vg,15
|
|
171
|
+
nxtomomill-2.2.0.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|