Commit ff65be41 authored by Sean Fitzgibbon's avatar Sean Fitzgibbon
Browse files

reformatting - no code changes

parent eb4d5877
#!/usr/bin/env python
#
#
# Copyright 2021 Sean Fitzgibbon, University of Oxford
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
import os
import os.path as op
import json
......@@ -31,19 +31,20 @@ from matplotlib import patches
import numpy as np
import matplotlib.pyplot as plt
def register_chart_to_slide(chart, slide, slide_res, out, config=None):
if config is None:
config = util.get_resource('chart.yaml')
config = util.get_resource("chart.yaml")
with open(config, 'r') as f:
with open(config, "r") as f:
config = yaml.load(f)
rlevel = config['slide']['resolution_level']
boundary_key = config['chart']['boundary_key']
rlevel = config["slide"]["resolution_level"]
boundary_key = config["chart"]["boundary_key"]
# set global variables required for summary plots
plots = config['general']['plots']
plots = config["general"]["plots"]
global DO_PLOTS
DO_PLOTS = plots
......@@ -54,11 +55,11 @@ def register_chart_to_slide(chart, slide, slide_res, out, config=None):
# create output dir
if not op.exists(out):
os.makedirs(out)
# load chart
contour, cells = neurolucida.read(chart)
slide_res = slide_res * (2**rlevel)
slide_res = slide_res * (2 ** rlevel)
# load slide
jp2 = glymur.Jp2k(slide)
......@@ -69,24 +70,28 @@ def register_chart_to_slide(chart, slide, slide_res, out, config=None):
init = _init_scale(img, slide_res, edge_crds, verbose=True)
print(init)
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}"
)
# calculate normal line to boundary points
xfm_edge_coords = _apply_xfm(init, edge_crds)
init_nrmls = normal(xfm_edge_coords)
# refine edge_coords (to image)
refined_edge_coords = _refine_edge_coord(img, slide_res, xfm_edge_coords, init_nrmls)
refined_edge_coords = _refine_edge_coord(
img, slide_res, xfm_edge_coords, init_nrmls
)
# estimate opimised affine transform
opt = transform.AffineTransform()
opt.estimate(edge_crds, refined_edge_coords)
print(opt)
print(
f'Rotation:\t{opt.rotation}\nTranslation:\t{opt.translation}\nScale:\t\t{list(opt.scale)}\nShear:\t\t{opt.shear}')
f"Rotation:\t{opt.rotation}\nTranslation:\t{opt.translation}\nScale:\t\t{list(opt.scale)}\nShear:\t\t{opt.shear}"
)
# save opt transform
np.savetxt(f'{out}/chart-to-image.xfm', opt.params)
np.savetxt(f"{out}/chart-to-image.xfm", opt.params)
# apply opt-xfm to contours and cells and save
......@@ -104,17 +109,17 @@ def register_chart_to_slide(chart, slide, slide_res, out, config=None):
coords = np.array(coords)
cog = np.mean(coords,axis=0)
ax.text(cog[0],cog[1],name)
ax.plot(coords[:,0],coords[:,1],'k-')
cog = np.mean(coords, axis=0)
ax.text(cog[0], cog[1], name)
ax.plot(coords[:, 0], coords[:, 1], "k-")
ax.set_xlabel("mm")
ax.set_ylabel("mm")
ax.set_title("Aligned Chart")
ax.set_xlabel('mm')
ax.set_ylabel('mm')
ax.set_title('Aligned Chart')
fig.savefig(f'{OUTDIR}/aligned_chart.png', bbox_inches='tight', dpi=300)
fig.savefig(f"{OUTDIR}/aligned_chart.png", bbox_inches="tight", dpi=300)
# fig, ax = plt.subplots(1, 5, figsize=(25, 5))
# fig, ax = plt.subplots(1, 5, figsize=(25, 5))
# ax[0].imshow(plt.imread(f'{OUTDIR}/chart_bounding_box.png'))
# ax[1].imshow(plt.imread(f'{OUTDIR}/image_bounding_box.png'))
# ax[2].imshow(plt.imread(f'{OUTDIR}/normals.png'))
......@@ -126,14 +131,12 @@ def register_chart_to_slide(chart, slide, slide_res, out, config=None):
# fig.savefig(f'{OUTDIR}/alignment.png', bbox_inches='tight', dpi=150)
with open(f'{out}/contour.json', 'w') as fp:
with open(f"{out}/contour.json", "w") as fp:
json.dump(contour_xfm, fp, indent=4)
if cells.size > 0:
cells_xfm = _apply_xfm(cells[:, :2] * [1, -1])
with open(f'{out}/cells.json', 'w') as fp:
with open(f"{out}/cells.json", "w") as fp:
json.dump(cells_xfm, fp, indent=4)
......@@ -151,12 +154,12 @@ def _refine_edge_coord(img, img_res, edge_coords, normals):
line_x = edge_x[:, np.newaxis] + nrml_x[:, np.newaxis] * line_smpl
line_y = edge_y[:, np.newaxis] + nrml_y[:, np.newaxis] * line_smpl
# find threshold for background
image = rgb2gray(img)
threshold_value = filters.threshold_otsu(image)
print(f'threshold={threshold_value}')
print(f"threshold={threshold_value}")
# binarise foreground
# NOTE: this assumes a bright background!
......@@ -170,7 +173,7 @@ def _refine_edge_coord(img, img_res, edge_coords, normals):
ridx = np.argmax([p0.area for p0 in p])
p = p[ridx]
cc0 = (cc == p.label)
cc0 = cc == p.label
if DO_PLOTS:
fig, ax = plt.subplots(figsize=(20, 30))
......@@ -179,16 +182,15 @@ def _refine_edge_coord(img, img_res, edge_coords, normals):
ax.imshow(cc0, extent=extent)
for line_x0, line_y0 in zip(line_x, line_y):
ax.plot(line_x0, line_y0, 'b.')
ax.plot(line_x0, line_y0, "b.")
ax.plot(edge_x, edge_y, 'r.')
ax.plot(edge_x, edge_y, "r.")
ax.set_xlabel('mm')
ax.set_ylabel('mm')
ax.set_title('Largest connected component + normals')
fig.savefig(f'{OUTDIR}/normals.png', bbox_inches='tight', dpi=300)
ax.set_xlabel("mm")
ax.set_ylabel("mm")
ax.set_title("Largest connected component + normals")
fig.savefig(f"{OUTDIR}/normals.png", bbox_inches="tight", dpi=300)
# sample image along normal line
......@@ -199,10 +201,13 @@ def _refine_edge_coord(img, img_res, edge_coords, normals):
min_idx = np.argmax(np.diff(line_int, axis=-1), axis=-1)
refined_edge_coords = np.stack([
line_x[np.arange(min_idx.size), min_idx] * img_res,
line_y[np.arange(min_idx.size), min_idx] * img_res,
], axis=-1)
refined_edge_coords = np.stack(
[
line_x[np.arange(min_idx.size), min_idx] * img_res,
line_y[np.arange(min_idx.size), min_idx] * img_res,
],
axis=-1,
)
# TODO: plot edge_coords + refined_edge_coords
if DO_PLOTS:
......@@ -211,17 +216,16 @@ def _refine_edge_coord(img, img_res, edge_coords, normals):
extent = np.array([0, img.shape[1], img.shape[0], 0]) * img_res
ax.imshow(img, extent=extent)
ax.plot(edge_x, edge_y, 'r.-')
ax.plot(refined_edge_coords[:, 0], refined_edge_coords[:, 1], 'g.-')
ax.plot(edge_x, edge_y, "r.-")
ax.plot(refined_edge_coords[:, 0], refined_edge_coords[:, 1], "g.-")
ax.set_xlabel('mm')
ax.set_ylabel('mm')
ax.set_xlabel("mm")
ax.set_ylabel("mm")
# ax.set_title('edge_co')
ax.legend(['edge_coords', 'refined_edge_coords'])
fig.savefig(f'{OUTDIR}/refined_coords.png', bbox_inches='tight', dpi=150)
ax.legend(["edge_coords", "refined_edge_coords"])
fig.savefig(f"{OUTDIR}/refined_coords.png", bbox_inches="tight", dpi=150)
return refined_edge_coords
......@@ -235,7 +239,7 @@ def _img_props(img, img_resolution, verbose=False):
threshold_value = filters.threshold_otsu(image)
if verbose:
print(f'threshold={threshold_value}')
print(f"threshold={threshold_value}")
# binarise foreground
# NOTE: this assumes a bright background!
......@@ -253,8 +257,8 @@ def _img_props(img, img_resolution, verbose=False):
bbox = np.array(p.bbox) * img_resolution
centroid = (
(bbox[0] + bbox[2])/2,
(bbox[1] + bbox[3])/2,
(bbox[0] + bbox[2]) / 2,
(bbox[1] + bbox[3]) / 2,
)
if DO_PLOTS:
......@@ -267,27 +271,27 @@ def _img_props(img, img_resolution, verbose=False):
rect = patches.Rectangle(
(bbox[1], bbox[0]),
bbox[3]-bbox[1],
bbox[2]-bbox[0],
bbox[3] - bbox[1],
bbox[2] - bbox[0],
linewidth=1,
edgecolor='r',
facecolor='none'
edgecolor="r",
facecolor="none",
)
ax.add_patch(rect)
ax.plot(centroid[1], centroid[0], 'ro')
ax.set_xlabel('mm')
ax.set_ylabel('mm')
ax.set_title('Image bounding box')
fig.savefig(f'{OUTDIR}/image_bounding_box.png', bbox_inches='tight', dpi=150)
ax.plot(centroid[1], centroid[0], "ro")
ax.set_xlabel("mm")
ax.set_ylabel("mm")
ax.set_title("Image bounding box")
fig.savefig(f"{OUTDIR}/image_bounding_box.png", bbox_inches="tight", dpi=150)
return {
'bbox': bbox,
'bbox_centroid': centroid,
'shape': (bbox[2]-bbox[0], bbox[3]-bbox[1])
}
"bbox": bbox,
"bbox_centroid": centroid,
"shape": (bbox[2] - bbox[0], bbox[3] - bbox[1]),
}
def _pnt_props(pnts, verbose=False):
......@@ -295,47 +299,46 @@ def _pnt_props(pnts, verbose=False):
x = pnts[:, 0]
y = pnts[:, 1]
bbox = (
np.min(y),
np.min(x),
np.max(y),
np.max(x)
)
bbox = (np.min(y), np.min(x), np.max(y), np.max(x))
centroid = (
(bbox[0] + bbox[2])/2,
(bbox[1] + bbox[3])/2,
(bbox[0] + bbox[2]) / 2,
(bbox[1] + bbox[3]) / 2,
)
if verbose:
print(f'bbox={bbox}')
print(f'centroid={centroid}')
print(f"bbox={bbox}")
print(f"centroid={centroid}")
if DO_PLOTS:
fig, ax = plt.subplots()
ax.plot(x, y, 'b.')
ax.axis('equal')
ax.plot(x, y, "b.")
ax.axis("equal")
ax.invert_yaxis()
rect = patches.Rectangle(
(bbox[1], bbox[0]),
bbox[3]-bbox[1],
bbox[2]-bbox[0],
bbox[3] - bbox[1],
bbox[2] - bbox[0],
linewidth=1,
edgecolor='r',
facecolor='none'
edgecolor="r",
facecolor="none",
)
ax.add_patch(rect)
plt.plot(centroid[1], centroid[0], 'ro')
plt.plot(centroid[1], centroid[0], "ro")
ax.set_title("Chart bounding box")
ax.set_title('Chart bounding box')
fig.savefig(f'{OUTDIR}/chart_bounding_box.png', bbox_inches='tight', dpi=150)
fig.savefig(f"{OUTDIR}/chart_bounding_box.png", bbox_inches="tight", dpi=150)
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, img_resolution, crd, verbose=False):
......@@ -344,13 +347,13 @@ def _init_scale(img, img_resolution, crd, verbose=False):
crd_p = _pnt_props(crd)
if verbose:
print(f'image props = {img_p}')
print(f'chart props = {crd_p}')
print(f"image props = {img_p}")
print(f"chart props = {crd_p}")
idx = np.array([[1, 2], [1, 0], [3, 2], [3, 0]])
src = np.array(crd_p['bbox'])[idx]
dest = np.array(img_p['bbox'])[idx]
src = np.array(crd_p["bbox"])[idx]
dest = np.array(img_p["bbox"])[idx]
a = transform.AffineTransform()
a.estimate(src, dest)
......@@ -359,7 +362,9 @@ def _init_scale(img, img_resolution, crd, verbose=False):
def _apply_xfm(xfm, pnts):
return (xfm.params @ np.concatenate((pnts, np.ones((pnts.shape[0], 1))), axis=-1).T).T[:, :2]
return (
xfm.params @ np.concatenate((pnts, np.ones((pnts.shape[0], 1))), axis=-1).T
).T[:, :2]
def normal(pnts):
......
#!/usr/bin/env python
#
#
# Copyright 2021 Sean Fitzgibbon, University of Oxford
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
import jinja2
import os.path as op
import os
import base64
def report_reg(dirs, out, embed=False):
images = [f'{d}/alignment.png' for d in dirs]
images = [f"{d}/alignment.png" for d in dirs]
images = [d for d in images if op.exists(d)]
images = [(op.dirname(f), to_base64(f) if embed else f) for f in images]
env = jinja2.Environment(
loader=jinja2.PackageLoader('slider', 'resources'),
autoescape=jinja2.select_autoescape(['html', 'xml']),
extensions=['jinja2.ext.do']
loader=jinja2.PackageLoader("slider", "resources"),
autoescape=jinja2.select_autoescape(["html", "xml"]),
extensions=["jinja2.ext.do"],
)
template = env.get_template('registration_report_template.html')
template = env.get_template("registration_report_template.html")
html = template.render(
images=images,
)
with open(out, 'w') as outfile:
with open(out, "w") as outfile:
outfile.write(html)
def to_base64(fname):
with open(fname, 'rb') as tmp:
with open(fname, "rb") as tmp:
tmp.seek(0)
s = base64.b64encode(tmp.read()).decode("utf-8")
return 'data:image/{};base64,'.format(op.splitext(fname)[-1]) + s
return "data:image/{};base64,".format(op.splitext(fname)[-1]) + s
This diff is collapsed.
#!/usr/bin/env python
#
#
# Copyright 2021 Sean Fitzgibbon, University of Oxford
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
import argparse
import sys
......@@ -22,33 +22,54 @@ from slider.chart_reg import register_chart_to_slide
from slider.batch import run_batch
from slider.report import report_reg
def add_slide_cli(subparsers):
"""
Set up slide-to-slide subparser instance.
"""
parser = subparsers.add_parser(
'SLIDE',
description='Register 2D slide to 2D slide',
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=55, width=100)
"SLIDE",
description="Register 2D slide to 2D slide",
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(
"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("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("--out", metavar="<dir>",
help="Output directory", default='./slide-to-slide.reg', type=str,
required=False)
parser.add_argument("--config", metavar="<config.json>",
help="configuration file", default=None, type=str,
required=False)
parser.set_defaults(method='slide')
parser.add_argument(
"--out",
metavar="<dir>",
help="Output directory",
default="./slide-to-slide.reg",
type=str,
required=False,
)
parser.add_argument(
"--config",
metavar="<config.json>",
help="configuration file",
default=None,
type=str,
required=False,
)
parser.set_defaults(method="slide")
def add_chart_cli(subparsers):
......@@ -56,23 +77,39 @@ def add_chart_cli(subparsers):
Set up chart-to-slide subparser instance.
"""
parser = subparsers.add_parser(
'CHART',
description='Register charting to 2D slide',
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=55, width=100)
)
parser.add_argument("chart", metavar="<chart>",
help="Neurolucida chart file", type=str)
parser.add_argument("slide", metavar="<slide>",
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>",
help="Output directory", default='./chart-to-slide.reg', type=str,
required=False)
parser.add_argument("--config", metavar="<config.yaml>",
help="configuration file", default=None, type=str,
required=False)
parser.set_defaults(method='chart')
"CHART",
description="Register charting to 2D slide",
formatter_class=lambda prog: argparse.HelpFormatter(
prog, max_help_position=55, width=100
),
)
parser.add_argument(
"chart", metavar="<chart>", help="Neurolucida chart file", type=str
)
parser.add_argument("slide", metavar="<slide>", 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>",
help="Output directory",
default="./chart-to-slide.reg",
type=str,
required=False,
)
parser.add_argument(
"--config",
metavar="<config.yaml>",
help="configuration file",
default=None,
type=str,
required=False,
)
parser.set_defaults(method="chart")
def add_applyxfm_cli(subparsers):
......@@ -80,36 +117,67 @@ 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)
"APPLYXFM",
description="Apply transform to image.",
formatter_class=lambda prog: argparse.HelpFormatter(
prog, max_help_position=55, width=100