nabu 2024.2.14__py3-none-any.whl → 2025.1.0__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.
- doc/doc_config.py +32 -0
- nabu/__init__.py +1 -1
- nabu/app/bootstrap_stitching.py +4 -2
- nabu/app/cast_volume.py +16 -14
- nabu/app/cli_configs.py +102 -9
- nabu/app/compare_volumes.py +1 -1
- nabu/app/composite_cor.py +2 -4
- nabu/app/diag_to_pix.py +5 -6
- nabu/app/diag_to_rot.py +10 -11
- nabu/app/double_flatfield.py +18 -5
- nabu/app/estimate_motion.py +75 -0
- nabu/app/multicor.py +28 -15
- nabu/app/parse_reconstruction_log.py +1 -0
- nabu/app/pcaflats.py +122 -0
- nabu/app/prepare_weights_double.py +1 -2
- nabu/app/reconstruct.py +1 -7
- nabu/app/reconstruct_helical.py +5 -9
- nabu/app/reduce_dark_flat.py +5 -4
- nabu/app/rotate.py +3 -1
- nabu/app/stitching.py +7 -2
- nabu/app/tests/test_reduce_dark_flat.py +2 -2
- nabu/app/validator.py +1 -4
- nabu/cuda/convolution.py +1 -1
- nabu/cuda/fft.py +1 -1
- nabu/cuda/medfilt.py +1 -1
- nabu/cuda/padding.py +1 -1
- nabu/cuda/src/backproj.cu +6 -6
- nabu/cuda/src/cone.cu +4 -0
- nabu/cuda/src/hierarchical_backproj.cu +14 -0
- nabu/cuda/utils.py +2 -2
- nabu/estimation/alignment.py +17 -31
- nabu/estimation/cor.py +27 -33
- nabu/estimation/cor_sino.py +2 -8
- nabu/estimation/focus.py +4 -8
- nabu/estimation/motion.py +557 -0
- nabu/estimation/tests/test_alignment.py +2 -0
- nabu/estimation/tests/test_motion_estimation.py +471 -0
- nabu/estimation/tests/test_tilt.py +1 -1
- nabu/estimation/tilt.py +6 -5
- nabu/estimation/translation.py +47 -1
- nabu/io/cast_volume.py +108 -18
- nabu/io/detector_distortion.py +5 -6
- nabu/io/reader.py +45 -6
- nabu/io/reader_helical.py +5 -4
- nabu/io/tests/test_cast_volume.py +2 -2
- nabu/io/tests/test_readers.py +41 -38
- nabu/io/tests/test_remove_volume.py +152 -0
- nabu/io/tests/test_writers.py +2 -2
- nabu/io/utils.py +8 -4
- nabu/io/writer.py +1 -2
- nabu/misc/fftshift.py +1 -1
- nabu/misc/fourier_filters.py +1 -1
- nabu/misc/histogram.py +1 -1
- nabu/misc/histogram_cuda.py +1 -1
- nabu/misc/padding_base.py +1 -1
- nabu/misc/rotation.py +1 -1
- nabu/misc/rotation_cuda.py +1 -1
- nabu/misc/tests/test_binning.py +1 -1
- nabu/misc/transpose.py +1 -1
- nabu/misc/unsharp.py +1 -1
- nabu/misc/unsharp_cuda.py +1 -1
- nabu/misc/unsharp_opencl.py +1 -1
- nabu/misc/utils.py +1 -1
- nabu/opencl/fft.py +1 -1
- nabu/opencl/padding.py +1 -1
- nabu/opencl/src/backproj.cl +6 -6
- nabu/opencl/utils.py +8 -8
- nabu/pipeline/config.py +2 -2
- nabu/pipeline/config_validators.py +46 -46
- nabu/pipeline/datadump.py +3 -3
- nabu/pipeline/estimators.py +271 -11
- nabu/pipeline/fullfield/chunked.py +103 -67
- nabu/pipeline/fullfield/chunked_cuda.py +5 -2
- nabu/pipeline/fullfield/computations.py +4 -1
- nabu/pipeline/fullfield/dataset_validator.py +0 -1
- nabu/pipeline/fullfield/get_double_flatfield.py +147 -0
- nabu/pipeline/fullfield/nabu_config.py +36 -17
- nabu/pipeline/fullfield/processconfig.py +41 -7
- nabu/pipeline/fullfield/reconstruction.py +14 -10
- nabu/pipeline/helical/dataset_validator.py +3 -4
- nabu/pipeline/helical/fbp.py +4 -4
- nabu/pipeline/helical/filtering.py +5 -4
- nabu/pipeline/helical/gridded_accumulator.py +10 -11
- nabu/pipeline/helical/helical_chunked_regridded.py +1 -0
- nabu/pipeline/helical/helical_reconstruction.py +12 -9
- nabu/pipeline/helical/helical_utils.py +1 -2
- nabu/pipeline/helical/nabu_config.py +2 -1
- nabu/pipeline/helical/span_strategy.py +1 -0
- nabu/pipeline/helical/weight_balancer.py +2 -3
- nabu/pipeline/params.py +20 -3
- nabu/pipeline/tests/__init__.py +0 -0
- nabu/pipeline/tests/test_estimators.py +240 -3
- nabu/pipeline/utils.py +1 -1
- nabu/pipeline/writer.py +1 -1
- nabu/preproc/alignment.py +0 -10
- nabu/preproc/ccd.py +53 -3
- nabu/preproc/ctf.py +8 -8
- nabu/preproc/ctf_cuda.py +1 -1
- nabu/preproc/double_flatfield_cuda.py +2 -2
- nabu/preproc/double_flatfield_variable_region.py +0 -1
- nabu/preproc/flatfield.py +307 -2
- nabu/preproc/flatfield_cuda.py +1 -2
- nabu/preproc/flatfield_variable_region.py +3 -3
- nabu/preproc/phase.py +2 -4
- nabu/preproc/phase_cuda.py +2 -2
- nabu/preproc/shift.py +4 -2
- nabu/preproc/shift_cuda.py +0 -1
- nabu/preproc/tests/test_ctf.py +4 -4
- nabu/preproc/tests/test_double_flatfield.py +1 -1
- nabu/preproc/tests/test_flatfield.py +1 -1
- nabu/preproc/tests/test_paganin.py +1 -3
- nabu/preproc/tests/test_pcaflats.py +154 -0
- nabu/preproc/tests/test_vshift.py +4 -1
- nabu/processing/azim.py +9 -5
- nabu/processing/convolution_cuda.py +6 -4
- nabu/processing/fft_base.py +7 -3
- nabu/processing/fft_cuda.py +25 -164
- nabu/processing/fft_opencl.py +28 -6
- nabu/processing/fftshift.py +1 -1
- nabu/processing/histogram.py +1 -1
- nabu/processing/muladd.py +0 -1
- nabu/processing/padding_base.py +1 -1
- nabu/processing/padding_cuda.py +0 -2
- nabu/processing/processing_base.py +12 -6
- nabu/processing/rotation_cuda.py +3 -1
- nabu/processing/tests/test_fft.py +2 -64
- nabu/processing/tests/test_fftshift.py +1 -1
- nabu/processing/tests/test_medfilt.py +1 -3
- nabu/processing/tests/test_padding.py +1 -1
- nabu/processing/tests/test_roll.py +1 -1
- nabu/processing/tests/test_rotation.py +4 -2
- nabu/processing/unsharp_opencl.py +1 -1
- nabu/reconstruction/astra.py +245 -0
- nabu/reconstruction/cone.py +39 -9
- nabu/reconstruction/fbp.py +7 -0
- nabu/reconstruction/fbp_base.py +36 -5
- nabu/reconstruction/filtering.py +59 -25
- nabu/reconstruction/filtering_cuda.py +22 -21
- nabu/reconstruction/filtering_opencl.py +10 -14
- nabu/reconstruction/hbp.py +26 -13
- nabu/reconstruction/mlem.py +55 -16
- nabu/reconstruction/projection.py +3 -5
- nabu/reconstruction/sinogram.py +1 -1
- nabu/reconstruction/sinogram_cuda.py +0 -1
- nabu/reconstruction/tests/test_cone.py +37 -2
- nabu/reconstruction/tests/test_deringer.py +4 -4
- nabu/reconstruction/tests/test_fbp.py +36 -15
- nabu/reconstruction/tests/test_filtering.py +27 -7
- nabu/reconstruction/tests/test_halftomo.py +28 -2
- nabu/reconstruction/tests/test_mlem.py +94 -64
- nabu/reconstruction/tests/test_projector.py +7 -2
- nabu/reconstruction/tests/test_reconstructor.py +1 -1
- nabu/reconstruction/tests/test_sino_normalization.py +0 -1
- nabu/resources/dataset_analyzer.py +210 -24
- nabu/resources/gpu.py +4 -4
- nabu/resources/logger.py +4 -4
- nabu/resources/nxflatfield.py +103 -37
- nabu/resources/tests/test_dataset_analyzer.py +37 -0
- nabu/resources/tests/test_extract.py +11 -0
- nabu/resources/tests/test_nxflatfield.py +5 -5
- nabu/resources/utils.py +16 -10
- nabu/stitching/alignment.py +8 -11
- nabu/stitching/config.py +44 -35
- nabu/stitching/definitions.py +2 -2
- nabu/stitching/frame_composition.py +8 -10
- nabu/stitching/overlap.py +4 -4
- nabu/stitching/sample_normalization.py +5 -5
- nabu/stitching/slurm_utils.py +2 -2
- nabu/stitching/stitcher/base.py +2 -0
- nabu/stitching/stitcher/dumper/base.py +0 -1
- nabu/stitching/stitcher/dumper/postprocessing.py +1 -1
- nabu/stitching/stitcher/post_processing.py +11 -9
- nabu/stitching/stitcher/pre_processing.py +37 -31
- nabu/stitching/stitcher/single_axis.py +2 -3
- nabu/stitching/stitcher_2D.py +2 -1
- nabu/stitching/tests/test_config.py +10 -11
- nabu/stitching/tests/test_sample_normalization.py +1 -1
- nabu/stitching/tests/test_slurm_utils.py +1 -2
- nabu/stitching/tests/test_y_preprocessing_stitching.py +11 -8
- nabu/stitching/tests/test_z_postprocessing_stitching.py +3 -3
- nabu/stitching/tests/test_z_preprocessing_stitching.py +27 -24
- nabu/stitching/utils/tests/__init__.py +0 -0
- nabu/stitching/utils/tests/test_post-processing.py +1 -0
- nabu/stitching/utils/utils.py +16 -18
- nabu/tests.py +0 -3
- nabu/testutils.py +62 -9
- nabu/utils.py +50 -20
- {nabu-2024.2.14.dist-info → nabu-2025.1.0.dist-info}/METADATA +7 -7
- nabu-2025.1.0.dist-info/RECORD +328 -0
- {nabu-2024.2.14.dist-info → nabu-2025.1.0.dist-info}/WHEEL +1 -1
- {nabu-2024.2.14.dist-info → nabu-2025.1.0.dist-info}/entry_points.txt +2 -1
- nabu/app/correct_rot.py +0 -70
- nabu/io/tests/test_detector_distortion.py +0 -178
- nabu-2024.2.14.dist-info/RECORD +0 -317
- /nabu/{stitching → app}/tests/__init__.py +0 -0
- {nabu-2024.2.14.dist-info → nabu-2025.1.0.dist-info}/licenses/LICENSE +0 -0
- {nabu-2024.2.14.dist-info → nabu-2025.1.0.dist-info}/top_level.txt +0 -0
nabu/app/pcaflats.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import numpy as np
|
|
4
|
+
import h5py
|
|
5
|
+
from .utils import parse_params_values
|
|
6
|
+
from ..utils import is_writeable
|
|
7
|
+
from .cli_configs import PCAFlatsConfig
|
|
8
|
+
from .. import version
|
|
9
|
+
from ..preproc.flatfield import PCAFlatsDecomposer
|
|
10
|
+
from ..io.reader import NXDarksFlats
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_flats_darks_in_nx(filename):
|
|
14
|
+
dfreader = NXDarksFlats(filename)
|
|
15
|
+
darks = np.concatenate([d for d in dfreader.get_raw_darks()], axis=0)
|
|
16
|
+
flats = np.concatenate([f for f in dfreader.get_raw_flats()], axis=0)
|
|
17
|
+
entry = dfreader.flats_reader.data_path.lstrip("/").split("/")[0]
|
|
18
|
+
return flats, darks, entry
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_flats_darks_from_h5(filename):
|
|
22
|
+
flats = []
|
|
23
|
+
darks = []
|
|
24
|
+
with h5py.File(filename, "r") as f:
|
|
25
|
+
for k, v in f.items():
|
|
26
|
+
if k == "1.1":
|
|
27
|
+
detector_name = decode_bytes(f["1.1/technique/tomoconfig/detector"][()][0])
|
|
28
|
+
else:
|
|
29
|
+
try:
|
|
30
|
+
image_key = v["technique/image_key"][()]
|
|
31
|
+
except:
|
|
32
|
+
raise NotImplementedError(
|
|
33
|
+
"Legacy h5 file format is not handled. The entry of the h5 file should contain a 'technique/image_key' group."
|
|
34
|
+
)
|
|
35
|
+
if image_key == 2: # Darks
|
|
36
|
+
darks.append(v[f"instrument/{detector_name}/data"][()])
|
|
37
|
+
elif image_key == 1: # Flats
|
|
38
|
+
flats.append(v[f"instrument/{detector_name}/data"][()])
|
|
39
|
+
|
|
40
|
+
flats = np.concatenate([f for f in flats], axis=0)
|
|
41
|
+
darks = np.concatenate([d for d in darks], axis=0)
|
|
42
|
+
return flats, darks, "entry0000" # TODO this will be problematic on the reconstruction side
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def pcaflats_decomposition(
|
|
46
|
+
flats, darks, pcaflats_filename="PCAFlats.h5", overwrite=False, entry="entry0000", nsigma=3.0
|
|
47
|
+
):
|
|
48
|
+
"""Compute the PCS decomposition of a series of flats and darks, possibly taken from various scans."""
|
|
49
|
+
try:
|
|
50
|
+
decomposer = PCAFlatsDecomposer(flats, darks, nsigma=nsigma)
|
|
51
|
+
decomposer.save_decomposition(pcaflats_filename, overwrite=overwrite, entry=entry)
|
|
52
|
+
success = True
|
|
53
|
+
except:
|
|
54
|
+
success = False
|
|
55
|
+
raise ValueError("An error occured in the PCA deccomposition.")
|
|
56
|
+
return success
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def decode_bytes(content):
|
|
60
|
+
if isinstance(content, bytes):
|
|
61
|
+
return content.decode()
|
|
62
|
+
else:
|
|
63
|
+
return content
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main(argv=None):
|
|
67
|
+
"""Compute PCA Flats on a series of datasets (h5 or NX)."""
|
|
68
|
+
if argv is None:
|
|
69
|
+
argv = sys.argv[1:]
|
|
70
|
+
|
|
71
|
+
args = parse_params_values(
|
|
72
|
+
PCAFlatsConfig,
|
|
73
|
+
parser_description=f"Compute a PCA Decomposition of flats acquired from various datasets..",
|
|
74
|
+
program_version="nabu " + version,
|
|
75
|
+
user_args=argv,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Get "where to write".
|
|
79
|
+
if args["output_filename"] is None:
|
|
80
|
+
abspath = os.path.abspath("./PCAFlats.hdf5")
|
|
81
|
+
else:
|
|
82
|
+
abspath = os.path.abspath(args["output_filename"])
|
|
83
|
+
|
|
84
|
+
pcaflats_dir = os.path.dirname(abspath)
|
|
85
|
+
pcaflats_filename = os.path.basename(abspath)
|
|
86
|
+
|
|
87
|
+
if is_writeable(pcaflats_dir):
|
|
88
|
+
output_path = os.path.join(pcaflats_dir, pcaflats_filename)
|
|
89
|
+
else:
|
|
90
|
+
raise ValueError(f"Output dir {pcaflats_dir} is not writeable.")
|
|
91
|
+
|
|
92
|
+
# raise error if file exists and overwrite=False
|
|
93
|
+
if not args["overwrite"] and os.path.exists(output_path):
|
|
94
|
+
raise FileExistsError(f"Output file {output_path} already exists. Use --overwrite to overwrite it.")
|
|
95
|
+
|
|
96
|
+
# Collect raw darks and flats
|
|
97
|
+
flats_stack = []
|
|
98
|
+
darks_stack = []
|
|
99
|
+
|
|
100
|
+
for dataset in args["datasets"]:
|
|
101
|
+
filename = os.path.basename(dataset)
|
|
102
|
+
kind = filename.split(".")[-1]
|
|
103
|
+
if kind == "nx":
|
|
104
|
+
flats, darks, entry = get_flats_darks_in_nx(dataset)
|
|
105
|
+
elif kind in ("h5", "hdf5"):
|
|
106
|
+
flats, darks, entry = get_flats_darks_from_h5(dataset)
|
|
107
|
+
|
|
108
|
+
flats_stack.append(flats)
|
|
109
|
+
darks_stack.append(darks)
|
|
110
|
+
|
|
111
|
+
flats = np.concatenate(flats_stack, axis=0)
|
|
112
|
+
darks = np.concatenate(darks_stack, axis=0)
|
|
113
|
+
|
|
114
|
+
exit(
|
|
115
|
+
pcaflats_decomposition(
|
|
116
|
+
flats, darks, pcaflats_filename=output_path, overwrite=args["overwrite"], entry=entry, nsigma=args["nsigma"]
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
main()
|
|
@@ -4,7 +4,6 @@ from scipy.special import erf # pylint: disable=all
|
|
|
4
4
|
import sys
|
|
5
5
|
import os
|
|
6
6
|
from scipy.ndimage import gaussian_filter
|
|
7
|
-
from nxtomo.nxobject.nxdetector import ImageKey
|
|
8
7
|
from nabu.resources.nxflatfield import update_dataset_info_flats_darks
|
|
9
8
|
from nabu.resources.dataset_analyzer import HDF5DatasetAnalyzer
|
|
10
9
|
from ..io.reader import load_images_from_dataurl_dict
|
|
@@ -57,7 +56,7 @@ def main(argv=None):
|
|
|
57
56
|
beam_profile = 0
|
|
58
57
|
my_flats = load_images_from_dataurl_dict(dataset_info.flats)
|
|
59
58
|
|
|
60
|
-
for
|
|
59
|
+
for flat in my_flats.values():
|
|
61
60
|
beam_profile += flat
|
|
62
61
|
beam_profile = beam_profile / len(list(dataset_info.flats.keys()))
|
|
63
62
|
|
nabu/app/reconstruct.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from tomoscan.io import HDF5File
|
|
2
1
|
from .. import version
|
|
2
|
+
from ..io.reader import list_hdf5_entries
|
|
3
3
|
from ..utils import list_match_queries
|
|
4
4
|
from ..pipeline.config import parse_nabu_config_file
|
|
5
5
|
from ..pipeline.config_validators import convert_to_int
|
|
@@ -92,12 +92,6 @@ def get_reconstructor(args, overwrite_options=None):
|
|
|
92
92
|
return reconstructor
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
def list_hdf5_entries(fname):
|
|
96
|
-
with HDF5File(fname, "r") as f:
|
|
97
|
-
entries = list(f.keys())
|
|
98
|
-
return entries
|
|
99
|
-
|
|
100
|
-
|
|
101
95
|
def main():
|
|
102
96
|
args = parse_params_values(
|
|
103
97
|
ReconstructConfig,
|
nabu/app/reconstruct_helical.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from .. import version
|
|
2
2
|
from ..resources.utils import is_hdf5_extension
|
|
3
3
|
from ..pipeline.config import parse_nabu_config_file
|
|
4
|
-
from ..pipeline.config_validators import convert_to_int
|
|
5
4
|
from .cli_configs import ReconstructConfig
|
|
6
5
|
from .utils import parse_params_values
|
|
7
6
|
from .reconstruct import update_reconstruction_start_end, get_log_file
|
|
@@ -39,7 +38,7 @@ def main_helical():
|
|
|
39
38
|
try:
|
|
40
39
|
from silx.math.fft.cufft import CUFFT
|
|
41
40
|
except: # can't catch narrower - cublasNotInitialized requires cublas !
|
|
42
|
-
CUFFT = None
|
|
41
|
+
CUFFT = None # noqa: F841
|
|
43
42
|
#
|
|
44
43
|
|
|
45
44
|
logfile = get_log_file(args["logfile"], args["log_file"], forbidden=[args["input_file"]])
|
|
@@ -58,9 +57,6 @@ def main_helical():
|
|
|
58
57
|
|
|
59
58
|
# Determine which reconstructor to use
|
|
60
59
|
reconstructor_cls = None
|
|
61
|
-
phase_method = None
|
|
62
|
-
if "phase" in proc.processing_steps:
|
|
63
|
-
phase_method = proc.processing_options["phase"]["method"]
|
|
64
60
|
|
|
65
61
|
# fix the reconstruction roi if not given
|
|
66
62
|
if "reconstruction" in proc.processing_steps:
|
|
@@ -71,11 +67,11 @@ def main_helical():
|
|
|
71
67
|
|
|
72
68
|
if proc.nabu_config["reconstruction"]["auto_size"]:
|
|
73
69
|
if 2 * rot_center > Nx:
|
|
74
|
-
w =
|
|
70
|
+
w = round(2 * rot_center)
|
|
75
71
|
else:
|
|
76
|
-
w =
|
|
77
|
-
rec_config["start_x"] =
|
|
78
|
-
rec_config["end_x"] =
|
|
72
|
+
w = round(2 * Nx - 2 * rot_center)
|
|
73
|
+
rec_config["start_x"] = round(rot_center - w / 2)
|
|
74
|
+
rec_config["end_x"] = round(rot_center + w / 2)
|
|
79
75
|
|
|
80
76
|
rec_config["start_y"] = rec_config["start_x"]
|
|
81
77
|
rec_config["end_y"] = rec_config["end_x"]
|
nabu/app/reduce_dark_flat.py
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import sys
|
|
2
|
-
import logging
|
|
3
|
-
import argparse
|
|
4
2
|
from typing import Optional
|
|
5
3
|
|
|
6
4
|
from nabu.app.cli_configs import ReduceDarkFlatConfig
|
|
@@ -14,6 +12,9 @@ from tomoscan.factory import Factory
|
|
|
14
12
|
from silx.io.url import DataUrl
|
|
15
13
|
|
|
16
14
|
|
|
15
|
+
reduce_methods = tuple(member.value for member in ReduceMethod)
|
|
16
|
+
|
|
17
|
+
|
|
17
18
|
def _create_data_urls(output_file: Optional[str], output_data_path: Optional[str], name: str):
|
|
18
19
|
"""
|
|
19
20
|
util function to compute reduced Data and metadata url(s)
|
|
@@ -67,8 +68,8 @@ def reduce_dark_flat(
|
|
|
67
68
|
"""
|
|
68
69
|
calculation of the darks / flats calling tomoscan utils function
|
|
69
70
|
"""
|
|
70
|
-
dark_method = ReduceMethod
|
|
71
|
-
flat_method = ReduceMethod
|
|
71
|
+
dark_method = ReduceMethod(dark_method) if dark_method is not None else None
|
|
72
|
+
flat_method = ReduceMethod(flat_method) if flat_method is not None else None
|
|
72
73
|
|
|
73
74
|
# 1. define url where to save the file
|
|
74
75
|
## 1.1 for darks
|
nabu/app/rotate.py
CHANGED
|
@@ -7,6 +7,8 @@ from multiprocessing.pool import ThreadPool
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
from tomoscan.io import HDF5File
|
|
9
9
|
from tomoscan.esrf.scan.nxtomoscan import NXtomoScan
|
|
10
|
+
|
|
11
|
+
from nabu.utils import first_generator_item
|
|
10
12
|
from ..io.utils import get_first_hdf5_entry
|
|
11
13
|
from ..processing.rotation import Rotation
|
|
12
14
|
from ..resources.logger import Logger, LoggerOrPrint
|
|
@@ -61,7 +63,7 @@ class HDF5ImagesStackRotation:
|
|
|
61
63
|
self.output_file = output_file
|
|
62
64
|
copy(self.input_file, output_file)
|
|
63
65
|
|
|
64
|
-
first_proj_url = self.dataset_info.projections[
|
|
66
|
+
first_proj_url = self.dataset_info.projections[first_generator_item(self.dataset_info.projections.keys())]
|
|
65
67
|
self.data_path = first_proj_url.data_path()
|
|
66
68
|
dirname, basename = posixpath.split(self.data_path)
|
|
67
69
|
self._data_path_dirname = dirname
|
nabu/app/stitching.py
CHANGED
|
@@ -67,12 +67,17 @@ def main():
|
|
|
67
67
|
futures = {}
|
|
68
68
|
# 2.1 launch jobs
|
|
69
69
|
slurm_job_progress_bars: dict = {}
|
|
70
|
+
|
|
71
|
+
# set job name
|
|
72
|
+
final_output_object_identifier = stitching_config.get_output_object().get_identifier().to_str()
|
|
73
|
+
stitching_config.slurm_config.job_name = f"stitching-{final_output_object_identifier}"
|
|
74
|
+
|
|
70
75
|
for i_job, (job, sub_config) in enumerate(
|
|
71
76
|
split_stitching_configuration_to_slurm_job(stitching_config, yield_configuration=True)
|
|
72
77
|
):
|
|
73
78
|
_logger.info(f"submit job nb {i_job}: handles {sub_config.slices}")
|
|
74
|
-
|
|
75
|
-
futures[
|
|
79
|
+
output_object = sub_config.get_output_object().get_identifier().to_str()
|
|
80
|
+
futures[output_object] = submit(job, timeout=999999)
|
|
76
81
|
# note on total=100: we only consider percentage in this case (providing advancement from slurm jobs)
|
|
77
82
|
slurm_job_progress_bars[job] = tqdm(
|
|
78
83
|
total=100,
|
|
@@ -9,7 +9,7 @@ except ImportError:
|
|
|
9
9
|
from tomoscan.test.utils import NXtomoMockContext
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
@pytest.fixture
|
|
12
|
+
@pytest.fixture
|
|
13
13
|
def hdf5_scan(tmp_path):
|
|
14
14
|
"""simple fixture to create a scan and provide it to another function"""
|
|
15
15
|
test_dir = tmp_path / "my_hdf5_scan"
|
|
@@ -26,7 +26,7 @@ def hdf5_scan(tmp_path):
|
|
|
26
26
|
|
|
27
27
|
@pytest.mark.parametrize("dark_method", (None, "first", "mean"))
|
|
28
28
|
@pytest.mark.parametrize("flat_method", (None, "last", "median"))
|
|
29
|
-
def test_reduce_dark_flat_hdf5(tmp_path, hdf5_scan, dark_method, flat_method):
|
|
29
|
+
def test_reduce_dark_flat_hdf5(tmp_path, hdf5_scan, dark_method, flat_method):
|
|
30
30
|
"""simply test output - processing is tested at tomoscan side"""
|
|
31
31
|
# test with default url
|
|
32
32
|
default_darks_path = os.path.join(hdf5_scan.path, hdf5_scan.get_dataset_basename() + "_darks.hdf5")
|
nabu/app/validator.py
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
4
1
|
import argparse
|
|
5
2
|
import sys
|
|
6
3
|
import os
|
|
@@ -19,7 +16,7 @@ def get_scans(path, entries: str):
|
|
|
19
16
|
if entries == "__all__":
|
|
20
17
|
entries = NXtomoScan.get_valid_entries(path)
|
|
21
18
|
for entry in entries:
|
|
22
|
-
res.append(NXtomoScan(path, entry))
|
|
19
|
+
res.append(NXtomoScan(path, entry)) # noqa: PERF401
|
|
23
20
|
else:
|
|
24
21
|
raise TypeError(f"{path} does not looks like a folder containing .EDF or a valid nexus file ")
|
|
25
22
|
return res
|
nabu/cuda/convolution.py
CHANGED
nabu/cuda/fft.py
CHANGED
nabu/cuda/medfilt.py
CHANGED
nabu/cuda/padding.py
CHANGED
nabu/cuda/src/backproj.cu
CHANGED
|
@@ -18,8 +18,8 @@ inline __device__ int is_in_circle(int x, int y, float center_x, float center_y,
|
|
|
18
18
|
This will return arr[y][x] where y is an int (exact access) and x is a float (linear interp horizontally)
|
|
19
19
|
*/
|
|
20
20
|
static inline __device__ float linear_interpolation(float* arr, int Nx, float x, int y) {
|
|
21
|
-
//
|
|
22
|
-
|
|
21
|
+
// if (x < 0 || x > Nx-1) return 0.0f; // texture address mode BORDER (CLAMP_TO_EDGE continues with edge)
|
|
22
|
+
if (x <= -0.5f || x >= Nx - 0.5f) return 0.0f; // texture address mode BORDER (CLAMP_TO_EDGE continues with edge)
|
|
23
23
|
int xm = (int) floorf(x);
|
|
24
24
|
int xp = (int) ceilf(x);
|
|
25
25
|
if ((xm == xp) || (xp >= Nx)) return arr[y*Nx+xm];
|
|
@@ -127,10 +127,10 @@ __global__ void backproj(
|
|
|
127
127
|
#endif
|
|
128
128
|
|
|
129
129
|
#ifdef USE_TEXTURES
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
sum1 += tex2D(tex_projections, h1 + 0.5f, proj + 0.5f);
|
|
131
|
+
sum2 += tex2D(tex_projections, h2 + 0.5f, proj + 0.5f);
|
|
132
|
+
sum3 += tex2D(tex_projections, h3 + 0.5f, proj + 0.5f);
|
|
133
|
+
sum4 += tex2D(tex_projections, h4 + 0.5f, proj + 0.5f);
|
|
134
134
|
#else
|
|
135
135
|
if (h1 >= 0 && h1 < num_bins) sum1 += linear_interpolation(d_sino, num_bins, h1, proj);
|
|
136
136
|
if (h2 >= 0 && h2 < num_bins) sum2 += linear_interpolation(d_sino, num_bins, h2, proj);
|
nabu/cuda/src/cone.cu
CHANGED
|
@@ -80,7 +80,11 @@ __global__ void devFDK_preweight(void* D_projData, unsigned int projPitch, unsig
|
|
|
80
80
|
|
|
81
81
|
const float fWeight = fW / fRayLength;
|
|
82
82
|
|
|
83
|
+
#ifndef RADIOS_LAYOUT
|
|
83
84
|
projData[(detectorV*iProjAngles+angle)*projPitch+detectorU] *= fWeight;
|
|
85
|
+
#else
|
|
86
|
+
projData[(angle*iProjV+detectorV)*projPitch+detectorU] *= fWeight;
|
|
87
|
+
#endif
|
|
84
88
|
|
|
85
89
|
fV += fDetVSize;
|
|
86
90
|
}
|
|
@@ -6,6 +6,20 @@
|
|
|
6
6
|
Please cite :
|
|
7
7
|
reference to be added...
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Generalized Hierarchical Backprojection (GHBP)
|
|
12
|
+
# for fast tomographic reconstruction from ultra high resolution images at non-negligible fan angles.
|
|
13
|
+
#
|
|
14
|
+
# Authors/Contributions:
|
|
15
|
+
# - Jonas Graetz, Fraunhofer IIS / Universitat Wurzburg: Algorithm Design and original OpenCL/Python implementation.
|
|
16
|
+
# - Alessandro Mirone, ESRF: CUDA translation, ESRF / BM18 integration, testing <mirone@esrf.fr>
|
|
17
|
+
# - Pierre Paleo, ESRF: ESRF / BM18 integration, testing <pierre.paleo@esrf.fr>
|
|
18
|
+
#
|
|
19
|
+
# JG was funded by the German Federal Ministry of Education and Research (BMBF), grant 05E2019,
|
|
20
|
+
# funding the development of BM18 at ESRF in collaboration with the Fraunhofer Gesellschaft,
|
|
21
|
+
# the Julius-Maximilians-Universitat Wurzburg, and the University of Passau
|
|
22
|
+
|
|
9
23
|
"""
|
|
10
24
|
*/
|
|
11
25
|
|
nabu/cuda/utils.py
CHANGED
|
@@ -197,12 +197,12 @@ def cuarray_shape_dtype(cuarray):
|
|
|
197
197
|
|
|
198
198
|
|
|
199
199
|
def get_shape_dtype(arr):
|
|
200
|
-
if isinstance(arr, garray.GPUArray
|
|
200
|
+
if isinstance(arr, (garray.GPUArray, np.ndarray)):
|
|
201
201
|
return arr.shape, arr.dtype
|
|
202
202
|
elif isinstance(arr, cuda.Array):
|
|
203
203
|
return cuarray_shape_dtype(arr)
|
|
204
204
|
else:
|
|
205
|
-
raise
|
|
205
|
+
raise TypeError("Unknown array type %s" % str(type(arr)))
|
|
206
206
|
|
|
207
207
|
|
|
208
208
|
def copy_array(dst, src, check=False, src_dtype=None, dst_x_in_bytes=0, dst_y=0):
|
nabu/estimation/alignment.py
CHANGED
|
@@ -87,20 +87,20 @@ class AlignmentBase:
|
|
|
87
87
|
if not len(shape_stack) == 3:
|
|
88
88
|
raise ValueError(
|
|
89
89
|
"A stack of 2-dimensional images is required. Shape of stack: %s"
|
|
90
|
-
% (" ".join(
|
|
90
|
+
% (" ".join("%d" % x for x in shape_stack))
|
|
91
91
|
)
|
|
92
92
|
if not len(shape_pos) == 1:
|
|
93
93
|
raise ValueError(
|
|
94
94
|
"Positions need to be a 1-dimensional array. Shape of the positions variable: %s"
|
|
95
|
-
% (" ".join(
|
|
95
|
+
% (" ".join("%d" % x for x in shape_pos))
|
|
96
96
|
)
|
|
97
97
|
if not shape_stack[0] == shape_pos[0]:
|
|
98
98
|
raise ValueError(
|
|
99
99
|
"The same number of images and positions is required."
|
|
100
100
|
+ " Shape of stack: %s, shape of positions variable: %s"
|
|
101
101
|
% (
|
|
102
|
-
" ".join(
|
|
103
|
-
" ".join(
|
|
102
|
+
" ".join("%d" % x for x in shape_stack),
|
|
103
|
+
" ".join("%d" % x for x in shape_pos),
|
|
104
104
|
)
|
|
105
105
|
)
|
|
106
106
|
|
|
@@ -110,18 +110,18 @@ class AlignmentBase:
|
|
|
110
110
|
shape_2 = np.squeeze(img_2).shape
|
|
111
111
|
if not len(shape_1) == 2:
|
|
112
112
|
raise ValueError(
|
|
113
|
-
"Images need to be 2-dimensional. Shape of image #1: %s" % (" ".join(
|
|
113
|
+
"Images need to be 2-dimensional. Shape of image #1: %s" % (" ".join("%d" % x for x in shape_1))
|
|
114
114
|
)
|
|
115
115
|
if not len(shape_2) == 2:
|
|
116
116
|
raise ValueError(
|
|
117
|
-
"Images need to be 2-dimensional. Shape of image #2: %s" % (" ".join(
|
|
117
|
+
"Images need to be 2-dimensional. Shape of image #2: %s" % (" ".join("%d" % x for x in shape_2))
|
|
118
118
|
)
|
|
119
119
|
if not np.all(shape_1 == shape_2):
|
|
120
120
|
raise ValueError(
|
|
121
121
|
"Images need to be of the same shape. Shape of image #1: %s, image #2: %s"
|
|
122
122
|
% (
|
|
123
|
-
" ".join(
|
|
124
|
-
" ".join(
|
|
123
|
+
" ".join("%d" % x for x in shape_1),
|
|
124
|
+
" ".join("%d" % x for x in shape_2),
|
|
125
125
|
)
|
|
126
126
|
)
|
|
127
127
|
|
|
@@ -153,7 +153,7 @@ class AlignmentBase:
|
|
|
153
153
|
if not (len(f_vals.shape) == 2):
|
|
154
154
|
raise ValueError(
|
|
155
155
|
"The fitted values should form a 2-dimensional array. Array of shape: [%s] was given."
|
|
156
|
-
% (" ".join(
|
|
156
|
+
% (" ".join("%d" % s for s in f_vals.shape))
|
|
157
157
|
)
|
|
158
158
|
if fy is None:
|
|
159
159
|
fy_half_size = (f_vals.shape[0] - 1) / 2
|
|
@@ -161,7 +161,7 @@ class AlignmentBase:
|
|
|
161
161
|
elif not (len(fy.shape) == 1 and np.all(fy.size == f_vals.shape[0])):
|
|
162
162
|
raise ValueError(
|
|
163
163
|
"Vertical coordinates should have the same length as values matrix. Sizes of fy: %d, f_vals: [%s]"
|
|
164
|
-
% (fy.size, " ".join(
|
|
164
|
+
% (fy.size, " ".join("%d" % s for s in f_vals.shape))
|
|
165
165
|
)
|
|
166
166
|
if fx is None:
|
|
167
167
|
fx_half_size = (f_vals.shape[1] - 1) / 2
|
|
@@ -169,7 +169,7 @@ class AlignmentBase:
|
|
|
169
169
|
elif not (len(fx.shape) == 1 and np.all(fx.size == f_vals.shape[1])):
|
|
170
170
|
raise ValueError(
|
|
171
171
|
"Horizontal coordinates should have the same length as values matrix. Sizes of fx: %d, f_vals: [%s]"
|
|
172
|
-
% (fx.size, " ".join(
|
|
172
|
+
% (fx.size, " ".join("%d" % s for s in f_vals.shape))
|
|
173
173
|
)
|
|
174
174
|
|
|
175
175
|
fy, fx = np.meshgrid(fy, fx, indexing="ij")
|
|
@@ -190,14 +190,7 @@ class AlignmentBase:
|
|
|
190
190
|
vertex_max_yx = [np.max(fy), np.max(fx)]
|
|
191
191
|
if np.any(vertex_yx < vertex_min_yx) or np.any(vertex_yx > vertex_max_yx):
|
|
192
192
|
raise ValueError(
|
|
193
|
-
"Fitted (y: {}, x: {}) positions are outside the input margins y: [{}, {}], and x: [{}, {}]"
|
|
194
|
-
vertex_yx[0],
|
|
195
|
-
vertex_yx[1],
|
|
196
|
-
vertex_min_yx[0],
|
|
197
|
-
vertex_max_yx[0],
|
|
198
|
-
vertex_min_yx[1],
|
|
199
|
-
vertex_max_yx[1],
|
|
200
|
-
)
|
|
193
|
+
f"Fitted (y: {vertex_yx[0]}, x: {vertex_yx[1]}) positions are outside the input margins y: [{vertex_min_yx[0]}, {vertex_max_yx[0]}], and x: [{vertex_min_yx[1]}, {vertex_max_yx[1]}]"
|
|
201
194
|
)
|
|
202
195
|
return vertex_yx
|
|
203
196
|
|
|
@@ -225,10 +218,10 @@ class AlignmentBase:
|
|
|
225
218
|
float
|
|
226
219
|
Estimated function max, according to the coordinates in fx.
|
|
227
220
|
"""
|
|
228
|
-
if
|
|
221
|
+
if len(f_vals.shape) not in (1, 2):
|
|
229
222
|
raise ValueError(
|
|
230
223
|
"The fitted values should be either one or a collection of 1-dimensional arrays. Array of shape: [%s] was given."
|
|
231
|
-
% (" ".join(
|
|
224
|
+
% (" ".join("%d" % s for s in f_vals.shape))
|
|
232
225
|
)
|
|
233
226
|
num_vals = f_vals.shape[0]
|
|
234
227
|
|
|
@@ -264,16 +257,9 @@ class AlignmentBase:
|
|
|
264
257
|
upper_bound_ok = vertex_x < vertex_max_x
|
|
265
258
|
if not np.all(lower_bound_ok * upper_bound_ok):
|
|
266
259
|
if len(f_vals.shape) == 1:
|
|
267
|
-
message = "Fitted position {} is outide the input margins [{}, {}]"
|
|
268
|
-
vertex_x, vertex_min_x, vertex_max_x
|
|
269
|
-
)
|
|
260
|
+
message = f"Fitted position {vertex_x} is outide the input margins [{vertex_min_x}, {vertex_max_x}]"
|
|
270
261
|
else:
|
|
271
|
-
message = "Fitted positions outside the input margins [{}, {}]: {} below and {} above"
|
|
272
|
-
vertex_min_x,
|
|
273
|
-
vertex_max_x,
|
|
274
|
-
np.sum(1 - lower_bound_ok),
|
|
275
|
-
np.sum(1 - upper_bound_ok),
|
|
276
|
-
)
|
|
262
|
+
message = f"Fitted positions outside the input margins [{vertex_min_x}, {vertex_max_x}]: {np.sum(1 - lower_bound_ok)} below and {np.sum(1 - upper_bound_ok)} above"
|
|
277
263
|
raise ValueError(message)
|
|
278
264
|
if return_vertex_val:
|
|
279
265
|
vertex_val = coeffs[0, :] + vertex_x * coeffs[1, :] / 2
|
|
@@ -354,7 +340,7 @@ class AlignmentBase:
|
|
|
354
340
|
if not (len(img_shape) == 2):
|
|
355
341
|
raise ValueError(
|
|
356
342
|
"The input image should be either a 1 or 2-dimensional array. Array of shape: [%s] was given."
|
|
357
|
-
% (" ".join(
|
|
343
|
+
% (" ".join("%d" % s for s in cc.shape))
|
|
358
344
|
)
|
|
359
345
|
other_axis = (axis + 1) % 2
|
|
360
346
|
# get pixel having the maximum value of the correlation array
|