Skip to content

Commit

Permalink
LabelImages.cropLabel now keeps spatial calibration
Browse files Browse the repository at this point in the history
  • Loading branch information
dlegland committed Aug 17, 2023
1 parent da836b9 commit de10333
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 48 deletions.
164 changes: 116 additions & 48 deletions src/main/java/inra/ijpb/label/LabelImages.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import ij.gui.ProfilePlot;
import ij.gui.Roi;
import ij.gui.ShapeRoi;
import ij.measure.Calibration;
import ij.measure.ResultsTable;
import ij.plugin.Selection;
import ij.process.ByteProcessor;
Expand All @@ -56,6 +57,8 @@
import inra.ijpb.data.image.ImageUtils;
import inra.ijpb.label.conncomp.FloodFillRegionComponentsLabeling;
import inra.ijpb.label.conncomp.FloodFillRegionComponentsLabeling3D;
import inra.ijpb.data.IntBounds2D;
import inra.ijpb.data.IntBounds3D;
import inra.ijpb.label.distmap.ChamferDistanceTransform2DFloat;
import inra.ijpb.label.distmap.ChamferDistanceTransform2DShort;
import inra.ijpb.label.distmap.ChamferDistanceTransform3DFloat;
Expand Down Expand Up @@ -333,15 +336,18 @@ public static final ImageProcessor binarize(ImageProcessor image, int label)
}

