Commit 2431cf75 authored by Sean Fitzgibbon's avatar Sean Fitzgibbon
Browse files

Added min_points_in_contour argument.

parent ddb8f9cc
......@@ -48,10 +48,11 @@ def register_chart_to_slide(
justify: Optional[str] = None,
field: Optional[str] = None,
do_convex_hull: bool = False,
min_points_in_contour: int = 3,
):
'''
"""
It takes a chart and a slide, and aligns the chart to the slide.
Args:
chart (str): The path to the chart file.
slide (str): The slide to be registered.
......@@ -60,7 +61,10 @@ def register_chart_to_slide(
config (Optional[str]): Path to the configuration file.
do_plots (Optional[bool]): Whether to plot the results of the alignment.
justify (Optional[str]): str
'''
field (Optional[str]): The field of the slide ('light' or 'dark').
do_convex_hull (bool): bool = False. Defaults to False
min_points_in_contour (int): The minimum number of points in a contour. If a contour has fewer points than this, it is discarded. Defaults to 3
"""
chart_name, slide_name = chart, slide
......@@ -90,11 +94,21 @@ def register_chart_to_slide(
outline = chart.get_contours()
edge_crds = [x.points[:, :2] * [1, -1] for x in outline]
# filter out contours with fewer points than min_points_in_contour
lengths = [crd.shape[0] for crd in edge_crds]
pt2idx = np.where(np.array(lengths) < min_points_in_contour)[0]
reqd_idx = np.setdiff1d(np.arange(len(lengths)), pt2idx)
edge_crds = [edge_crds[idx] for idx in list(reqd_idx)]
edge_crds_cat = np.concatenate(edge_crds)
if do_convex_hull:
hull = ConvexHull(edge_crds_cat)
edge_crds_cat = np.concatenate([edge_crds_cat[simplex,:] for simplex in hull.simplices], axis=0)
edge_crds_cat = np.concatenate(
[edge_crds_cat[simplex, :] for simplex in hull.simplices], axis=0
)
# load slide, convert to grayscale, and invert if light-field
......@@ -114,7 +128,7 @@ def register_chart_to_slide(
if field is None:
field = estimate_field(img)
print(f'Estimated field is: {field}')
print(f"Estimated field is: {field}")
if field == "light":
img = 1 - img
......@@ -288,7 +302,7 @@ def register_chart_to_slide(
# continue
plot_contour(ax[7], coords, name, color=cmap(idx))
plt.suptitle(f'{chart_name}\n{slide_name}')
plt.suptitle(f"{chart_name}\n{slide_name}")
fig.savefig(f"{outdir}/alignment.png", bbox_inches="tight", dpi=300)
# plt.close(fig)
......@@ -345,7 +359,12 @@ def estimate_field(img, mask=None):
if mask is None:
edges = np.zeros(img.shape, dtype=bool)
edges[:, 0:2], edges[:, -2:], edges[0:2, :], edges[-2:, :] = True, True, True, True
edges[:, 0:2], edges[:, -2:], edges[0:2, :], edges[-2:, :] = (
True,
True,
True,
True,
)
else:
edges = mask ^ morphology.binary_erosion(mask, selem=morphology.disk(10))
......@@ -409,8 +428,8 @@ def segment_foreground(
# ridx = np.where([p0.area > min_component_size for p0 in p])[0]
# brainmask = np.sum(np.stack([(cc == p[r].label) for r in ridx], axis=2), axis=2)
a = np.array([p0.area for p0 in p])
a = a/np.max(a)
ridx = np.where(a>0.25)[0]
a = a / np.max(a)
ridx = np.where(a > 0.25)[0]
brainmask = np.isin(cc, [p[i].label for i in ridx])
# erode and dilate to clean up edges
......@@ -421,22 +440,22 @@ def segment_foreground(
def optimise_xfm(mask, mask_resolution, contour, xfm, n_iter=100):
'''
"""
Given a mask, a contour, and an initial transformation, optimise_xfm() iteratively optimises the
transformation by finding the best fit between the contour and the mask, and then returns the best
transformation by finding the best fit between the contour and the mask, and then returns the best
fit transformation.
Args:
mask: the binary mask of the structure of interest
mask_resolution: The resolution of the mask image.
contour: the contour to be transformed
xfm: the initial transform
n_iter: number of iterations to run the optimisation for. Defaults to 100
Returns:
The optimise_xfm function returns the optimised xfm, the mean distance between the optimised xfm
and the contour, and the optimised xfm and the contour at each iteration.
'''
"""
mdist = np.zeros(n_iter)
iter_props = [None] * n_iter
......@@ -533,20 +552,20 @@ def optimise_xfm_worker(image, image_resolution, contour, xfm_init, line_extent=
def image_props(mask, mask_resolution):
'''
"""
Given a mask, return a dictionary of properties about the mask
Args:
mask: The binary mask.
mask_resolution: The resolution of the mask.
Returns:
A dictionary with the following keys:
- bbox: The bounding box of the object in the original image.
- bbox_centroid: The centroid of the bounding box.
- shape: The shape of the bounding box.
- aspect_ratio
'''
"""
p = measure.regionprops(mask.astype(int))
......@@ -612,20 +631,19 @@ def justify_bbox(bbox, aspect_ratio, justify):
def initialise_xfm(image, image_resolution, contour, tol=0.05, justify=None):
'''
"""
Calculate the transform (xfm) based on bounding box corners
Args:
image: the image to be transformed
image_resolution: The resolution of the image.
contour: the contour to be transformed
tol: tolerance for aspect ratio mismatch between slide and contour
justify: str
Returns:
the transform (xfm), the image properties (image_p) and the contour properties (contour_p).
'''
"""
brainmask = segment_foreground(image)
......@@ -654,7 +672,9 @@ def initialise_xfm(image, image_resolution, contour, tol=0.05, justify=None):
directions = ("left", "right")
hull = ConvexHull(contour)
contour_hull = np.concatenate([contour[simplex,:] for simplex in hull.simplices], axis=0)
contour_hull = np.concatenate(
[contour[simplex, :] for simplex in hull.simplices], axis=0
)
for dir_idx, dir in enumerate(directions):
bbox = justify_bbox(image_p["bbox"], contour_p["aspect_ratio"], dir)
......@@ -729,32 +749,32 @@ def initialise_xfm(image, image_resolution, contour, tol=0.05, justify=None):
def apply_xfm(xfm, pnts):
'''
"""
Given a transformation matrix and a set of points, apply the transformation to the points and return
the result
Args:
xfm: the transformation matrix
pnts: the points to transform
Returns:
The transformed points.
'''
"""
return (
xfm.params @ np.concatenate((pnts, np.ones((pnts.shape[0], 1))), axis=-1).T
).T[:, :2]
def normal(pnts):
'''
"""
Given a set of points, find the normal vector to the surface defined by those points
Args:
pnts: the points to be triangulated
Returns:
The normal vector of the plane defined by the two points.
'''
"""
d_pnts = np.roll(pnts, 1, axis=0) - np.roll(pnts, -1, axis=0)
normal = np.stack([d_pnts[:, 1], -d_pnts[:, 0]], axis=-1)
......
......@@ -101,14 +101,14 @@ def add_chart_cli(subparsers):
type=str,
required=False,
)
parser.add_argument(
"--boundary_key",
metavar="<key>",
help="Name of boundary contour in chart",
default=None,
type=str,
required=False,
)
# parser.add_argument(
# "--boundary_key",
# metavar="<key>",
# help="Name of boundary contour in chart",
# default=None,
# type=str,
# required=False,
# )
parser.add_argument(
"--justify",
metavar="<left-right>",
......@@ -126,6 +126,14 @@ def add_chart_cli(subparsers):
type=str,
required=False,
)
parser.add_argument(
"--min_points_in_contour",
metavar="<n_points>",
help="Minimum points required in a contour for inclusion.",
default=3,
type=int,
required=False,
)
parser.set_defaults(method="chart")
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment