Image analysis for the mTor project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

187 lines
7.4 KiB

import pathlib
from PIL import Image
from .commons import RE_DIGITS, OUTPUT_FOLDER_NAMES, TifImage
class Parameters(dict):
""" namespacing the parameters for a analysis with nice access
parmeters used for analysing the images
boost: linear factor for brightness on color coded images
charts_y_limit: common scale for the y axis in graphs
colored_dir: path to the directory to hold color coded images
cut_bottom: bottom edge for croping an image
cut_left: left edge for croping an image
cut_pad_x: pixels added to left or right edge to crop an image
cut_pad_y: pixels added to top or bottom edge to crop an image
cut_right: right edge for croping an image
cut_top: left edge for croping an image
cuts_dir: path to the directory of croped images
data_dir: path to the directory for graphs and numeric data
folder: name of the directory that holds the tif images
guard_filter_polynom: polynomal scalar for smoothing the guards histogram
guard_filter_window: window scalar for smoothing the guards histogram
guard_histogram_bins: number of bins when constructing guards histogram
guard_max_value: calculated threshold for the gurards
guard_stats: which values to use for caclulation of guards
guards_minima_min_dist: min distance between minimas in guards histogram
image_height: height of an image
image_width: width of an image
intensity_max: highest intensity found in all images
intensity_min: lowest intensity found in all images
left_guard_column: name of the most used left guard value column
left_guard_name: left guard value group name
offset: calculated the lowest intensity to scale by
outlier_upper_limit: calculated threshold value for outliers
peak_min_distance: min distance for peaks in the final results
peak_threshold: threshold peak height in the final results
right_guard_column: name of the most used left guard value column
right_guard_name: right guard value group name
roi_bottom: bottom edge of the ROI
roi_column: name for the ROI
roi_left: bottom left of the ROI
roi_name: roi value group name
roi_right: bottom right of the ROI
roi_stats: which values to use for caclulation of roi
roi_top: top edge of the ROI
rolling_min_window: size of the window for the rolling min calculation
savgol_filter_polynom: polynomal scalar for smoothing the final results
savgol_filter_window: window scalar for smoothing the final results
scale: calculated factor to boost image intensities
tif_dir: path to the directory with the original tif images
"""
def __init__(self, folder, top, right, bottom, left, **kargs):
super().__init__(self)
self.folder = folder
# defaults
self.boost = 5
self.cut_pad_x = 50
self.cut_pad_y = 25
self.guard_histogram_bins = 100
self.guard_filter_window = 7
self.guard_filter_polynom = 3
self.guards_minima_min_dist = 10
self.rolling_min_window = 5
self.savgol_filter_window = 51
self.savgol_filter_polynom = 1
self.peak_min_distance = 5
self.peak_threshold = 0.2
# labels for data items
self.roi_name = "roi"
self.roi_stats = "average"
self.roi_column = f"{self.roi_name}.{self.roi_stats}"
self.left_guard_name = "left"
self.right_guard_name = "right"
self.guard_stats = "average"
self.left_guard_column = f"{self.left_guard_name}.{self.guard_stats}"
self.right_guard_column = f"{self.right_guard_name}.{self.guard_stats}"
# arbitrary keyword arguments, updating defaults if necessary
self.update(kargs)
# create list of images
self.tif_dir = pathlib.Path(folder)
if not self.tif_dir.is_dir():
raise IOError(f"Not a directory: {self.folder}")
self.tif_list = self.scan_image_dir()
# set the names of output directories
for name in OUTPUT_FOLDER_NAMES:
sub_path = self.tif_dir / name
self[f"{name}_dir"] = sub_path
# get image dimensions
width, height = self.get_size_of_one_image()
self.image_width = width
self.image_height = height
self.set_roi_and_cut_size(top, right, bottom, left)
def __getattr__(self, key):
""" return a parameters value as instance property """
return self[key]
def __setattr__(self, key, value):
""" set a parameters value via instance property """
self[key] = value
def __getstate__(self):
# Copy the object's state from self.__dict__ which contains
# all our instance attributes. Always use the dict.copy()
# method to avoid modifying the original state.
return self.__dict__.copy()
def __setstate__(self, state):
# Restore instance attributes (i.e., filename and lineno).
self.__dict__.update(state)
def scan_image_dir(self):
""" scan a folder for tif images """
visible = (
item
for item in self.tif_dir.iterdir()
if not item.stem.startswith(".")
)
tifs = (item for item in visible if item.suffix == ".tif")
results = []
for path in tifs:
try:
match = RE_DIGITS.search(path.name).group()
except AttributeError as ex:
raise ValueError(
f"no sequence number found in {path.name}"
) from ex
results.append(TifImage(path=path, frame=int(match)))
return results
def get_size_of_one_image(self):
""" get the dimension of one Image
the dimensions should be the same on all images. this is ensured in
an other workflow.
"""
image = self.tif_list[0]
with open(image.path, "rb") as filehandle:
image = Image.open(filehandle)
size = image.size
return size
def set_roi_and_cut_size(self, top, right, bottom, left):
if right < left:
left, right = right, left
if top > bottom:
top, bottom = bottom, top
if (
top < 0
or left < 0
or bottom > self.image_height
or right > self.image_width
):
raise ValueError("ROI out of bounce")
self.roi_top = top
self.roi_bottom = bottom
self.roi_right = right
self.roi_left = left
self.cut_top = min(self.image_height, top - self.cut_pad_y)
self.cut_bottom = max(0, bottom + self.cut_pad_y)
self.cut_right = min(self.image_width, right + self.cut_pad_x)
self.cut_left = max(0, left - self.cut_pad_x)
def get_descriptions(self):
docs = type(self).__doc__
lines = (line.strip() for line in docs.split("\n"))
has_colon = (line for line in lines if ":" in line)
pairs = (line.split(":") for line in has_colon)
pairs_cleanup = ((k.strip(), v.strip()) for k, v in pairs)
non_empty = ((k, v) for k, v in pairs_cleanup if k and v)
return dict(non_empty)