nxtomomill 2.2.0.dev1__tar.gz → 2.2.0.dev2__tar.gz

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.
Files changed (177) hide show
  1. {nxtomomill-2.2.0.dev1/nxtomomill.egg-info → nxtomomill-2.2.0.dev2}/PKG-INFO +1 -1
  2. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/h52nx.py +35 -1
  3. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/baseacquisition.py +122 -102
  4. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/standardacquisition.py +17 -1
  5. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/tests/test_acquisition.py +75 -6
  6. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/utils/_context.py +1 -0
  7. nxtomomill-2.2.0.dev2/nxtomomill/converter/hdf5/acquisition/utils/_keys.py +29 -0
  8. nxtomomill-2.2.0.dev2/nxtomomill/converter/hdf5/tests/test_err_handling.py +48 -0
  9. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/tests/test_h52nx_utils.py +9 -5
  10. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/tests/test_hdf5converter.py +132 -0
  11. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/dx2nx.py +9 -0
  12. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/edf2nx/general_section.py +6 -3
  13. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/edf2nx/unit_section.py +3 -0
  14. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/_context.py +2 -0
  15. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/general_section.py +5 -1
  16. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/keys_section.py +3 -0
  17. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/tests/test_edf2nx_model.py +12 -0
  18. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/tests/test_h52nx_model.py +15 -2
  19. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2/nxtomomill.egg-info}/PKG-INFO +1 -1
  20. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill.egg-info/SOURCES.txt +1 -0
  21. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill.egg-info/top_level.txt +0 -2
  22. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/pyproject.toml +1 -1
  23. nxtomomill-2.2.0.dev1/nxtomomill/converter/hdf5/acquisition/utils/_keys.py +0 -10
  24. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/LICENSE +0 -0
  25. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/README.md +0 -0
  26. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/doc/conf.py +0 -0
  27. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/__init__.py +0 -0
  28. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/__main__.py +0 -0
  29. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/__init__.py +0 -0
  30. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/blissfluo2nx.py +0 -0
  31. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/blissfluoconfig.py +0 -0
  32. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/dxfile2nx.py +0 -0
  33. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/edf2nx.py +0 -0
  34. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/edf2nx_check.py +0 -0
  35. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/edfconfig.py +0 -0
  36. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/fluo2nx.py +0 -0
  37. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/fluoconfig.py +0 -0
  38. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/fscan2nx.py +0 -0
  39. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/h5config.py +0 -0
  40. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/nxcopy.py +0 -0
  41. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/patch_nx.py +0 -0
  42. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/split_nxfile.py +0 -0
  43. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_bliss_fluo2nx_app.py +0 -0
  44. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_dxfile2nx_app.py +0 -0
  45. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_edf2nx_app.py +0 -0
  46. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_edf_config_app.py +0 -0
  47. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_fluo2nx_app.py +0 -0
  48. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_h52nx_app.py +0 -0
  49. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_h5_config_app.py +0 -0
  50. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_nx_copy.py +0 -0
  51. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_patch_nx_app.py +0 -0
  52. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/tests/test_split_nxfile_app.py +0 -0
  53. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/z_concatenate_scans.py +0 -0
  54. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/app/zstages2nxs.py +0 -0
  55. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/__init__.py +0 -0
  56. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/baseconverter.py +0 -0
  57. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/dxfile/__init__.py +0 -0
  58. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/dxfile/dxfileconverter.py +0 -0
  59. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/dxfile/tests/test_dxfile.py +0 -0
  60. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/edf/__init__.py +0 -0
  61. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/edf/checks.py +0 -0
  62. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/edf/edfconverter.py +0 -0
  63. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/edf/tests/test_checks.py +0 -0
  64. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/edf/tests/test_edf2nx.py +0 -0
  65. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/edf/tests/test_edf2nx_check.py +0 -0
  66. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/fluo/__init__.py +0 -0
  67. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/fluo/blissfluoscan.py +0 -0
  68. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/fluo/fluoconverter.py +0 -0
  69. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/fluo/fluoscan.py +0 -0
  70. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/fluo/tests/__init__.py +0 -0
  71. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/fluo/tests/test_fluoscan2D.py +0 -0
  72. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/fluo/tests/test_fluoscan3D.py +0 -0
  73. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/fscan/__init__.py +0 -0
  74. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/fscan/fscanconverter.py +0 -0
  75. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/__init__.py +0 -0
  76. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/__init__.py +0 -0
  77. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/_bliss_activeconfig.py +0 -0
  78. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/backandforth.py +0 -0
  79. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/blisstomoconfig.py +0 -0
  80. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/multitomo.py +0 -0
  81. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/pcotomoacquisition.py +0 -0
  82. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/tests/bliss_tomo_datasets.py +0 -0
  83. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/tests/test_acquisition_utils.py +0 -0
  84. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/tests/test_back_and_forth.py +0 -0
  85. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/tests/test_bliss_tomo_config.py +0 -0
  86. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/tests/test_holotomo.py +0 -0
  87. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/tests/test_legacy.py +0 -0
  88. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/tests/test_multitomo_zseries.py +0 -0
  89. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/tests/test_multitomoacquisition.py +0 -0
  90. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/utils/__init__.py +0 -0
  91. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/utils/_scan_type_finder.py +0 -0
  92. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/utils/bliss_scan_type.py +0 -0
  93. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/utils/detector.py +0 -0
  94. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/utils/machine_current.py +0 -0
  95. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/utils/series.py +0 -0
  96. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/utils/timestamps.py +0 -0
  97. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisition/zseriesacquisition.py +0 -0
  98. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/acquisitionConstructor.py +0 -0
  99. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/hdf5converter.py +0 -0
  100. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/post_processing/__init__.py +0 -0
  101. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/post_processing/dark_flat_copy.py +0 -0
  102. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/tests/test_frame_flip.py +0 -0
  103. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/hdf5/utils.py +0 -0
  104. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/converter/version.py +0 -0
  105. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/__init__.py +0 -0
  106. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/config/__init__.py +0 -0
  107. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/config/configbase.py +0 -0
  108. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/config/confighandler.py +1 -1
  109. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/config/dxconfig.py +0 -0
  110. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/config/edfconfig.py +0 -0
  111. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/config/fluoconfig.py +0 -0
  112. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/config/hdf5config.py +0 -0
  113. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/config/tests/__init__.py +0 -0
  114. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/config/tests/test_confighandler.py +0 -0
  115. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/tests/test_frame_group.py +0 -0
  116. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/io/tests/utils.py +0 -0
  117. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/__init__.py +0 -0
  118. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/base/ConfigBase.py +0 -0
  119. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/base/FrmFlatToNestedBase.py +0 -0
  120. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/base/NestedModelBase.py +0 -0
  121. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/base/__init__.py +0 -0
  122. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/base/general_section.py +0 -0
  123. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/base/instrument_section.py +0 -0
  124. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/base/source_section.py +0 -0
  125. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/blissfluo2nx/__init__.py +0 -0
  126. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/blissfluo2nx/blissfluo2nx.py +0 -0
  127. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/blissfluo2nx/general_section.py +0 -0
  128. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/edf2nx/__init__.py +0 -0
  129. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/edf2nx/dark_and_flat_section.py +0 -0
  130. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/edf2nx/detector_section.py +0 -0
  131. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/edf2nx/edf2nx.py +0 -0
  132. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/edf2nx/keys_section.py +0 -0
  133. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/edf2nx/sample_section.py +0 -0
  134. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/fluo2nx/__init__.py +0 -0
  135. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/fluo2nx/fluo2nx.py +0 -0
  136. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/fluo2nx/general_section.py +0 -0
  137. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/FrameGroup.py +0 -0
  138. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/__init__.py +0 -0
  139. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/_acquisitionstep.py +0 -0
  140. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/entries_and_title_section.py +0 -0
  141. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/extra_params_section.py +0 -0
  142. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/frame_type_section.py +0 -0
  143. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/h52nx.py +0 -0
  144. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/h52nx/multitomo_section.py +0 -0
  145. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/tests/test_context.py +0 -0
  146. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/tests/test_fluo2nx_model.py +0 -0
  147. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/tests/test_io_utils.py +0 -0
  148. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/models/utils.py +0 -0
  149. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/settings.py +0 -0
  150. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/test.py +0 -0
  151. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/__init__.py +0 -0
  152. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/datasets.py +0 -0
  153. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/resources/__init__.py +0 -0
  154. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/resources/edf2nx_config_files/__init__.py +0 -0
  155. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/resources/fluo2nx_config_files/__init__.py +0 -0
  156. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/resources/h52nx_config_files/__init__.py +0 -0
  157. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/test_version.py +0 -0
  158. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/utils/__init__.py +0 -0
  159. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/utils/bliss.py +0 -0
  160. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/tests/utils/dxfile.py +0 -0
  161. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/__init__.py +0 -0
  162. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/_os.py +0 -0
  163. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/flat_reducer.py +1 -1
  164. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/h5pyutils.py +0 -0
  165. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/hdf5.py +0 -0
  166. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/io.py +0 -0
  167. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/nexus.py +0 -0
  168. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/pintutils.py +0 -0
  169. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/tests/test_flat_reducer.py +0 -0
  170. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/tests/test_h5pyutils.py +0 -0
  171. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/tests/test_nexus_utils.py +0 -0
  172. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/tests/test_utils.py +0 -0
  173. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill/utils/utils.py +0 -0
  174. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill.egg-info/dependency_links.txt +0 -0
  175. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill.egg-info/entry_points.txt +0 -0
  176. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/nxtomomill.egg-info/requires.txt +0 -0
  177. {nxtomomill-2.2.0.dev1 → nxtomomill-2.2.0.dev2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nxtomomill
3
- Version: 2.2.0.dev1
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:
@@ -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
- converter.convert()
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 .utils._keys import keys_to_str_tuple
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: let it propagate, the
414
- # caller decides how to handle it (e.g. translations
415
- # fall back to None instead of trying other keys)
416
- raise
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
- for grp in (
633
- self._get_positioners_node(root_node),
634
- root_node,
635
- self._get_measurement_node(root_node),
636
- ):
637
- try:
638
- angles, unit = self._get_node_values_for_frame_array(
639
- node=grp,
640
- n_frame=n_frame,
641
- keys=keys_to_str_tuple(self.configuration.rotation_angle_keys),
642
- info_retrieve="rotation angle",
643
- expected_unit=_ureg.degree,
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
- for grp in self._get_positioners_node(root_node), root_node:
661
- try:
662
- x_tr, unit = self._get_node_values_for_frame_array(
663
- node=grp,
664
- n_frame=n_frame,
665
- keys=keys_to_str_tuple(self.configuration.sample_x_keys),
666
- info_retrieve="sample x translation",
667
- expected_unit=_ureg.mm,
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
- for grp in self._get_positioners_node(root_node), root_node:
685
- try:
686
- y_tr, unit = self._get_node_values_for_frame_array(
687
- node=grp,
688
- n_frame=n_frame,
689
- keys=keys_to_str_tuple(self.configuration.sample_y_keys),
690
- info_retrieve="sample y translation",
691
- expected_unit=_ureg.mm,
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
- for grp in self._get_positioners_node(root_node), root_node:
709
- try:
710
- x_tr, unit = self._get_node_values_for_frame_array(
711
- node=grp,
712
- n_frame=n_frame,
713
- keys=keys_to_str_tuple(self.configuration.translation_x_keys),
714
- info_retrieve="x base translation",
715
- expected_unit=_ureg.mm,
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
- for grp in self._get_positioners_node(root_node), root_node:
738
- try:
739
- y_tr, unit = self._get_node_values_for_frame_array(
740
- node=grp,
741
- n_frame=n_frame,
742
- keys=keys_to_str_tuple(self.configuration.translation_y_keys),
743
- info_retrieve="y base translation",
744
- expected_unit=_ureg.mm,
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
- for grp in BaseAcquisition._get_positioners_node(root_node), root_node:
769
- try:
770
- z_tr, unit = BaseAcquisition._get_node_values_for_frame_array(
771
- node=grp,
772
- n_frame=n_frame,
773
- keys=keys_to_str_tuple(configuration.translation_z_keys),
774
- info_retrieve="z translation",
775
- expected_unit=_ureg.mm,
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
- if self.configuration.resolve_vds:
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 x, y and z are set to None (instead of failing)
60
- when the corresponding dataset reports an 'ERR' value.
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
- assert std_acq._get_translation_x(root_node=root_node, n_frame=1) is None
84
- assert std_acq._get_translation_y(root_node=root_node, n_frame=1) is None
85
- assert std_acq._get_translation_z(root_node=root_node, n_frame=1) is None
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):
@@ -94,6 +94,7 @@ def update_config_keys_from_tomo_config_context(
94
94
  _Key(
95
95
  dataset=real_dataset_name,
96
96
  contexts=key.contexts - {Proxy.TOMO_CONFIG},
97
+ resolved_from_tomo_config=True,
97
98
  )
98
99
  )
99
100
  return tuple(candidates)
@@ -0,0 +1,29 @@
1
+ """A set of utils related to keys management"""
2
+
3
+ from .....models.h52nx._context import _Key
4
+
5
+
6
+ def keys_to_str_tuple(keys: tuple[_Key]) -> tuple[str]:
7
+ for key in keys:
8
+ if not isinstance(key, _Key):
9
+ raise TypeError(f"key should be an instance of {_Key}. Got {type(key)}")
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(dataset="alias_sy+alias_ty", contexts=set()),
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 == (_Key(dataset="single_su", contexts=set()),)
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():