Skip to content

Commit

Permalink
merged utils and preproc modules
Browse files Browse the repository at this point in the history
  • Loading branch information
niksirbi committed Jan 10, 2024
1 parent eb4752c commit eb22122
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 146 deletions.
6 changes: 4 additions & 2 deletions brainglobe_template_builder/napari/midline_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
QWidget,
)

from brainglobe_template_builder.preproc import align_to_midline
from brainglobe_template_builder.utils import get_midline_points
from brainglobe_template_builder.preproc import (

Check warning on line 11 in brainglobe_template_builder/napari/midline_widget.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/napari/midline_widget.py#L11

Added line #L11 was not covered by tests
align_to_midline,
get_midline_points,
)


class FindMidline(QWidget):

Check warning on line 17 in brainglobe_template_builder/napari/midline_widget.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/napari/midline_widget.py#L17

Added line #L17 was not covered by tests
Expand Down
143 changes: 128 additions & 15 deletions brainglobe_template_builder/preproc.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,62 @@
from typing import Literal
from itertools import product
from typing import Literal, Union

Check warning on line 2 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L1-L2

Added lines #L1 - L2 were not covered by tests

import numpy as np
from scipy.ndimage import affine_transform
from scipy.spatial.transform import Rotation
from skimage import filters, morphology
from skimage import filters, measure, morphology

Check warning on line 7 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L4-L7

Added lines #L4 - L7 were not covered by tests

from brainglobe_template_builder.utils import (
extract_largest_object,
fit_plane_to_points,
threshold_image,
)

def _extract_largest_object(binary_image):

Check warning on line 10 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L10

Added line #L10 was not covered by tests
"""Keep only the largest object in a binary image.
Parameters
----------
binary_image : np.ndarray
A binary image.
Returns
-------
np.ndarray
A binary image containing only the largest object.
"""
labeled_image = measure.label(binary_image)
regions = measure.regionprops(labeled_image)
largest_region = max(regions, key=lambda region: region.area)
return labeled_image == largest_region.label

Check warning on line 26 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L23-L26

Added lines #L23 - L26 were not covered by tests


def _threshold_image(

Check warning on line 29 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L29

Added line #L29 was not covered by tests
image: np.ndarray,
method: Literal["triangle", "otsu", "isodata"] = "triangle",
) -> Union[np.ndarray, None]:
"""Threshold an image using the specified method to get a binary mask.
Parameters
----------
image : np.ndarray
Image to threshold.
method : str
Thresholding method to use. One of 'triangle', 'otsu', and 'isodata'
(corresponding to methods from the skimage.filters module).
Defaults to 'triangle'.
Returns
-------
np.ndarray
A binary mask.
"""

method_to_func = {

Check warning on line 50 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L50

Added line #L50 was not covered by tests
"triangle": filters.threshold_triangle,
"otsu": filters.threshold_otsu,
"isodata": filters.threshold_isodata,
}
if method in method_to_func.keys():
thresholded = method_to_func[method](image)
return image > thresholded

Check warning on line 57 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L55-L57

Added lines #L55 - L57 were not covered by tests
else:
raise ValueError(f"Unknown thresholding method {method}")

Check warning on line 59 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L59

Added line #L59 was not covered by tests


