Source code for axeap.core.roi

'''Region of Interest (ROI)

ROIs define regions of an image.
'''

import numpy as np
from .conventions import X, Y
from .item import DataItem, DataItemSet

[docs]class ROI(DataItem): '''Region of Interest superclass. Used as parent of specific ROIs. Do not instantiate directly. ''' def _selectArea(img): """ Set all areas of image not in ROI to 0. Parameters ---------- img : :obj:`numpy.ndarray` The image to process. """ raise NotImplementedError("ROI is an abstract class and should not be instantiated") # def apply(self, scan:Scan) -> Scan: # return Scan(self._selectArea(scan.getImg()).filled(0)) def __repr__(self): return "ROI()" def __hash__(self): """Hash is computed as hash of string representation of ROI. String representation must therefore be comprehensive of data encoded by ROI. """ return hash(tuple(bytes(repr(self),'ascii'))) # Hack much?
[docs]class ComboROI(ROI): """ROI composed of a combination of other ROIs. """
[docs] def __init__(self, *rois, **kwargs): """ Parameters ---------- *rois : List of :obj:`ROI`) The component ROIs to combine. """ ROI.__init__(self, **kwargs) self.comps = rois # Component ROIs
def _selectArea(self, img): invmask = None for roi in self.comps: if invmask is None: invmask = ~roi._selectArea(img).mask else: invmask = invmask + ~roi._selectArea(img).mask return np.ma.array(img, mask= ~invmask) def __repr__(self): return "ComboROI("+"".join([str(c)+',' for c in self.comps])+")"
[docs]class RectangleROI(ROI): '''Rectangular ROI '''
[docs] def __init__(self, p1, p2, **kwargs): ''' Parameters ---------- p1 : array-like Coordinates (x,y) of one corner of rectangular. p2 : array-like Coordinates (x,y) of diagonally opposite corner to `p1`. ''' ROI.__init__(self, **kwargs) self.lox, self.hix = sorted((p1[X],p2[X])) self.loy, self.hiy = sorted((p1[Y],p2[Y])) self.lox, self.loy, self.hix, self.hiy = \ (int(np.ceil(self.lox)), int(np.ceil(self.loy)), \ int(np.ceil(self.hix)), int(np.ceil(self.hiy)))
[docs] def fromHVROIs(hroi, vroi): '''Create Rectangular ROI from horizontal and vertical ROIs. Parameters ---------- hroi : :obj:`HROI` Horizontal ROI. vroi : :obj:`.VROI` Vertical ROI. Returns ------- :obj:`RectangleROI` Rectangular ROI spanning vertical and horizontal regions. ''' return RectangleROI((hroi.lo, vroi.lo), (hroi.hi, vroi.hi))
def _selectArea(self, img): # Use slices to create no-copy view # return np.ma.array(img[self.lox:self.hix+1, # self.loy:self.hiy+1], copy=False) # Let's try masks instead mask = np.ones(img.shape) mask[self.lox:self.hix+1,self.loy:self.hiy+1] = 0 return np.ma.array(img, mask=mask) def __repr__(self): return f"RectangleROI(({self.lox},{self.loy}), ({self.hix},{self.hiy}))" def __eq__(self, other): if isinstance(other, type(self)): return self.lox==other.lox and self.loy==other.loy and \ self.hix==other.hix and self.hiy==other.hiy else: return False def __hash__(self): return ROI.__hash__(self)
[docs]class EllipseROI(ROI): """Ellipse-shaped ROI"""
[docs] def __init__(self, p, rx, ry, **kwargs): """ Parameters ---------- p : array-like Coordinates (x,y) for center of ellipse. rx : :obj:`float` Radius along x-axis. ry : :obj:`float` Radius along y-axis. """ ROI.__init__(self, **kwargs) self.x, self.y, self.rx, self.ry = *p, rx, ry
def _selectArea(self, img): mask = np.ones(img.shape) for y in np.arange(np.ceil(self.y-self.ry), np.ceil(self.y+self.ry), 1): #print('y', y) halfwidth = self.rx*(1-((self.y-y)/self.ry)**2)**(1/2) lox, hix = self.x-halfwidth, self.x+halfwidth for x in np.arange(np.ceil(lox), np.ceil(hix), 1): #print('x', x) mask[int(x),int(y)] = 0 return np.ma.array(img, mask=mask) # Could use cv2.ellipse instead def __repr__(self): return f"EllipseROI(({self.x},{self.y})," \ f"{self.rx},{self.ry})"
[docs]class SpanROI(ROI): """ Abstract superclass to :obj:`HROI` and :obj:`VROI`. Do not instantiate. """
[docs] def __init__(self, p1, p2, **kwargs): """ Parameters ---------- p1 : :obj:`float` Lower end of span. p2 : :obj:`float` Higher end of span. """ ROI.__init__(self, **kwargs) self.lo, self.hi = sorted((int(np.ceil(p1)), int(np.ceil(p2))))
def __iter__(self): return iter((self.lo, self.hi))
[docs]class HROI(SpanROI): """ROI spanning range along x-axis.""" def _selectArea(self, img): mask = np.ones(img.shape) mask[self.lo:self.hi+1,:] = 0 return np.ma.array(img, mask=mask) def __repr__(self): return f"HROI(x={self.lo}:{self.hi})" def __mul__(self, other): return RectangleROI.fromHVROIs(self, other)
[docs]class VROI(SpanROI): """ROI spanning range along y-axis.""" def _selectArea(self, img): mask = np.ones(img.shape) mask[:, self.lo:self.hi+1] = 0 return np.ma.array(img, mask=mask) def __repr__(self): return f"VROI(y={self.lo}:{self.hi})" def __mul__(self, other): return RectangleROI.fromHVROIs(other, self)
[docs]class ROISet(DataItemSet): """:obj:`.core.item.DataItemSet` for :obj:`ROI`'s """
[docs] def getROIsInside(self, nroi): """Get ROIs in set contained by given ROI Parameters ---------- nroi : :obj:`ROI` ROI used to choose ROIs from set. Returns ------- :obj:`list` List of ROIs contained in nroi. """ if not isinstance(nroi, RectangleROI): return [] # TODO: Add functionality for non-rectangular ROIs inside = [roi for roi in self if \ roi.lox >= nroi.lox and roi.loy >= nroi.loy and \ roi.hix <= nroi.hix and roi.hiy <= nroi.hiy] return inside