NeuNorm 2.2.1__tar.gz → 2.2.2__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 (240) hide show
  1. {neunorm-2.2.1 → neunorm-2.2.2}/CHANGELOG.md +17 -0
  2. {neunorm-2.2.1 → neunorm-2.2.2}/PKG-INFO +1 -1
  3. neunorm-2.2.2/src/neunorm/_version.py +1 -0
  4. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/processing/normalizer.py +44 -14
  5. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_background_roi_pooled.py +96 -0
  6. neunorm-2.2.1/src/neunorm/_version.py +0 -1
  7. {neunorm-2.2.1 → neunorm-2.2.2}/.claude/agents/bug-hunter.md +0 -0
  8. {neunorm-2.2.1 → neunorm-2.2.2}/.claude/skills/bughunt/SKILL.md +0 -0
  9. {neunorm-2.2.1 → neunorm-2.2.2}/.claude/skills/post-merge/SKILL.md +0 -0
  10. {neunorm-2.2.1 → neunorm-2.2.2}/.claude/skills/release/SKILL.md +0 -0
  11. {neunorm-2.2.1 → neunorm-2.2.2}/.claude/skills/review-pipeline/SKILL.md +0 -0
  12. {neunorm-2.2.1 → neunorm-2.2.2}/.claude/workflows/bughunt-deep.js +0 -0
  13. {neunorm-2.2.1 → neunorm-2.2.2}/.claude/workflows/dual-family-review.js +0 -0
  14. {neunorm-2.2.1 → neunorm-2.2.2}/.gitattributes +0 -0
  15. {neunorm-2.2.1 → neunorm-2.2.2}/.github/CODEOWNERS +0 -0
  16. {neunorm-2.2.1 → neunorm-2.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {neunorm-2.2.1 → neunorm-2.2.2}/.github/dependabot.yml +0 -0
  18. {neunorm-2.2.1 → neunorm-2.2.2}/.github/release.yml +0 -0
  19. {neunorm-2.2.1 → neunorm-2.2.2}/.github/workflows/codeql.yml +0 -0
  20. {neunorm-2.2.1 → neunorm-2.2.2}/.github/workflows/dependency-scan.yml +0 -0
  21. {neunorm-2.2.1 → neunorm-2.2.2}/.github/workflows/test_and_deploy.yaml +0 -0
  22. {neunorm-2.2.1 → neunorm-2.2.2}/.github/workflows/update-lockfiles.yml +0 -0
  23. {neunorm-2.2.1 → neunorm-2.2.2}/.gitignore +0 -0
  24. {neunorm-2.2.1 → neunorm-2.2.2}/.grype.yaml +0 -0
  25. {neunorm-2.2.1 → neunorm-2.2.2}/.pre-commit-config.yaml +0 -0
  26. {neunorm-2.2.1 → neunorm-2.2.2}/.readthedocs.yaml +0 -0
  27. {neunorm-2.2.1 → neunorm-2.2.2}/.zenodo.json +0 -0
  28. {neunorm-2.2.1 → neunorm-2.2.2}/AGENTS.md +0 -0
  29. {neunorm-2.2.1 → neunorm-2.2.2}/CITATION.cff +0 -0
  30. {neunorm-2.2.1 → neunorm-2.2.2}/CLAUDE.md +0 -0
  31. {neunorm-2.2.1 → neunorm-2.2.2}/CONTRIBUTING.md +0 -0
  32. {neunorm-2.2.1 → neunorm-2.2.2}/LICENSE +0 -0
  33. {neunorm-2.2.1 → neunorm-2.2.2}/README.md +0 -0
  34. {neunorm-2.2.1 → neunorm-2.2.2}/SECURITY.md +0 -0
  35. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/NeuNorm/__init__.py +0 -0
  36. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/NeuNorm/_utilities.py +0 -0
  37. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/NeuNorm/exporter.py +0 -0
  38. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/NeuNorm/loader.py +0 -0
  39. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/NeuNorm/normalization.py +0 -0
  40. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/NeuNorm/roi.py +0 -0
  41. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/README.md +0 -0
  42. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/conda.recipe/meta.yaml +0 -0
  43. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/df/DC0000.tif +0 -0
  44. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/df/DC0001.tif +0 -0
  45. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/df/DC0002.tif +0 -0
  46. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0001.tif +0 -0
  47. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0002.tif +0 -0
  48. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0003.tif +0 -0
  49. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0004.tif +0 -0
  50. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0005.tif +0 -0
  51. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0006.tif +0 -0
  52. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0007.tif +0 -0
  53. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0008.tif +0 -0
  54. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0009.tif +0 -0
  55. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0010.tif +0 -0
  56. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0011.tif +0 -0
  57. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0012.tif +0 -0
  58. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0013.tif +0 -0
  59. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0014.tif +0 -0
  60. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/ob/0015.tif +0 -0
  61. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0002.tif +0 -0
  62. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0003.tif +0 -0
  63. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0004.tif +0 -0
  64. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0005.tif +0 -0
  65. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0006.tif +0 -0
  66. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0007.tif +0 -0
  67. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0008.tif +0 -0
  68. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0009.tif +0 -0
  69. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0010.tif +0 -0
  70. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0011.tif +0 -0
  71. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0012.tif +0 -0
  72. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0013.tif +0 -0
  73. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0014.tif +0 -0
  74. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/data/sample/0015.tif +0 -0
  75. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/Makefile +0 -0
  76. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/readthedocs-environment.yml +0 -0
  77. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/_static/normalization_equation.png +0 -0
  78. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/_static/normalization_principle.png +0 -0
  79. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/_static/progress_bar_loading.png +0 -0
  80. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/_static/progress_bar_normalization.png +0 -0
  81. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/conf.py +0 -0
  82. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/cropping_data.rst +0 -0
  83. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/dark_field_correction.rst +0 -0
  84. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/export_data.rst +0 -0
  85. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/index.rst +0 -0
  86. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/installation.rst +0 -0
  87. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/loading.rst +0 -0
  88. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/normalization.rst +0 -0
  89. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/notebook.rst +0 -0
  90. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/retrieve_normalized_data.rst +0 -0
  91. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/tutorial_normalization_with_fewer_ob_than_sample.ipynb +0 -0
  92. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/tutorial_using_array_input.ipynb +0 -0
  93. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/documentation/source/tutorial_using_folder_input.ipynb +0 -0
  94. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/environment.yml +0 -0
  95. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/notebooks/debugging_multi_roi_bug.ipynb +0 -0
  96. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/notebooks/notebook_display_with_time_estimation.ipynb +0 -0
  97. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/notebooks/testing_new_auto_gamma_filtering.ipynb +0 -0
  98. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/notebooks/tutorial_normalization_with_fewer_ob_than_sample.ipynb +0 -0
  99. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/notebooks/tutorial_normalization_with_fewer_ob_than_sample_MAYBE_BUG.ipynb +0 -0
  100. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/notebooks/tutorial_using_array_input.ipynb +0 -0
  101. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/notebooks/tutorial_using_array_input_and_export_array.ipynb +0 -0
  102. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/notebooks/tutorial_using_array_input_and_several_roi.ipynb +0 -0
  103. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/notebooks/tutorial_using_folder_input.ipynb +0 -0
  104. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/paper/paper.bib +0 -0
  105. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/paper/paper.md +0 -0
  106. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/scripts/clean_build_artifacts.sh +0 -0
  107. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/NeuNorm/normalizing_test.py +0 -0
  108. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/NeuNorm/test_cropping.py +0 -0
  109. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/NeuNorm/test_export.py +0 -0
  110. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/NeuNorm/test_loading.py +0 -0
  111. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/NeuNorm/test_roi.py +0 -0
  112. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/NeuNorm/test_utilities.py +0 -0
  113. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/different_format/df001_4_by_4.tif +0 -0
  114. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/different_format/image001_4_by_4.tif +0 -0
  115. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/different_format/image001_with_gamma.tif +0 -0
  116. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/different_format/not_supported_file.fake +0 -0
  117. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/different_format/ob001_4_by_4.tif +0 -0
  118. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/fits/df/df001.fits +0 -0
  119. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/fits/ob/ob001.fits +0 -0
  120. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/fits/ob/ob002.fits +0 -0
  121. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/fits/ob/ob003.fits +0 -0
  122. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/fits/test_roi/ob.fits +0 -0
  123. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/fits/test_roi/sample.fits +0 -0
  124. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/format_not_suppored.txt +0 -0
  125. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/hdf5/dump_file.hdf5 +0 -0
  126. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/ob001.fits +0 -0
  127. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/df/df001.tif +0 -0
  128. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/df/df002.tif +0 -0
  129. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/df/df003.tif +0 -0
  130. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/ob/ob001.tif +0 -0
  131. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/ob/ob002.tif +0 -0
  132. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/ob/ob003.tif +0 -0
  133. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/sample_with_gamma/image001.tif +0 -0
  134. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/special_ob/ob_0001_2rois.tiff +0 -0
  135. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/special_sample/image_0001_2rois.tiff +0 -0
  136. {neunorm-2.2.1 → neunorm-2.2.2}/archive/neunorm-1.x/tests/data/tif/special_sample/image_0001_roi_no_ob.tiff +0 -0
  137. {neunorm-2.2.1 → neunorm-2.2.2}/codecov.yaml +0 -0
  138. {neunorm-2.2.1 → neunorm-2.2.2}/docs/api.rst +0 -0
  139. {neunorm-2.2.1 → neunorm-2.2.2}/docs/conf.py +0 -0
  140. {neunorm-2.2.1 → neunorm-2.2.2}/docs/development/DEVELOPMENT_PLAN.md +0 -0
  141. {neunorm-2.2.1 → neunorm-2.2.2}/docs/index.md +0 -0
  142. {neunorm-2.2.1 → neunorm-2.2.2}/docs/migration.md +0 -0
  143. {neunorm-2.2.1 → neunorm-2.2.2}/docs/workflows/README.md +0 -0
  144. {neunorm-2.2.1 → neunorm-2.2.2}/docs/workflows/mars_ccd_cmos.md +0 -0
  145. {neunorm-2.2.1 → neunorm-2.2.2}/docs/workflows/mars_tpx3.md +0 -0
  146. {neunorm-2.2.1 → neunorm-2.2.2}/docs/workflows/venus_ccd_cmos.md +0 -0
  147. {neunorm-2.2.1 → neunorm-2.2.2}/docs/workflows/venus_tpx1.md +0 -0
  148. {neunorm-2.2.1 → neunorm-2.2.2}/docs/workflows/venus_tpx3.md +0 -0
  149. {neunorm-2.2.1 → neunorm-2.2.2}/examples/pulse_reconstruction_explained.ipynb +0 -0
  150. {neunorm-2.2.1 → neunorm-2.2.2}/pixi.lock +0 -0
  151. {neunorm-2.2.1 → neunorm-2.2.2}/pyproject.toml +0 -0
  152. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/__init__.py +0 -0
  153. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/data_models/__init__.py +0 -0
  154. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/data_models/core.py +0 -0
  155. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/data_models/roi.py +0 -0
  156. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/data_models/tof.py +0 -0
  157. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/exporters/__init__.py +0 -0
  158. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/exporters/hdf5_writer.py +0 -0
  159. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/exporters/tiff_writer.py +0 -0
  160. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/filters/__init__.py +0 -0
  161. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/filters/gamma_filter.py +0 -0
  162. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/loaders/__init__.py +0 -0
  163. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/loaders/event_loader.py +0 -0
  164. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/loaders/fits_loader.py +0 -0
  165. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/loaders/metadata_loader.py +0 -0
  166. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/loaders/stack_loader.py +0 -0
  167. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/loaders/tiff_loader.py +0 -0
  168. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/pipelines/__init__.py +0 -0
  169. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/pipelines/mars_ccd.py +0 -0
  170. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/pipelines/mars_tpx3.py +0 -0
  171. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/pipelines/venus_ccd.py +0 -0
  172. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/pipelines/venus_tpx1.py +0 -0
  173. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/pipelines/venus_tpx3_event.py +0 -0
  174. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/pipelines/venus_tpx3_histogram.py +0 -0
  175. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/processing/__init__.py +0 -0
  176. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/processing/air_region_corrector.py +0 -0
  177. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/processing/dark_corrector.py +0 -0
  178. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/processing/reference_preparer.py +0 -0
  179. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/processing/roi_clipper.py +0 -0
  180. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/processing/run_combiner.py +0 -0
  181. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/processing/spatial_rebinner.py +0 -0
  182. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/processing/uncertainty_calculator.py +0 -0
  183. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/tof/__init__.py +0 -0
  184. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/tof/binning.py +0 -0
  185. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/tof/coordinate_converter.py +0 -0
  186. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/tof/event_converter.py +0 -0
  187. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/tof/histogram_rebinner.py +0 -0
  188. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/tof/pixel_detector.py +0 -0
  189. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/tof/pulse_reconstruction.py +0 -0
  190. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/tof/resonance.py +0 -0
  191. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/tof/statistics_analyzer.py +0 -0
  192. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/utils/__init__.py +0 -0
  193. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/utils/_numba_compat.py +0 -0
  194. {neunorm-2.2.1 → neunorm-2.2.2}/src/neunorm/utils/constants.py +0 -0
  195. {neunorm-2.2.1 → neunorm-2.2.2}/tests/data/fits/sample/image001.fits +0 -0
  196. {neunorm-2.2.1 → neunorm-2.2.2}/tests/data/fits/sample/image002.fits +0 -0
  197. {neunorm-2.2.1 → neunorm-2.2.2}/tests/data/fits/sample/image003.fits +0 -0
  198. {neunorm-2.2.1 → neunorm-2.2.2}/tests/data/tif/sample/image001.tif +0 -0
  199. {neunorm-2.2.1 → neunorm-2.2.2}/tests/data/tif/sample/image002.tif +0 -0
  200. {neunorm-2.2.1 → neunorm-2.2.2}/tests/data/tif/sample/image003.tif +0 -0
  201. {neunorm-2.2.1 → neunorm-2.2.2}/tests/integration/test_tof_pipeline.py +0 -0
  202. {neunorm-2.2.1 → neunorm-2.2.2}/tests/neunorm/test_version.py +0 -0
  203. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_air_region_corrector.py +0 -0
  204. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_analyze_statistics.py +0 -0
  205. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_binning_config.py +0 -0
  206. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_binning_conversions.py +0 -0
  207. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_constants.py +0 -0
  208. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_dark_corrector.py +0 -0
  209. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_division_by_zero_guards.py +0 -0
  210. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_event_converter.py +0 -0
  211. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_event_loader.py +0 -0
  212. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_eventdata_auto_validation.py +0 -0
  213. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_fits_loader.py +0 -0
  214. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_gamma_filter.py +0 -0
  215. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_hdf5_writer.py +0 -0
  216. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_mars_ccd.py +0 -0
  217. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_mars_tpx3.py +0 -0
  218. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_metadata_loader.py +0 -0
  219. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_normalizer.py +0 -0
  220. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_numba_compat.py +0 -0
  221. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_parallel_processing.py +0 -0
  222. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_pixel_detector.py +0 -0
  223. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_pulse_reconstruction.py +0 -0
  224. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_rebin_spatial.py +0 -0
  225. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_rebin_tof.py +0 -0
  226. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_reference_preparer.py +0 -0
  227. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_refine_boundaries_jit.py +0 -0
  228. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_remaining_jit.py +0 -0
  229. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_resonance_detection.py +0 -0
  230. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_roi.py +0 -0
  231. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_roi_clipper.py +0 -0
  232. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_run_combiner.py +0 -0
  233. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_tiff_loader.py +0 -0
  234. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_tiff_writer.py +0 -0
  235. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_uncertainty.py +0 -0
  236. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_variance_propagation_fix.py +0 -0
  237. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_venus_ccd.py +0 -0
  238. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_venus_tpx1.py +0 -0
  239. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_venus_tpx3_event.py +0 -0
  240. {neunorm-2.2.1 → neunorm-2.2.2}/tests/unit/test_venus_tpx3_histogram.py +0 -0
@@ -5,6 +5,22 @@ All notable changes to NeuNorm are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.2.2] - 2026-07-02
9
+
10
+ ### Fixed
11
+
12
+ - **`background_roi` can now reproduce legacy 1.x zero-count-ROI semantics** (completes the
13
+ downstream-superset goal of
14
+ [#172](https://github.com/ornlneutronimaging/NeuNorm/issues/172) /
15
+ [#159](https://github.com/ornlneutronimaging/NeuNorm/issues/159)): the pooled-mean
16
+ strictly-positive/finite guard can be opted out with
17
+ `apply_background_roi(..., strict=False)` or
18
+ `normalize_transmission(...)` / `normalize_with_dark(..., background_roi_strict=False)`,
19
+ letting a zero-count background ROI propagate `inf`/`nan` through the division exactly as
20
+ NeuNorm 1.x did (iBeatles pins this behavior). The default stays strict — a
21
+ non-positive/non-finite pooled mean raises `ValueError` — and structural errors (bad ROI
22
+ bounds, missing dims) always raise.
23
+
8
24
  ## [2.2.1] - 2026-07-01
9
25
 
10
26
  ### Added
@@ -196,6 +212,7 @@ documentation are archived under
196
212
  [`archive/neunorm-1.x/`](archive/neunorm-1.x/); released 1.x versions remain
197
213
  available on PyPI and the `conda-forge` channel (`pip install "NeuNorm<2"`).
198
214
 
215
+ [2.2.2]: https://github.com/ornlneutronimaging/NeuNorm/releases/tag/v2.2.2
199
216
  [2.2.1]: https://github.com/ornlneutronimaging/NeuNorm/releases/tag/v2.2.1
200
217
  [2.2.0]: https://github.com/ornlneutronimaging/NeuNorm/releases/tag/v2.2.0
201
218
  [2.1.0]: https://github.com/ornlneutronimaging/NeuNorm/releases/tag/v2.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: NeuNorm
3
- Version: 2.2.1
3
+ Version: 2.2.2
4
4
  Summary: Neutron imaging normalization and TOF data processing for ORNL facilities
5
5
  Project-URL: homepage, https://github.com/ornlneutronimaging/NeuNorm
6
6
  Project-URL: repository, https://github.com/ornlneutronimaging/NeuNorm
@@ -0,0 +1 @@
1
+ __version__ = "2.2.2"
@@ -64,6 +64,7 @@ def _pooled_roi_coefficient(
64
64
  data: sc.DataArray,
65
65
  rois_bounds: list[tuple[int, int, int, int]],
66
66
  name: str,
67
+ strict: bool = True,
67
68
  ) -> sc.Variable:
68
69
  """Per-image **pooled** background coefficient over one or more ROIs.
69
70
 
@@ -73,8 +74,11 @@ def _pooled_roi_coefficient(
73
74
  the summed counts and the pixel count (spatial ``(x, y)`` masks assumed). ``x1``/``y1`` are
74
75
  exclusive stops. Returns a variance-bearing scipp Variable (the variance of the pooled mean).
75
76
 
76
- Raises ``ValueError`` on an invalid/out-of-bounds ROI, missing ``x``/``y`` dims, no unmasked
77
- pixels, or a non-positive/non-finite pooled mean (which would silently yield inf/nan output).
77
+ Raises ``ValueError`` on an invalid/out-of-bounds ROI or missing ``x``/``y`` dims. With
78
+ ``strict`` (default) it also rejects a non-positive/non-finite pooled mean (which would
79
+ silently yield inf/nan output); ``strict=False`` skips only that guard and lets zeros
80
+ propagate through the division — the 1.x semantics, for downstreams reproducing legacy
81
+ outputs bit for bit.
78
82
  """
79
83
  if "x" not in data.dims or "y" not in data.dims:
80
84
  raise ValueError(f"{name} must have 'x' and 'y' dimensions for background_roi normalization")
@@ -99,7 +103,7 @@ def _pooled_roi_coefficient(
99
103
  n_unmasked = roi_n if n_unmasked is None else n_unmasked + roi_n
100
104
 
101
105
  coeff = total / n_unmasked
102
- if not bool(sc.all(sc.isfinite(coeff)).value) or sc.min(coeff).value <= 0:
106
+ if strict and (not bool(sc.all(sc.isfinite(coeff)).value) or sc.min(coeff).value <= 0):
103
107
  raise ValueError(
104
108
  f"background_roi {name} pooled mean must be strictly positive and finite "
105
109
  f"(min={sc.min(coeff).value}); the ROI(s) must contain positive counts in every image"
@@ -111,10 +115,11 @@ def _background_roi_means(
111
115
  sample: sc.DataArray,
112
116
  ob: sc.DataArray,
113
117
  rois_bounds: list[tuple[int, int, int, int]],
118
+ strict: bool = True,
114
119
  ) -> tuple[sc.Variable, sc.Variable]:
115
120
  """Per-image pooled background means (cs, co) for sample and OB over the same ROI list."""
116
- cs = _pooled_roi_coefficient(sample, rois_bounds, "sample")
117
- co = _pooled_roi_coefficient(ob, rois_bounds, "ob")
121
+ cs = _pooled_roi_coefficient(sample, rois_bounds, "sample", strict=strict)
122
+ co = _pooled_roi_coefficient(ob, rois_bounds, "ob", strict=strict)
118
123
  return cs, co
119
124
 
120
125
 
@@ -174,7 +179,9 @@ def _roi_dark_mean_covariance(
174
179
  roi_var_sum = sc.variances(sc.sum(d_roi, dim=["x", "y"]).data) # counts**2 (scalar)
175
180
  intersection_var_sum = roi_var_sum if intersection_var_sum is None else intersection_var_sum + roi_var_sum
176
181
 
177
- # n_s, n_o > 0 is guaranteed upstream (_pooled_roi_coefficient raises on an all-masked ROI).
182
+ # n_s, n_o > 0 is guaranteed upstream under strict (_pooled_roi_coefficient raises on an
183
+ # all-masked ROI); with strict=False an all-masked ROI gives n=0 and the resulting
184
+ # non-finite covariance is zeroed by the isfinite guard on the over-count below.
178
185
  return intersection_var_sum / (n_s * n_o)
179
186
 
180
187
 
@@ -185,6 +192,7 @@ def normalize_transmission( # noqa: C901
185
192
  proton_charge_ob: Optional[Union[float, sc.Variable]] = None,
186
193
  pc_uncertainty: float = 0.005,
187
194
  background_roi: Optional[BackgroundROILike] = None,
195
+ background_roi_strict: bool = True,
188
196
  ) -> sc.DataArray:
189
197
  """
190
198
  Normalize sample by open beam to compute transmission.
@@ -220,9 +228,15 @@ def normalize_transmission( # noqa: C901
220
228
  spanning ``w+1`` pixels), use ``ROI(..., inclusive=True)``; see ``apply_background_roi`` for the
221
229
  open-beam-less form. Mutually exclusive with ``proton_charge_sample`` / ``proton_charge_ob``.
222
230
  Uncertainty is propagated first-order (the in-ROI sample/ROI-mean correlation is not
223
- corrected). Raises ``ValueError`` if the pooled mean is not strictly positive and finite in
224
- every image. Indices are resolved against the passed arrays; if a pipeline crops with ``roi``
225
- first, give ``background_roi`` in the post-crop frame.
231
+ corrected). Unless ``background_roi_strict=False``, raises ``ValueError`` if the pooled mean
232
+ is not strictly positive and finite in every image. Indices are resolved against the passed
233
+ arrays; if a pipeline crops with ``roi`` first, give ``background_roi`` in the post-crop
234
+ frame.
235
+ background_roi_strict : bool, optional
236
+ With the default ``True``, a non-positive/non-finite pooled background mean raises
237
+ ``ValueError``. ``False`` skips only that guard and lets zeros propagate through the
238
+ division (inf/nan output) — the legacy 1.x semantics, for downstreams reproducing 1.x
239
+ outputs bit for bit. Structural errors (bad ROI bounds, missing dims) always raise.
226
240
 
227
241
  Returns
228
242
  -------
@@ -263,7 +277,7 @@ def normalize_transmission( # noqa: C901
263
277
  "background_roi is the flux-normalization proxy for when proton charge is unavailable."
264
278
  )
265
279
  logger.info("Applying background-ROI flux normalization with ROI(s) {}", roi_list)
266
- cs, co = _background_roi_means(sample, ob, roi_list)
280
+ cs, co = _background_roi_means(sample, ob, roi_list, strict=background_roi_strict)
267
281
  # scipp refuses to broadcast a variance-bearing scalar across the image (it would introduce
268
282
  # correlations), so divide by the variance-free means and re-add their variance contribution
269
283
  # below. Handle cs and co INDEPENDENTLY — the two inputs may carry variance on one side only
@@ -374,6 +388,7 @@ def normalize_with_dark(
374
388
  proton_charge_ob: Optional[Union[float, sc.Variable]] = None,
375
389
  pc_uncertainty: float = 0.005,
376
390
  background_roi: Optional[BackgroundROILike] = None,
391
+ background_roi_strict: bool = True,
377
392
  ) -> sc.DataArray:
378
393
  """Dark-correct and normalize in one step, treating the shared dark frame correctly.
379
394
 
@@ -405,6 +420,9 @@ def normalize_with_dark(
405
420
  covariance term ``2*T^2*Cov(cs,co)/(cs*co)`` (``Cov(cs,co) = Var(mean(D_roi))``) — the
406
421
  ROI-mean analog of the pixel-level correction. (The in-ROI pixel/ROI-mean correlation
407
422
  remains uncorrected, as documented on ``normalize_transmission``.)
423
+ background_roi_strict : bool, optional
424
+ See ``normalize_transmission``: ``False`` skips the strictly-positive/finite pooled-mean
425
+ guard and lets zeros propagate (legacy 1.x semantics).
408
426
 
409
427
  Returns
410
428
  -------
@@ -416,7 +434,13 @@ def normalize_with_dark(
416
434
  sample_dc = subtract_dark(sample, dark)
417
435
  ob_dc = subtract_dark(ob, dark)
418
436
  transmission = normalize_transmission(
419
- sample_dc, ob_dc, proton_charge_sample, proton_charge_ob, pc_uncertainty, background_roi=background_roi
437
+ sample_dc,
438
+ ob_dc,
439
+ proton_charge_sample,
440
+ proton_charge_ob,
441
+ pc_uncertainty,
442
+ background_roi=background_roi,
443
+ background_roi_strict=background_roi_strict,
420
444
  )
421
445
 
422
446
  # Correct the shared-dark double-count. normalize_transmission propagated
@@ -437,7 +461,7 @@ def normalize_with_dark(
437
461
  # for proton charge, or k = co/cs (ratio of dark-corrected ROI means) for background_roi. Use
438
462
  # the coefficient values only (variance-free) — this is a variance correction, first-order in k.
439
463
  if background_roi is not None:
440
- cs, co = _background_roi_means(sample_dc, ob_dc, roi_list)
464
+ cs, co = _background_roi_means(sample_dc, ob_dc, roi_list, strict=background_roi_strict)
441
465
  cs_v, co_v = sc.values(cs), sc.values(co)
442
466
  k_squared = (co_v / cs_v) ** 2
443
467
  else:
@@ -493,6 +517,7 @@ def _proton_charge_ratio_squared(
493
517
  def apply_background_roi(
494
518
  data: sc.DataArray,
495
519
  background_roi: BackgroundROILike,
520
+ strict: bool = True,
496
521
  ) -> sc.DataArray:
497
522
  """Flux-flatten a stack by its pooled background-ROI mean (no open beam).
498
523
 
@@ -504,7 +529,7 @@ def apply_background_roi(
504
529
  First-order uncertainty from the pooled ROI mean is propagated
505
530
  (``Var += corrected**2 * Var(coeff) / coeff**2``); the in-ROI pixel/ROI-mean correlation is not
506
531
  corrected. Reductions are mask-aware. Raises ``ValueError`` if the pooled mean is not strictly
507
- positive and finite in every image.
532
+ positive and finite in every image (unless ``strict=False``).
508
533
 
509
534
  Parameters
510
535
  ----------
@@ -512,6 +537,11 @@ def apply_background_roi(
512
537
  Image stack with ``x``/``y`` dims (e.g. ``(spectral, x, y)``), optionally carrying variance.
513
538
  background_roi : ROI/tuple or a sequence of them
514
539
  Sample-free background region(s), pooled.
540
+ strict : bool, optional
541
+ With the default ``True``, a non-positive/non-finite pooled mean raises ``ValueError``.
542
+ ``False`` skips only that guard and lets zeros propagate through the division (inf/nan
543
+ output) — the legacy 1.x semantics, for downstreams reproducing 1.x outputs bit for bit.
544
+ Structural errors (bad ROI bounds, missing dims) always raise.
515
545
 
516
546
  Returns
517
547
  -------
@@ -520,7 +550,7 @@ def apply_background_roi(
520
550
  """
521
551
  roi_list = as_roi_bounds_list(background_roi)
522
552
  logger.info("Applying sample-only background-ROI flux flattening with ROI(s) {}", roi_list)
523
- coeff = _pooled_roi_coefficient(data, roi_list, "data")
553
+ coeff = _pooled_roi_coefficient(data, roi_list, "data", strict=strict)
524
554
  coeff_var = sc.variances(coeff) if coeff.variances is not None else None
525
555
  coeff = coeff.copy()
526
556
  coeff.variances = None
@@ -153,3 +153,99 @@ def test_apply_background_roi_propagates_first_order_variance():
153
153
  np.testing.assert_allclose(out.values[7, 7], val, rtol=1e-6)
154
154
  expected_var = s[7, 7] / pool**2 + val**2 * var_pool / pool**2
155
155
  np.testing.assert_allclose(out.variances[7, 7], expected_var, rtol=1e-6)
156
+
157
+
158
+ def test_background_roi_nonstrict_zero_count_roi_propagates_inf():
159
+ """strict=False skips the positivity guard: a zero-count ROI yields inf, not ValueError.
160
+
161
+ The 1.x / iBeatles semantics (a zero-count background ROI propagates inf in the sample-only
162
+ path) — the escape hatch that lets downstreams reproduce 1.x outputs bit for bit.
163
+ """
164
+ import pytest
165
+
166
+ s = np.ones((2, 8, 8))
167
+ s[:, 0:2, 0:2] = 0.0 # ROI (0,0,2,2) pooled mean -> 0
168
+ da = sc.DataArray(sc.array(dims=["N_image", "x", "y"], values=s, unit="counts"))
169
+
170
+ # default remains strict: raises
171
+ with pytest.raises(ValueError, match="strictly positive"):
172
+ apply_background_roi(da, (0, 0, 2, 2))
173
+
174
+ # non-strict: 1.x placement exactly — in-ROI 0/0 = nan, outside 1/0 = inf
175
+ out = apply_background_roi(da, (0, 0, 2, 2), strict=False)
176
+ in_roi = np.zeros((2, 8, 8), dtype=bool)
177
+ in_roi[:, 0:2, 0:2] = True
178
+ assert np.isnan(out.values[in_roi]).all()
179
+ assert np.isinf(out.values[~in_roi]).all()
180
+
181
+ # matched-OB path: same opt-out on normalize_transmission, same placement
182
+ # (OB coefficient is 1, so T inherits the sample-side inf/nan layout)
183
+ ob = sc.DataArray(sc.array(dims=["N_image", "x", "y"], values=np.ones((2, 8, 8)), unit="counts"))
184
+ with pytest.raises(ValueError, match="strictly positive"):
185
+ normalize_transmission(da, ob, background_roi=(0, 0, 2, 2))
186
+ t = normalize_transmission(da, ob, background_roi=(0, 0, 2, 2), background_roi_strict=False)
187
+ assert np.isnan(t.values[in_roi]).all()
188
+ assert np.isinf(t.values[~in_roi]).all()
189
+
190
+
191
+ def test_background_roi_nonstrict_variance_nonfiniteness_is_confined():
192
+ """Non-strict with variances: the zero-mean frame goes non-finite, other frames stay intact."""
193
+ s = np.full((2, 8, 8), 4.0)
194
+ s[0, 0:2, 0:2] = 0.0 # frame 0 ROI mean -> 0; frame 1 ROI mean = 4
195
+ da = sc.DataArray(sc.array(dims=["N_image", "x", "y"], values=s, variances=np.ones((2, 8, 8)), unit="counts"))
196
+
197
+ out = apply_background_roi(da, (0, 0, 2, 2), strict=False) # must not raise
198
+ # frame 1 is untouched by the degenerate frame: finite values AND variances, exact values
199
+ assert np.isfinite(out.values[1]).all() and np.isfinite(out.variances[1]).all()
200
+ np.testing.assert_allclose(out.values[1], 1.0, rtol=1e-12) # uniform frame / its own mean
201
+ # frame 0 carries the non-finite result of the 1.x zero-mean division
202
+ assert not np.isfinite(out.values[0]).all()
203
+
204
+
205
+ def test_normalize_with_dark_background_roi_strict_optout():
206
+ """normalize_with_dark threads background_roi_strict through BOTH coefficient sites.
207
+
208
+ Variance-bearing inputs so the k=co/cs shared-dark correction branch (the second
209
+ _background_roi_means call) executes. Zero dark-corrected ROI mean: strict raises,
210
+ non-strict propagates non-finite values without raising. Positive ROI mean: strict and
211
+ non-strict results are identical.
212
+ """
213
+ import pytest
214
+
215
+ from neunorm.processing.normalizer import normalize_with_dark
216
+
217
+ def _stack(vals):
218
+ vals = np.asarray(vals, dtype=float)
219
+ return sc.DataArray(sc.array(dims=["N_image", "x", "y"], values=vals, variances=vals + 1.0, unit="counts"))
220
+
221
+ def _frame(vals):
222
+ vals = np.asarray(vals, dtype=float)
223
+ return sc.DataArray(sc.array(dims=["x", "y"], values=vals, variances=vals + 1.0, unit="counts"))
224
+
225
+ dark = _frame(np.full((8, 8), 5.0))
226
+ ob = _stack(np.full((1, 8, 8), 100.0))
227
+ s = np.full((1, 8, 8), 80.0)
228
+ s[:, 0:2, 0:2] = 5.0 # dark-corrected sample ROI mean -> 0
229
+ sample = _stack(s)
230
+
231
+ with pytest.raises(ValueError, match="strictly positive"):
232
+ normalize_with_dark(sample, ob, dark, background_roi=(0, 0, 2, 2))
233
+ t = normalize_with_dark(sample, ob, dark, background_roi=(0, 0, 2, 2), background_roi_strict=False)
234
+ assert not np.isfinite(t.values).all() # 1.x zero-mean division propagates
235
+
236
+ # positive ROI mean: the flag must not change anything (values OR variances)
237
+ s_ok = np.full((1, 8, 8), 80.0)
238
+ sample_ok = _stack(s_ok)
239
+ t_strict = normalize_with_dark(sample_ok, ob, dark, background_roi=(0, 0, 2, 2))
240
+ t_lax = normalize_with_dark(sample_ok, ob, dark, background_roi=(0, 0, 2, 2), background_roi_strict=False)
241
+ np.testing.assert_array_equal(t_strict.values, t_lax.values)
242
+ np.testing.assert_array_equal(t_strict.variances, t_lax.variances)
243
+
244
+
245
+ def test_background_roi_nonstrict_still_raises_structural_errors():
246
+ """strict=False relaxes ONLY the positivity guard — bad ROI bounds still raise."""
247
+ import pytest
248
+
249
+ da = sc.DataArray(sc.array(dims=["N_image", "x", "y"], values=np.ones((2, 8, 8)), unit="counts"))
250
+ with pytest.raises(ValueError, match="exceeds"):
251
+ apply_background_roi(da, (0, 0, 99, 99), strict=False)
@@ -1 +0,0 @@
1
- __version__ = "2.2.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes