예제 #1
0
    def get_parent_object_mask_image(self, parent_object_id):
        '''Extracts the bounding box for a given parent object from
        :attr:`parent_label_image <jtlib.features.PointPattern.parent_label_image>`.

        Returns
        -------
        numpy.ndarray[bool]
            mask image for given object
        '''
        bbox = self._parent_bboxes[parent_object_id]
        img = utils.extract_bbox(self.parent_label_image, bbox=bbox, pad=1)
        return img == parent_object_id
예제 #2
0
    def get_object_mask_image(self, object_id):
        '''Extracts the bounding box for a given object from
        :attr:`label_image <jtlib.features.Features.label_image>`.

        Returns
        -------
        numpy.ndarray[bool]
            mask image for given object
        '''
        bbox = self._bboxes[object_id]
        img = utils.extract_bbox(self.label_image, bbox=bbox, pad=1)
        return img == object_id
예제 #3
0
    def get_object_intensity_image(self, object_id):
        '''Extracts the bounding box for a given object from
        :attr:`intensity_image <jtlib.features.Features.intensity_image>`.

        Returns
        -------
        numpy.ndarray[numpy.uint16 or numpy.uint8]
            intensity image for given object; the size of the image is
            determined by the bounding box of the object
        '''
        if self.intensity_image is None:
            raise ValueError('No intensity image available.')
        bbox = self._bboxes[object_id]
        return utils.extract_bbox(self.intensity_image, bbox=bbox, pad=1)
예제 #4
0
    def get_points_object_label_image(self, parent_object_id):
        '''Extracts the bounding box for a given parent object from
        :attr:`label_image <jtlib.features.PointPattern.label_image>`.

        Parameters
        ----------
        parant_object_id: int
            ID of object in
            :attr:`parent_label_image <jtlib.features.PointPattern.parent_label_image>`

        Returns
        -------
        numpy.ndarray[numpy.int32]
            label image for all objects falling within the bounding box of the
            given parent object

        '''
        bbox = self._parent_bboxes[parent_object_id]
        parent_img = self.get_parent_object_mask_image(parent_object_id)
        img = utils.extract_bbox(self.label_image, bbox=bbox, pad=1)
        img[~parent_img] = 0
        return img
