Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Import Spots handling single pixels and planar pixels properly #105

Merged
merged 16 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4160b0a
Handle the case of single pixel values and covariances that are only …
stefanhahmann May 31, 2024
27b17dd
Add a SphereRenderer and a Demo for small labels to ellipsoid conversion
stefanhahmann May 31, 2024
1a85c02
Extend Small Label Demo with some Circle and a line example
stefanhahmann May 31, 2024
24152e8
Change some code formatting in LabelImageUtilsTest
stefanhahmann Jun 3, 2024
03b5ac3
Change assertion in testCreateSpotFromNonLabelImage()
stefanhahmann Jun 3, 2024
9e92a0a
Replace test testImportSpotsFromBdvChannel() by multiple tests
stefanhahmann Jun 3, 2024
d9be389
Remove obsolete LegacyInjector.preinit(); from some test im LabelImag…
stefanhahmann Jun 3, 2024
369a331
Increase JVM max ram to 2GB also for sonar cloud job
stefanhahmann Jun 3, 2024
81662b4
Checking internal pool index removed, since it adds no value to the u…
stefanhahmann Jun 5, 2024
8e11f3e
Bounding sphere radius check removed from unit test
stefanhahmann Jun 5, 2024
72b5f9f
Use arrayEquals for coordinate check in unit test.
stefanhahmann Jun 5, 2024
cc96d49
Introduce a new method getSemiAxesOfSpot() in LabelImageUtilsTest
stefanhahmann Jun 5, 2024
9e77262
Transform ellipsoid center and ellipsoid axes to mastodon coordinate …
stefanhahmann Jun 5, 2024
6fe7eb2
Let the user decide, which source has been used for the segmentation
stefanhahmann Jun 6, 2024
bb4a321
Move generation of Image in SmallLabelDemo
stefanhahmann Jun 5, 2024
22302a3
Add new class SpotRenderer
stefanhahmann Jun 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@
</activation>
<build>
<plugins>
<!-- Configure the maven-surefire-plugin to use a heap size of 1gb while running tests. -->
<!-- Configure the maven-surefire-plugin to use a heap size of 2gb while running tests. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
Expand All @@ -226,13 +226,13 @@
<id>coverage</id>
<build>
<plugins>
<!-- Configure the maven-surefire-plugin to use a heap size of 1gb while running tests for jacoco coverage analysis. -->
<!-- Configure the maven-surefire-plugin to use a heap size of 2gb while running tests for jacoco coverage analysis. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<argLine>@{argLine} -Xmx1g</argLine>
<argLine>@{argLine} -Xmx2g</argLine>
</configuration>
</plugin>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@
import bdv.viewer.SourceAndConverter;
import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription;
import mpicbg.spim.data.sequence.TimePoint;
import mpicbg.spim.data.sequence.VoxelDimensions;
import net.imagej.ImgPlus;
import net.imglib2.Cursor;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.type.numeric.RealType;
import net.imglib2.util.Cast;
import net.imglib2.util.LinAlgHelpers;
import net.imglib2.util.Pair;
import net.imglib2.util.ValuePair;
import net.imglib2.view.Views;
Expand Down Expand Up @@ -68,6 +69,10 @@ public class LabelImageUtils

private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );

private static final double SIGMA = 5d;

private static final double SINGLE_PIXEL_COVARIANCE = 0.05d;

