示例#1
0
def test_slicing():
    """Test the `slice_depth` method."""

    column = CoreColumn(img1, top=1.0, base=2.0)
    height = column.height

    # Superset should have no effect
    superset_column = column.slice_depth(top=0.0, base=3.0)
    assert superset_column == column

    # These should reduce data
    slice1 = column.slice_depth(base=1.5)
    assert slice1.height < column.height

    slice2 = column.slice_depth(top=1.5)
    assert slice2.height < column.height

    # These should not be allowed
    with pytest.raises(AssertionError):
        _ = column.slice_depth(top=1.75, base=1.25)

    with pytest.raises(AssertionError):
        _ = column.slice_depth(top=2.5)

    with pytest.raises(AssertionError):
        _ = column.slice_depth(base=0.5)
示例#2
0
def test_image_only_save_load():
    """Test numpy image-only save."""

    save_column = CoreColumn(img1, top=1.0, base=2.0)

    with tempfile.TemporaryDirectory() as TEMP_PATH:

        save_column.save(TEMP_PATH,
                         name='testcol',
                         pickle=False,
                         image=True,
                         depths=False)

        load_column = CoreColumn.load(TEMP_PATH, 'testcol', top=1.0, base=2.0)

    assert load_column == save_column, 'Loaded should match saved.'
示例#3
0
def test_addition():
    """Test various column combination possibilities."""

    column1 = CoreColumn(img1, top=1.0, base=2.0)
    column2 = CoreColumn(img2, top=2.0, base=3.0)
    column3 = CoreColumn(img3, top=3.0, base=4.0)

    # Adjacent images -> should not fill between
    one_plus_two = column1 + column2
    assert one_plus_two.height == (height1 + height2)

    # Gap is bigger than default `add_tol`
    with pytest.raises(UserWarning):
        _ = column1 + column3

    # Should only be able to add in depth order
    with pytest.raises(UserWarning):
        _ = column2 + column1

    # Change `add_tol`
    column1.add_tol = 1.0
    column1.add_mode = "collapse"
    one_plus_three = column1 + column3
    assert one_plus_three.height == (height1 +
                                     height3), "collapse == naive vstack"

    # Make sure 'fill' ends up filling
    column1.add_mode = "fill"
    one_plus_three = column1 + column3
    assert one_plus_three.height > (
        height1 + height3), "`fill` should have filled something"
示例#4
0
def test_construction():
    """Try various construction arguments that should fail or succeed."""

    # 1D array should fail
    with pytest.raises(ValueError):
        _ = CoreColumn(np.random.random(100), top=1.0, base=2.0)

    # 4D array should fail
    with pytest.raises(ValueError):
        _ = CoreColumn(np.expand_dims(img1, -1), top=1.0, base=2.0)

    # No depth info should fail
    with pytest.raises(AssertionError):
        _ = CoreColumn(img1)

    # Just depths should be fine
    _ = CoreColumn(img1, depths=np.linspace(1.0, 2.0, num=img1.shape[0]))

    # Depth and image size mismatch should fail
    with pytest.raises(AssertionError):
        _ = CoreColumn(img1, depths=np.linspace(1.0, 2.0, num=100))

    # Grayscale should be allowed
    gray_column = CoreColumn(color.rgb2gray(img1), top=1.0, base=2.0)
    assert gray_column.channels == 1, "Grayscale image should have 1 channel"
