From 7742d5674c022a55070ed78b9332c81513ffb6e5 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Mon, 12 Oct 2020 12:14:24 +0200 Subject: [PATCH 01/14] ProcessConfig: rotation_axis_position_halftomo for left CoR --- nabu/resources/processconfig.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nabu/resources/processconfig.py b/nabu/resources/processconfig.py index 40309d16..51cd5b65 100644 --- a/nabu/resources/processconfig.py +++ b/nabu/resources/processconfig.py @@ -220,6 +220,11 @@ class ProcessConfig: if rec_options["enable_halftomo"]: rec_options["angles"] = rec_options["angles"][:rec_options["angles"].size//2] cor_i = int(round(rec_options["rotation_axis_position"])) + # + n_x = self.dataset_infos.radio_dims[0] + if cor_i < n_x / 2: + cor_i = n_x - cor_i + # # New key rec_options["rotation_axis_position_halftomo"] = (2*cor_i-1)/2. # New key -- GitLab From 43d3ba9a437ecb9111a2c01f5cdf8c470e083707 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Mon, 12 Oct 2020 12:15:05 +0200 Subject: [PATCH 02/14] app.FullField: temporarily drop support for X-Y ROI, get correct reconstruction shape --- nabu/app/fullfield.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/nabu/app/fullfield.py b/nabu/app/fullfield.py index 957c7734..32f2be15 100644 --- a/nabu/app/fullfield.py +++ b/nabu/app/fullfield.py @@ -454,10 +454,15 @@ class FullFieldPipeline: @use_options("reconstruction", "reconstruction") def _prepare_reconstruction(self): options = self.processing_options["reconstruction"] - x_s, x_e = options["start_x"], options["end_x"]+1 - y_s, y_e = options["start_y"], options["end_y"]+1 - self._rec_roi = (x_s, x_e, y_s, y_e) - self._allocate_recs(y_e - y_s, x_e - x_s) + # ~ x_s, x_e = options["start_x"], options["end_x"]+1 + # ~ y_s, y_e = options["start_y"], options["end_y"]+1 + # ~ self._rec_roi = (x_s, x_e, y_s, y_e) + # ~ self._allocate_recs(y_e - y_s, x_e - x_s) + + na, nx = self._get_shape("reconstruction") + self._allocate_recs(nx, nx) + + self._rec_roi = None @use_options("reconstruction", "reconstruction") def _init_reconstruction(self): -- GitLab From 841f6ed055b3d6c982fdbb28fd6c21b3cebb1477 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Mon, 12 Oct 2020 12:16:27 +0200 Subject: [PATCH 03/14] preproc.sinogram: Use a temporary bad-looking but working solution --- nabu/preproc/sinogram.py | 8 +++++++- nabu/preproc/sinogram_cuda.py | 11 ++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/nabu/preproc/sinogram.py b/nabu/preproc/sinogram.py index d1ff14ac..524489b3 100644 --- a/nabu/preproc/sinogram.py +++ b/nabu/preproc/sinogram.py @@ -71,7 +71,13 @@ class SinoProcessing(object): ) self.rot_center = rot_center self._rot_center_int = int(round(self.rot_center)) - + # DEBUG + rc = self._rot_center_int + if rc < (self.n_x - 1)/2: + rc = self.n_x - rc + self._rot_center_int = rc + self.rot_center = self.n_x - self.rot_center + # def _set_halftomo(self, halftomo): self.halftomo = halftomo diff --git a/nabu/preproc/sinogram_cuda.py b/nabu/preproc/sinogram_cuda.py index aca5caed..3a69004a 100644 --- a/nabu/preproc/sinogram_cuda.py +++ b/nabu/preproc/sinogram_cuda.py @@ -80,15 +80,24 @@ class CudaSinoProcessing(SinoProcessing, CudaProcessing): else: sinos = garray.zeros(self.sinos_halftomo_shape, dtype=np.float32) + # need to use a contiguous 2D, array otherwise kernel does not work + d_radio = radios[:, 0, :].copy() for i in range(n_z): + d_radio[:] = radios[:, i, :].copy() + # DEBUG + d_radio.set(d_radio.get()[:, ::-1].copy()) + # self.halftomo_kernel( - radios[:, i, :].copy(), # need to copy this 2D, array otherwise kernel does not work + d_radio, sinos[i], self.d_halftomo_weights, n_a, n_x, rc, grid=self._halftomo_gridsize, block=self._halftomo_blksize ) + # DEBUG + sinos[i].set(sinos[i].get()[:, ::-1].copy()) + # return sinos -- GitLab From 3b44fee3a866178ad540190bb12313644ed6147d Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Mon, 12 Oct 2020 16:47:51 +0200 Subject: [PATCH 04/14] Remove halftomo-CoR flip from ProcessConfig --- nabu/resources/processconfig.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/nabu/resources/processconfig.py b/nabu/resources/processconfig.py index 51cd5b65..a8645b58 100644 --- a/nabu/resources/processconfig.py +++ b/nabu/resources/processconfig.py @@ -220,12 +220,7 @@ class ProcessConfig: if rec_options["enable_halftomo"]: rec_options["angles"] = rec_options["angles"][:rec_options["angles"].size//2] cor_i = int(round(rec_options["rotation_axis_position"])) - # - n_x = self.dataset_infos.radio_dims[0] - if cor_i < n_x / 2: - cor_i = n_x - cor_i - # - # New key + # New keys rec_options["rotation_axis_position_halftomo"] = (2*cor_i-1)/2. # New key rec_options["cor_estimated_auto"] = isinstance(nabu_config["reconstruction"]["rotation_axis_position"], str) -- GitLab From 98113149bac9c5439bde4726376e6b08fc6831f2 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Mon, 12 Oct 2020 16:49:28 +0200 Subject: [PATCH 05/14] Sinogram: use _halftomo_flip indicator --- nabu/preproc/sinogram.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nabu/preproc/sinogram.py b/nabu/preproc/sinogram.py index 524489b3..6fa08c5a 100644 --- a/nabu/preproc/sinogram.py +++ b/nabu/preproc/sinogram.py @@ -71,12 +71,14 @@ class SinoProcessing(object): ) self.rot_center = rot_center self._rot_center_int = int(round(self.rot_center)) - # DEBUG + # If CoR is on the left: "flip" the logic + self._halftomo_flip = False rc = self._rot_center_int if rc < (self.n_x - 1)/2: - rc = self.n_x - rc + rc = self.n_x - 1 - rc self._rot_center_int = rc self.rot_center = self.n_x - self.rot_center + self._halftomo_flip = True # def _set_halftomo(self, halftomo): -- GitLab From 61831ffb917dae9c2c9f6bd26e3cd87ea3fe51bb Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Mon, 12 Oct 2020 16:50:02 +0200 Subject: [PATCH 06/14] CudaSinoProcessing: add xflip_kernel --- nabu/preproc/sinogram_cuda.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/nabu/preproc/sinogram_cuda.py b/nabu/preproc/sinogram_cuda.py index 3a69004a..309a0c05 100644 --- a/nabu/preproc/sinogram_cuda.py +++ b/nabu/preproc/sinogram_cuda.py @@ -42,6 +42,20 @@ class CudaSinoProcessing(SinoProcessing, CudaProcessing): d = self.n_x - rc # will have to be adapted for varying axis pos self.halftomo_weights = np.linspace(0, 1, d, endpoint=True, dtype="f") self.d_halftomo_weights = garray.to_gpu(self.halftomo_weights) + if self._halftomo_flip: + self.xflip_kernel = CudaKernel( + "reverse2D_x", + get_cuda_srcfile("ElementOp.cu"), + signature="Pii" + ) + blk = (32, 32, 1) + self._xflip_blksize = blk + self._xflip_gridsize_1 = ( + updiv(self.n_x, blk[0]), + updiv(self.n_angles, blk[1]), + 1 + ) + self._xflip_gridsize_2 = self._halftomo_gridsize # Overwrite parent method @@ -84,9 +98,11 @@ class CudaSinoProcessing(SinoProcessing, CudaProcessing): d_radio = radios[:, 0, :].copy() for i in range(n_z): d_radio[:] = radios[:, i, :].copy() - # DEBUG - d_radio.set(d_radio.get()[:, ::-1].copy()) - # + if self._halftomo_flip: + self.xflip_kernel( + d_radio, n_x, n_a, + grid=self._xflip_gridsize_1, block=self._xflip_blksize + ) self.halftomo_kernel( d_radio, sinos[i], @@ -95,9 +111,11 @@ class CudaSinoProcessing(SinoProcessing, CudaProcessing): grid=self._halftomo_gridsize, block=self._halftomo_blksize ) - # DEBUG - sinos[i].set(sinos[i].get()[:, ::-1].copy()) - # + if self._halftomo_flip: + self.xflip_kernel( + sinos[i], 2*rc, n_a2, + grid=self._xflip_gridsize_2, block=self._xflip_blksize + ) return sinos @@ -134,7 +152,3 @@ class CudaSinoNormalization(SinoNormalization, CudaProcessing): sino, *self._cuda_kernel_args, **self._cuda_kernel_kwargs ) return sino - - - - -- GitLab From 7183b54ef833e1acee0baeda613c3897128dc241 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Mon, 12 Oct 2020 16:50:34 +0200 Subject: [PATCH 07/14] Cuda: add reverse2D_x kernel --- nabu/cuda/src/ElementOp.cu | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/nabu/cuda/src/ElementOp.cu b/nabu/cuda/src/ElementOp.cu index 22bd2da1..909a09df 100644 --- a/nabu/cuda/src/ElementOp.cu +++ b/nabu/cuda/src/ElementOp.cu @@ -86,3 +86,20 @@ __global__ void nlog(float* array, int Nx, int Ny, int Nz, float clip_min, float #endif array[pos] = -logf(val); } + + + +// Reverse elements of a 2D array along "x", i.e: +// arr = arr[:, ::-1] +// launched with grid (Nx/2, Ny) +__global__ void reverse2D_x(float* array, int Nx, int Ny) { + uint x = blockDim.x * blockIdx.x + threadIdx.x; + uint y = blockDim.y * blockIdx.y + threadIdx.y; + if ((x >= Nx/2) || (y >= Ny)) return; + uint pos = y*Nx + x; + uint pos2 = y*Nx + (Nx - 1 - x); + float tmp = array[pos]; + array[pos] = array[pos2]; + array[pos2] = tmp; +} + -- GitLab From 98ba49c5c1e037c2651aab8a981d6a7ebed111b0 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Mon, 12 Oct 2020 16:51:11 +0200 Subject: [PATCH 08/14] dataset_validator: Make 2*CoR work when CoR is on the left --- nabu/resources/dataset_validator.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/nabu/resources/dataset_validator.py b/nabu/resources/dataset_validator.py index 22dd5d31..3e5813fa 100644 --- a/nabu/resources/dataset_validator.py +++ b/nabu/resources/dataset_validator.py @@ -50,6 +50,16 @@ class NabuValidator(object): return res + def _get_nx_ny(self): + if self.nabu_config["reconstruction"]["enable_halftomo"]: + cor = int(round(self.dataset_infos.axis_position)) + nx = max(2*cor, 2 * (self.dataset_infos.radio_dims[0] - 1 - cor)) + else: + nx, nz = self.dataset_infos.radio_dims + ny = nx + return nx, ny + + def convert_negative_indices(self): """ Convert any negative index to the corresponding positive index. @@ -59,8 +69,7 @@ class NabuValidator(object): if self.nabu_config["reconstruction"]["enable_halftomo"]: if self.dataset_infos.axis_position is None: raise ValueError("Cannot use rotation axis position in the middle of the detector when half tomo is enabled") - cor = int(round(self.dataset_infos.axis_position)) - ny = nx = 2*cor + nx, ny = self._get_nx_ny() what = ( ("reconstruction", "start_x", nx), ("reconstruction", "end_x", nx), @@ -73,6 +82,7 @@ class NabuValidator(object): self.nabu_config[section][key] = self._convert_negative_idx( self.nabu_config[section][key], upper_bound ) + print(self.nabu_config["reconstruction"]) def check_not_empty(self): @@ -199,8 +209,7 @@ class NabuValidator(object): nx, nz = self.dataset_infos.radio_dims rec_params = self.nabu_config["reconstruction"] if rec_params["enable_halftomo"]: - cor = int(round(self.dataset_infos.axis_position)) - ny = nx = 2*cor + ny, nx = self._get_nx_ny() what = ( ("start_x", "end_x", nx), ("start_y", "end_y", nx), -- GitLab From b483876ffcf45b9347d6a5a72f0c4b66b0f921c1 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Mon, 12 Oct 2020 16:51:31 +0200 Subject: [PATCH 09/14] FullField: Restore start_xyz and end_xyz --- nabu/app/fullfield.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/nabu/app/fullfield.py b/nabu/app/fullfield.py index 32f2be15..95bfbd51 100644 --- a/nabu/app/fullfield.py +++ b/nabu/app/fullfield.py @@ -454,15 +454,11 @@ class FullFieldPipeline: @use_options("reconstruction", "reconstruction") def _prepare_reconstruction(self): options = self.processing_options["reconstruction"] - # ~ x_s, x_e = options["start_x"], options["end_x"]+1 - # ~ y_s, y_e = options["start_y"], options["end_y"]+1 - # ~ self._rec_roi = (x_s, x_e, y_s, y_e) - # ~ self._allocate_recs(y_e - y_s, x_e - x_s) + x_s, x_e = options["start_x"], options["end_x"]+1 + y_s, y_e = options["start_y"], options["end_y"]+1 + self._rec_roi = (x_s, x_e, y_s, y_e) + self._allocate_recs(y_e - y_s, x_e - x_s) - na, nx = self._get_shape("reconstruction") - self._allocate_recs(nx, nx) - - self._rec_roi = None @use_options("reconstruction", "reconstruction") def _init_reconstruction(self): @@ -473,6 +469,8 @@ class FullFieldPipeline: if options["enable_halftomo"]: rot_center = options["rotation_axis_position_halftomo"] + if self.sino_builder._halftomo_flip: + rot_center = self.sino_builder.rot_center else: rot_center = options["rotation_axis_position"] if options.get("cor_estimated_auto", False): -- GitLab From 3a26934445073f8f4b654ee78cc7c784c888b121 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Tue, 13 Oct 2020 10:28:51 +0200 Subject: [PATCH 10/14] Remove debug trace --- nabu/resources/dataset_validator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nabu/resources/dataset_validator.py b/nabu/resources/dataset_validator.py index 3e5813fa..4cc2bf9b 100644 --- a/nabu/resources/dataset_validator.py +++ b/nabu/resources/dataset_validator.py @@ -82,7 +82,6 @@ class NabuValidator(object): self.nabu_config[section][key] = self._convert_negative_idx( self.nabu_config[section][key], upper_bound ) - print(self.nabu_config["reconstruction"]) def check_not_empty(self): -- GitLab From 3ce241f264be3c56228fe3ceb437b9388c5d2e12 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Tue, 13 Oct 2020 10:39:01 +0200 Subject: [PATCH 11/14] resources.computations: Take left CoR into account when estimating memory --- nabu/resources/computations.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nabu/resources/computations.py b/nabu/resources/computations.py index ec7b600b..79a7ff68 100644 --- a/nabu/resources/computations.py +++ b/nabu/resources/computations.py @@ -69,7 +69,9 @@ def estimate_required_memory(process_config, chunk_size=None, radios_and_slices= if "rotation_axis_position_halftomo" in rec_config: # Slice has a different shape in half acquisition. # Not sure if start_x, ..., end_y are supported - Nx_rec = 2 * rec_config["rotation_axis_position_halftomo"] + rc = rec_config["rotation_axis_position_halftomo"] + Nx_rec = 2 * rc + Nx_rec = max(2 * rc, 2 * (Nx - rc)) Ny_rec = Nx_rec else: Nx_rec = (rec_config["end_x"] - rec_config["start_x"] + 1) -- GitLab From d160f000ddd6f90869bd2562d12a9f9e96a150b0 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Tue, 13 Oct 2020 14:12:34 +0200 Subject: [PATCH 12/14] preproc.sinogram: add left-CoR for numpy backend --- nabu/preproc/sinogram.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nabu/preproc/sinogram.py b/nabu/preproc/sinogram.py index 6fa08c5a..a9a6242a 100644 --- a/nabu/preproc/sinogram.py +++ b/nabu/preproc/sinogram.py @@ -131,7 +131,12 @@ class SinoProcessing(object): else: sinos = np.zeros(self.sinos_halftomo_shape, dtype=np.float32) for i in range(n_z): - sinos[i][:] = convert_halftomo(radios[:, i, :], self._rot_center_int) + radio = radios[:, i, :] + if self._halftomo_flip: + radio = radio[:, ::-1] + sinos[i][:] = convert_halftomo(radio, self._rot_center_int) + if self._halftomo_flip: + sinos[i][:] = sinos[i][:, ::-1] return sinos -- GitLab From f8cc166c330b75adfade5c8028acd134147d51b6 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Tue, 13 Oct 2020 14:13:09 +0200 Subject: [PATCH 13/14] Add unit test for left-CoR --- nabu/preproc/tests/test_halftomo.py | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/nabu/preproc/tests/test_halftomo.py b/nabu/preproc/tests/test_halftomo.py index 627706fa..35b2214b 100644 --- a/nabu/preproc/tests/test_halftomo.py +++ b/nabu/preproc/tests/test_halftomo.py @@ -60,3 +60,51 @@ class TestHalftomo: sino_halftomo = d_sinos.get()[0] _, err = compare_arrays(sino_halftomo, self.sino_ref, self.tol, return_residual=True) assert err < self.tol, "Something wrong with SinoProcessing.radios_to_sino, halftomo=True" + + @staticmethod + def _flip_array(arr): + if arr.ndim == 2: + return np.fliplr(arr) + res = np.zeros_like(arr) + for i in range(arr.shape[0]): + res[i] = np.fliplr(arr[i]) + return res + + def test_halftomo_left(self): + na, nz, nx = self.radios.shape + left_cor = nx - 1 - self.rot_center + radios = self._flip_array(self.radios) + sino_processing = SinoProcessing( + radios_shape=radios.shape, rot_center=left_cor, halftomo=True + ) + sinos_halftomo = sino_processing.radios_to_sinos(radios) + _, err = compare_arrays( + sinos_halftomo[0], + self._flip_array(self.sino_ref), + self.tol, + return_residual=True, + ) + assert ( + err < self.tol + ), "Something wrong with SinoProcessing.radios_to_sino, halftomo=True" + + @pytest.mark.skipif(not (__has_pycuda__), reason="Need pycuda for this test") + def test_cuda_halftomo_left(self): + na, nz, nx = self.radios.shape + left_cor = nx - 1 - self.rot_center + radios = self._flip_array(self.radios) + sino_processing = CudaSinoProcessing( + radios_shape=radios.shape, rot_center=left_cor, halftomo=True + ) + d_radios = garray.to_gpu(radios) + d_sinos = garray.zeros(sino_processing.sinos_halftomo_shape, "f") + sino_processing.radios_to_sinos(d_radios, output=d_sinos) + sino_halftomo = d_sinos.get()[0] + _, err = compare_arrays( + sino_halftomo, self._flip_array(self.sino_ref), + self.tol, return_residual=True + ) + assert ( + err < self.tol + ), "Something wrong with SinoProcessing.radios_to_sino, halftomo=True" + -- GitLab From 1015942bfb99576ed226eff2338cb89fa4449fc6 Mon Sep 17 00:00:00 2001 From: Pierre Paleo Date: Tue, 13 Oct 2020 14:13:27 +0200 Subject: [PATCH 14/14] Apply black on test_halftomo.py --- nabu/preproc/tests/test_halftomo.py | 35 ++++++++++++++++------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/nabu/preproc/tests/test_halftomo.py b/nabu/preproc/tests/test_halftomo.py index 35b2214b..2a739ed5 100644 --- a/nabu/preproc/tests/test_halftomo.py +++ b/nabu/preproc/tests/test_halftomo.py @@ -5,12 +5,13 @@ import h5py from nabu.testutils import compare_arrays, utilstest from nabu.preproc.sinogram import SinoProcessing, convert_halftomo from nabu.cuda.utils import __has_pycuda__ + if __has_pycuda__: import pycuda.gpuarray as garray from nabu.preproc.sinogram_cuda import CudaSinoProcessing -@pytest.fixture(scope='class') +@pytest.fixture(scope="class") def bootstrap(request): cls = request.cls radio, sino_ref, cor = get_data_h5("halftomo.h5") @@ -26,40 +27,42 @@ def bootstrap(request): def get_data_h5(*dataset_path): dataset_relpath = os.path.join(*dataset_path) dataset_path = utilstest.getfile(dataset_relpath) - with h5py.File(dataset_path, 'r') as hf: + with h5py.File(dataset_path, "r") as hf: radio = hf["entry/radio/results/data"][()] sino = hf["entry/sino/results/data"][()] cor = hf["entry/sino/configuration/configuration/rotation_axis_position"][()] return radio, sino, cor - -@pytest.mark.usefixtures('bootstrap') +@pytest.mark.usefixtures("bootstrap") class TestHalftomo: - def test_halftomo(self): sino_processing = SinoProcessing( - radios_shape=self.radios.shape, - rot_center=self.rot_center, - halftomo=True + radios_shape=self.radios.shape, rot_center=self.rot_center, halftomo=True ) sinos_halftomo = sino_processing.radios_to_sinos(self.radios) - _, err = compare_arrays(sinos_halftomo[0], self.sino_ref, self.tol, return_residual=True) - assert err < self.tol, "Something wrong with SinoProcessing.radios_to_sino, halftomo=True" + _, err = compare_arrays( + sinos_halftomo[0], self.sino_ref, self.tol, return_residual=True + ) + assert ( + err < self.tol + ), "Something wrong with SinoProcessing.radios_to_sino, halftomo=True" - @pytest.mark.skipif(not(__has_pycuda__), reason="Need pycuda for this test") + @pytest.mark.skipif(not (__has_pycuda__), reason="Need pycuda for this test") def test_cuda_halftomo(self): sino_processing = CudaSinoProcessing( - radios_shape=self.radios.shape, - rot_center=self.rot_center, - halftomo=True + radios_shape=self.radios.shape, rot_center=self.rot_center, halftomo=True ) d_radios = garray.to_gpu(self.radios) d_sinos = garray.zeros(sino_processing.sinos_halftomo_shape, "f") sino_processing.radios_to_sinos(d_radios, output=d_sinos) sino_halftomo = d_sinos.get()[0] - _, err = compare_arrays(sino_halftomo, self.sino_ref, self.tol, return_residual=True) - assert err < self.tol, "Something wrong with SinoProcessing.radios_to_sino, halftomo=True" + _, err = compare_arrays( + sino_halftomo, self.sino_ref, self.tol, return_residual=True + ) + assert ( + err < self.tol + ), "Something wrong with SinoProcessing.radios_to_sino, halftomo=True" @staticmethod def _flip_array(arr): -- GitLab