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")
[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"], } # compatibility for drymass.cfg files created with # drymass < 0.1.3 (API changed in qpimage 0.1.6). if bg_amp_kw["fit_profile"] == "ramp": bg_amp_kw["fit_profile"] = "tilt" if bg_pha_kw["fit_profile"] == "ramp": bg_pha_kw["fit_profile"] = "tilt" else: bg_amp_kw = None bg_pha_kw = None 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_pha_kw=bg_pha_kw, bg_pha_bin=cfg["bg"]["phase binary threshold"], 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])