예제 #5
0
def separate_clumped_objects(clumps_image, min_cut_area, min_area, max_area,
        max_circularity, max_convexity):
    '''Separates objects in `clumps_image` based on morphological criteria.

    Parameters
    ----------
    clumps_image: numpy.ndarray[Union[numpy.int32, numpy.bool]]
        objects that should be separated
    min_cut_area: int
        minimal area an object must have (prevents cuts that would result
        in too small objects)
    min_area: int
        minimal area an object must have to be considered a clump
    max_area: int
        maximal area an object can have to be considered a clump
    max_circularity: float
        maximal circularity an object must have to be considerd a clump
    max_convexity: float
        maximal convexity an object must have to be considerd a clump

    Returns
    -------
    numpy.ndarray[numpy.uint32]
        separated objects

    See also
    --------
    :class:`jtlib.features.Morphology`
    '''

    logger.info('separate clumped objects')
    label_image, n_objects = mh.label(clumps_image)
    if n_objects == 0:
        logger.debug('no objects')
        return label_image

    pad = 1
    cutting_pass = 1
    separated_image = label_image.copy()
    while True:
        logger.info('cutting pass #%d', cutting_pass)
        cutting_pass += 1
        label_image = mh.label(label_image > 0)[0]

        f = Morphology(label_image)
        values = f.extract()
        index = (
            (min_area < values['Morphology_Area']) &
            (values['Morphology_Area'] <= max_area) &
            (values['Morphology_Convexity'] <= max_convexity) &
            (values['Morphology_Circularity'] <= max_circularity)
        )
        clumped_ids = values[index].index.values
        not_clumped_ids = values[~index].index.values

        if len(clumped_ids) == 0:
            logger.debug('no more clumped objects')
            break

        mh.labeled.remove_regions(label_image, not_clumped_ids, inplace=True)
        mh.labeled.relabel(label_image, inplace=True)
        bboxes = mh.labeled.bbox(label_image)
        for oid in np.unique(label_image[label_image > 0]):
            bbox = bboxes[oid]
            logger.debug('process clumped object #%d', oid)
            obj_image = extract_bbox(label_image, bboxes[oid], pad=pad)
            obj_image = obj_image == oid

            # Rescale distance intensities to make them independent of clump size
            dist = mh.stretch(mh.distance(obj_image))

            # Find peaks that can be used as seeds for the watershed transform
            thresh = mh.otsu(dist)
            peaks = dist > thresh
            n = mh.label(peaks)[1]
            if n == 1:
                logger.debug(
                    'only one peak detected - perform iterative erosion'
                )
                # Iteratively shrink the peaks until we have two peaks that we
                # can use to separate the clump.
                while True:
                    tmp = mh.morph.open(mh.morph.erode(peaks))
                    n = mh.label(tmp)[1]
                    if n == 2 or n == 0:
                        if n == 2:
                            peaks = tmp
                        break
                    peaks = tmp

            # Select the two biggest peaks, since we want only two objects.
            peaks = mh.label(peaks)[0]
            sizes = mh.labeled.labeled_size(peaks)
            index = np.argsort(sizes)[::-1][1:3]
            for label in np.unique(peaks):
                if label not in index:
                    peaks[peaks == label] = 0
            peaks = mh.labeled.relabel(peaks)[0]
            regions = mh.cwatershed(np.invert(dist), peaks)

            # Use the line separating watershed regions to make the cut
            se = np.ones((3,3), np.bool)
            line = mh.labeled.borders(regions, Bc=se)
            line[~obj_image] = 0
            line = mh.morph.dilate(line)

            # Ensure that cut is reasonable given user-defined criteria
            test_cut_image = obj_image.copy()
            test_cut_image[line] = False
            subobjects, n_subobjects = mh.label(test_cut_image)
            sizes = mh.labeled.labeled_size(subobjects)
            smaller_object_area = np.min(sizes)
            smaller_id = np.where(sizes == smaller_object_area)[0][0]
            smaller_object = subobjects == smaller_id

            do_cut = (
                (smaller_object_area > min_cut_area) &
                (np.sum(line) > 0)
            )
            if do_cut:
                logger.debug('cut object #%d', oid)
                y, x = np.where(line)
                y_offset, x_offset = bboxes[oid][[0, 2]] - pad - 1
                y += y_offset
                x += x_offset
                label_image[y, x] = 0
                separated_image[y, x] = 0
            else:
                logger.debug('don\'t cut object #%d', oid)
                mh.labeled.remove_regions(label_image, oid, inplace=True)

    return mh.label(separated_image)[0]
