Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions httomo/method_wrappers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
# import all other wrappers to make sure they are available to the factory function
# (add imports here when createing new wrappers)
import httomo.method_wrappers.datareducer
import httomo.method_wrappers.sino360_to_180
import httomo.method_wrappers.dezinging
import httomo.method_wrappers.distortion_correction
import httomo.method_wrappers.seam_blender
import httomo.method_wrappers.average_frames
import httomo.method_wrappers.images
import httomo.method_wrappers.reconstruction
import httomo.method_wrappers.rotation
Expand Down
37 changes: 37 additions & 0 deletions httomo/method_wrappers/average_frames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from httomo.method_wrappers.generic import GenericMethodWrapper
from httomo.block_interfaces import T
import numpy as np


class AverageFramesWrapper(GenericMethodWrapper):
"""
Wrapper for frames/projection averaging.
"""

@classmethod
def should_select_this_class(cls, module_path: str, method_name: str) -> bool:
return "average_projection_frames" in method_name

def _preprocess_data(self, block: T) -> T:
# when the angular preview is getting changed by averaging the angles should be changed accordingly
config_params = self._config_params
k = config_params["projection_averaging_factor"]

n_proj = block.data.shape[0] # original data angular size
n_full = n_proj // k
remainder = n_proj % k

n_out = n_full + (remainder > 0)

averaged_angles = np.empty(n_out, dtype=block.angles.dtype)

if n_full:
averaged_angles[:n_full] = (
block.angles_radians[: n_full * k].reshape(n_full, k).mean(axis=1)
)

if remainder:
averaged_angles[-1] = block.angles_radians[n_full * k :].mean()

block.angles_radians = averaged_angles
return block
11 changes: 2 additions & 9 deletions httomo/method_wrappers/reconstruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,17 @@


class ReconstructionWrapper(GenericMethodWrapper):
"""Wraps reconstruction functions, limiting the length of the angles array
before calling the method."""
"""Wraps reconstruction functions."""

@classmethod
def should_select_this_class(cls, module_path: str, method_name: str) -> bool:
return module_path.endswith(".algorithm")

def _preprocess_data(self, block: T) -> T:
# this is essential for the angles cutting below to be valid
assert (
self.pattern == Pattern.sinogram
), "reconstruction methods must be sinogram"

# for 360 degrees data the angular dimension will be truncated while angles are not.
# Truncating angles if the angular dimension has got a different size
datashape0 = block.data.shape[0]
if datashape0 != len(block.angles_radians):
block.angles_radians = block.angles_radians[0:datashape0]
assert len(block.angles_radians) == block.data.shape[0]
self._input_shape = block.data.shape
return super()._preprocess_data(block)

Expand Down
18 changes: 18 additions & 0 deletions httomo/method_wrappers/sino360_to_180.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from httomo.method_wrappers.generic import GenericMethodWrapper
from httomo.block_interfaces import T


class Sino360to180Wrapper(GenericMethodWrapper):
"""
Wrapper to perform extended FoV (360degrees) data conversion to a standard 180 degrees data.
The wrapper is responsible for changing the angles after the data has changed.
"""

@classmethod
def should_select_this_class(cls, module_path: str, method_name: str) -> bool:
return "sino_360_to_180" in method_name

