from abc import ABC, abstractmethod
import numpy as np
[docs]
class Kernel:
validShapes = ['Circle', 'Square', 'Diamond']
class _Kernel(ABC):
def __init__(self, shape, radius):
if radius < 1:
raise ValueError("Radius must be a positive integer.")
self.shape = shape
self.radius = radius
self.kernel = self.createKernel()
def setShape(self, shape):
if shape not in self.getValidShapes():
raise ValueError(f"Invalid shape: {shape}. Valid shapes are: {self.getValidShapes()}")
self.shape = shape
def getValidShapes(self):
return Kernel.validShapes
@abstractmethod
def createKernel(self):
raise Exception("Abstract method createKernel of class Kernel called!")
class _Kernel2D(_Kernel):
def __init__(self, shape, radius):
super().__init__(shape, radius)
self.kernel = self.createKernel()
def makeCircle(self, radius):
y, x = np.ogrid[-radius: radius + 1, -radius: radius + 1]
mask = x**2 + y**2 <= radius**2
return mask.astype(np.uint8)
def makeSquare(self, radius):
size = 2 * radius + 1
return np.ones((size, size), dtype=np.uint8)
def makeDiamond(self, radius):
size = 2 * radius + 1
mask = np.zeros((size, size), dtype=np.uint8)
for i in range(size):
for j in range(size):
if abs(i - radius) + abs(j - radius) <= radius:
mask[i, j] = 1
return mask.astype(np.uint8)
def createKernel(self):
if self.shape == 'Circle':
return self.makeCircle(self.radius)
elif self.shape == 'Square':
return self.makeSquare(self.radius)
elif self.shape == 'Diamond':
return self.makeDiamond(self.radius)
else:
raise ValueError(f"Invalid shape: {self.shape}. Valid shapes are: {self.getValidShapes()}")
class _Kernel3D(_Kernel):
def __init__(self, shape, radius, anisotropy=1.0):
super().__init__(shape, radius)
self.anisotropy = anisotropy
self.kernel = self.createKernel()
def makeCircle(self, radius, anisotropy):
z_radius = int(radius * anisotropy)
y, x, z = np.ogrid[-radius: radius + 1, -radius: radius + 1, -z_radius: z_radius + 1]
mask = x**2 + y**2 + (z / anisotropy)**2 <= radius**2
return mask.astype(np.uint8)
def createKernel(self):
if self.shape == 'Circle':
return self.makeCircle(self.radius, self.anisotropy)
elif self.shape == 'Square':
return self.makeSquare(self.radius)
elif self.shape == 'Diamond':
return self.makeDiamond(self.radius)
else:
raise ValueError(f"Invalid shape: {self.shape}. Valid shapes are: {self.getValidShapes()}")
def makeSquare(self, radius):
size = 2 * radius + 1
return np.ones((size, size, size), dtype=np.uint8)
def makeDiamond(self, radius):
size = 2 * radius + 1
mask = np.zeros((size, size, size), dtype=np.uint8)
for i in range(size):
for j in range(size):
for k in range(size):
if abs(i - radius) + abs(j - radius) + abs(k - radius) <= radius:
mask[i, j, k] = 1
return mask.astype(np.uint8)
[docs]
@staticmethod
def makeKernel(shape, radius, dims, scale={}):
if 'Z' in dims:
scale_axes = {ax: scale.get(ax, 1.0) for ax in dims}
return Kernel._Kernel3D(shape, radius, scale_axes['Z']/scale_axes['X']).kernel
else:
return Kernel._Kernel2D(shape, radius).kernel
if __name__ == "__main__":
shape = 'Circle'
radius = 3
dims = ['Y', 'X']
kernel = Kernel.makeKernel(shape, radius, dims)