import numpy as np
from ..utils import funcargparse
_default_indexing={"rc":"rcb","xy":"xyt"}
[docs]
def convert_shape_indexing(shape, src, dst, axes=(0,1)):
"""
Convert image indexing style.
`shape` is the source image shape (2-tuple), `src` and `dst` are current format and desired format.
Formats can be ``"rcb"`` (first index is row, second is column, rows count from the bottom), ``"rct"`` (same, but rows count from the top).
``"xyb"`` (first index is column, second is row, rows count from the bottom), or ``"xyt"`` (same but rows count form the top).
``"rc"`` is interpreted as ``"rct"``, ``"xy"`` as ``"xyt"``
"""
src=_default_indexing.get(src,src)
dst=_default_indexing.get(dst,dst)
funcargparse.check_parameter_range(src,"src",["rcb","rct","xyb","xyt"])
funcargparse.check_parameter_range(dst,"dst",["rcb","rct","xyb","xyt"])
if src[:2]==dst[:2]:
return shape
else:
shape=list(shape)
shape[axes[0]],shape[axes[1]]=shape[axes[1]],shape[axes[0]]
return tuple(shape)
def _flip(img, axis):
"""Flip an image along the given axis"""
try:
return np.flip(img,axis)
except AttributeError:
idx=range(img.shape[axis]-1,-1,-1)
return img.take(idx,axis=axis)
[docs]
def convert_image_indexing(img, src, dst, axes=(0,1)):
"""
Convert image indexing style.
`img` is the source image (ND numpy array with N>=2),
`src` and `dst` are current format and desired format,
`axes` specify correspondingly the row and the column axes (by default, the first two array axes).
Formats can be ``"rcb"`` (first index is row, second is column, rows count from the bottom), ``"rct"`` (same, but rows count from the top).
``"xyb"`` (first index is column, second is row, rows count from the bottom), or ``"xyt"`` (same but rows count form the top).
``"rc"`` is interpreted as ``"rct"``, ``"xy"`` as ``"xyt"``
"""
src=_default_indexing.get(src,src)
dst=_default_indexing.get(dst,dst)
funcargparse.check_parameter_range(src,"src",["rcb","rct","xyb","xyt"])
funcargparse.check_parameter_range(dst,"dst",["rcb","rct","xyb","xyt"])
if src==dst:
return img
if src[:2]==dst[:2]: # same order, different row direction
return _flip(img,axes[0]) if src[:2]=="rc" else _flip(img,axes[1])
if src[2]==dst[2]: # same row direction, different order
if src[2]=="t":
return img.swapaxes(*axes)
if src=="rcb":
return _flip(_flip(img,axes[0]).swapaxes(*axes),axes[1])
else:
return _flip(_flip(img,axes[1]).swapaxes(*axes),axes[0])
# different row direction, different order
if src=="rcb": # dst=="xyt"
return _flip(img,axes[0]).swapaxes(*axes)
if src=="rct": # dst=="xyb"
return _flip(img.swapaxes(*axes),axes[1])
if src=="xyb": # dst=="rct"
return _flip(img,axes[1]).swapaxes(*axes)
if src=="xyt": # dst=="rcb"
return _flip(img.swapaxes(*axes),axes[0])
[docs]
class ROI:
def __init__(self, imin=0, imax=None, jmin=0, jmax=None):
self.imin=imin
self.imax=imax
self.jmin=jmin
self.jmax=jmax
self._order()
def _order(self):
if self.imax is not None:
self.imin,self.imax=sorted((self.imin,self.imax))
if self.jmax is not None:
self.jmin,self.jmax=sorted((self.jmin,self.jmax))
def _get_limited(self, shape=None):
if shape is None:
if self.imax is None or self.jmax is None:
raise ValueError("one of the ROI dimensions is unconstrained")
return self.imin,self.imax,self.jmin,self.jmax
imin=max(self.imin,0)
imax=shape[0] if self.imax is None else min(self.imax,shape[0])
jmin=max(self.jmin,0)
jmax=shape[1] if self.jmax is None else min(self.jmax,shape[1])
return sorted((imin,imax))+sorted((jmin,jmax))
[docs]
def copy(self):
return ROI(self.imin,self.imax,self.jmin,self.jmax)
def __repr__(self):
return "{}{}".format(self.__class__.__name__,self.tup())
[docs]
def center(self, shape=None):
imin,imax,jmin,jmax=self._get_limited(shape)
return (imin+imax)/2, (jmin+jmax)/2
[docs]
def size(self, shape=None):
imin,imax,jmin,jmax=self._get_limited(shape)
return (imax-imin), (jmax-jmin)
[docs]
def area(self, shape=None):
size=self.size(shape)
return size[0]*size[1]
[docs]
def tup(self, shape=None):
return self._get_limited(shape)
[docs]
def ispan(self, shape=None):
return self.tup(shape)[0:2]
[docs]
def jspan(self, shape=None):
return self.tup(shape)[2:4]
[docs]
@classmethod
def from_centersize(cls, center, size, shape=None):
size=funcargparse.as_sequence(size,2)
imin,imax=int(round(center[0]-abs(size[0])/2)),int(round(center[0]+abs(size[0])/2))
jmin,jmax=int(round(center[1]-abs(size[1])/2)),int(round(center[1]+abs(size[1])/2))
res=cls(imin,imax,jmin,jmax)
if shape is not None:
res.limit(shape)
return res
[docs]
@classmethod
def intersect(cls, *args):
imin=max(r.imin for r in args)
imax=min(r.imax for r in args)
jmin=max(r.jmin for r in args)
jmax=min(r.jmax for r in args)
if imin>=imax or jmin>=jmax:
return None
return cls(imin,imax,jmin,jmax)
[docs]
def limit(self, shape):
self.imin,self.imax,self.jmin,self.jmax=self._get_limited(shape)
return self
[docs]
def get_region(image, center, size, axis=(-2,-1)):
"""
Get part of the image with the given center and size (both are tuples ``(i, j)``).
The region is automatically reduced if a part of it is outside of the image.
"""
roi=ROI.from_centersize(center,size,shape=(image.shape[axis[0]],image.shape[axis[1]]))
ispan,jspan=roi.ispan(),roi.jspan()
index=[slice(None)]*image.ndim
index[axis[0]]=slice(ispan[0],ispan[1])
index[axis[1]]=slice(jspan[0],jspan[1])
return image[tuple(index)]
[docs]
def get_region_sum(image, center, size, axis=(-2,-1)):
"""
Sum part of the image with the given center and size (both are tuples ``(i, j)``).
The region is automatically reduced if a part of it is outside of the image.
Return tuple ``(sum, area)``, where area is the actual summer region are (in pixels).
"""
roi=ROI.from_centersize(center,size,shape=(image.shape[axis[0]],image.shape[axis[1]]))
ispan,jspan=roi.ispan(),roi.jspan()
index=[slice(None)]*image.ndim
index[axis[0]]=slice(ispan[0],ispan[1])
index[axis[1]]=slice(jspan[0],jspan[1])
return np.sum(image[tuple(index)],axis=axis), roi.area()