Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Add decimator and gaussian filter #32

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
Prev Previous commit
enh: add tests
  • Loading branch information
oesteban authored and jhlegarreta committed Dec 22, 2024
commit 48184b5b260a2fca61c1863e65c8cd69e1a0bf5c
13 changes: 4 additions & 9 deletions src/nifreeze/data/filtering.py
Original file line number Diff line number Diff line change
@@ -129,17 +129,17 @@

"""

data = np.squeeze(data) # Drop unused dimensions
ndim = data.ndim

Check warning on line 133 in src/nifreeze/data/filtering.py

Codecov / codecov/patch

src/nifreeze/data/filtering.py#L132-L133

Added lines #L132 - L133 were not covered by tests

if isinstance(vox_width, Number):
vox_width = tuple([vox_width] * min(3, ndim))

Check warning on line 136 in src/nifreeze/data/filtering.py

Codecov / codecov/patch

src/nifreeze/data/filtering.py#L136

Added line #L136 was not covered by tests

# Do not smooth across time/orientation (if present in 4D data)
if ndim == 4 and len(vox_width) == 3:
vox_width = (*vox_width, 0)

Check warning on line 140 in src/nifreeze/data/filtering.py

Codecov / codecov/patch

src/nifreeze/data/filtering.py#L140

Added line #L140 was not covered by tests

return _gs(data, vox_width)

Check warning on line 142 in src/nifreeze/data/filtering.py

Codecov / codecov/patch

src/nifreeze/data/filtering.py#L142

Added line #L142 was not covered by tests


def downsample(
@@ -193,12 +193,12 @@

if smooth:
if smooth is True:
smooth = datashape[:3] / shape[:3]
data = gaussian_filter(data, smooth)

Check warning on line 197 in src/nifreeze/data/filtering.py

Codecov / codecov/patch

src/nifreeze/data/filtering.py#L196-L197

Added lines #L196 - L197 were not covered by tests

extents = (
apply_affine(imnii.affine, datashape - 0.5)
- apply_affine(imnii.affine, (-0.5, -0.5, -0.5))
extents = np.abs(
apply_affine(imnii.affine, datashape - 1)
- apply_affine(imnii.affine, (0.0, 0.0, 0.0))
)
newzooms = extents / shape

@@ -231,14 +231,13 @@
data,
locations.T,
order=order,
mode="constant",
cval=0,
mode="mirror",
prefilter=True,
).reshape(shape)

# Set negative values to zero (optional)
if order > 2 and nonnegative:
resampled[resampled < 0] = 0

Check warning on line 240 in src/nifreeze/data/filtering.py

Codecov / codecov/patch

src/nifreeze/data/filtering.py#L240

Added line #L240 was not covered by tests

# Create new Nifti image with updated information
newnii = Nifti1Image(resampled, newaffine, imnii.header)
@@ -252,7 +251,6 @@
in_file: str,
factor: int | tuple[int, int, int],
smooth: bool | tuple[int, int, int] = True,
order: int = 3,
nonnegative: bool = True,
) -> Nifti1Image:
"""
@@ -278,9 +276,6 @@
Alternatively, a tuple of three integers can be provided to specify
different smoothing kernel sizes for each spatial dimension. Setting to
False disables smoothing.
order : :obj:`int`, optional (default=3)
The order of the spline interpolation used for downsampling. Higher
orders provide smoother results but are computationally more expensive.
nonnegative : :obj:`bool`, optional (default=``True``)
If True, negative values in the downsampled data are set to zero.

@@ -299,15 +294,15 @@
factor = tuple([factor] * min(3, ndim))

if any(f <= 0 for f in factor[:3]):
raise ValueError("All spatial downsampling factors must be positive.")

Check warning on line 297 in src/nifreeze/data/filtering.py

Codecov / codecov/patch

src/nifreeze/data/filtering.py#L297

Added line #L297 was not covered by tests

if ndim == 4 and len(factor) == 3:
factor = (*factor, 0)

Check warning on line 300 in src/nifreeze/data/filtering.py

Codecov / codecov/patch

src/nifreeze/data/filtering.py#L300

Added line #L300 was not covered by tests

if smooth:
if smooth is True:
smooth = factor
data = gaussian_filter(data, smooth)

Check warning on line 305 in src/nifreeze/data/filtering.py

Codecov / codecov/patch

src/nifreeze/data/filtering.py#L304-L305

Added lines #L304 - L305 were not covered by tests

# Update affine transformation
newaffine = imnii.affine.copy()
43 changes: 31 additions & 12 deletions test/test_filtering.py
Original file line number Diff line number Diff line change
@@ -38,23 +38,19 @@
)
@pytest.mark.parametrize(
("zoom_x", ),
# [(1.0, ), (-1.0, ), (2.0, ), (-2.0, )],
[(2.0,)],
[(1.0, ), (-1.0, ), (2.0, ), (-2.0, )],
)
@pytest.mark.parametrize(
("zoom_y", ),
# [(1.0, ), (-1.0, ), (2.0, ), (-2.0, )],
[(-2.0,)],
[(1.0, ), (-1.0, ), (2.0, ), (-2.0, )],
)
@pytest.mark.parametrize(
("zoom_z", ),
# [(1.0, ), (-1.0, ), (2.0, ), (-2.0, )],
[(-2.0,)],
[(1.0, ), (-1.0, ), (2.0, ), (-2.0, )],
)
@pytest.mark.parametrize(
("angle_x", ),
# [(0.0, ), (0.2, ), (-0.05, )],
[(-0.05,)]
[(0.0, ), (0.2, ), (-0.05, )],
)
@pytest.mark.parametrize(
("angle_y", ),
@@ -82,6 +78,7 @@ def test_decimation(
angle_y,
angle_z,
offsets,
outdir,
):
"""Exercise decimation."""

@@ -120,8 +117,30 @@ def test_decimation(
test_image.to_filename(fname)

# Need to define test oracle. For now, just see if it doesn't smoke.
out = decimate(fname, factor=2, smooth=False, order=1)
out.to_filename(tmp_path / "decimated.nii.gz")
out = decimate(fname, factor=2, smooth=False)

out = downsample(fname, shape=(10, 10, 10), smooth=False, order=0)

if outdir:
from niworkflows.interfaces.reportlets.registration import (
SimpleBeforeAfterRPT as SimpleBeforeAfter,
)

out.to_filename(tmp_path / "decimated.nii.gz")

SimpleBeforeAfter(
after_label="Decimated",
before_label="Original",
after=str(tmp_path / "decimated.nii.gz"),
before=str(fname),
out_report=str(outdir / f'decimated-{tmp_path.name}.svg'),
).run()

out = downsample(fname, shape=(10, 10, 10), smooth=False, order=1)
out.to_filename(tmp_path / "downsampled.nii.gz")
out.to_filename(tmp_path / "downsampled.nii.gz")
SimpleBeforeAfter(
after_label="Downsampled",
before_label="Original",
after=str(tmp_path / "downsampled.nii.gz"),
before=str(fname),
out_report=str(outdir / f'downsampled-{tmp_path.name}.svg'),
).run()