diff --git a/src/main/java/org/mastodon/mamut/io/importer/trackmate/CommonTrackMateFeatureDeclarations.java b/src/main/java/org/mastodon/mamut/io/importer/trackmate/CommonTrackMateFeatureDeclarations.java new file mode 100644 index 000000000..5aac6f5cc --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/importer/trackmate/CommonTrackMateFeatureDeclarations.java @@ -0,0 +1,94 @@ +package org.mastodon.mamut.io.importer.trackmate; + +import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FEATURE_ATTRIBUTE; +import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FEATURE_DIMENSION_ATTRIBUTE; +import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FEATURE_ISINT_ATTRIBUTE; +import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FEATURE_NAME_ATTRIBUTE; +import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FEATURE_SHORT_NAME_ATTRIBUTE; +import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FEATURE_TAG; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.jdom2.Element; + +public class CommonTrackMateFeatureDeclarations +{ + + public static final List< CommonTrackMateFeatureDeclaration > spotFeatureDeclarations = Arrays.asList( new CommonTrackMateFeatureDeclaration[] { + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.VISIBILITY_FEATURE_NAME, "Visibility", "Visibility", "NONE", true ), + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.POSITION_X_FEATURE_NAME, "X", "X", "POSITION", false ), + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.POSITION_Y_FEATURE_NAME, "Y", "Y", "POSITION", false ), + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.POSITION_Z_FEATURE_NAME, "Z", "Z", "POSITION", false ), + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.POSITION_T_FEATURE_NAME, "T", "T", "TIME", false ), + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.FRAME_FEATURE_NAME, "Frame", "Frame", "NONE", true ), + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.RADIUS_FEATURE_NAME, "Radius", "R", "LENGTH", false ), + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.QUALITY_FEATURE_NAME, "Quality", "Quality", "QUALITY", false ), + } ); + + public static final List< CommonTrackMateFeatureDeclaration > edgeFeatureDeclarations = Arrays.asList( new CommonTrackMateFeatureDeclaration[] { + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.EDGE_SOURCE_ATTRIBUTE, "Source spot ID", "Source ID", "NONE", true ), + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.EDGE_TARGET_ATTRIBUTE, "Target spot ID", "Target ID", "NONE", true ), + } ); + + public static final List< CommonTrackMateFeatureDeclaration > trackFeatureDeclarations = Arrays.asList( new CommonTrackMateFeatureDeclaration[] { + new CommonTrackMateFeatureDeclaration( TrackMateXMLKeys.TRACK_ID_ATTRIBUTE, "Track ID", "ID", "NONE", true ), + } ); + + public static class CommonTrackMateFeatureDeclaration + { + public final String key; + + public final String name; + + public final String shortName; + + public final String dimension; + + public final boolean isInt; + + public CommonTrackMateFeatureDeclaration( final String key, final String name, final String shortName, final String dimension, final boolean isInt ) + { + this.key = key; + this.name = name; + this.shortName = shortName; + this.dimension = dimension; + this.isInt = isInt; + } + + public Element toElement() + { + final Element fel = new Element( FEATURE_TAG ); + fel.setAttribute( FEATURE_ATTRIBUTE, key ); + fel.setAttribute( FEATURE_NAME_ATTRIBUTE, name ); + fel.setAttribute( FEATURE_SHORT_NAME_ATTRIBUTE, shortName ); + fel.setAttribute( FEATURE_DIMENSION_ATTRIBUTE, dimension ); + fel.setAttribute( FEATURE_ISINT_ATTRIBUTE, "" + isInt ); + return fel; + } + } + + /** + * List of Mastodon spot projection keys, translated to the TrackMate export + * name, that are not needed in the exported XML file, as they are already + * exported via TrackMate builtin features. + */ + public static final List< String > redundantSpotProjectionKeys = Arrays.asList( + "Spot_position_X", "Spot_position_Y", "Spot_position_Z", "Spot_radius", "Spot_frame" ); + + /** + * List of Mastodon link projection keys, translated to the TrackMate export + * name, that are not needed in the exported XML file, as they are already + * exported via TrackMate builtin features. + */ + public static final List< String > redundantLinkProjectionKeys = Arrays.asList( + "Link_target_IDs_Source_spot_id", "Link_target_IDs_Target_spot_id" ); + + /** + * List of Mastodon track projection keys, translated to the TrackMate + * export name, that are not needed in the exported XML file, as they are + * already exported via TrackMate builtin features. + */ + public static final List< String > redundantTrackProjectionKeys = Collections.emptyList(); +} diff --git a/src/main/java/org/mastodon/mamut/io/importer/trackmate/MamutExporter.java b/src/main/java/org/mastodon/mamut/io/importer/trackmate/MamutExporter.java index 2e9415d56..c0b7fb54c 100644 --- a/src/main/java/org/mastodon/mamut/io/importer/trackmate/MamutExporter.java +++ b/src/main/java/org/mastodon/mamut/io/importer/trackmate/MamutExporter.java @@ -54,6 +54,7 @@ import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FOLDER_ATTRIBUTE; import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FRAME_ATTRIBUTE; import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FRAME_FEATURE_NAME; +import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.FRAME_INTERVAL_ATTRIBUTE; import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.GUI_STATE_TAG; import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.HEIGHT_ATTRIBUTE; import static org.mastodon.mamut.io.importer.trackmate.TrackMateXMLKeys.ID_FEATURE_NAME; @@ -361,6 +362,7 @@ private Element imageDataToXml() attributes.add( new Attribute( PIXEL_WIDTH_ATTRIBUTE, Double.toString( pixelWidth ) ) ); attributes.add( new Attribute( PIXEL_HEIGHT_ATTRIBUTE, Double.toString( pixelHeight ) ) ); attributes.add( new Attribute( VOXEL_DEPTH_ATTRIBUTE, Double.toString( voxelDepth ) ) ); + attributes.add( new Attribute( FRAME_INTERVAL_ATTRIBUTE, Double.toString( 1. ) ) ); final Element timePointsElement = document .getRootElement() @@ -540,8 +542,15 @@ private Element edgeToXml( final Link edge, final int sourceSpotID, final int ta // Link features. for ( final ExportFeatureProjection< Link > p : linkFeatureProjections ) { - final String attName; final String origName = p.attributeName; + /* + * Do not export link features that are common TrackMate features + * and that we already exported above. + */ + if ( CommonTrackMateFeatureDeclarations.redundantLinkProjectionKeys.contains( origName ) ) + continue; + + final String attName; /* * If the model to export was imported from a TrackMate or a MaMuT * file, it will already contain features with the same name that @@ -634,8 +643,15 @@ private Element spotToXml( final Spot spot ) for (final ExportFeatureProjection< Spot > p : spotFeatureProjections ) { - final String attName; final String origName = p.attributeName; + /* + * Do not export spot features that are common TrackMate features + * and that we already exported above. + */ + if ( CommonTrackMateFeatureDeclarations.redundantSpotProjectionKeys.contains( origName ) ) + continue; + + final String attName; /* * If the model to export was imported from a TrackMate or a MaMuT * file, it will already contain features with the same name that @@ -660,9 +676,18 @@ else if ( origName.startsWith( "IMPORTED_" ) ) attName = origName; } - attributes.add( new Attribute( - attName, - Double.toString( p.projection.value( spot ) ) ) ); + + String val; + if ( p.projection instanceof IntFeatureProjection ) + { + val = Integer.toString( (int) p.projection.value( spot ) ); + } + else + { + // Assume double. + val = Double.toString( p.projection.value( spot ) ); + } + attributes.add( new Attribute( attName, val ) ); } final Element spotElement = new Element( SPOT_ELEMENT_TAG ); @@ -673,6 +698,7 @@ else if ( origName.startsWith( "IMPORTED_" ) ) private Element featuresDeclarationToXml() { final Element featuresElement = new Element( FEATURE_DECLARATION_TAG ); + appendBasicFeatureDeclarations( featuresElement ); appendFeaturesDeclarationOfClass( Spot.class, featuresElement, SPOT_FEATURE_DECLARATION_TAG ); appendFeaturesDeclarationOfClass( Link.class, featuresElement, EDGE_FEATURE_DECLARATION_TAG ); // Create an empty declaration for track features, for now. @@ -680,21 +706,53 @@ private Element featuresDeclarationToXml() return featuresElement; } + private void appendBasicFeatureDeclarations( final Element featuresElement ) + { + // Spots. + final Element spotFeaturesElement = getOrAddChild( featuresElement, SPOT_FEATURE_DECLARATION_TAG ); + CommonTrackMateFeatureDeclarations.spotFeatureDeclarations.forEach( cf -> spotFeaturesElement.addContent( cf.toElement() ) ); + + // Edges. + final Element edgeFeaturesElement = getOrAddChild( featuresElement, EDGE_FEATURE_DECLARATION_TAG ); + CommonTrackMateFeatureDeclarations.edgeFeatureDeclarations.forEach( cf -> edgeFeaturesElement.addContent( cf.toElement() ) ); + + // Tracks. + final Element trackFeaturesElement = getOrAddChild( featuresElement, TRACK_FEATURE_DECLARATION_TAG ); + CommonTrackMateFeatureDeclarations.trackFeatureDeclarations.forEach( cf -> trackFeaturesElement.addContent( cf.toElement() ) ); + } + @SuppressWarnings( { "unchecked", "rawtypes" } ) private < T > void appendFeaturesDeclarationOfClass( final Class< T > clazz, final Element featuresElement, final String classFeatureDeclarationTag ) { final List< ExportFeatureProjection< T > > projections; + List< String > redundantFeatures; if ( clazz.equals( Spot.class ) ) + { projections = ( List ) spotFeatureProjections; + redundantFeatures = CommonTrackMateFeatureDeclarations.redundantSpotProjectionKeys; + } else if ( clazz.equals( Link.class ) ) + { projections = ( List ) linkFeatureProjections; + redundantFeatures = CommonTrackMateFeatureDeclarations.redundantLinkProjectionKeys; + } else + { projections = Collections.emptyList(); + redundantFeatures = CommonTrackMateFeatureDeclarations.redundantTrackProjectionKeys; + } - final Element classFeaturesElement = new Element( classFeatureDeclarationTag ); + final Element classFeaturesElement = getOrAddChild( featuresElement, classFeatureDeclarationTag ); for ( final ExportFeatureProjection< T > p : projections ) { + /* + * Do not export features that are common TrackMate features and + * that we already declared. + */ + if ( redundantFeatures.contains( p.attributeName ) ) + continue; + final String isint = ( p.projection instanceof IntFeatureProjection ) ? "true" : "false"; @@ -710,7 +768,17 @@ else if ( clazz.equals( Link.class ) ) fel.setAttribute( FEATURE_ISINT_ATTRIBUTE, isint ); classFeaturesElement.addContent( fel ); } - featuresElement.addContent( classFeaturesElement ); + } + + private Element getOrAddChild( final Element parent, final String childName ) + { + Element child = parent.getChild( childName ); + if ( null == child ) + { + child = new Element( childName ); + parent.addContent( child ); + } + return child; } private static Document getSAXParsedDocument( final String fileName ) @@ -991,4 +1059,5 @@ public static final void export( final File target, final Model model, final Mam exporter.appendGuiState(); exporter.write( target ); } + } diff --git a/src/main/java/org/mastodon/mamut/io/importer/trackmate/TrackMateXMLKeys.java b/src/main/java/org/mastodon/mamut/io/importer/trackmate/TrackMateXMLKeys.java index 9f5dee71f..7028b5f25 100644 --- a/src/main/java/org/mastodon/mamut/io/importer/trackmate/TrackMateXMLKeys.java +++ b/src/main/java/org/mastodon/mamut/io/importer/trackmate/TrackMateXMLKeys.java @@ -125,6 +125,8 @@ public class TrackMateXMLKeys public static final String NFRAMES_ATTRIBUTE = "nframes"; + public static final String FRAME_INTERVAL_ATTRIBUTE = "timeinterval"; + public static final String PIXEL_WIDTH_ATTRIBUTE = "pixelwidth"; public static final String PIXEL_HEIGHT_ATTRIBUTE = "pixelheight";