def create_mask(

Check warning on line 62 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L62

Added line #L62 was not covered by tests
Expand Down Expand Up @@ -49,26 +96,92 @@ def create_mask(
if image.ndim != 3:
raise ValueError("Image must be 3D")

Check warning on line 97 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L96-L97

Added lines #L96 - L97 were not covered by tests

# Apply gaussian filter to image
if gauss_sigma > 0:
data_smoothed = filters.gaussian(image, sigma=gauss_sigma)

Check warning on line 100 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L99-L100

Added lines #L99 - L100 were not covered by tests
else:
data_smoothed = image

Check warning on line 102 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L102

Added line #L102 was not covered by tests

# Threshold the (smoothed) image
binary = threshold_image(data_smoothed, method=threshold_method)
binary = _threshold_image(data_smoothed, method=threshold_method)
mask = _extract_largest_object(binary)

Check warning on line 105 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L104-L105

Added lines #L104 - L105 were not covered by tests

# Keep only the largest object in the binary image
mask = extract_largest_object(binary)

# Erode the mask
if erosion_size > 0:
mask = morphology.binary_erosion(

Check warning on line 108 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L107-L108

Added lines #L107 - L108 were not covered by tests
mask, footprint=np.ones((erosion_size,) * image.ndim)
)
return mask

Check warning on line 111 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L111

Added line #L111 was not covered by tests


def get_midline_points(mask: np.ndarray):

Check warning on line 114 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L114

Added line #L114 was not covered by tests
"""Get a set of 9 points roughly on the x axis midline of a 3D binary mask.
Parameters
----------
mask : np.ndarray
A binary mask of shape (z, y, x).
Returns
-------
np.ndarray
An array of shape (9, 3) containing the midline points.
"""

# Check input
if mask.ndim != 3:
raise ValueError("Mask must be 3D")

Check warning on line 130 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L129-L130

Added lines #L129 - L130 were not covered by tests

try:
mask = mask.astype(bool)
except ValueError:
raise ValueError("Mask must be binary")

Check warning on line 135 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L132-L135

Added lines #L132 - L135 were not covered by tests

# Derive mask properties
props = measure.regionprops(measure.label(mask))[0]

Check warning on line 138 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L138

Added line #L138 was not covered by tests
# bbox in shape (3, 2): for each dim (row) the min and max (col)
bbox = np.array(props.bbox).reshape(2, 3).T
bbox_ranges = bbox[:, 1] - bbox[:, 0]

Check warning on line 141 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L140-L141

Added lines #L140 - L141 were not covered by tests
# mask centroid in shape (3,)
centroid = np.array(props.centroid)

Check warning on line 143 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L143

Added line #L143 was not covered by tests

# Find slices at 1/4, 2/4, and 3/4 of the z and y dimensions
z_slices = [bbox_ranges[0] / 4 * i for i in [1, 2, 3]]
y_slices = [bbox_ranges[1] / 4 * i for i in [1, 2, 3]]

Check warning on line 147 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L146-L147

Added lines #L146 - L147 were not covered by tests
# Find points at the intersection the centroid's x slice
# with the above y and z slices.
# This produces a set of 9 points roughly on the midline
points = list(product(z_slices, y_slices, [centroid[2]]))

Check warning on line 151 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L151

Added line #L151 was not covered by tests

return np.array(points)

Check warning on line 153 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L153

Added line #L153 was not covered by tests


def _fit_plane_to_points(

Check warning on line 156 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L156

Added line #L156 was not covered by tests
points: np.ndarray,
) -> np.ndarray:
"""Fit a plane to a set of 3D points.
Parameters
----------
points : np.ndarray
An array of shape (n, 3) containing the points.
Returns
-------
np.ndarray
The normal vector to the plane, with shape (3,).
"""

# Ensure points are 3D
if points.shape[1] != 3:
raise ValueError("Points array must have 3 columns (z, y, x)")

Check warning on line 174 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L173-L174

Added lines #L173 - L174 were not covered by tests

centered_points = points - np.mean(points, axis=0)

Check warning on line 176 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L176

Added line #L176 was not covered by tests

# Use SVD to get the normal vector to the plane
_, _, vh = np.linalg.svd(centered_points)
normal_vector = vh[-1]

Check warning on line 180 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L179-L180

Added lines #L179 - L180 were not covered by tests

return normal_vector

Check warning on line 182 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L182

Added line #L182 was not covered by tests


def align_to_midline(

Check warning on line 185 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L185

Added line #L185 was not covered by tests
image: np.ndarray,
points: np.ndarray,
Expand Down Expand Up @@ -105,7 +218,7 @@ def align_to_midline(
raise ValueError("Axis must be one of 'x', 'y', or 'z'")

Check warning on line 218 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L213-L218

Added lines #L213 - L218 were not covered by tests

# Fit a plane to the points
normal_vector = fit_plane_to_points(points)
normal_vector = _fit_plane_to_points(points)

Check warning on line 221 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L221

Added line #L221 was not covered by tests

# Compute centroid of the midline points
centroid = np.mean(points, axis=0)

Check warning on line 224 in brainglobe_template_builder/preproc.py

View check run for this annotation

Codecov / codecov/patch

brainglobe_template_builder/preproc.py#L224

Added line #L224 was not covered by tests
Expand Down
129 changes: 0 additions & 129 deletions brainglobe_template_builder/utils.py

This file was deleted.

0 comments on commit eb22122

Please sign in to comment.