"""Class for processing individual frames"""
import copy
import numpy as np
import scipy
from .baseclass import ImageSeries
[docs]class ProcessedImageSeries(ImageSeries):
"""imsageseries based on existing one with image processing options
Parameters
----------
imser: ImageSeries
an existing imageseries
oplist: list
list of processing operations; each option is a (key, data) pair,
with key specifying the operation to perform using specified data
frame_list: list of ints or None, default = None
specify subset of frames by list; if None, then all frames are used
"""
FLIP = 'flip'
DARK = 'dark'
RECT = 'rectangle'
ADD = 'add'
GAUSS_LAPLACE = 'gauss_laplace'
def __init__(self, imser, oplist, **kwargs):
self._imser = imser
self._meta = copy.deepcopy(imser.metadata)
self._oplist = oplist
self._frames = kwargs.pop('frame_list', None)
self._hasframelist = (self._frames is not None)
if self._hasframelist:
self._update_omega()
self._opdict = {}
self.addop(self.DARK, self._subtract_dark)
self.addop(self.FLIP, self._flip)
self.addop(self.RECT, self._rectangle)
self.addop(self.ADD, self._add)
self.addop(self.GAUSS_LAPLACE, self._gauss_laplace)
def __getitem__(self, key):
return self._process_frame(self._get_index(key))
def _get_index(self, key):
return self._frames[key] if self._hasframelist else key
def __len__(self):
return len(self._frames) if self._hasframelist else len(self._imser)
def __iter__(self):
return (self[i] for i in range(len(self)))
def _process_frame(self, key):
# note: key refers to original imageseries
img = np.copy(self._imser[key])
for k, d in self.oplist:
func = self._opdict[k]
img = func(img, d)
return img
def _subtract_dark(self, img, dark):
# need to check for values below zero
# !!! careful, truncation going on here;necessary to promote dtype?
# This has been performance tested with the following:
# 1. return np.where(img > dark, img - dark, 0)
# 2. return np.clip(img - dark, a_min=0, a_max=None)
# 3. return (img - dark).clip(min=0)
# 4. ret = img - dark
# ret[ret < 0] = 0
# return ret
# Method 1 was the slowest, and method 4 was the fastest, perhaps
# because it creates fewer copies of the data.
ret = img - dark
ret[ret < 0] = 0
return ret
def _rectangle(self, img, r):
# restrict to rectangle
return img[r[0][0]:r[0][1], r[1][0]:r[1][1]]
def _flip(self, img, flip):
if flip in ('y', 'v'): # about y-axis (vertical)
pimg = img[:, ::-1]
elif flip in ('x', 'h'): # about x-axis (horizontal)
pimg = img[::-1, :]
elif flip in ('vh', 'hv', 'r180'): # 180 degree rotation
pimg = img[::-1, ::-1]
elif flip in ('t', 'T'): # transpose (possible shape change)
pimg = img.T
elif flip in ('ccw90', 'r90'): # rotate 90 (possible shape change)
pimg = img.T[::-1, :]
elif flip in ('cw90', 'r270'): # rotate 270 (possible shape change)
pimg = img.T[:, ::-1]
else:
pimg = img
return pimg
def _add(self, img, addend):
# To avoid overflow/underflow, always convert to float
if not np.issubdtype(img.dtype, np.floating):
img = img.astype(np.float32)
return img + addend
def _gauss_laplace(self, img, sigma):
return scipy.ndimage.gaussian_laplace(img, sigma)
def _update_omega(self):
"""Update omega if there is a framelist"""
if "omega" in self.metadata:
omega = self.metadata["omega"]
self.metadata["omega"] = omega[self._frames]
#
# ==================== API
#
@property
def dtype(self):
return self[0].dtype
@property
def shape(self):
return self[0].shape
@property
def metadata(self):
# this is a modifiable copy of metadata of the original imageseries
return self._meta
[docs] def addop(self, key, func):
"""Add operation to processing options
*key* - string to use to specify this op
*func* - function to call for this op: f(data)
"""
self._opdict[key] = func
@property
def oplist(self):
"""list of operations to apply"""
return self._oplist