Commit 233b0513 authored by Sean Fitzgibbon's avatar Sean Fitzgibbon
Browse files

Added apply_xfm support, changed coordinate system to mm (instead of pixels),...

Added apply_xfm support, changed coordinate system to mm (instead of pixels), added infrastructure for changing resolution
parent 2ceba27c
...@@ -14,19 +14,20 @@ from matplotlib import patches ...@@ -14,19 +14,20 @@ from matplotlib import patches
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
def register_chart_to_slide(chart, slide, slide_res, out, rlevel=4, boundary_key='outline'):
def register_chart_to_slide(chart, slide, out, rlevel=0, boundary_key='outline'):
# load chart # load chart
contour, cells = neurolucida.read(chart) contour, cells = neurolucida.read(chart)
slide_res = slide_res * (2**rlevel)
# load slide # load slide
jp2 = glymur.Jp2k(slide) jp2 = glymur.Jp2k(slide)
img = jp2.read(rlevel=rlevel) img = jp2.read(rlevel=rlevel)
# initial scaling based on boundng boxes # initial scaling based on boundng boxes
edge_crds = contour[boundary_key][:, :2] * [1, -1] edge_crds = contour[boundary_key][:, :2] * [1, -1]
init = _init_scale(img, edge_crds, verbose=True, plots=False) init = _init_scale(img, slide_res, edge_crds, verbose=True, plots=False)
print(init) print(init)
print( print(
f'Rotation: {init.rotation}, Translation: {init.translation}, Scale: {init.scale}, Shear: {init.shear}') f'Rotation: {init.rotation}, Translation: {init.translation}, Scale: {init.scale}, Shear: {init.shear}')
...@@ -36,7 +37,7 @@ def register_chart_to_slide(chart, slide, out, rlevel=0, boundary_key='outline') ...@@ -36,7 +37,7 @@ def register_chart_to_slide(chart, slide, out, rlevel=0, boundary_key='outline')
init_nrmls = normal(xfm_edge_coords, plot=False, distance=1, verbose=True) init_nrmls = normal(xfm_edge_coords, plot=False, distance=1, verbose=True)
# refine edge_coords (to image) # refine edge_coords (to image)
refined_edge_coords = _refine_edge_coord(img, xfm_edge_coords, init_nrmls) refined_edge_coords = _refine_edge_coord(img, slide_res, xfm_edge_coords, init_nrmls)
# estimate opimised affine transform # estimate opimised affine transform
opt = transform.AffineTransform() opt = transform.AffineTransform()
...@@ -65,7 +66,7 @@ def register_chart_to_slide(chart, slide, out, rlevel=0, boundary_key='outline') ...@@ -65,7 +66,7 @@ def register_chart_to_slide(chart, slide, out, rlevel=0, boundary_key='outline')
json.dump(cells_xfm, fp, indent=4) json.dump(cells_xfm, fp, indent=4)
def _refine_edge_coord(img, edge_coords, normals): def _refine_edge_coord(img, img_res, edge_coords, normals):
""" """
Refine edge_coord by sampling binarised image along normal (to edge) and looking for big step change. Refine edge_coord by sampling binarised image along normal (to edge) and looking for big step change.
""" """
...@@ -74,15 +75,14 @@ def _refine_edge_coord(img, edge_coords, normals): ...@@ -74,15 +75,14 @@ def _refine_edge_coord(img, edge_coords, normals):
edge_x, edge_y = np.round(edge_coords.T).astype(int) edge_x, edge_y = np.round(edge_coords.T).astype(int)
nrml_x, nrml_y = normals.T nrml_x, nrml_y = normals.T
line_smpl = np.arange(-20, 20) line_smpl = np.arange(-20, 20) * img_res
line_x = np.round( line_x = edge_x[:, np.newaxis] + nrml_x[:, np.newaxis] * line_smpl
edge_x[:, np.newaxis] + nrml_x[:, np.newaxis] * line_smpl line_y = edge_y[:, np.newaxis] + nrml_y[:, np.newaxis] * line_smpl
).astype(int)
line_y = np.round( line_x = np.round(line_x / img_res).astype(int)
edge_y[:, np.newaxis] + nrml_y[:, np.newaxis] * line_smpl line_y = np.round(line_y / img_res).astype(int)
).astype(int)
# find threshold for background # find threshold for background
image = rgb2gray(img) image = rgb2gray(img)
...@@ -114,11 +114,11 @@ def _refine_edge_coord(img, edge_coords, normals): ...@@ -114,11 +114,11 @@ def _refine_edge_coord(img, edge_coords, normals):
line_y[np.arange(min_idx.size), min_idx], line_y[np.arange(min_idx.size), min_idx],
], axis=-1) ], axis=-1)
return refined_edge_coords return refined_edge_coords * img_res
def _img_props(img, verbose=False, plots=False): def _img_props(img, img_resolution, verbose=False, plots=False):
# convert to grayscale # convert to grayscale
image = rgb2gray(img) image = rgb2gray(img)
...@@ -142,7 +142,7 @@ def _img_props(img, verbose=False, plots=False): ...@@ -142,7 +142,7 @@ def _img_props(img, verbose=False, plots=False):
ridx = np.argmax([p0.area for p0 in p]) ridx = np.argmax([p0.area for p0 in p])
p = p[ridx] p = p[ridx]
bbox = p.bbox bbox = np.array(p.bbox) * img_resolution
centroid = ( centroid = (
(bbox[0] + bbox[2])/2, (bbox[0] + bbox[2])/2,
...@@ -150,25 +150,29 @@ def _img_props(img, verbose=False, plots=False): ...@@ -150,25 +150,29 @@ def _img_props(img, verbose=False, plots=False):
) )
# TODO: make plots work again # TODO: make plots work again
if plots: # if plots:
plt.imshow(cc == p.label) # plt.imshow(cc == p.label)
plt.colorbar() # plt.colorbar()
rect = patches.Rectangle( # rect = patches.Rectangle(
(bbox[1], bbox[0]), # (bbox[1], bbox[0]),
bbox[3]-bbox[1], # bbox[3]-bbox[1],
bbox[2]-bbox[0], # bbox[2]-bbox[0],
linewidth=1, # linewidth=1,
edgecolor='r', # edgecolor='r',
facecolor='none' # facecolor='none'
) # )
plt.gca().add_patch(rect) # plt.gca().add_patch(rect)
plt.plot(centroid[1], centroid[0], 'ro') # plt.plot(centroid[1], centroid[0], 'ro')
plt.show() # plt.show()
return {'bbox': bbox, 'bbox_centroid': centroid, 'shape': (bbox[2]-bbox[0], bbox[3]-bbox[1])} return {
'bbox': bbox,
'bbox_centroid': centroid,
'shape': (bbox[2]-bbox[0], bbox[3]-bbox[1])
}
def _pnt_props(pnts, verbose=False, plots=False): def _pnt_props(pnts, verbose=False, plots=False):
...@@ -213,9 +217,9 @@ def _pnt_props(pnts, verbose=False, plots=False): ...@@ -213,9 +217,9 @@ def _pnt_props(pnts, verbose=False, plots=False):
return {'bbox': bbox, 'bbox_centroid': centroid, 'shape': (bbox[2]-bbox[0], bbox[3]-bbox[1])} return {'bbox': bbox, 'bbox_centroid': centroid, 'shape': (bbox[2]-bbox[0], bbox[3]-bbox[1])}
def _init_scale(img, crd, verbose=False, plots=False): def _init_scale(img, img_resolution, crd, verbose=False, plots=False):
img_p = _img_props(img) img_p = _img_props(img, img_resolution)
crd_p = _pnt_props(crd) crd_p = _pnt_props(crd)
if verbose: if verbose:
......
...@@ -141,25 +141,44 @@ SNAPSHOT_EXT = "png" ...@@ -141,25 +141,44 @@ SNAPSHOT_EXT = "png"
# NumPy print formatting # NumPy print formatting
np.set_printoptions(precision=4) np.set_printoptions(precision=4)
def _load_jpg2k(file, resolution, resolution_level, dtype=None):
# load jp2
import glymur
jp2= glymur.Jp2k(file)
img = jp2.read(rlevel=resolution_level)
# adjust resolution by resolution_level
resolution = resolution * (2**resolution_level)
# create timg
timg = TImage.fromarray(img, tensor_axes=(2,), dtype=dtype)
timg.resolution = resolution
return timg
def _load_image(p): def _load_image(p):
if p.file.lower().endswith('.jp2'): # if jpg2k if p.file.lower().endswith('.jp2'): # if jpg2k
# load jp2 # # load jp2
import glymur # import glymur
from tirl.scripts.mnd.image import set_mask # from tirl.scripts.mnd.image import set_mask
jp2= glymur.Jp2k(p.file) # jp2= glymur.Jp2k(p.file)
img = jp2.read(rlevel=p.resolution_level) # img = jp2.read(rlevel=p.resolution_level)
# # adjust resolution by resolution_level
# p.resolution = p.resolution * (2**p.resolution_level)
# adjust resolution by resolution_level # # create timg
p.resolution = p.resolution * (2**p.resolution_level) # timg = TImage.fromarray(img, dtype=p.dtype, tensor_axes=(2,))
# timg.resolution = p.resolution
# create timg timg = _load_jpg2k(p.file, p.resolution, p.resolution_level, p.dtype)
timg = TImage.fromarray(img, dtype=p.dtype, tensor_axes=(2,))
timg.resolution = p.resolution
# add mask # add mask
set_mask(timg, scope=globals(), **p.mask) tirl.scripts.mnd.image.set_mask(timg, scope=globals(), **p.mask)
# Export # Export
tirl.scripts.mnd.inout.export(timg, p.export, default=None) tirl.scripts.mnd.inout.export(timg, p.export, default=None)
...@@ -887,4 +906,51 @@ def register_slide_to_slide(moving, moving_res, fixed, fixed_res, out, config): ...@@ -887,4 +906,51 @@ def register_slide_to_slide(moving, moving_res, fixed, fixed_res, out, config):
config["general"]["paramlogfile"] = f'{out}/paramlog.log' config["general"]["paramlogfile"] = f'{out}/paramlog.log'
# Run registration script # Run registration script
run(config) run(config)
\ No newline at end of file
def apply_slide_xfm(moving, moving_res, fixed, fixed_res, moving_reg, fixed_reg, out, rlevel=5, inverse=False):
moving_reg = tirl.load(moving_reg)
fixed_reg = tirl.load(fixed_reg)
# get transformation chain from registration
if inverse:
raise NotImplementedError('Inverse not yet implemented')
else:
chain = fixed_reg.domain.chain
chain += moving_reg.domain.chain.inverse()
# update input/output resolution in transformation chain
new_res_moving = moving_res * (2**rlevel)
new_res_fixed = fixed_res * (2**rlevel)
chain[0].parameters[:] = [new_res_fixed, new_res_fixed]
chain[-1].parameters[:] = [1 / new_res_moving, 1 / new_res_moving]
# load native resolution images
moving_native = _load_jpg2k(moving, moving_res, rlevel)
fixed_native = _load_jpg2k(fixed, fixed_res, rlevel)
# fixed pixel coordinates in native res
v = fixed_native.domain.get_voxel_coordinates()
# Map through transformation chain
fixed_coords_in_moving_native = chain.map(v)
# Interpolate
moving_value = moving_native.interpolator(fixed_coords_in_moving_native)
moving_value_reshaped = moving_value.reshape((fixed_native.shape[0],fixed_native.shape[1],3))
import glymur
glymur.Jp2k(out, data=np.round(moving_value_reshaped).astype(moving_native.dtype))
# import imageio
# imageio.imsave('test_moving.png', np.round(moving_value_reshaped).astype(moving_native.dtype))
# imageio.imsave('test_fixed.png', fixed_native.data)
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import argparse import argparse
import sys import sys
from slider.slide_reg import register_slide_to_slide from slider.slide_reg import register_slide_to_slide, apply_slide_xfm
from slider.chart_reg import register_chart_to_slide from slider.chart_reg import register_chart_to_slide
def add_slide_cli(subparsers): def add_slide_cli(subparsers):
...@@ -26,7 +26,7 @@ def add_slide_cli(subparsers): ...@@ -26,7 +26,7 @@ def add_slide_cli(subparsers):
help="Fixed image resolution (mm)", type=float) help="Fixed image resolution (mm)", type=float)
parser.add_argument("--out", metavar="<dir>", parser.add_argument("--out", metavar="<dir>",
help="Output directory", default='./slider.reg', type=str, help="Output directory", default='./slide-to-slide.reg', type=str,
required=False) required=False)
parser.add_argument("--config", metavar="<config.json>", parser.add_argument("--config", metavar="<config.json>",
help="configuration file", default=None, type=str, help="configuration file", default=None, type=str,
...@@ -47,12 +47,44 @@ def add_chart_cli(subparsers): ...@@ -47,12 +47,44 @@ def add_chart_cli(subparsers):
help="Chart file", type=str) help="Chart file", type=str)
parser.add_argument("slide", metavar="<slide>", parser.add_argument("slide", metavar="<slide>",
help="Fixed slide", type=str) help="Fixed slide", type=str)
parser.add_argument("slide_res", metavar="<slide-resolution>",
help="Slide image resolution (mm)", type=float)
parser.add_argument("--out", metavar="<dir>", parser.add_argument("--out", metavar="<dir>",
help="Output directory", default='./slider.reg', type=str, help="Output directory", default='./chart-to-slide.reg', type=str,
required=False) required=False)
parser.set_defaults(method='chart') parser.set_defaults(method='chart')
def add_applyxfm_cli(subparsers):
"""
Set up applyxfm subparser instance.
"""
parser = subparsers.add_parser(
'APPLYXFM',
description='Apply transform to image.',
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=55, width=100)
)
parser.add_argument("moving", metavar="<moving>",
help="Moving slide image", type=str)
parser.add_argument("moving_res", metavar="<moving-resolution>",
help="Moving image resolution (mm)", type=float)
parser.add_argument("fixed", metavar="<fixed>",
help="Fixed slide image", type=str)
parser.add_argument("fixed_res", metavar="<fixed-resolution>",
help="Fixed image resolution (mm)", type=float)
parser.add_argument("moving_reg", metavar="<moving-reg>",
help="Moving timg from registration", type=str)
parser.add_argument("fixed_reg", metavar="<fixed-reg>",
help="Fixed timg from registration", type=str)
parser.add_argument("out", metavar="<dir>",
help="Name for resampled output image", type=str)
parser.set_defaults(method='applyxfm')
if __name__ == "__main__": if __name__ == "__main__":
""" Main program code. """ """ Main program code. """
...@@ -61,6 +93,7 @@ if __name__ == "__main__": ...@@ -61,6 +93,7 @@ if __name__ == "__main__":
subparsers = parser.add_subparsers() subparsers = parser.add_subparsers()
add_slide_cli(subparsers) add_slide_cli(subparsers)
add_chart_cli(subparsers) add_chart_cli(subparsers)
add_applyxfm_cli(subparsers)
# --- # ---
...@@ -75,5 +108,7 @@ if __name__ == "__main__": ...@@ -75,5 +108,7 @@ if __name__ == "__main__":
register_slide_to_slide(**args) register_slide_to_slide(**args)
elif method == 'chart': elif method == 'chart':
register_chart_to_slide(**args) register_chart_to_slide(**args)
elif method == 'applyxfm':
apply_slide_xfm(**args)
else: else:
raise RuntimeError(f'Unknown method: {method}') raise RuntimeError(f'Unknown method: {method}')
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