示例#5
0
    def segment(
        self,
        img,
        depth_range,
        add_tol=None,
        add_mode="fill",
        layout_params={},
        show=False,
        colors=None,
    ):
        """Detect and segment core columns in `img`, return single aggregated `CoreColumn` instance.

        Parameters
        ----------
        img : str or array
            Filename or RGB image array to segment.
        depth_range : list(float)
            Top and bottom depths of set of columns in image.
        add_tol : float, optional
            Tolerance for adding discontinuous `CoreColumn`s.
            Default=None results in tolerance ~ image resolution.
        add_mode : one of {'fill', 'collapse'}, optional
            Add mode for generated `CoreColumn` instances (see `CoreColumn` docs)
        layout_params : dict, optional
            Any layout parameters to override.
        show : boolean, optional
            Set to True to show image with predictions overlayed.
        colors : list, optional
            A list of RGBA tuples, one for each in `class_names` (excluding 'BG').
            Values should be in range [0.0, 1.0], If None, uses random colors.
            Has no effect unless `show=True`.

        Returns
        -------
        img_col : CoreColumn
            Single aggregated ``CoreColumn`` instance
        """
        # Note: assignment calls setter to update, checks validity
        self.layout_params = layout_params

        # Is `depth_range` sane?
        if min(depth_range) == 0.0 or depth_range[1] - depth_range[0] == 0.0:
            raise UserWarning(
                f"`depth_range` {depth_range} starts at 0.0 or has no extent")

        # If `img` points to a file, read it. Otherwise assumed to be valid image array.
        if isinstance(img, (str, Path)):
            print(f"Reading file: {img}")
            img = io.imread(img)

        # Set up expected number of columns and their top/base depths
        col_tops, col_bases = self.expected_tops_bases(
            depth_range, self.layout_params["col_height"])
        num_expected = len(col_tops)

        # Get MRCNN column predictions
        preds = self.model.detect([img], verbose=0)[0]
        if show:
            if colors is not None:
                assert len(colors) == (
                    len(self.class_names) -
                    1), "Number of `colors` must match number of classes"
                colors = [colors[i - 1] for i in preds["class_ids"]]
            viz.show_preds(img, preds, self.class_names, colors=colors)

        # Select masks for column class
        col_masks = preds["masks"][:, :,
                                   preds["class_ids"] == self.column_class_id]

        # Check that number of columns matches expectation
        num_cols = col_masks.shape[-1]
        if num_cols != num_expected:
            raise UserWarning(
                f"Number of detected columns {num_cols} does not match \
                             expectation of {num_expected}")

        # Convert 3D binary masks to 2D integer labels array
        col_labels = utils.masks_to_labels(col_masks)

        # Get sorted `skimage` regions for column masks
        col_regions = utils.sort_regions(measure.regionprops(col_labels),
                                         self.layout_params["order"])

        # Figure out crop endpoints, set related args
        crop_axis = 0 if self.layout_params["orientation"] == "l2r" else 1

        # Set up `endpts` for bbox adjustment
        if self.endpts_is_auto:
            if self.layout_params["endpts"] == "auto":
                regions = col_regions
            else:
                # 'auto_all' mode
                regions = measure.regionprops(
                    utils.masks_to_labels(preds["masks"]))

            endpts = utils.maximum_extent(regions, crop_axis)

        elif self.endpts_is_class:
            measure_idxs = np.where(
                preds["class_ids"] == self.endpts_class_id)[0]

            # If object not detected, then ignore for cropping
            if measure_idxs.size == 0:
                print(
                    "`endpts` class not detected, cropping will use `auto` method"
                )
                regions = col_regions

            # Otherwise, use bbox of instance with highest confidence score
            else:
                best_idx = measure_idxs[np.argmax(
                    preds["scores"][measure_idxs])]
                regions = measure.regionprops(
                    (preds["masks"][:, :, best_idx]).astype(np.int))

            endpts = utils.maximum_extent(regions, crop_axis)

        elif self.endpts_is_coords:
            endpts = self.layout_params["endpts"]

        else:
            raise RuntimeError()

        # Set single argument lambda functions to apply to column regions / region images
        crop_fn = lambda region: utils.crop_region(
            img, col_labels, region, axis=crop_axis, endpts=endpts)
        transform_fn = lambda region: utils.rotate_vertical(
            region, self.layout_params["orientation"])

        # Apply cropping and rotation to column regions
        crops = [transform_fn(crop_fn(region)) for region in col_regions]

        # Assemble `CoreColumn` objects from masked/cropped image regions
        cols = [
            CoreColumn(crop, top=t, base=b, add_tol=add_tol, add_mode=add_mode)
            for crop, t, b in zip(crops, col_tops, col_bases)
        ]

        # Slice the bottom depth if necessary
        cols[-1] = cols[-1].slice_depth(base=depth_range[1])

        # Return the concatenation of all column objects
        return reduce(add, cols)