Skip to content

Commit

Permalink
Added Region and SegmentedImage classes to jicbioimage.segment namesp…
Browse files Browse the repository at this point in the history
…ace package.
  • Loading branch information
tjelvar-olsson committed Feb 19, 2016
1 parent ea7e8c3 commit 9188b82
Show file tree
Hide file tree
Showing 5 changed files with 528 additions and 5 deletions.
183 changes: 182 additions & 1 deletion jicbioimage/segment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,195 @@
"""

import numpy as np
import scipy.ndimage as nd
import skimage.measure
import skimage.morphology

from jicbioimage.core.image import SegmentedImage
from jicbioimage.core.image import Image
from jicbioimage.core.transform import transformation
from jicbioimage.core.util.array import false_color

__version__ = "0.1.0"

class Region(np.ndarray):
"""Class representing a region of interest in an image.
The :class:`jicbioimage.core.region.Region` class is a subclass of
numpy.ndarray.
However, note that it will compress any data given to it to boolean.
>>> import numpy as np
>>> ar = np.array([-1, 0, 1, 2])
>>> Region(ar)
Region([ True, False, True, True], dtype=bool)
To select an particular element use the
:func:`jicbioimage.core.region.Region.select_from_array` class method.
>>> Region.select_from_array(ar, identifier=2)
Region([False, False, False, True], dtype=bool)
"""

def __new__(cls, input_array):
obj = np.asarray(input_array).view(cls)
return obj.astype(bool)

@classmethod
def select_from_array(cls, array, identifier):
"""Return a region from a numpy array.
:param array: :class:`numpy.ndarray`
:param identifier: value representing the region to select in the array
:returns: :class:`jicbioimage.core.region.Region`
"""

base_array = np.zeros(array.shape)
array_coords = np.where(array == identifier)
base_array[array_coords] = 1

return cls(base_array)

@property
def inner(self):
"""Region formed by taking non-border elements.
:returns: :class:`jicbioimage.core.region.Region`
"""

inner_array = nd.morphology.binary_erosion(self)
return Region(inner_array)

@property
def border(self):
"""Region formed by taking border elements.
:returns: :class:`jicbioimage.core.region.Region`
"""

border_array = self - self.inner
return Region(border_array)

@property
def convex_hull(self):
"""Region representing the convex hull.
:returns: :class:`jicbioimage.core.region.Region`
"""
hull_array = skimage.morphology.convex_hull_image(self)
return Region(hull_array)

@property
def area(self):
"""Number of non-zero elements.
:returns: int
"""
return np.count_nonzero(self)

@property
def index_arrays(self):
"""All nonzero elements as a pair of arrays."""
return np.where(self == True)

@property
def points(self):
"""Region as a list of points."""
return list(zip(*self.index_arrays))

@property
def perimeter(self):
"""Return the perimiter.
:returns: int
"""
return self.border.area

def dilate(self, iterations=1):
"""Return a dilated region.
:param iterations: number of iterations to use in dilation
:returns: :class:`jicbioimage.core.region.Region`
"""
dilated_array = nd.morphology.binary_dilation(self,
iterations=iterations)
return Region(dilated_array)


class SegmentedImage(Image):
"""Class representing the results of applying a segmentation to an image.
Each unique pixel value represents a different region of the segmentation.
0 represents background and positive integers represent the different
regions.
"""

@property
def identifiers(self):
"""Return a set of unique identifiers in the segmented image."""

return set(np.unique(self)) - set([0])

@property
def number_of_segments(self):
"""Return the number of segments present in the segmented image."""

return len(self.identifiers)

def region_by_identifier(self, identifier):
"""Return region of interest corresponding to the supplied identifier.
:param identifier: integer corresponding to the segment of interest
:returns: `jicbioimage.core.region.Region`
"""

if identifier < 0:
raise(ValueError("Identifier must be a positive integer."))

if not np.equal(np.mod(identifier, 1), 0):
raise(ValueError("Identifier must be a positive integer."))

if identifier == 0:
raise(ValueError("0 represents the background."))

return Region.select_from_array(self, identifier)

@property
def background(self):
"""Return the segmented image background.
In other words the region with pixel values 0.
:returns: `jicbioimage.core.region.Region`
"""

return Region.select_from_array(self, 0)

@property
def false_color_image(self):
"""Return segmentation as a false color image.
:returns: `jicbioimage.core.image.Image`
"""
return Image.from_array(false_color(self))

@property
def grayscale_image(self):
"""Return segmentation using raw identifiers.
:returns: `jicbioimage.core.image.Image`
"""
return Image.from_array(self)

def png(self, width=None):
"""Return png string of image.
:param width: integer specifying the desired width
:returns: png as a string
"""
return self.false_color_image.png(width)


@transformation
def connected_components(image, connectivity=2, background=None):
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def run_tests(self):
'jicbioimage.core',
'jicbioimage.transform',
'numpy',
'scipy',
'scikit-image',
]
)

0 comments on commit 9188b82

Please sign in to comment.