예제 #6
0
def separate_clumped_objects(clumps_image, min_cut_area, min_area, max_area,
        max_circularity, max_convexity):
    '''Separates objects in `clumps_image` based on morphological criteria.

    Parameters
    ----------
    clumps_image: numpy.ndarray[Union[numpy.int32, numpy.bool]]
        objects that should be separated
    min_cut_area: int
        minimal area an object must have (prevents cuts that would result
        in too small objects)
    min_area: int
        minimal area an object must have to be considered a clump
    max_area: int
        maximal area an object can have to be considered a clump
    max_circularity: float
        maximal circularity an object must have to be considerd a clump
    max_convexity: float
        maximal convexity an object must have to be considerd a clump

    Returns
    -------
    numpy.ndarray[numpy.uint32]
        separated objects

    See also
    --------
    :class:`jtlib.features.Morphology`
    '''

    logger.info('separate clumped objects')
    label_image, n_objects = mh.label(clumps_image)
    if n_objects == 0:
        logger.debug('no objects')
        return label_image

    pad = 1
    cutting_pass = 1
    separated_image = label_image.copy()
    while True:
        logger.info('cutting pass #%d', cutting_pass)
        cutting_pass += 1
        label_image = mh.label(label_image > 0)[0]

        f = Morphology(label_image)
        values = f.extract()
        index = (
            (min_area < values['Morphology_Area']) &
            (values['Morphology_Area'] <= max_area) &
            (values['Morphology_Convexity'] <= max_convexity) &
            (values['Morphology_Circularity'] <= max_circularity)
        )
        clumped_ids = values[index].index.values
        not_clumped_ids = values[~index].index.values

        if len(clumped_ids) == 0:
            logger.debug('no more clumped objects')
            break

        mh.labeled.remove_regions(label_image, not_clumped_ids, inplace=True)
        mh.labeled.relabel(label_image, inplace=True)
        bboxes = mh.labeled.bbox(label_image)
        for oid in np.unique(label_image[label_image > 0]):
            bbox = bboxes[oid]
            logger.debug('process clumped object #%d', oid)
            obj_image = extract_bbox(label_image, bboxes[oid], pad=pad)
            obj_image = obj_image == oid

            # Rescale distance intensities to make them independent of clump size
            dist = mh.stretch(mh.distance(obj_image))

            # Find peaks that can be used as seeds for the watershed transform
            thresh = mh.otsu(dist)
            peaks = dist > thresh
            n = mh.label(peaks)[1]
            if n == 1:
                logger.debug(
                    'only one peak detected - perform iterative erosion'
                )
                # Iteratively shrink the peaks until we have two peaks that we
                # can use to separate the clump.
                while True:
                    tmp = mh.morph.open(mh.morph.erode(peaks))
                    n = mh.label(tmp)[1]
                    if n == 2 or n == 0:
                        if n == 2:
                            peaks = tmp
                        break
                    peaks = tmp

            # Select the two biggest peaks, since we want only two objects.
            peaks = mh.label(peaks)[0]
            sizes = mh.labeled.labeled_size(peaks)
            index = np.argsort(sizes)[::-1][1:3]
            for label in np.unique(peaks):
                if label not in index:
                    peaks[peaks == label] = 0
            peaks = mh.labeled.relabel(peaks)[0]
            regions = mh.cwatershed(np.invert(dist), peaks)

            # Use the line separating watershed regions to make the cut
            se = np.ones((3,3), np.bool)
            line = mh.labeled.borders(regions, Bc=se)
            line[~obj_image] = 0
            line = mh.morph.dilate(line)

            # Ensure that cut is reasonable given user-defined criteria
            test_cut_image = obj_image.copy()
            test_cut_image[line] = False
            subobjects, n_subobjects = mh.label(test_cut_image)
            sizes = mh.labeled.labeled_size(subobjects)
            smaller_object_area = np.min(sizes)
            smaller_id = np.where(sizes == smaller_object_area)[0][0]
            smaller_object = subobjects == smaller_id

            do_cut = (
                (smaller_object_area > min_cut_area) &
                (np.sum(line) > 0)
            )
            if do_cut:
                logger.debug('cut object #%d', oid)
                y, x = np.where(line)
                y_offset, x_offset = bboxes[oid][[0, 2]] - pad - 1
                y += y_offset
                x += x_offset
                label_image[y, x] = 0
                separated_image[y, x] = 0
            else:
                logger.debug('don\'t cut object #%d', oid)
                mh.labeled.remove_regions(label_image, oid, inplace=True)

    return mh.label(separated_image)[0]
