Source code for drymass.cli
import io
from os import fspath
import pathlib
import sys
import matplotlib.image as mpimg
import numpy as np
import qpimage
from skimage.external import tifffile
from . import config
from . import dialog
from . import plot
from ..anasphere import analyze_sphere
from ..converter import convert
from ..extractroi import extract_roi
#: Matplotlib images of sensor data with labeled ROI (TIFF)
FILE_SENSOR_WITH_ROI_IMAGE = "sensor_roi_images.tif"
#: Matplotlib images of sphere analysis (TIFF)
FILE_SPHERE_ANALYSIS_IMAGE = "sphere_{}_{}_images.tif"
[docs]def cli_analyze_sphere(path=None, ret_data=False):
"""Perform sphere analysis"""
path_in, path_out = dialog.main(path=path,
req_meta=["medium index",
"pixel size um",
"wavelength nm"])
cfg = config.ConfigFile(path_out)
h5roi = cli_extract_roi(path=path_in, ret_data=True)
print("Performing sphere analysis... ", end="", flush=True)
# canny edge detection parameters
edgekw = {
"clip_rmin": cfg["sphere"]["edge clip radius min"],
"clip_rmax": cfg["sphere"]["edge clip radius max"],
"mult_coarse": cfg["sphere"]["edge coarse"],
"mult_fine": cfg["sphere"]["edge fine"],
"maxiter": cfg["sphere"]["edge iter"],
}
# image fitting parameters
imagekw = {
"crel": cfg["sphere"]["image fit range position"],
"rrel": cfg["sphere"]["image fit range radius"],
"nrel": cfg["sphere"]["image fit range refractive index"],
"fix_pha_offset": cfg["sphere"]["image fix phase offset"],
"max_iter": cfg["sphere"]["image iter"],
"stop_dc": cfg["sphere"]["image stop delta position"],
"stop_dr": cfg["sphere"]["image stop delta radius"],
"stop_dn": cfg["sphere"]["image stop delta refractive index"],
"verbose": cfg["sphere"]["image verbosity"],
}
h5sim, changed = analyze_sphere(
h5roi=h5roi,
dir_out=path_out,
r0=cfg["specimen"]["size um"] / 2 * 1e-6,
method=cfg["sphere"]["method"],
model=cfg["sphere"]["model"],
alpha=cfg["sphere"]["refraction increment"],
rad_fact=cfg["sphere"]["radial inclusion factor"],
edgekw=edgekw,
imagekw=imagekw,
ret_changed=True,
)
print("Done.")
if changed and cfg["output"]["sphere images"]:
print("Plotting sphere images... ", end="", flush=True)
tifout = path_out / FILE_SPHERE_ANALYSIS_IMAGE.format(
cfg["sphere"]["method"],
cfg["sphere"]["model"]
)
# plot h5series and rmgr with matplotlib
with qpimage.QPSeries(h5file=h5roi, h5mode="r") as qps_roi, \
qpimage.QPSeries(h5file=h5sim, h5mode="r") as qps_sim, \
tifffile.TiffWriter(fspath(tifout), imagej=True) as tf:
for ii in range(len(qps_roi)):
qpi_real = qps_roi[ii]
qpi_sim = qps_sim[ii]
assert qpi_real["identifier"] in qpi_sim["identifier"]
imio = io.BytesIO()
plot.plot_qpi_sphere(qpi_real=qpi_real,
qpi_sim=qpi_sim,
path=imio,
simtype=cfg["sphere"]["model"])
imio.seek(0)
imdat = (mpimg.imread(imio) * 255).astype("uint8")
tf.save(imdat, compress=9)
print("Done")
if ret_data:
return h5sim
[docs]def cli_convert(path=None, ret_data=False):
"""Convert input data to QPSeries data"""
path_in, path_out = dialog.main(path=path,
req_meta=["pixel size um",
"wavelength nm"])
cfg = config.ConfigFile(path_out)
print("Converting input data... ", end="", flush=True)
meta_data = {"pixel size": cfg["meta"]["pixel size um"] * 1e-6,
"wavelength": cfg["meta"]["wavelength nm"] * 1e-9,
"medium index": cfg["meta"]["medium index"],
}
holo_kw = {"filter_name": cfg["holo"]["filter name"],
"filter_size": cfg["holo"]["filter size"],
"sideband": cfg["holo"]["sideband"],
}
bg_data_amp = parse_bg_value(cfg["bg"]["amplitude data"],
reldir=path_in.parent)
bg_data_pha = parse_bg_value(cfg["bg"]["phase data"],
reldir=path_in.parent)
h5series, ds, changed = convert(path_in=path_in,
dir_out=path_out,
meta_data=meta_data,
holo_kw=holo_kw,
bg_data_amp=bg_data_amp,
bg_data_pha=bg_data_pha,
write_tif=cfg["output"]["sensor tif data"],
ret_dataset=True,
ret_changed=True,
)
if "hologram" not in ds.storage_type:
# remove "holo" section from configuration
cfg.remove_section("holo")
if changed:
print("Done.")
else:
print("Reusing.")
if ret_data:
return h5series
[docs]def cli_extract_roi(path=None, ret_data=False):
"""Extract regions of interest"""
path_in, path_out = dialog.main(path=path)
# cli_convert will ask for the required meta data
h5series = cli_convert(path=path_in, ret_data=True)
# get the configuration after cli_convert was run
cfg = config.ConfigFile(path_out)
print("Extracting ROIs... ", end="", flush=True)
# background correction
if cfg["bg"]["enabled"]:
bg_amp_kw = {"fit_offset": cfg["bg"]["amplitude offset"],
"fit_profile": cfg["bg"]["amplitude profile"],
"border_perc": cfg["bg"]["amplitude border perc"],
"border_px": cfg["bg"]["amplitude border px"],
}
bg_pha_kw = {"fit_offset": cfg["bg"]["phase offset"],
"fit_profile": cfg["bg"]["phase profile"],
"border_perc": cfg["bg"]["phase border perc"],
"border_px": cfg["bg"]["phase border px"],
}
# Only get the Canny edge detection parameters if needed
if not (np.isnan(cfg["bg"]["phase mask sphere"])
and np.isnan(cfg["bg"]["amplitude mask sphere"])):
dialog.main(path=path, req_meta=["medium index"])
# This will generate the [sphere] section in drymass.cfg
edge_kw = {
"clip_rmin": cfg["sphere"]["edge clip radius min"],
"clip_rmax": cfg["sphere"]["edge clip radius max"],
"mult_coarse": cfg["sphere"]["edge coarse"],
"mult_fine": cfg["sphere"]["edge fine"],
"maxiter": cfg["sphere"]["edge iter"],
}
else:
edge_kw = {}
else:
bg_amp_kw = None
bg_pha_kw = None
edge_kw = {}
h5roi, rmgr, changed = extract_roi(
h5series=h5series,
dir_out=path_out,
size_m=cfg["specimen"]["size um"] * 1e-6,
size_var=cfg["roi"]["size variation"],
max_ecc=cfg["roi"]["eccentricity max"],
dist_border=cfg["roi"]["dist border px"],
pad_border=cfg["roi"]["pad border px"],
exclude_overlap=cfg["roi"]["exclude overlap px"],
ignore_data=cfg["roi"]["ignore data"],
bg_amp_kw=bg_amp_kw,
bg_amp_bin=cfg["bg"]["amplitude binary threshold"],
bg_amp_mask_radial_clearance=cfg["bg"]["amplitude mask sphere"],
bg_pha_kw=bg_pha_kw,
bg_pha_bin=cfg["bg"]["phase binary threshold"],
bg_pha_mask_radial_clearance=cfg["bg"]["phase mask sphere"],
bg_sphere_edge_kw=edge_kw,
search_enabled=cfg["roi"]["enabled"],
ret_roimgr=True,
ret_changed=True,
)
if changed:
print("Done.")
else:
print("Reusing.")
if len(rmgr) == 0:
print("No ROIs could be found!\n"
+ "Please try editing '{}':\n".format(cfg.path)
+ "- correct specimen size "
+ "({})\n".format(strpar(cfg, "specimen", "size um"))
+ "- increase allowed size variation "
+ "({})\n".format(strpar(cfg, "roi", "size variation"))
+ "- increase maximum allowed eccentricity "
+ "({})\n".format(strpar(cfg, "roi", "eccentricity max"))
+ "- reduce minimum distance to image border in pixels "
+ "({})\n".format(strpar(cfg, "roi", "dist border px"))
+ "- reduce minimum distance inbetween ROIs "
+ "({})".format(strpar(cfg, "roi", "exclude overlap px"))
)
sys.exit(1)
if changed and cfg["output"]["roi images"]:
print("Plotting detected ROIs... ", end="", flush=True)
tifout = path_out / FILE_SENSOR_WITH_ROI_IMAGE
# plot h5series and rmgr with matplotlib
with qpimage.QPSeries(h5file=h5series, h5mode="r") as qps, \
tifffile.TiffWriter(fspath(tifout), imagej=True) as tf:
for ii in range(len(qps)):
# new indexing convention in drymass 0.6.0
image_index = ii + 1
rois = rmgr.get_from_image_index(image_index)
imio = io.BytesIO()
plot.plot_qpi_phase(qps[ii],
rois=rois,
path=imio,
labels_excluded=cfg["roi"]["ignore data"])
imio.seek(0)
imdat = (mpimg.imread(imio) * 255).astype("uint8")
tf.save(imdat, compress=9)
print("Done")
if ret_data:
return h5roi
[docs]def parse_bg_value(bg, reldir):
"""Determine the background to use from the configuration key"""
if isinstance(bg, str):
# can be a file name relative to the input directory
# or an absolute path.
reldir = pathlib.Path(reldir)
path = reldir / bg
if not path.exists():
path = pathlib.Path(bg)
bg = path
return bg
[docs]def strpar(cfg, section, key):
"""String representation of a section/key combination"""
return "[{}] {} = {}".format(section, key, cfg[section][key])