private LabelImageUtils()
{
// prevent from instantiation
Expand All @@ -84,14 +89,13 @@ private LabelImageUtils()
* @param statusService the status service to report progress to.
*/
static void createSpotsFromLabelImage( final IntFunction< RandomAccessibleInterval< RealType< ? > > > frameProvider,
final IntFunction< AffineTransform3D > transformProvider,
final Model model, final double scaleFactor, final boolean linkSpotsWithSameLabels,
final AbstractSequenceDescription< ?, ?, ? > sequenceDescription,
final StatusService statusService )
{
final ModelGraph graph = model.getGraph();
final List< TimePoint > frames = sequenceDescription.getTimePoints().getTimePointsOrdered();
// NB: Use the dimensions of the first source and the first time point only without checking if they are equal in other sources and time points.
final VoxelDimensions voxelDimensions = sequenceDescription.getViewSetups().get( 0 ).getVoxelSize();
ReentrantReadWriteLock lock = graph.getLock();
lock.writeLock().lock();
int count = 0;
Expand All @@ -103,7 +107,8 @@ static void createSpotsFromLabelImage( final IntFunction< RandomAccessibleInterv
TimePoint frame = frames.get( i );
int frameId = frame.getId();
final RandomAccessibleInterval< RealType< ? > > rai = frameProvider.apply( frameId );
count += createSpotsForFrame( graph, rai, frameId, voxelDimensions, scaleFactor );
final AffineTransform3D transform = transformProvider.apply( frameId );
count += createSpotsForFrame( graph, rai, frameId, transform, scaleFactor );
if ( statusService != null )
statusService.showProgress( i + 1, numTimepoints );
}
Expand All @@ -124,12 +129,11 @@ static void createSpotsFromLabelImage( final IntFunction< RandomAccessibleInterv
* @param graph the graph to add the spots to.
* @param frame the image data to read and process.
* @param frameId the frame id the spots should belong to.
* @param voxelDimensions the dimensions of the voxels in the image.
* @param scaleFactor the scale factor to use for the ellipsoid. 1 means 2.2σ and is the default.
* @return the number of spots created.
*/
private static int createSpotsForFrame( final ModelGraph graph, final RandomAccessibleInterval< RealType< ? > > frame,
final int frameId, final VoxelDimensions voxelDimensions, final double scaleFactor )
final int frameId, final AffineTransform3D transform, final double scaleFactor )
{
logger.debug( "Computing mean, covariance of all labels at frame {}", frameId );
logger.debug( "Dimensions of frame: {}, {}, {}", frame.dimension( 0 ), frame.dimension( 1 ), frame.dimension( 2 ) );
Expand All @@ -150,7 +154,7 @@ private static int createSpotsForFrame( final ModelGraph graph, final RandomAcce

logger.debug( "Found {} label(s) in frame {}. Range: [{}, {}]", numLabels, frameId, minimumLabel, maximumLabel );
Label[] labels = extractLabelsFromFrame( frame, minimumLabel, numLabels );
return createSpotsFromFrameLabels( graph, frameId, labels, voxelDimensions, scaleFactor );
return createSpotsFromFrameLabels( graph, frameId, labels, transform, scaleFactor );
}

/**
Expand Down Expand Up @@ -186,34 +190,56 @@ private static Pair< Integer, Integer > getPixelValueInterval( final RandomAcces
* @param graph the graph to add the spots to.
* @param frameId the frame id the spots should belong to.
* @param labels the labels in the frame.
* @param voxelDimensions the dimensions of the voxels in the image.
* @param transform the affine transform to convert from pixel to mastodon coordinates.
* @param scaleFactor the size factor to use for the ellipsoid. 1 means 2.2σ and is the default.
* @return the number of spots created.
*/
private static int createSpotsFromFrameLabels( final ModelGraph graph, final int frameId, final Label[] labels,
final VoxelDimensions voxelDimensions, final double scaleFactor )
final AffineTransform3D transform, final double scaleFactor )
{
int count = 0;
// combine the sums into mean and covariance matrices, then add the corresponding spot
for ( final Label label : labels )
{
// skip labels that are not present in the image or have only one pixel
if ( label == null || label.numPixels < 2 )
// skip labels that are not present in the image or do not have at least 1 pixel
if ( label == null || label.numPixels < 1 )
continue;
double[] mean = label.covariances.getMeans();
double[][] cov = label.covariances.get();
scale( mean, voxelDimensions );
scale( cov, scaleFactor, voxelDimensions );
double[][] cov;
if ( label.numPixels == 1 )
cov = new double[ mean.length ][ mean.length ];
else
cov = label.covariances.get();
for ( int i = 0; i < cov.length; i++ )
cov[ i ][ i ] += SINGLE_PIXEL_COVARIANCE;
// transform ellipsoid center to mastodon coordinate system
transform.apply( mean, mean );
// scale ellipsoid axes to desired factor
scale( cov, scaleFactor );

// transform ellipsoid axes to mastodon coordinate system
double[][] transformMatrix = new double[ 3 ][ 4 ];
transform.toMatrix( transformMatrix );
double[][] matrix3x3 = {
{ transformMatrix[ 0 ][ 0 ], transformMatrix[ 0 ][ 1 ], transformMatrix[ 0 ][ 2 ] },
{ transformMatrix[ 1 ][ 0 ], transformMatrix[ 1 ][ 1 ], transformMatrix[ 1 ][ 2 ] },
{ transformMatrix[ 2 ][ 0 ], transformMatrix[ 2 ][ 1 ], transformMatrix[ 2 ][ 2 ] }
};
double[][] temp = new double[ 3 ][ 3 ];
double[][] covTransformed = new double[ 3 ][ 3 ];
LinAlgHelpers.mult( matrix3x3, cov, temp );
LinAlgHelpers.multABT( temp, matrix3x3, covTransformed );

try
{
Spot spot = graph.addVertex().init( frameId, mean, cov );
Spot spot = graph.addVertex().init( frameId, mean, covTransformed );
spot.setLabel( String.valueOf( label.value ) );
count++;
}
catch ( Exception e )
{
logger.trace( "Could not add vertex to graph. Mean: {}, Covariance: {}", Arrays.toString( mean ),
Arrays.deepToString( cov ) );
Arrays.deepToString( covTransformed ) );
}
}
logger.debug( "Added {} spot(s) to frame {}", count, frameId );
Expand Down Expand Up @@ -248,19 +274,6 @@ private static Label[] extractLabelsFromFrame( final RandomAccessibleInterval< R
return labels;
}

/**
* Scales the mean vector by the voxel dimensions.
* @param mean the mean vector to scale.
* @param voxelDimensions the dimensions of the voxels in the image.
*/
private static void scale( final double[] mean, final VoxelDimensions voxelDimensions )
{
if ( mean.length != voxelDimensions.numDimensions() )
throw new IllegalArgumentException( "Mean vector has wrong dimension." );
for ( int i = 0; i < mean.length; i++ )
mean[ i ] = mean[ i ] * voxelDimensions.dimension( i );
}

/**
* Returns the dimensions of the given image as an array in the order x, y, z, t.
* @param imgPlus the image to get the dimensions from.
Expand Down Expand Up @@ -306,12 +319,14 @@ public static boolean dimensionsMatch( final SharedBigDataViewerData sharedBigDa
/**
* Imports spots from the given ImageJ image into the given project model.
* @param projectModel the project model to add the spots to.
* @param sourceIndex the index of the source, to which the segmentation image given in imgPlus corresponds.
* @param imgPlus the image to import the spots from.
* @param scaleFactor the scale factor to use for the ellipsoid. 1 means 2.2σ and is the default.
* @param linkSpotsWithSameLabels whether to link spots with the same labels.
* @throws IllegalArgumentException if the dimensions of the given image do not match the dimensions of the big data viewer image contained in the project model.
*/
public static void importSpotsFromImgPlus( final ProjectModel projectModel, final ImgPlus< ? > imgPlus, final double scaleFactor,
public static void importSpotsFromImgPlus( final ProjectModel projectModel, final int sourceIndex, final ImgPlus< ? > imgPlus,
final double scaleFactor,
final boolean linkSpotsWithSameLabels )
{
logger.debug( "ImageJ image: {}", imgPlus.getName() );
Expand All @@ -321,7 +336,12 @@ public static void importSpotsFromImgPlus( final ProjectModel projectModel, fina
+ " do not match the dimensions of the big data viewer image." );
IntFunction< RandomAccessibleInterval< RealType< ? > > > frameProvider =
frameId -> Cast.unchecked( Views.hyperSlice( imgPlus.getImg(), 3, frameId ) );
createSpotsFromLabelImage( frameProvider, projectModel.getModel(), scaleFactor, linkSpotsWithSameLabels,
IntFunction< AffineTransform3D > transformProvider = frameId -> {
AffineTransform3D transform = new AffineTransform3D();
projectModel.getSharedBdvData().getSources().get( sourceIndex ).getSpimSource().getSourceTransform( frameId, 0, transform );
return transform;
};
createSpotsFromLabelImage( frameProvider, transformProvider, projectModel.getModel(), scaleFactor, linkSpotsWithSameLabels,
sharedBdvData.getSpimData().getSequenceDescription(),
projectModel.getContext().getService( StatusService.class ) );
}
Expand All @@ -339,29 +359,29 @@ public static void importSpotsFromBdvChannel( final ProjectModel projectModel, f
{
IntFunction< RandomAccessibleInterval< RealType< ? > > > frameProvider =
frameId -> Cast.unchecked( source.getSource( frameId, 0 ) );
createSpotsFromLabelImage( frameProvider, projectModel.getModel(), scaleFactor, linkSpotsWithSameLabels,
IntFunction< AffineTransform3D > transformProvider = frameId -> {
AffineTransform3D transform = new AffineTransform3D();
source.getSourceTransform( frameId, 0, transform );
return transform;
};
createSpotsFromLabelImage( frameProvider, transformProvider, projectModel.getModel(), scaleFactor, linkSpotsWithSameLabels,
projectModel.getSharedBdvData().getSpimData().getSequenceDescription(),
projectModel.getContext().getService( StatusService.class ) );
}

/**
* Scales the covariance matrix the given sigma and the voxel dimensions.
* Scales the covariance matrix using the scale factor and the voxel dimensions.
* @param covariance the covariance matrix to scale.
* @param scaleFactor the factor to scale the covariance matrix with.
* @param voxelDimensions the dimensions of the voxels in the image.
* @throws IllegalArgumentException if the covariance matrix has not the same dimensions as the given voxelDimensions.
*/
public static void scale( final double[][] covariance, final double scaleFactor, final VoxelDimensions voxelDimensions )
public static void scale( final double[][] covariance, final double scaleFactor )
{
if ( covariance.length != voxelDimensions.numDimensions() )
throw new IllegalArgumentException( "Covariance matrix has wrong dimension." );
for ( int i = 0; i < covariance.length; i++ )
{
if ( covariance[ i ].length != voxelDimensions.numDimensions() )
throw new IllegalArgumentException( "Covariance matrix has wrong dimension." );
for ( int j = i; j < covariance.length; j++ )
{
covariance[ i ][ j ] *= Math.pow( scaleFactor, 2 ) * 5 * voxelDimensions.dimension( i ) * voxelDimensions.dimension( j );
covariance[ i ][ j ] *= Math.pow( scaleFactor, 2 ) * SIGMA;
// the covariance matrix is symmetric!
if ( i != j )
covariance[ j ][ i ] = covariance[ i ][ j ];
Expand All @@ -383,6 +403,22 @@ public static List< String > getSourceNames( final SharedBigDataViewerData share
return choices;
}

/**
* Returns the index of the given image source name in the given big data viewer data.
* @param imgSource the image source name to get the id for.
* @param sharedBdvData the big data viewer data to get the source index from.
* @return the source index.
* @throws IllegalArgumentException if the source name was not found in the big data viewer data.
*/
public static int getSourceIndex( final String imgSource, final SharedBigDataViewerData sharedBdvData )
{
final List< SourceAndConverter< ? > > sources = sharedBdvData.getSources();
for ( int i = 0; i < sources.size(); i++ )
if ( sources.get( i ).getSpimSource().getName().equals( imgSource ) )
return i;
throw new IllegalArgumentException( "The source " + imgSource + " was not found in the big data viewer data." );
}

/**
* A class to hold the pixel count, mean and covariances of a label in the image.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@
import org.scijava.ItemIO;
import org.scijava.ItemVisibility;
import org.scijava.command.Command;
import org.scijava.command.ContextCommand;
import org.scijava.command.DynamicCommand;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
import java.util.Arrays;
import java.util.List;

@Plugin( type = Command.class, label = "Import spots from ImageJ image" )
public class ImportSpotsFromImgPlusView< T > extends ContextCommand
public class ImportSpotsFromImgPlusView< T > extends DynamicCommand
{

private static final int WIDTH = 15;
Expand Down Expand Up @@ -76,6 +77,9 @@ public class ImportSpotsFromImgPlusView< T > extends ContextCommand
@Parameter( label = "Link spots having the same labels in consecutive frames", description = "This option assumes that labels from the input images are unique for one tracklet." )
boolean linkSpotsWithSameLabels = false;

@Parameter( label = "Image source that has been used for the external segmentation", initializer = "initImgSourceChoices" )
public String imgSourceChoice = "";

@SuppressWarnings( "unused" )
private void validateImageData()
{
Expand All @@ -96,7 +100,8 @@ public void run()
{
if ( isCanceled() )
return;
LabelImageUtils.importSpotsFromImgPlus( projectModel, imgPlus, scaleFactor, linkSpotsWithSameLabels );
int sourceIndex = LabelImageUtils.getSourceIndex( imgSourceChoice, projectModel.getSharedBdvData() );
LabelImageUtils.importSpotsFromImgPlus( projectModel, sourceIndex, imgPlus, scaleFactor, linkSpotsWithSameLabels );
}

@SuppressWarnings( "unused" )
Expand All @@ -113,4 +118,11 @@ private void initMessage()
documentation = String.format( TEMPLATE, imgPlus.getName(), imgPlusDimensions[ 0 ], imgPlusDimensions[ 1 ], imgPlusDimensions[ 2 ],
imgPlusDimensions[ 3 ], bdvDimensions[ 0 ], bdvDimensions[ 1 ], bdvDimensions[ 2 ], bdvDimensions[ 3 ], dimensionMatch );
}

@SuppressWarnings( "unused" )
private void initImgSourceChoices()
{
List< String > choices = LabelImageUtils.getSourceNames( projectModel.getSharedBdvData() );
getInfo().getMutableInput( "imgSourceChoice", String.class ).setChoices( choices );
}
}
Loading
Loading