예제 #7
0
def separate_clumped_objects(clumps_image,
                             min_cut_area,
                             min_area,
                             max_area,
                             max_circularity,
                             max_convexity,
                             allow_trimming=True):
    '''Separates objects in `clumps_image` based on morphological criteria.
    
    Parameters
    ----------
    clumps_image: numpy.ndarray[Union[numpy.int32, numpy.bool]]
        objects that should be separated
    min_cut_area: int
        minimal area an object must have (prevents cuts that would result
        in too small objects)
    min_area: int
        minimal area an object must have to be considered a clump
    max_area: int
        maximal area an object can have to be considered a clump
    max_circularity: float
        maximal circularity an object must have to be considerd a clump
    max_convexity: float
        maximal convexity an object must have to be considerd a clump
    allow_trimming: boolean
        Some cuts may create a tiny third object. If this boolean is true,
        tertiary objects < trimming_threshold (10) pixels will be removed
        
    Returns
    -------
    numpy.ndarray[numpy.uint32]
        separated objects
    See also
    --------
    :class:`jtlib.features.Morphology`
    '''

    logger.info('separate clumped objects')
    trimming_threshold = 10

    label_image, n_objects = mh.label(clumps_image, NEIGHBORHOOD8)
    if n_objects == 0:
        logger.debug('no objects')
        return label_image

    pad = 1
    cutting_pass = 1
    separated_image = label_image.copy()
    while True:
        logger.info('cutting pass #%d', cutting_pass)
        cutting_pass += 1
        label_image = mh.label(label_image > 0, NEIGHBORHOOD8)[0]

        f = Morphology(label_image)
        values = f.extract()
        index = ((min_area < values['Morphology_Area']) &
                 (values['Morphology_Area'] <= max_area) &
                 (values['Morphology_Convexity'] <= max_convexity) &
                 (values['Morphology_Circularity'] <= max_circularity))
        clumped_ids = values[index].index.values
        not_clumped_ids = values[~index].index.values

        if len(clumped_ids) == 0:
            logger.debug('no more clumped objects')
            break

        mh.labeled.remove_regions(label_image, not_clumped_ids, inplace=True)
        mh.labeled.relabel(label_image, inplace=True)
        bboxes = mh.labeled.bbox(label_image)
        for oid in np.unique(label_image[label_image > 0]):
            bbox = bboxes[oid]
            logger.debug('process clumped object #%d', oid)
            obj_image = extract_bbox(label_image, bboxes[oid], pad=pad)
            obj_image = obj_image == oid

            # Rescale distance intensities to make them independent of clump size
            dist = mh.stretch(mh.distance(obj_image))

            # Find peaks that can be used as seeds for the watershed transform
            thresh = mh.otsu(dist)
            peaks = dist > thresh
            n = mh.label(peaks)[1]
            if n == 1:
                logger.debug(
                    'only one peak detected - perform iterative erosion')
                # Iteratively shrink the peaks until we have two peaks that we
                # can use to separate the clump.
                while True:
                    tmp = mh.morph.open(mh.morph.erode(peaks))
                    n = mh.label(tmp)[1]
                    if n == 2 or n == 0:
                        if n == 2:
                            peaks = tmp
                        break
                    peaks = tmp

            # Select the two biggest peaks, since we want only two objects.
            peaks = mh.label(peaks)[0]
            sizes = mh.labeled.labeled_size(peaks)
            index = np.argsort(sizes)[::-1][1:3]
            for label in np.unique(peaks):
                if label not in index:
                    peaks[peaks == label] = 0
            peaks = mh.labeled.relabel(peaks)[0]
            regions = mh.cwatershed(np.invert(dist), peaks)

            # Use the line separating watershed regions to make the cut
            line = mh.labeled.borders(regions, NEIGHBORHOOD8)
            line[~obj_image] = 0

            # Ensure that cut is reasonable given user-defined criteria
            test_cut_image = obj_image.copy()
            test_cut_image[line] = False
            subobjects, n_subobjects = mh.label(test_cut_image, NEIGHBORHOOD8)
            sizes = mh.labeled.labeled_size(subobjects)
            smaller_object_area = np.min(sizes)

            # Deal with an edge-case: If trimming is active & there are more
            # than 2 objects created by the cut, check if they are very small.
            # If so, remove them.
            if allow_trimming and n_subobjects > 2 and smaller_object_area < trimming_threshold:
                tiny_objects = np.nonzero(
                    sizes < trimming_threshold)[0].tolist()
                # Remove objects by adding them to the cutting line
                for trim_obj in tiny_objects:
                    line[subobjects == trim_obj] = True
                    logger.debug('Trimming an object of size: {}'.format(
                        sizes[trim_obj]))

                # Redo calculation if split should be applied
                test_cut_image = obj_image.copy()
                test_cut_image[line] = False
                subobjects, n_subobjects = mh.label(test_cut_image,
                                                    NEIGHBORHOOD8)
                sizes = mh.labeled.labeled_size(subobjects)
                smaller_object_area = np.min(sizes)

            logger.debug('Number of objects: {}'.format(n_subobjects))

            do_cut = ((smaller_object_area > min_cut_area) &
                      (np.sum(line) > 0))
            if do_cut:
                logger.debug('cut object #%d', oid)
                y, x = np.where(line)
                y_offset, x_offset = bboxes[oid][[0, 2]] - pad
                y += y_offset
                x += x_offset
                label_image[y, x] = 0
                separated_image[y, x] = 0
            else:
                logger.debug('don\'t cut object #%d', oid)
                mh.labeled.remove_regions(label_image, oid, inplace=True)

    return mh.label(separated_image, NEIGHBORHOOD8)[0]