/**
* Returns a binary image that contains only the selected particle or
* region, by automatically cropping the image and eventually adding some
* borders.
*
* @param imagePlus an image containing label of particles
* @param label the label of the particle to select
* @param border the number of pixels to add to each side of the particle
* @return a smaller binary image containing only the selected particle
*/
* Returns a binary image that contains only the region identified by its
* label, by automatically cropping the image and eventually adding some
* borders.
*
* @param imagePlus
* an image containing region labels
* @param label
* the label of the particle to select
* @param border
* the number of pixels to add to each side of the particle
* @return a smaller binary image containing only the selected region
*/
public static final ImagePlus cropLabel(ImagePlus imagePlus, int label, int border)
{
String newName = imagePlus.getShortTitle() + "-crop";
Expand All @@ -350,31 +356,66 @@ public static final ImagePlus cropLabel(ImagePlus imagePlus, int label, int bord
// Compute the cropped image
if (imagePlus.getStackSize() == 1)
{
// crop label in image processor
ImageProcessor image = imagePlus.getProcessor();
ImageProcessor cropped = LabelImages.cropLabel(image, label, border);
IntBounds2D bounds = labelBounds(image, label);
ImageProcessor cropped = LabelImages.cropLabel(image, label, bounds, border);
croppedPlus = new ImagePlus(newName, cropped);

// update spatial calibration
Calibration calib = croppedPlus.getCalibration().copy();
calib.xOrigin += (bounds.getXMin() - border) * calib.pixelWidth;
calib.yOrigin += (bounds.getYMin() - border) * calib.pixelHeight;
croppedPlus.setCalibration(calib);
}
else
{
// crop label in image stack
ImageStack image = imagePlus.getStack();
ImageStack cropped = LabelImages.cropLabel(image, label, border);
IntBounds3D bounds = labelBounds(image, label);
ImageStack cropped = LabelImages.cropLabel(image, label, bounds, border);
croppedPlus = new ImagePlus(newName, cropped);

// update spatial calibration
Calibration calib = croppedPlus.getCalibration().copy();
calib.xOrigin += (bounds.getXMin() - border) * calib.pixelWidth;
calib.yOrigin += (bounds.getYMin() - border) * calib.pixelHeight;
calib.zOrigin += (bounds.getZMin() - border) * calib.pixelDepth;
croppedPlus.setCalibration(calib);
}

return croppedPlus;
}

/**
* Returns a binary image that contains only the selected particle or
* region, by automatically cropping the image and eventually adding some
* borders.
*
* @param image a, image containing label of particles
* @param label the label of the particle to select
* @param border the number of pixels to add to each side of the particle
* @return a smaller binary image containing only the selected particle
*/
* Returns a binary image that contains only the region identified by its
* label, by automatically cropping the image and eventually adding some
* borders.
*
* @param image
* an image containing label of particles
* @param label
* the label of the particle to select
* @param border
* the number of pixels to add to each side of the particle
* @return a smaller binary image containing only the selected particle
*/
public static final ImageProcessor cropLabel(ImageProcessor image, int label, int border)
{
IntBounds2D bounds = labelBounds(image, label);
return cropLabel(image, label, bounds, border);
}

/**
* Computes the bounds of a region within a label image.
*
* @param image
* the label map image containing the region.
* @param label
* the label of the region
* @return the bounds of the region
*/
public static final IntBounds2D labelBounds(ImageProcessor image, int label)
{
// image size
int sizeX = image.getWidth();
Expand All @@ -392,7 +433,7 @@ public static final ImageProcessor cropLabel(ImageProcessor image, int label, in
for (int x = 0; x < sizeX; x++)
{
// process only specified label
int val = image.get(x, y);
int val = (int) image.getf(x, y);
if (val != label)
{
continue;
Expand All @@ -405,41 +446,64 @@ public static final ImageProcessor cropLabel(ImageProcessor image, int label, in
ymax = max(ymax, y);
}
}

return new IntBounds2D(xmin, xmax, ymin, ymax);
}

private static final ImageProcessor cropLabel(ImageProcessor image, int label, IntBounds2D bounds, int border)
{
// Compute size of result, taking into account border
int sizeX2 = (xmax - xmin + 1 + 2 * border);
int sizeY2 = (ymax - ymin + 1 + 2 * border);
int sizeX2 = bounds.getWidth() + 2 * border;
int sizeY2 = bounds.getHeight() + 2 * border;

// allocate memory for result image
ImageProcessor result = new ByteProcessor(sizeX2, sizeY2);

// fill result with binary label
for (int y = ymin, y2 = border; y <= ymax; y++, y2++)
for (int y = border, yref = bounds.getYMin(); y < sizeY2 - border; y++, yref++)
{
for (int x = xmin, x2 = border; x <= xmax; x++, x2++)
for (int x = border, xref = bounds.getXMin(); x < sizeX2 - border; x++, xref++)
{
if ((image.get(x, y)) == label)
if (((int) image.getf(xref, yref)) == label)
{
result.set(x2, y2, 255);
result.set(x, y, 255);
}

}
}

return result;
}


/**
* Returns a binary image that contains only the selected particle or
* region, by automatically cropping the image and eventually adding some
* borders.
*
* @param image a 3D image containing label of particles
* @param label the label of the particle to select
* @param border the number of voxels to add to each side of the particle
* @return a smaller binary image containing only the selected particle
*/
* Returns a binary image that contains only the region identified by its
* label, by automatically cropping the image and eventually adding some
* borders.
*
* @param image
* a 3D label map containing label of regions
* @param label
* the label of the particle to select
* @param border
* the number of voxels to add to each side of the particle
* @return a smaller binary image containing only the selected particle
*/
public static final ImageStack cropLabel(ImageStack image, int label, int border)
{
IntBounds3D bounds = labelBounds(image, label);
return cropLabel(image, label, bounds, border);
}

/**
* Computes the bounds of a region within a 3D label image.
*
* @param image
* the image containing the region.
* @param label
* the label of the region
* @return the 3D bounds of the region
*/
public static final IntBounds3D labelBounds(ImageStack image, int label)
{
// image size
int sizeX = image.getWidth();
Expand Down Expand Up @@ -479,31 +543,35 @@ public static final ImageStack cropLabel(ImageStack image, int label, int border
}
}

return new IntBounds3D(xmin, xmax, ymin, ymax, zmin, zmax);
}

private static final ImageStack cropLabel(ImageStack image, int label, IntBounds3D bounds, int border)
{
// Compute size of result, taking into account border
int sizeX2 = (xmax - xmin + 1 + 2 * border);
int sizeY2 = (ymax - ymin + 1 + 2 * border);
int sizeZ2 = (zmax - zmin + 1 + 2 * border);
int sizeX2 = bounds.getWidth() + 2 * border;
int sizeY2 = bounds.getHeight() + 2 * border;
int sizeZ2 = bounds.getDepth() + 2 * border;

// allocate memory for result image
ImageStack result = ImageStack.create(sizeX2, sizeY2, sizeZ2, 8);

// fill result with binary label
for (int z = zmin, z2 = border; z <= zmax; z++, z2++)
for (int z = border, zref = bounds.getZMin(); z < sizeZ2 - border; z++, zref++)
{
for (int y = ymin, y2 = border; y <= ymax; y++, y2++)
for (int y = border, yref = bounds.getYMin(); y < sizeY2 - border; y++, yref++)
{
for (int x = xmin, x2 = border; x <= xmax; x++, x2++)
for (int x = border, xref = bounds.getXMin(); x < sizeX2 - border; x++, xref++)
{
if (((int) image.getVoxel(x, y, z)) == label)
if (((int) image.getVoxel(xref, yref, zref)) == label)
{
result.setVoxel(x2, y2, z2, 255);
result.setVoxel(x, y, z, 255);
}
}
}
}
}

return result;

}

/**
Expand Down
63 changes: 63 additions & 0 deletions src/test/java/inra/ijpb/label/LabelImagesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.measure.Calibration;
import ij.process.ByteProcessor;
import ij.process.ColorProcessor;
import ij.process.FloatProcessor;
Expand All @@ -41,6 +42,68 @@

public class LabelImagesTest
{
@Test
public final void test_cropLabel_ImagePlus2D()
{
// create a byte processor containing four labels
ImageProcessor image = new ByteProcessor(10, 10);
for (int y = 0; y < 3; y++)
{
for (int x = 0; x < 3; x++)
{
image.set(x + 1, y + 1, 1);
image.set(x + 5, y + 1, 2);
image.set(x + 1, y + 5, 3);
image.set(x + 5, y + 5, 4);
}
}
ImagePlus imagePlus = new ImagePlus("image", image);

ImagePlus cropPlus = LabelImages.cropLabel(imagePlus, 4, 1);

assertEquals(5, cropPlus.getWidth());
assertEquals(5, cropPlus.getHeight());

Calibration calib = cropPlus.getCalibration();
assertEquals(4, calib.xOrigin, 0.01);
assertEquals(4, calib.yOrigin, 0.01);
}

@Test
public final void test_cropLabel_ImagePlus3D()
{
// create a byte processor containing four labels
ImageStack image = ImageStack.create(8, 8, 8, 8);
for (int z = 0; z < 3; z++)
{
for (int y = 0; y < 3; y++)
{
for (int x = 0; x < 3; x++)
{
image.setVoxel(x + 1, y + 1, z + 1, 1);
image.setVoxel(x + 5, y + 1, z + 1, 2);
image.setVoxel(x + 1, y + 5, z + 1, 3);
image.setVoxel(x + 5, y + 5, z + 1, 4);
image.setVoxel(x + 1, y + 1, z + 5, 5);
image.setVoxel(x + 5, y + 1, z + 5, 6);
image.setVoxel(x + 1, y + 5, z + 5, 7);
image.setVoxel(x + 5, y + 5, z + 5, 8);
}
}
}
ImagePlus imagePlus = new ImagePlus("image", image);

ImagePlus cropPlus = LabelImages.cropLabel(imagePlus, 8, 1);
assertEquals(5, cropPlus.getWidth());
assertEquals(5, cropPlus.getHeight());
assertEquals(5, cropPlus.getImageStackSize());

Calibration calib = cropPlus.getCalibration();
assertEquals(4, calib.xOrigin, 0.01);
assertEquals(4, calib.yOrigin, 0.01);
assertEquals(4, calib.zOrigin, 0.01);
}

@Test
public final void testLabelToRGB_ImageProcessorByteArrayColor()
{
Expand Down

0 comments on commit de10333

Please sign in to comment.