Source code for drymass.cli
import io
import numbers
from os import fspath
import pathlib
import sys
import numpy as np
from PIL import Image
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 = np.array(Image.open(imio))
tf.save(imdat)
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)
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"],
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)):
rois = rmgr.get_from_image_index(ii)
imio = io.BytesIO()
plot.plot_qpi_phase(qps[ii],
rois=rois,
path=imio)
imio.seek(0)
imdat = np.array(Image.open(imio))
tf.save(imdat)
print("Done")
if ret_data:
return h5roi
[docs]def parse_bg_value(bg, reldir):
"""Determine the background to use from the configuration key"""
if bg == "none":
bg = None
elif isinstance(bg, numbers.Integral):
# indexing starts at 1
bg -= 1
elif 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])