Skip to content

Commit

Permalink
Label image to spots: also returns the label corresponding to created…
Browse files Browse the repository at this point in the history
… spots.
  • Loading branch information
tinevez committed Jul 10, 2024
1 parent 2c56943 commit fc19713
Showing 1 changed file with 110 additions and 50 deletions.
160 changes: 110 additions & 50 deletions src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@

import java.awt.Polygon;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;

import fiji.plugin.trackmate.Spot;
Expand Down Expand Up @@ -441,7 +443,7 @@ public static final < T extends RealType< T >, S extends RealType< S > > List< S
{
if ( input.numDimensions() != 2 )
throw new IllegalArgumentException( "Can only process 2D images with this method, but got " + input.numDimensions() + "D." );

// Get labeling.
final ImgLabeling< Integer, IntType > labeling = toLabeling( input, interval, threshold, numThreads );
return fromLabelingWithROI( labeling, interval, calibration, simplify, qualityImage );
Expand Down Expand Up @@ -476,15 +478,64 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot
final double[] calibration,
final boolean simplify,
final RandomAccessibleInterval< S > qualityImage )
{
final Map< Integer, List< Spot > > map = fromLabelingWithROIMap( labeling, interval, calibration, simplify, qualityImage );
final List<Spot> spots = new ArrayList<>();
for ( final List< Spot > s : map.values() )
spots.addAll( s );

return spots;
}

/**
* Creates spots <b>with ROIs</b> from a <b>2D</b> label image. The quality
* value is read from a secondary image, by taking the max value in each
* ROI.
* <p>
* The spots are returned in a map, where the key is the integer value of
* the label they correspond to in the label image. Because one spot
* corresponds to one connected component in the label image, there might be
* several spots for a label, hence the values of the map are list of spots.
*
* @param <R>
* the type that backs-up the labeling.
* @param <S>
* the type of the quality image. Must be real, scalar.
* @param labeling
* the labeling, must be zero-min and 2D..
* @param interval
* the interval, used to reposition the spots from the zero-min
* labeling to the proper coordinates.
* @param calibration
* the physical calibration.
* @param simplify
* if <code>true</code> the polygon will be post-processed to be
* smoother and contain less points.
* @param qualityImage
* the image in which to read the quality value.
* @return a map linking the label integer value to the list of spots, with
* ROI, it corresponds to.
*/
public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integer, List< Spot > > fromLabelingWithROIMap(
final ImgLabeling< Integer, R > labeling,
final Interval interval,
final double[] calibration,
final boolean simplify,
final RandomAccessibleInterval< S > qualityImage )
{
if ( labeling.numDimensions() != 2 )
throw new IllegalArgumentException( "Can only process 2D images with this method, but got " + labeling.numDimensions() + "D." );

final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling );

// Parse regions to create polygons on boundaries.
final List< Polygon > polygons = new ArrayList<>( regions.getExistingLabels().size() );
/*
* Map of label in the label image to a collection of polygons around
* this label. Because 1 polygon correspond to 1 connected component,
* there might be several polygons for a label.
*/
final Map< Integer, List< Polygon > > polygonsMap = new HashMap<>( regions.getExistingLabels().size() );
final Iterator< LabelRegion< Integer > > iterator = regions.iterator();
// Parse regions to create polygons on boundaries.
while ( iterator.hasNext() )
{
final LabelRegion< Integer > region = iterator.next();
Expand All @@ -494,66 +545,75 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot
for ( final Polygon polygon : pp )
polygon.translate( ( int ) region.min( 0 ), ( int ) region.min( 1 ) );

polygons.addAll( pp );
final Integer label = region.getLabel();
polygonsMap.put( label, pp );
}

// Quality image.
final List< Spot > spots = new ArrayList<>( polygons.size() );

// Storage for results.
final Map< Integer, List< Spot > > output = new HashMap<>( polygonsMap.size() );

// Simplify them and compute a quality.
for ( final Polygon polygon : polygons )
for ( final Integer label : polygonsMap.keySet() )
{
final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON );
final List< Spot > spots = new ArrayList<>( polygonsMap.size() );
output.put( label, spots );

final List< Polygon > polygons = polygonsMap.get( label );
for ( final Polygon polygon : polygons )
{
final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON );

// Create Spot ROI.
final PolygonRoi fRoi;
if ( simplify )
fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE );
else
fRoi = roi;
// Create Spot ROI.
final PolygonRoi fRoi;
if ( simplify )
fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE );
else
fRoi = roi;

// Don't include ROIs that have been shrunk to < 1 pixel.
if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. )
continue;
// Don't include ROIs that have been shrunk to < 1 pixel.
if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. )
continue;

final Polygon fPolygon = fRoi.getPolygon();
final double[] xpoly = new double[ fPolygon.npoints ];
final double[] ypoly = new double[ fPolygon.npoints ];
for ( int i = 0; i < fPolygon.npoints; i++ )
{
xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + fPolygon.xpoints[ i ] - 0.5 );
ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 );
}
final Polygon fPolygon = fRoi.getPolygon();
final double[] xpoly = new double[ fPolygon.npoints ];
final double[] ypoly = new double[ fPolygon.npoints ];
for ( int i = 0; i < fPolygon.npoints; i++ )
{
xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + fPolygon.xpoints[ i ] - 0.5 );
ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 );
}

final Spot spot = SpotRoi.createSpot( xpoly, ypoly, -1. );
final Spot spot = SpotRoi.createSpot( xpoly, ypoly, -1. );

// Measure quality.
final double quality;
if ( null == qualityImage )
{
quality = fRoi.getStatistics().area;
}
else
{
final String name = "QualityImage";
final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y };
final double[] cal = new double[] { calibration[ 0 ], calibration[ 1 ] };
final String[] units = new String[] { "unitX", "unitY" };
final ImgPlus< S > qualityImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( qualityImage ), name, axes, cal, units );
final IterableInterval< S > iterable = SpotUtil.iterable( spot, qualityImgPlus );
double max = Double.NEGATIVE_INFINITY;
for ( final S s : iterable )
// Measure quality.
final double quality;
if ( null == qualityImage )
{
final double val = s.getRealDouble();
if ( val > max )
max = val;
quality = fRoi.getStatistics().area;
}
quality = max;
else
{
final String name = "QualityImage";
final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y };
final double[] cal = new double[] { calibration[ 0 ], calibration[ 1 ] };
final String[] units = new String[] { "unitX", "unitY" };
final ImgPlus< S > qualityImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( qualityImage ), name, axes, cal, units );
final IterableInterval< S > iterable = SpotUtil.iterable( spot, qualityImgPlus );
double max = Double.NEGATIVE_INFINITY;
for ( final S s : iterable )
{
final double val = s.getRealDouble();
if ( val > max )
max = val;
}
quality = max;
}
spot.putFeature( Spot.QUALITY, quality );
spots.add( spot );
}
spot.putFeature( Spot.QUALITY, quality );
spots.add( spot );
}
return spots;
return output;
}

private static final double distanceSquaredBetweenPoints( final double vx, final double vy, final double wx, final double wy )
Expand Down Expand Up @@ -649,7 +709,7 @@ private static final void douglasPeucker( final List< double[] > list, final int
*/
public static final List< double[] > douglasPeucker( final List< double[] > list, final double epsilon )
{
final List< double[] > resultList = new ArrayList< >();
final List< double[] > resultList = new ArrayList<>();
douglasPeucker( list, 0, list.size(), epsilon, resultList );
return resultList;
}
Expand Down

0 comments on commit fc19713

Please sign in to comment.