Commit fa6d0484 authored by inhuszar's avatar inhuszar
Browse files

Updated histology_to_block with the library functions.

parent 17f40a8c
...@@ -80,5 +80,6 @@ setup(name="tirl", ...@@ -80,5 +80,6 @@ setup(name="tirl",
"tirl/transformations/linear", "tirl/transformations/linear",
"tirl/transformations/nonlinear", "tirl/transformations/nonlinear",
"tirl/usermodules"], "tirl/usermodules"],
scripts=["tirl/tirl"] scripts=["tirl/tirl",
"tirl/tirlvision/sliceview.py"]
) )
No preview for this file type
...@@ -29,6 +29,6 @@ The package includes the following modules: ...@@ -29,6 +29,6 @@ The package includes the following modules:
""" """
from tirl.scripts.mnd import io from tirl.scripts.mnd import inout
from tirl.scripts.mnd import image from tirl.scripts.mnd import image
from tirl.scripts.mnd import general from tirl.scripts.mnd import general
\ No newline at end of file
...@@ -5,47 +5,54 @@ ...@@ -5,47 +5,54 @@
"author": "Istvan N Huszar" "author": "Istvan N Huszar"
}, },
"general": { "general": {
"name": "histology_to_block",
"direction": "h2b", "direction": "h2b",
"system": "linux",
"verbosity": "debug", "verbosity": "debug",
"logfile": null, "logfile": null,
"outdir": "/Users/inhuszar/temp/histo_to_block/4La/reg_svs_automask", "outputdir": "/mnt/rio/temp/histreg/histo_to_block/4La/reg",
"stages": ["rotation", "rigid", "affine", "nonlinear"], "stages": ["rotation", "rigid", "affine", "nonlinear"],
"warnings": false "warnings": false
}, },
"histology": { "histology": {
"file": "/Users/inhuszar/temp/histo_to_block/4La/NP140-14-4La-2-PLP.svs", "file": "/mnt/rio/temp/histreg/histo_to_block/4La/NP140-14-4La-2-PLP.svs",
"alternative": null,
"storage": "mem", "storage": "mem",
"dtype": "<f4", "dtype": "<f4",
"resolution": 0.008,
"mask": { "mask": {
"file": null, "file": null,
"function": null, "normalise": true,
"normalise": false "function": "labkmeans",
}, "automask": {
"automask": { "thr": 0,
"thr": 0, "uthr": 1
"uthr": 1 }
}, },
"resolution": 0.008,
"preview": false, "preview": false,
"actions": ["resample_histology", "labkmeans", "histology_preproc"] "export": true,
"snapshot": true
}, },
"block": { "block": {
"file": "/Users/inhuszar/temp/histo_to_block/4La/block_4La_sup_seg.tif", "file": "/mnt/rio/temp/histreg/histo_to_block/4La/block_4La_sup_seg.tif",
"storage": "mem", "storage": "mem",
"dtype": "<f4", "dtype": "<f4",
"resolution": 0.050,
"mask": { "mask": {
"file": null, "file": null,
"normalise": true,
"function": null, "function": null,
"normalise": false "automask": {
}, "thr": 0,
"automask": { "uthr": 1
"thr": 0, }
"uthr": 1
}, },
"resolution": 0.050,
"preview": false, "preview": false,
"actions": ["block_preproc"] "export": false,
"snapshot": false
},
"preprocessing": {
"histology": ["match_block_resolution", "histology_preprocessing"],
"block": ["block_preprocessing"]
}, },
"regparams": { "regparams": {
"init": { "init": {
...@@ -96,7 +103,7 @@ ...@@ -96,7 +103,7 @@
"scaling": [20, 10, 5, 2], "scaling": [20, 10, 5, 2],
"smoothing": [0, 0, 0, 0], "smoothing": [0, 0, 0, 0],
"xtol_rel": 0.01, "xtol_rel": 0.01,
"xtol_abs": [0.001, 0.001, 0.1, 0.001, 0.001, 0.1], "xtol_abs": [0.001, 0.1, 0.1, 0.1, 0.001, 0.1],
"opt_step": [0.01, 0.01, 1, 0.01, 0.01, 1], "opt_step": [0.01, 0.01, 1, 0.01, 0.01, 1],
"visualise": false "visualise": false
}, },
......
...@@ -159,14 +159,14 @@ def run_stage(target, source, stage_no, counter, scope, config): ...@@ -159,14 +159,14 @@ def run_stage(target, source, stage_no, counter, scope, config):
# Export stage result as TImage # Export stage result as TImage
ext = ts.EXTENSIONS["TImage"] ext = ts.EXTENSIONS["TImage"]
scr.io.export(target, q.export.timage, default=os.path.join( scr.inout.export(target, q.export.timage, default=os.path.join(
p.general.outputdir, f"{counter}_stage{stage_no}.{ext}")) p.general.outputdir, f"{counter}_stage{stage_no}.{ext}"))
# Create snapshot showing end-stage alignment # Create snapshot showing end-stage alignment
snpfile = os.path.join( snpfile = os.path.join(
p.general.outputdir, f"{counter}_stage{stage_no}.{SNAPSHOT_EXT}") p.general.outputdir, f"{counter}_stage{stage_no}.{SNAPSHOT_EXT}")
scr.io.snapshot(source.evaluate(target.domain), q.export.snapshot, scr.inout.snapshot(source.evaluate(target.domain), q.export.snapshot,
default=snpfile) default=snpfile)
# Save mask if requested # Save mask if requested
if target.tmask() is not None: if target.tmask() is not None:
...@@ -175,14 +175,14 @@ def run_stage(target, source, stage_no, counter, scope, config): ...@@ -175,14 +175,14 @@ def run_stage(target, source, stage_no, counter, scope, config):
targetmaskfile = os.path.join( targetmaskfile = os.path.join(
p.general.outputdir, p.general.outputdir,
f"{counter}_stage{stage_no}_targetmask.{SNAPSHOT_EXT}") f"{counter}_stage{stage_no}_targetmask.{SNAPSHOT_EXT}")
scr.io.snapshot(mask, q.export.target_mask, default=targetmaskfile) scr.inout.snapshot(mask, q.export.target_mask, default=targetmaskfile)
if source.tmask() is not None: if source.tmask() is not None:
from tirl.timage import TImage from tirl.timage import TImage
mask = TImage.fromTField(target.tmask()) mask = TImage.fromTField(target.tmask())
sourcemaskfile = os.path.join( sourcemaskfile = os.path.join(
p.general.outputdir, p.general.outputdir,
f"{counter}_stage{stage_no}_sourcemask.{SNAPSHOT_EXT}") f"{counter}_stage{stage_no}_sourcemask.{SNAPSHOT_EXT}")
scr.io.snapshot(mask, q.export.source_mask, default=sourcemaskfile) scr.inout.snapshot(mask, q.export.source_mask, default=sourcemaskfile)
# Visualise end-stage alignment # Visualise end-stage alignment
if q.visualise: if q.visualise:
......
This diff is collapsed.
...@@ -171,6 +171,37 @@ def perform_image_operations(img, *actions, scope=None, other=None): ...@@ -171,6 +171,37 @@ def perform_image_operations(img, *actions, scope=None, other=None):
return lastimg return lastimg
def flip_x(img, **kwargs):
"""
Flips the image along the vertical axis.
"""
img.data[...] = img.data[::-1, ...]
if img.tmask() is not None:
img.mask[...] = img.mask[::-1]
def flip_y(img, **kwargs):
"""
Flips the image along the horizontal axis.
"""
img.data[...] = img.data[:, ::-1, ...]
if img.tmask() is not None:
img.mask[...] = img.mask[:, ::-1]
def rgb2yiq(timg):
"""
Convert RGB to YIQ, or leave as grayscale.
"""
from tirl.operations.tensor import TensorOperator
if timg.tsize >= 3:
mat = np.asarray([[ 0.299, 0.587, 0.114],
[0.5959, -0.2746, -0.3213],
[0.2115, -0.5227, 0.3112]])
return TensorOperator(lambda t: np.einsum("ij,...j", mat, t))(timg)
else:
return timg
\ No newline at end of file
...@@ -135,6 +135,87 @@ def snapshot(timg, exportattr, default=None): ...@@ -135,6 +135,87 @@ def snapshot(timg, exportattr, default=None):
raise TypeError(f"Invalid export attribute: {exportattr}") raise TypeError(f"Invalid export attribute: {exportattr}")
def load_histology(**imageinputmodule):
"""
Loads histology slide from file as TImage.
:param q: configurations related to the histology input
:type q: AttrMap
"""
# Inintialise import parameters
q = DEFAULT_IMAGEINPUT_MODULE.copy()
q.update(imageinputmodule)
q = AttrMap(q)
import os
from tirl.timage import TImage
# Load image
if q.file is None:
raise FileNotFoundError("No histology image file was specified.")
elif isinstance(q.file, str):
if not os.path.isfile(q.file):
raise FileNotFoundError(
f"Histology image file does not exist: {q.file}")
if q.file.lower().endswith(
(ts.EXTENSIONS["TImage"], ts.EXTENSIONS["TIRLObject"])):
img = load_timage(q.file)
logger.info(f"Loaded histology image from {q.file}: {img}")
return img
elif q.file.lower().endswith(".svs"):
import tirl.loader
img = TImage.fromfile(q.file, storage=q.storage, dtype=q.dtype,
loader=tirl.loader.OpenSlideLoader,
loader_kwargs={"level": 2})
logger.info(f"Loaded histology image from {q.file}: {img}")
else:
try:
img = TImage.fromfile(q.file, storage=q.storage, dtype=q.dtype)
logger.info(f"Loaded histology image from {q.file}: {img}")
except Exception as exc:
logger.error(exc.args)
logger.error("Error while trying to load {}.")
raise
else:
raise TypeError(f"Invalid image file specification: {q.file}")
# ------------------------------------------------------------------------ #
# The operations below are only carried out if the image was loaded from
# an "ordinary" image file, not a TImage file.
# ------------------------------------------------------------------------ #
# Set resolution based on user input
img.resolution = q.resolution
logger.info(f"Pixel size of the histology image was set per user "
f"specification to {q.resolution} [a.u./px].")
# Set mask
from tirl.scripts.mnd.image import set_mask
set_mask(img, scope=dict(q).get("scope", globals()), **q.mask)
# Perform operations
from tirl.scripts.mnd.image import perform_image_operations
perform_image_operations(img, *q.actions,
scope=dict(q).get("scope", globals()), other=None)
# Preview
if q.preview is True:
img.preview()
# Export
export(img, q.export, default=None)
# Snapshot
snapshot(img, q.snapshot, default=None)
return img
def load_image(**imageinputmodule): def load_image(**imageinputmodule):
""" """
Loads 2D image as a TImage. If the input is a TImage file, it is loaded as Loads 2D image as a TImage. If the input is a TImage file, it is loaded as
...@@ -178,7 +259,7 @@ def load_image(**imageinputmodule): ...@@ -178,7 +259,7 @@ def load_image(**imageinputmodule):
raise raise
else: else:
raise TypeError(f"Invalid volume file specification: {q.file}") raise TypeError(f"Invalid image file specification: {q.file}")
# ------------------------------------------------------------------------ # # ------------------------------------------------------------------------ #
# The operations below are only carried out if the image was loaded from # The operations below are only carried out if the image was loaded from
......
...@@ -126,11 +126,11 @@ def run(cnf=None, **options): ...@@ -126,11 +126,11 @@ def run(cnf=None, **options):
if p.slice.export is True: if p.slice.export is True:
ext = ts.EXTENSIONS["TImage"] ext = ts.EXTENSIONS["TImage"]
p.slice.export = os.path.join(p.general.outputdir, f"slice.{ext}") p.slice.export = os.path.join(p.general.outputdir, f"slice.{ext}")
slice_ = scr.io.load_image(**p.slice) slice_ = scr.inout.load_image(**p.slice)
if p.volume.export is True: if p.volume.export is True:
ext = ts.EXTENSIONS["TImage"] ext = ts.EXTENSIONS["TImage"]
p.volume.export = os.path.join(p.general.outputdir, f"volume.{ext}") p.volume.export = os.path.join(p.general.outputdir, f"volume.{ext}")
volume = scr.io.load_volume(**p.volume) volume = scr.inout.load_volume(**p.volume)
# Having loaded both images, perform actions on the brain slice photo prior # Having loaded both images, perform actions on the brain slice photo prior
# to registration, unless it was loaded from an alternative source. # to registration, unless it was loaded from an alternative source.
......
...@@ -25,6 +25,7 @@ Part of the FMRIB Software Library (FSL) ...@@ -25,6 +25,7 @@ Part of the FMRIB Software Library (FSL)
Author: Istvan N. Huszar Author: Istvan N. Huszar
Available options: Available options:
home displays the installation directory
version displays the library version version displays the library version
config [text editor] modify library-wide constants config [text editor] modify library-wide constants
info <file> display information about a TIRL file info <file> display information about a TIRL file
...@@ -56,6 +57,15 @@ def version(*args): ...@@ -56,6 +57,15 @@ def version(*args):
print(tirl.__version__) print(tirl.__version__)
def home(*args):
"""
Displays the directory where TIRL is installed.
"""
import tirl
print(tirl.home())
def config(*args): def config(*args):
""" """
TIRL config utility. Allows the user to edit the settings.py file of TIRL config utility. Allows the user to edit the settings.py file of
...@@ -77,7 +87,6 @@ def info(*args): ...@@ -77,7 +87,6 @@ def info(*args):
Displays the ASCII header of a TIRL file using pretty formatting. Displays the ASCII header of a TIRL file using pretty formatting.
""" """
# TODO: Make this feature compatible with TIRL v2.0
from tirl.tirlfile import info from tirl.tirlfile import info
if args: if args:
info(args[0]) info(args[0])
...@@ -142,6 +151,7 @@ def _module_add(src, *args): ...@@ -142,6 +151,7 @@ def _module_add(src, *args):
f"You need write permission in the directory {pkgdir} to " f"You need write permission in the directory {pkgdir} to "
f"change the active TIRL installation.") f"change the active TIRL installation.")
# Copy the module source to the requested package directory # Copy the module source to the requested package directory
src = str(src) + ".py" if not str(src).lower().endswith(".py") else str(src)
shutil.copy2(src, os.path.join(pkgdir, os.path.basename(src))) shutil.copy2(src, os.path.join(pkgdir, os.path.basename(src)))
...@@ -152,15 +162,23 @@ def _module_edit(src, *args): ...@@ -152,15 +162,23 @@ def _module_edit(src, *args):
module path with respect to the main TIRL package. module path with respect to the main TIRL package.
""" """
import os
import tirl import tirl
import subprocess import subprocess
if str(src).lower().endswith(".py"):
src = str(src)[:-3]
if "." not in src:
src = "usermodules." + src
module_file = tirl.home(*src.split(".")) + ".py" module_file = tirl.home(*src.split(".")) + ".py"
if not os.path.isfile(module_file):
raise FileNotFoundError(f"Module file does not exist: {module_file}")
editor = "vim" if not args else args[0] editor = "vim" if not args else args[0]
confirm = f"Do you wish to EDIT {module_file}? [y/n] ".lower() confirm = f"Do you wish to EDIT {module_file}? [y/n] ".lower()
while True: while True:
ans = input(confirm) ans = input(confirm)
if ans in ("y", "yes", "ye"): if ans in ("y", "yes", "ye"):
subprocess.run([editor, module_file]) subprocess.run([editor, module_file])
return
elif ans in ("n", "no"): elif ans in ("n", "no"):
break break
else: else:
...@@ -175,6 +193,8 @@ def _module_remove(src): ...@@ -175,6 +193,8 @@ def _module_remove(src):
""" """
import os import os
import tirl import tirl
if str(src).endswith(".py"):
src = str(src)[:-3]
target = tirl.home("usermodules", *src.split(".")) target = tirl.home("usermodules", *src.split("."))
if not os.path.isdir(target): if not os.path.isdir(target):
target += ".py" target += ".py"
...@@ -186,6 +206,7 @@ def _module_remove(src): ...@@ -186,6 +206,7 @@ def _module_remove(src):
if ans in ("y", "yes", "ye"): if ans in ("y", "yes", "ye"):
try: try:
os.remove(target) os.remove(target)
return
except: except:
raise PermissionError( raise PermissionError(
f"You need elevated permissions to remove {target}.") f"You need elevated permissions to remove {target}.")
......
...@@ -420,6 +420,30 @@ def load(fname): ...@@ -420,6 +420,30 @@ def load(fname):
return object_dump return object_dump
def header(fname):
""" Returns the header of a TIRLFile. """
with open(fname, "rb") as fp:
fp.seek(len(MAGIC) + 2 * UINT8.nbytes)
hdrsize = bytes2int(fp.read(UINT64.nbytes), UINT64)
hdr = fp.read(hdrsize)
return json.loads(hdr.decode())
def info(fname):
"""
Prints the formatted file header without parsing the binary part of the
file.
"""
from pygments import highlight, lexers, formatters
hdr = header(fname)
formatted_json = json.dumps(hdr, indent=4)
colorful_json = highlight(formatted_json.encode("UTF-8"),
lexers.JsonLexer(),
formatters.TerminalFormatter())
print(colorful_json)
def encode(node): def encode(node):
""" """
Recursive function that traverses an object dump and replaces Recursive function that traverses an object dump and replaces
......
#!/usr/bin/env python #!/usr/bin/env fslpython
# -*- coding: utf-8 -*-
import tirl # _______ _____ _____ _
# |__ __| |_ _| | __ \ | |
# | | | | | |__) | | |
# | | | | | _ / | |
# | | _| |_ | | \ \ | |____
# |_| |_____| |_| \_\ |______|
#
# Copyright (C) 2018-2020 University of Oxford
# Part of the FMRIB Software Library (FSL)
# Author: Istvan N. Huszar
# Date: 23 June 2020
# SHBASECOPYRIGHT
__version__ = "2020.6.0"
# DEPENDENCIES
import sys
import argparse
import numpy as np import numpy as np
from mayavi import mlab from mayavi import mlab
from scipy.spatial import Delaunay from scipy.spatial import Delaunay
# TIRL IMPORTS
import tirl
# IMPLEMENTATION
def sliceview():
"""
Main program code.
"""
def surfvis(): def surfvis():
images = [ images = [
...@@ -43,6 +81,28 @@ def volslicer(fig): ...@@ -43,6 +81,28 @@ def volslicer(fig):
mlab.show() mlab.show()
def create_cli():
"""
Creates command-line interface.
"""
usage = """./sliceview --slice """
parser = argparse.ArgumentParser(
prog="sliceview", usage=usage, description=descr)
def main(*args):
""" Main program code. """
parser = create_cli()
if args:
sliceview(parser.parse_args(args))
else:
parser.print_help()
if __name__ == "__main__": if __name__ == "__main__":
fig = surfvis() main(*sys.argv)
volslicer(fig)
...@@ -16,6 +16,7 @@ dependencies: ...@@ -16,6 +16,7 @@ dependencies:
- imagecodecs-lite