def _postprocess_data(self, block: T) -> T:
# for 360 degrees data the angular dimension is truncated so the angles should be changed in a similar fashion.
block.angles_radians = block.angles_radians[0 : block.data.shape[0]]
return block
1 change: 1 addition & 0 deletions httomo/runner/method_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ def calculate_max_slices(
data_dtype: np.dtype,
slicing_dim: int,
non_slice_dims_shape: Tuple[int, int],
angles: np.ndarray,
available_memory: int,
) -> Tuple[int, int]:
"""If it runs on GPU, determine the maximum number of slices that can fit in the
Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ def FBP2d_astra():
def FBP3d_tomobar_denoising():
return "docs/source/pipelines_full/FBP3d_tomobar_denoising.yaml"

@pytest.fixture
def angles_averaging():
return "docs/source/pipelines_full/angles_averaging.yaml"


@pytest.fixture
def FISTA3d_tomobar():
Expand Down
48 changes: 48 additions & 0 deletions tests/method_wrappers/test_360_to_180.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from httomo.method_wrappers import make_method_wrapper
from httomo.method_wrappers.sino360_to_180 import Sino360to180Wrapper
from httomo.runner.auxiliary_data import AuxiliaryData
from httomo.runner.dataset import DataSetBlock
from ..testing_utils import make_mock_preview_config, make_mock_repo
from httomo_backends.methods_database.query import Pattern

import numpy as np
from mpi4py import MPI
from pytest_mock import MockerFixture


def test_sino_360_to_180(mocker: MockerFixture):
GLOBAL_SHAPE = (10, 20, 30)
GLOBAL_SHAPE_MOD = (5, 20, 30)

class FakeModule:
def sino_360_to_180_tester(data):
np.testing.assert_array_equal(data, 1)
return data

mocker.patch(
"httomo.method_wrappers.generic.import_module", return_value=FakeModule
)
wrp = make_method_wrapper(
make_mock_repo(mocker, pattern=Pattern.sinogram),
"mocked_module_path.morph",
"sino_360_to_180_tester",
MPI.COMM_WORLD,
make_mock_preview_config(mocker),
)
assert isinstance(wrp, Sino360to180Wrapper)

aux_data = AuxiliaryData(angles=2.0 * np.ones(GLOBAL_SHAPE[0], dtype=np.float32))
data = np.ones(
GLOBAL_SHAPE_MOD, dtype=np.float32
) # assuming the data already averaged here by factor of 2
input = DataSetBlock(
data[:, 0:3, :],
slicing_dim=1,
aux_data=aux_data,
chunk_shape=GLOBAL_SHAPE_MOD,
global_shape=GLOBAL_SHAPE_MOD,
)

wrp.execute(input)

assert aux_data.get_angles().shape[0] == 5
50 changes: 50 additions & 0 deletions tests/method_wrappers/test_angle_average.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from httomo.method_wrappers import make_method_wrapper
from httomo.method_wrappers.average_frames import AverageFramesWrapper
from httomo.runner.auxiliary_data import AuxiliaryData
from httomo.runner.dataset import DataSetBlock
from ..testing_utils import make_mock_preview_config, make_mock_repo
from httomo_backends.methods_database.query import Pattern

import numpy as np
from mpi4py import MPI
from pytest_mock import MockerFixture


def test_angle_averaging(mocker: MockerFixture):
GLOBAL_SHAPE = (10, 20, 30)

class FakeModule:
def average_projection_frames_tester(data, projection_averaging_factor):
np.testing.assert_array_equal(data, 1)
return data

mocker.patch(
"httomo.method_wrappers.generic.import_module", return_value=FakeModule
)
wrp = make_method_wrapper(
make_mock_repo(mocker, pattern=Pattern.sinogram),
"mocked_module_path.morph",
"average_projection_frames_tester",
MPI.COMM_WORLD,
make_mock_preview_config(mocker),
projection_averaging_factor=2,
)
assert isinstance(wrp, AverageFramesWrapper)

aux_data = AuxiliaryData(
angles=2.0 * np.ones(GLOBAL_SHAPE[0] + 10, dtype=np.float32)
)
data = np.ones(
GLOBAL_SHAPE, dtype=np.float32
) # assuming the data already averaged here by factor of 2
input = DataSetBlock(
data[:, 0:3, :],
slicing_dim=1,
aux_data=aux_data,
chunk_shape=GLOBAL_SHAPE,
global_shape=GLOBAL_SHAPE,
)

wrp.execute(input)

assert aux_data.get_angles().shape[0] == 5
44 changes: 0 additions & 44 deletions tests/method_wrappers/test_reconstruction.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,13 @@
import math
from httomo.method_wrappers import make_method_wrapper
from httomo.method_wrappers.reconstruction import ReconstructionWrapper
from httomo.runner.auxiliary_data import AuxiliaryData
from httomo.runner.dataset import DataSetBlock
from ..testing_utils import make_mock_preview_config, make_mock_repo

from httomo_backends.methods_database.query import Pattern

import numpy as np
from mpi4py import MPI
from pytest_mock import MockerFixture


def test_recon_handles_reconstruction_angle_reshape(mocker: MockerFixture):
GLOBAL_SHAPE = (10, 20, 30)

class FakeModule:
# we give the angles a different name on purpose
def recon_tester(data, theta):
np.testing.assert_array_equal(data, 1)
np.testing.assert_array_equal(theta, 2)
assert data.shape[0] == len(theta)
return data

mocker.patch(
"httomo.method_wrappers.generic.import_module", return_value=FakeModule
)
wrp = make_method_wrapper(
make_mock_repo(mocker, pattern=Pattern.sinogram),
"mocked_module_path.algorithm",
"recon_tester",
MPI.COMM_WORLD,
make_mock_preview_config(mocker),
)
assert isinstance(wrp, ReconstructionWrapper)

aux_data = AuxiliaryData(
angles=2.0 * np.ones(GLOBAL_SHAPE[0] + 10, dtype=np.float32)
)
data = np.ones(GLOBAL_SHAPE, dtype=np.float32)
input = DataSetBlock(
data[:, 0:3, :],
slicing_dim=1,
aux_data=aux_data,
chunk_shape=GLOBAL_SHAPE,
global_shape=GLOBAL_SHAPE,
)

wrp.execute(input)

assert aux_data.get_angles().shape[0] == GLOBAL_SHAPE[0]


def test_recon_handles_reconstruction_axisswap(mocker: MockerFixture):
class FakeModule:
def recon_tester(data, theta):
Expand Down
79 changes: 79 additions & 0 deletions tests/test_parallel_pipeline_big.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,85 @@ def test_pipe_parallel_FBP3d_tomobar_k11_38730_in_memory_preview(
assert res_norm < 1e-6


# ########################################################################
@pytest.mark.full_data_parallel
def test_angles_averaging_LPRec_i12_119647_preview(
get_files: Callable,
cmd_mpirun,
i12_119647,
angles_averaging,
FBP3d_tomobar_k11_38730_npz,
output_folder,
):

change_value_parameters_method_pipeline(
angles_averaging,
method=[
"standard_tomo",
"standard_tomo",
"standard_tomo",
"standard_tomo",
"average_projection_frames",
],
key=[
"data_path",
"image_key_path",
"rotation_angles",
"preview",
"projection_averaging_factor",
],
value=[
"/entry/imaging/data",
"/entry/instrument/imaging/image_key",
{"data_path": "/entry/imaging_sum/gts_cs_theta"},
{"detector_y": {"start": 800, "stop": 1200}},
12,
],
)

cmd_mpirun.insert(9, i12_119647)
cmd_mpirun.insert(10, angles_averaging)
cmd_mpirun.insert(11, output_folder)

process = Popen(
cmd_mpirun, env=os.environ, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE
)
output, error = process.communicate()
print(output)

files = get_files(output_folder)

# #: check the generated reconstruction (hdf5 file)
# h5_files = list(filter(lambda x: ".h5" in x, files))
# assert len(h5_files) == 1

# # load the pre-saved numpy array for comparison bellow
# data_gt = FBP3d_tomobar_k11_38730_npz["data"]
# axis_slice = FBP3d_tomobar_k11_38730_npz["axis_slice"]
# slices, sizeX, sizeY = np.shape(data_gt)

# step = axis_slice // (slices + 2)
# # store for the result
# data_result = np.zeros((slices, sizeX, sizeY), dtype=np.float32)

# path_to_data = "data/"
# h5_file_name = "FBP3d_tomobar"
# for file_to_open in h5_files:
# if h5_file_name in file_to_open:
# h5f = h5py.File(file_to_open, "r")
# index_prog = step
# for i in range(slices):
# data_result[i, :, :] = h5f[path_to_data][:, index_prog, :]
# index_prog += step
# h5f.close()
# else:
# message_str = f"File name with {h5_file_name} string cannot be found."
# raise FileNotFoundError(message_str)

# residual_im = data_gt - data_result
# res_norm = np.linalg.norm(residual_im.flatten()).astype("float32")
# assert res_norm < 1e-6

# ########################################################################


Expand Down
Loading