diff --git a/src/main/java/org/mastodon/mamut/io/csv/CSVImporter.java b/src/main/java/org/mastodon/mamut/io/csv/CSVImporter.java index 63cdfd0..04e41ac 100644 --- a/src/main/java/org/mastodon/mamut/io/csv/CSVImporter.java +++ b/src/main/java/org/mastodon/mamut/io/csv/CSVImporter.java @@ -33,12 +33,19 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import java.util.stream.Collectors; +import org.apache.commons.lang3.tuple.Pair; import org.mastodon.RefPool; +import org.mastodon.collection.IntRefMap; import org.mastodon.collection.RefCollection; +import org.mastodon.collection.ref.IntRefHashMap; import org.mastodon.feature.Dimension; import org.mastodon.feature.Feature; import org.mastodon.feature.FeatureModel; @@ -49,11 +56,16 @@ import org.mastodon.feature.Multiplicity; import org.mastodon.feature.io.FeatureSerializer; import org.mastodon.io.FileIdToObjectMap; +import org.mastodon.mamut.io.importer.ModelImporter; +import org.mastodon.mamut.model.Link; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.ModelGraph; import org.mastodon.mamut.model.Spot; +import org.mastodon.model.tag.TagSetStructure; import org.mastodon.properties.IntPropertyMap; import org.mastodon.tracking.mamut.detection.DetectionQualityFeature; +import org.mastodon.ui.coloring.GlasbeyLut; +import org.mastodon.util.TagSetUtils; import org.scijava.plugin.Plugin; import org.scijava.util.VersionUtils; @@ -64,7 +76,7 @@ import net.imglib2.algorithm.Algorithm; -public class CSVImporter implements Algorithm +public class CSVImporter extends ModelImporter implements Algorithm { public static final String PLUGIN_VERSION = VersionUtils.getVersion( CSVImporter.class ); @@ -83,10 +95,16 @@ public class CSVImporter implements Algorithm private final String qualityColumnName; + private final String radiusColumnName; + private final String idColumnName; + private final String parentIdColumnName; + private final String labelColumnName; + private final String tagColumnName; + private final double radius; private final double xOrigin; @@ -109,12 +127,16 @@ private CSVImporter( final String zColumnName, final String frameColumnName, final String qualityColumName, + final String radiusColumnName, final String idColumnName, + final String parentIdColumnName, final String labelColumnName, + final String tagColumnName, final double xOrigin, final double yOrigin, final double zOrigin ) { + super( model ); this.model = model; this.filePath = filePath; this.separator = separator; @@ -124,8 +146,11 @@ private CSVImporter( this.zColumnName = zColumnName; this.frameColumnName = frameColumnName; this.qualityColumnName = qualityColumName; + this.radiusColumnName = radiusColumnName; this.idColumnName = idColumnName; + this.parentIdColumnName = parentIdColumnName; this.labelColumnName = labelColumnName; + this.tagColumnName = tagColumnName; this.xOrigin = xOrigin; this.yOrigin = yOrigin; this.zOrigin = zOrigin; @@ -196,11 +221,10 @@ public boolean process() .withSeparator( separator ) .withIgnoreQuotations( true ) .build(); - try + try (final CSVReader reader = new CSVReaderBuilder( new FileReader( filePath ) ) + .withCSVParser( parser ) + .build()) { - final CSVReader reader = new CSVReaderBuilder( new FileReader( filePath ) ) - .withCSVParser( parser ) - .build(); final Iterator< String[] > it = reader.iterator(); /* @@ -267,6 +291,10 @@ public boolean process() ? null : DetectionQualityFeature.getOrRegister( model.getFeatureModel(), graph.vertices().getRefPool() ); + Integer radiuscol = null; + if ( null != radiusColumnName && !radiusColumnName.isEmpty() ) + radiuscol = headerMap.get( radiusColumnName ); + Integer idcol = null; if ( null != idColumnName && !idColumnName.isEmpty() ) idcol = headerMap.get( idColumnName ); @@ -275,10 +303,24 @@ public boolean process() ? null : OriginalIdFeature.getOrRegister( model.getFeatureModel(), graph.vertices().getRefPool() ); + Integer parentIdcol = null; + if ( null != parentIdColumnName && !parentIdColumnName.isEmpty() ) + parentIdcol = headerMap.get( parentIdColumnName ); + Integer labelcol = null; if ( null != labelColumnName && !labelColumnName.isEmpty() ) labelcol = headerMap.get( labelColumnName ); + Integer tagcol = null; + if ( null != tagColumnName && !tagColumnName.isEmpty() ) + tagcol = headerMap.get( tagColumnName ); + + TagSetStructure.TagSet importedTagSet = null; + if ( null != tagcol ) + importedTagSet = parseTagsFromFile( parser, tagcol ); + + IntRefMap< Spot > spotMap = new IntRefHashMap<>( model.getGraph().vertices().getRefPool(), -1 ); + /* * Iterate over the rest of lines. */ @@ -286,7 +328,12 @@ public boolean process() final WriteLock lock = graph.getLock().writeLock(); lock.lock(); final Spot vref = graph.vertexRef(); + final Spot parentVertexRef = graph.vertexRef(); + final Link edgeRef = graph.edgeRef(); final double[] pos = new double[ 3 ]; + startImport(); + if ( null != tagcol ) + model.getTagSetModel().pauseListeners(); try { @@ -298,30 +345,46 @@ public boolean process() try { - pos[ 0 ] = Double.parseDouble( record[ xcol ] ) + xOrigin; - pos[ 1 ] = Double.parseDouble( record[ ycol ] ) + yOrigin; - pos[ 2 ] = Double.parseDouble( record[ zcol ] ) + zOrigin; - final int t = Integer.parseInt( record[ framecol ] ); + pos[ 0 ] = Double.parseDouble( record[ xcol ].trim() ) + xOrigin; + pos[ 1 ] = Double.parseDouble( record[ ycol ].trim() ) + yOrigin; + pos[ 2 ] = Double.parseDouble( record[ zcol ].trim() ) + zOrigin; + final int t = Integer.parseInt( record[ framecol ].trim() ); + + double r = radius; + if ( null != radiuscol ) + r = Double.parseDouble( record[ radiuscol ].trim() ); - final Spot spot = graph.addVertex( vref ).init( t, pos, radius ); + final Spot spot = graph.addVertex( vref ).init( t, pos, r ); if ( null != idcol ) { - final int id = Integer.parseInt( record[ idcol ] ); + final int id = Integer.parseInt( record[ idcol ].trim() ); originalIdFeature.set( spot, id ); + if ( null != parentIdcol ) + spotMap.put( id, spot ); if ( null == labelcol ) spot.setLabel( "" + id ); } if ( null != labelcol ) { - spot.setLabel( record[ labelcol ] ); + String label = record[ labelcol ].trim(); + spot.setLabel( label ); } + double q = 1.; if ( null != qualitycol ) { - q = Double.parseDouble( record[ qualitycol ] ); + q = Double.parseDouble( record[ qualitycol ].trim() ); qualityFeature.set( spot, q ); } + + if ( null != tagcol ) + { + String label = record[ tagcol ].trim(); + TagSetStructure.Tag tag = TagSetUtils.findTag( importedTagSet, label ); + TagSetUtils.tagSpot( model, importedTagSet, tag, spot ); + TagSetUtils.tagLinks( model, importedTagSet, tag, spot.incomingEdges() ); + } } catch ( final NumberFormatException nfe ) { @@ -330,11 +393,18 @@ public boolean process() continue; } } + if ( null != parentIdcol ) + parseLinksFromFile( parser, spotMap, idcol, vref, parentIdcol, parentVertexRef, edgeRef ); } finally { lock.unlock(); graph.releaseRef( vref ); + graph.releaseRef( parentVertexRef ); + graph.releaseRef( edgeRef ); + if ( null != tagcol ) + model.getTagSetModel().resumeListeners(); + finishImport(); } } catch ( final FileNotFoundException e ) @@ -343,6 +413,12 @@ public boolean process() e.printStackTrace(); return false; } + catch ( final IOException e ) + { + errorMessage = "Error reading file " + filePath; + e.printStackTrace(); + return false; + } /* * Return. @@ -351,6 +427,63 @@ public boolean process() return true; } + private void parseLinksFromFile( final CSVParser parser, final IntRefMap< Spot > spotMap, final int idCol, final Spot spotRef, + final int parentIdcol, final Spot parentSpotRef, final Link edgeRef ) + { + try (final CSVReader readerTags = new CSVReaderBuilder( new FileReader( filePath ) ) + .withCSVParser( parser ) + .build()) + { + Iterator< String[] > csvIterator = readerTags.iterator(); + csvIterator.next(); + while ( csvIterator.hasNext() ) + { + final String[] line = csvIterator.next(); + final int spotId = Integer.parseInt( line[ idCol ].trim() ); + final Spot spot = spotMap.get( spotId, spotRef ); + final int parentId = Integer.parseInt( line[ parentIdcol ].trim() ); + final Spot parent = spotMap.get( parentId, parentSpotRef ); + if ( parent != null && spot != null ) + model.getGraph().addEdge( parent, spot, edgeRef ).init(); + } + } + catch ( final IOException e ) + { + errorMessage = "Error reading file " + filePath; + e.printStackTrace(); + } + } + + private TagSetStructure.TagSet parseTagsFromFile( final CSVParser parser, int labelcol ) + { + try (final CSVReader readerTags = new CSVReaderBuilder( new FileReader( filePath ) ) + .withCSVParser( parser ) + .build()) + { + Iterator< String[] > csvIterator = readerTags.iterator(); + + Set< String > tags = new HashSet<>(); + csvIterator.next(); + while ( csvIterator.hasNext() ) + { + final String[] line = csvIterator.next(); + String tag = line[ labelcol ].trim(); + tags.add( tag ); + } + GlasbeyLut glasbeyLut = new GlasbeyLut(); + List< Pair< String, Integer > > tagsAndColors = + tags.stream().map( tag -> Pair.of( tag, glasbeyLut.next() ) ).collect( Collectors.toList() ); + + return TagSetUtils.addNewTagSetToModel( model, "Imported Tags", tagsAndColors ); + } + catch ( final IOException e ) + { + errorMessage = "Error reading file " + filePath; + e.printStackTrace(); + return null; + } + } + @Override public String getErrorMessage() { @@ -447,10 +580,16 @@ public static class Builder private String qualityColumnName; + private String radiusColumnName; + private String idColumnName; + private String parentIdColumnName; + private String labelColumnName; + private String tagColumnName; + private double xOrigin = 0.; private double yOrigin = 0.; @@ -507,18 +646,36 @@ public Builder qualityColumnName( final String qualityColumnName ) return this; } + public Builder radiusColumnName( final String radiusColumnName ) + { + this.radiusColumnName = radiusColumnName; + return this; + } + public Builder idColumnName( final String idColumnName ) { this.idColumnName = idColumnName; return this; } + public Builder parentIdColumnName( final String parentIdColumnName ) + { + this.parentIdColumnName = parentIdColumnName; + return this; + } + public Builder labelColumnName( final String labelColumnName ) { this.labelColumnName = labelColumnName; return this; } + public Builder tagColumnName( final String tagColumnName ) + { + this.tagColumnName = tagColumnName; + return this; + } + public Builder xOrigin( final double xOrigin ) { this.xOrigin = xOrigin; @@ -608,8 +765,11 @@ public CSVImporter get() zColumnName, frameColumnName, qualityColumnName, + radiusColumnName, idColumnName, + parentIdColumnName, labelColumnName, + tagColumnName, xOrigin, yOrigin, zOrigin ); diff --git a/src/main/java/org/mastodon/mamut/io/csv/plugin/ui/CSVImporterPanel.java b/src/main/java/org/mastodon/mamut/io/csv/plugin/ui/CSVImporterPanel.java index f3a071a..6e0c6ee 100644 --- a/src/main/java/org/mastodon/mamut/io/csv/plugin/ui/CSVImporterPanel.java +++ b/src/main/java/org/mastodon/mamut/io/csv/plugin/ui/CSVImporterPanel.java @@ -69,10 +69,16 @@ public class CSVImporterPanel extends JPanel final JComboBox< String > comboBoxQualityCol; + final JComboBox< String > comboBoxRadiusCol; + final JComboBox< String > comboBoxNameCol; final JComboBox< String > comboBoxIDCol; + final JComboBox< String > comboBoxParentIdCol; + + final JComboBox< String > comboBoxTagCol; + final JComboBox< String > comboBoxTrackCol; final JCheckBox chckbxImportTracks; @@ -144,12 +150,21 @@ public CSVImporterPanel() gbc_separator.gridy = 4; panelControl.add( separator, gbc_separator ); + final JLabel lblMandatory = new JLabel( "Mandatory" ); + lblMandatory.setFont( new Font( Font.SANS_SERIF, Font.ITALIC, 12 ) ); + final GridBagConstraints gbc_lblRadiusMandatory = new GridBagConstraints(); + gbc_lblRadiusMandatory.anchor = GridBagConstraints.EAST; + gbc_lblRadiusMandatory.insets = new Insets( 0, 0, 5, 50 ); + gbc_lblRadiusMandatory.gridx = 0; + gbc_lblRadiusMandatory.gridy = 5; + panelControl.add( lblMandatory, gbc_lblRadiusMandatory ); + final JLabel lblRadius = new JLabel( "Radius:" ); final GridBagConstraints gbc_lblRadius = new GridBagConstraints(); gbc_lblRadius.anchor = GridBagConstraints.EAST; gbc_lblRadius.insets = new Insets( 0, 0, 5, 5 ); gbc_lblRadius.gridx = 0; - gbc_lblRadius.gridy = 5; + gbc_lblRadius.gridy = 6; panelControl.add( lblRadius, gbc_lblRadius ); ftfRadius = new JFormattedTextField( NumberFormat.getNumberInstance() ); @@ -159,7 +174,7 @@ public CSVImporterPanel() gbc_ftfRadius.insets = new Insets( 5, 5, 5, 5 ); gbc_ftfRadius.fill = GridBagConstraints.HORIZONTAL; gbc_ftfRadius.gridx = 1; - gbc_ftfRadius.gridy = 5; + gbc_ftfRadius.gridy = 6; panelControl.add( ftfRadius, gbc_ftfRadius ); labelRadiusUnit = new JLabel(); @@ -167,7 +182,7 @@ public CSVImporterPanel() gbc_labelRadiusUnitl.anchor = GridBagConstraints.WEST; gbc_labelRadiusUnitl.insets = new Insets( 0, 0, 5, 5 ); gbc_labelRadiusUnitl.gridx = 3; - gbc_labelRadiusUnitl.gridy = 5; + gbc_labelRadiusUnitl.gridy = 6; panelControl.add( labelRadiusUnit, gbc_labelRadiusUnitl ); final JLabel lblXColumn = new JLabel( "X column:" ); @@ -175,7 +190,7 @@ public CSVImporterPanel() gbc_lblXColumn.anchor = GridBagConstraints.EAST; gbc_lblXColumn.insets = new Insets( 5, 5, 5, 5 ); gbc_lblXColumn.gridx = 0; - gbc_lblXColumn.gridy = 6; + gbc_lblXColumn.gridy = 7; panelControl.add( lblXColumn, gbc_lblXColumn ); comboBoxXCol = new JComboBox<>(); @@ -184,7 +199,7 @@ public CSVImporterPanel() gbc_comboBoxX.insets = new Insets( 5, 5, 5, 0 ); gbc_comboBoxX.fill = GridBagConstraints.HORIZONTAL; gbc_comboBoxX.gridx = 1; - gbc_comboBoxX.gridy = 6; + gbc_comboBoxX.gridy = 7; panelControl.add( comboBoxXCol, gbc_comboBoxX ); final JLabel lblYColumn = new JLabel( "Y column:" ); @@ -192,7 +207,7 @@ public CSVImporterPanel() gbc_lblYColumn.anchor = GridBagConstraints.EAST; gbc_lblYColumn.insets = new Insets( 5, 5, 5, 5 ); gbc_lblYColumn.gridx = 0; - gbc_lblYColumn.gridy = 7; + gbc_lblYColumn.gridy = 8; panelControl.add( lblYColumn, gbc_lblYColumn ); comboBoxYCol = new JComboBox<>(); @@ -201,7 +216,7 @@ public CSVImporterPanel() gbc_comboBoxY.insets = new Insets( 5, 5, 5, 0 ); gbc_comboBoxY.fill = GridBagConstraints.HORIZONTAL; gbc_comboBoxY.gridx = 1; - gbc_comboBoxY.gridy = 7; + gbc_comboBoxY.gridy = 8; panelControl.add( comboBoxYCol, gbc_comboBoxY ); final JLabel lblZColumn = new JLabel( "Z column:" ); @@ -209,7 +224,7 @@ public CSVImporterPanel() gbc_lblZColumn.anchor = GridBagConstraints.EAST; gbc_lblZColumn.insets = new Insets( 5, 5, 5, 5 ); gbc_lblZColumn.gridx = 0; - gbc_lblZColumn.gridy = 8; + gbc_lblZColumn.gridy = 9; panelControl.add( lblZColumn, gbc_lblZColumn ); comboBoxZCol = new JComboBox<>(); @@ -218,7 +233,7 @@ public CSVImporterPanel() gbc_comboBoxZ.insets = new Insets( 5, 5, 5, 0 ); gbc_comboBoxZ.fill = GridBagConstraints.HORIZONTAL; gbc_comboBoxZ.gridx = 1; - gbc_comboBoxZ.gridy = 8; + gbc_comboBoxZ.gridy = 9; panelControl.add( comboBoxZCol, gbc_comboBoxZ ); final JLabel lblFrameColumn = new JLabel( "Frame column:" ); @@ -226,7 +241,7 @@ public CSVImporterPanel() gbc_lblFrameColumn.anchor = GridBagConstraints.EAST; gbc_lblFrameColumn.insets = new Insets( 5, 5, 5, 5 ); gbc_lblFrameColumn.gridx = 0; - gbc_lblFrameColumn.gridy = 9; + gbc_lblFrameColumn.gridy = 10; panelControl.add( lblFrameColumn, gbc_lblFrameColumn ); comboBoxFrameCol = new JComboBox<>(); @@ -235,15 +250,33 @@ public CSVImporterPanel() gbc_comboBoxFrame.insets = new Insets( 5, 5, 5, 0 ); gbc_comboBoxFrame.fill = GridBagConstraints.HORIZONTAL; gbc_comboBoxFrame.gridx = 1; - gbc_comboBoxFrame.gridy = 9; + gbc_comboBoxFrame.gridy = 10; panelControl.add( comboBoxFrameCol, gbc_comboBoxFrame ); + final JSeparator separatorOptional = new JSeparator(); + final GridBagConstraints gbc_separatorOptional = new GridBagConstraints(); + gbc_separatorOptional.fill = GridBagConstraints.BOTH; + gbc_separatorOptional.gridwidth = 5; + gbc_separatorOptional.insets = new Insets( 5, 5, 5, 5 ); + gbc_separatorOptional.gridx = 0; + gbc_separatorOptional.gridy = 11; + panelControl.add( separatorOptional, gbc_separatorOptional ); + + final JLabel lblOptional = new JLabel( "Optional" ); + lblOptional.setFont( new Font( Font.SANS_SERIF, Font.ITALIC, 12 ) ); + final GridBagConstraints gbc_lblRadiusOptional = new GridBagConstraints(); + gbc_lblRadiusOptional.anchor = GridBagConstraints.EAST; + gbc_lblRadiusOptional.insets = new Insets( 0, 0, 5, 50 ); + gbc_lblRadiusOptional.gridx = 0; + gbc_lblRadiusOptional.gridy = 12; + panelControl.add( lblOptional, gbc_lblRadiusOptional ); + final JLabel lblTrackColumn = new JLabel( "Track column:" ); final GridBagConstraints gbc_lblTrackColumn = new GridBagConstraints(); gbc_lblTrackColumn.anchor = GridBagConstraints.EAST; gbc_lblTrackColumn.insets = new Insets( 5, 5, 5, 5 ); gbc_lblTrackColumn.gridx = 0; - gbc_lblTrackColumn.gridy = 10; + gbc_lblTrackColumn.gridy = 13; panelControl.add( lblTrackColumn, gbc_lblTrackColumn ); lblTrackColumn.setVisible( false ); // TODO @@ -253,7 +286,7 @@ public CSVImporterPanel() gbc_comboBoxTrackCol.insets = new Insets( 5, 5, 5, 0 ); gbc_comboBoxTrackCol.fill = GridBagConstraints.HORIZONTAL; gbc_comboBoxTrackCol.gridx = 1; - gbc_comboBoxTrackCol.gridy = 10; + gbc_comboBoxTrackCol.gridy = 13; panelControl.add( comboBoxTrackCol, gbc_comboBoxTrackCol ); chckbxImportTracks.addActionListener( ( e ) -> comboBoxTrackCol.setEnabled( chckbxImportTracks.isSelected() ) ); comboBoxTrackCol.setEnabled( chckbxImportTracks.isSelected() ); @@ -264,7 +297,7 @@ public CSVImporterPanel() gbc_lblQualityColumn.anchor = GridBagConstraints.EAST; gbc_lblQualityColumn.insets = new Insets( 5, 5, 5, 5 ); gbc_lblQualityColumn.gridx = 0; - gbc_lblQualityColumn.gridy = 11; + gbc_lblQualityColumn.gridy = 14; panelControl.add( lblQualityColumn, gbc_lblQualityColumn ); comboBoxQualityCol = new JComboBox<>(); @@ -273,15 +306,32 @@ public CSVImporterPanel() gbc_comboBoxQuality.insets = new Insets( 5, 5, 5, 0 ); gbc_comboBoxQuality.fill = GridBagConstraints.HORIZONTAL; gbc_comboBoxQuality.gridx = 1; - gbc_comboBoxQuality.gridy = 11; + gbc_comboBoxQuality.gridy = 14; panelControl.add( comboBoxQualityCol, gbc_comboBoxQuality ); - final JLabel lblNameColumn = new JLabel( "Name column:" ); + final JLabel lblRadiusColumn = new JLabel( "Radius column:" ); + final GridBagConstraints gbc_lblRadiusColumn = new GridBagConstraints(); + gbc_lblRadiusColumn.anchor = GridBagConstraints.EAST; + gbc_lblRadiusColumn.insets = new Insets( 5, 5, 5, 5 ); + gbc_lblRadiusColumn.gridx = 0; + gbc_lblRadiusColumn.gridy = 15; + panelControl.add( lblRadiusColumn, gbc_lblRadiusColumn ); + + comboBoxRadiusCol = new JComboBox<>(); + final GridBagConstraints gbc_comboBoxRadius = new GridBagConstraints(); + gbc_comboBoxRadius.gridwidth = 4; + gbc_comboBoxRadius.insets = new Insets( 5, 5, 5, 0 ); + gbc_comboBoxRadius.fill = GridBagConstraints.HORIZONTAL; + gbc_comboBoxRadius.gridx = 1; + gbc_comboBoxRadius.gridy = 15; + panelControl.add( comboBoxRadiusCol, gbc_comboBoxRadius ); + + final JLabel lblNameColumn = new JLabel( "Label column:" ); final GridBagConstraints gbc_lblNameColumn = new GridBagConstraints(); gbc_lblNameColumn.anchor = GridBagConstraints.EAST; gbc_lblNameColumn.insets = new Insets( 5, 5, 5, 5 ); gbc_lblNameColumn.gridx = 0; - gbc_lblNameColumn.gridy = 12; + gbc_lblNameColumn.gridy = 16; panelControl.add( lblNameColumn, gbc_lblNameColumn ); comboBoxNameCol = new JComboBox<>(); @@ -290,7 +340,7 @@ public CSVImporterPanel() gbc_comboBoxName.insets = new Insets( 5, 5, 5, 0 ); gbc_comboBoxName.fill = GridBagConstraints.HORIZONTAL; gbc_comboBoxName.gridx = 1; - gbc_comboBoxName.gridy = 12; + gbc_comboBoxName.gridy = 16; panelControl.add( comboBoxNameCol, gbc_comboBoxName ); final JLabel lblIdColumn = new JLabel( "ID column:" ); @@ -298,7 +348,7 @@ public CSVImporterPanel() gbc_lblIdColumn.anchor = GridBagConstraints.EAST; gbc_lblIdColumn.insets = new Insets( 5, 5, 5, 5 ); gbc_lblIdColumn.gridx = 0; - gbc_lblIdColumn.gridy = 13; + gbc_lblIdColumn.gridy = 17; panelControl.add( lblIdColumn, gbc_lblIdColumn ); comboBoxIDCol = new JComboBox<>(); @@ -307,15 +357,58 @@ public CSVImporterPanel() gbc_comboBoxID.gridwidth = 4; gbc_comboBoxID.fill = GridBagConstraints.HORIZONTAL; gbc_comboBoxID.gridx = 1; - gbc_comboBoxID.gridy = 13; + gbc_comboBoxID.gridy = 17; panelControl.add( comboBoxIDCol, gbc_comboBoxID ); + final JLabel lblParentIdColumn = new JLabel( "Parent ID column:" ); + final GridBagConstraints gbc_lblParentIdColumn = new GridBagConstraints(); + gbc_lblParentIdColumn.anchor = GridBagConstraints.EAST; + gbc_lblParentIdColumn.insets = new Insets( 5, 5, 5, 5 ); + gbc_lblParentIdColumn.gridx = 0; + gbc_lblParentIdColumn.gridy = 18; + panelControl.add( lblParentIdColumn, gbc_lblParentIdColumn ); + + comboBoxParentIdCol = new JComboBox<>(); + final GridBagConstraints gbc_comboBoxParentID = new GridBagConstraints(); + gbc_comboBoxParentID.insets = new Insets( 5, 5, 5, 0 ); + gbc_comboBoxParentID.gridwidth = 4; + gbc_comboBoxParentID.fill = GridBagConstraints.HORIZONTAL; + gbc_comboBoxParentID.gridx = 1; + gbc_comboBoxParentID.gridy = 18; + panelControl.add( comboBoxParentIdCol, gbc_comboBoxParentID ); + + final JLabel lblTagColumn = new JLabel( "Tag column:" ); + final GridBagConstraints gbc_lblTagColumn = new GridBagConstraints(); + gbc_lblTagColumn.anchor = GridBagConstraints.EAST; + gbc_lblTagColumn.insets = new Insets( 5, 5, 5, 5 ); + gbc_lblTagColumn.gridx = 0; + gbc_lblTagColumn.gridy = 19; + panelControl.add( lblTagColumn, gbc_lblTagColumn ); + + comboBoxTagCol = new JComboBox<>(); + final GridBagConstraints gbc_comboBoxTag = new GridBagConstraints(); + gbc_comboBoxTag.insets = new Insets( 5, 5, 5, 0 ); + gbc_comboBoxTag.gridwidth = 4; + gbc_comboBoxTag.fill = GridBagConstraints.HORIZONTAL; + gbc_comboBoxTag.gridx = 1; + gbc_comboBoxTag.gridy = 19; + panelControl.add( comboBoxTagCol, gbc_comboBoxTag ); + + final JSeparator separatorButton = new JSeparator(); + final GridBagConstraints gbc_separatorButton = new GridBagConstraints(); + gbc_separatorButton.fill = GridBagConstraints.BOTH; + gbc_separatorButton.gridwidth = 5; + gbc_separatorButton.insets = new Insets( 5, 5, 5, 5 ); + gbc_separatorButton.gridx = 0; + gbc_separatorButton.gridy = 20; + panelControl.add( separatorButton, gbc_separatorButton ); + final JPanel panelButtonExport = new JPanel(); final GridBagConstraints gbc_panelButtonExport = new GridBagConstraints(); gbc_panelButtonExport.anchor = GridBagConstraints.EAST; gbc_panelButtonExport.gridwidth = 5; gbc_panelButtonExport.gridx = 0; - gbc_panelButtonExport.gridy = 14; + gbc_panelButtonExport.gridy = 21; panelControl.add( panelButtonExport, gbc_panelButtonExport ); final FlowLayout flowLayout = ( FlowLayout ) panelButtonExport.getLayout(); flowLayout.setAlignment( FlowLayout.RIGHT ); diff --git a/src/main/java/org/mastodon/mamut/io/csv/plugin/ui/CSVImporterUIController.java b/src/main/java/org/mastodon/mamut/io/csv/plugin/ui/CSVImporterUIController.java index b36f814..b58a4eb 100644 --- a/src/main/java/org/mastodon/mamut/io/csv/plugin/ui/CSVImporterUIController.java +++ b/src/main/java/org/mastodon/mamut/io/csv/plugin/ui/CSVImporterUIController.java @@ -113,7 +113,10 @@ public void run() .labelColumnName( ( String ) view.comboBoxNameCol.getSelectedItem() ) .frameColumnName( ( String ) view.comboBoxFrameCol.getSelectedItem() ) .qualityColumnName( ( String ) view.comboBoxQualityCol.getSelectedItem() ) + .radiusColumnName( ( String ) view.comboBoxRadiusCol.getSelectedItem() ) .idColumnName( ( String ) view.comboBoxIDCol.getSelectedItem() ) + .parentIdColumnName( ( String ) view.comboBoxParentIdCol.getSelectedItem() ) + .tagColumnName( ( String ) view.comboBoxTagCol.getSelectedItem() ) .get(); if ( !importer.checkInput() || !importer.process() ) @@ -287,11 +290,17 @@ private boolean readHeaders() headers.add( NONE_COLUMN ); final String[] nonMandatory = headers.toArray( new String[] {} ); view.comboBoxQualityCol.setModel( new DefaultComboBoxModel<>( nonMandatory ) ); + view.comboBoxRadiusCol.setModel( new DefaultComboBoxModel<>( nonMandatory ) ); view.comboBoxNameCol.setModel( new DefaultComboBoxModel<>( nonMandatory ) ); view.comboBoxIDCol.setModel( new DefaultComboBoxModel<>( nonMandatory ) ); + view.comboBoxParentIdCol.setModel( new DefaultComboBoxModel<>( nonMandatory ) ); + view.comboBoxTagCol.setModel( new DefaultComboBoxModel<>( nonMandatory ) ); int idcol = headers.indexOf( NONE_COLUMN ); + int motheridcol = headers.indexOf( NONE_COLUMN ); + int tagcol = headers.indexOf( NONE_COLUMN ); int qualitycol = headers.indexOf( NONE_COLUMN ); + int radiuscol = headers.indexOf( NONE_COLUMN ); int namecol = headers.indexOf( NONE_COLUMN ); for ( int i = 0; i < nonMandatory.length; i++ ) { @@ -306,10 +315,22 @@ private boolean readHeaders() if ( current.toLowerCase().startsWith( "q" ) ) qualitycol = i; + + if ( current.toLowerCase().startsWith( "r" ) || current.toLowerCase().startsWith( "radius" ) ) + radiuscol = i; + + if ( current.toLowerCase().startsWith( "mother" ) ) + motheridcol = i; + + if ( current.toLowerCase().startsWith( "tag" ) ) + tagcol = i; } view.comboBoxIDCol.setSelectedIndex( idcol ); + view.comboBoxParentIdCol.setSelectedIndex( motheridcol ); + view.comboBoxTagCol.setSelectedIndex( tagcol ); view.comboBoxQualityCol.setSelectedIndex( qualitycol ); + view.comboBoxRadiusCol.setSelectedIndex( radiuscol ); view.comboBoxNameCol.setSelectedIndex( namecol ); } catch ( final FileNotFoundException e ) @@ -329,8 +350,11 @@ private void clearComboBoxes() comboBoxes.add( view.comboBoxZCol ); comboBoxes.add( view.comboBoxFrameCol ); comboBoxes.add( view.comboBoxQualityCol ); + comboBoxes.add( view.comboBoxRadiusCol ); comboBoxes.add( view.comboBoxNameCol ); comboBoxes.add( view.comboBoxIDCol ); + comboBoxes.add( view.comboBoxParentIdCol ); + comboBoxes.add( view.comboBoxTagCol ); comboBoxes.add( view.comboBoxTrackCol ); for ( final JComboBox< String > cb : comboBoxes ) cb.setModel( new DefaultComboBoxModel<>() ); diff --git a/src/test/java/org/mastodon/mamut/StartImageJ.java b/src/test/java/org/mastodon/mamut/StartImageJ.java new file mode 100644 index 0000000..bd458e0 --- /dev/null +++ b/src/test/java/org/mastodon/mamut/StartImageJ.java @@ -0,0 +1,54 @@ +/*- + * #%L + * mastodon-deep-lineage + * %% + * Copyright (C) 2022 - 2024 Stefan Hahmann + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut; + +import org.scijava.Context; +import org.scijava.ui.UIService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.invoke.MethodHandles; + +/** + * Shows the ImageJ main window, with Mastodon installed. + * + * @author Stefan Hahmann + */ +public class StartImageJ +{ + private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() ); + + public static void main( String... args ) + { + logger.info( "Starting ImageJ..." ); + Context context = new Context(); + UIService uiService = context.service( UIService.class ); + uiService.showUI(); + } +} diff --git a/src/test/java/org/mastodon/mamut/io/csv/CSVImporterTest.java b/src/test/java/org/mastodon/mamut/io/csv/CSVImporterTest.java index 55e357f..c446134 100644 --- a/src/test/java/org/mastodon/mamut/io/csv/CSVImporterTest.java +++ b/src/test/java/org/mastodon/mamut/io/csv/CSVImporterTest.java @@ -28,14 +28,23 @@ */ package org.mastodon.mamut.io.csv; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import java.io.IOException; import java.net.URL; +import java.util.Iterator; +import java.util.List; import org.junit.Test; import org.mastodon.mamut.model.Model; +import org.mastodon.mamut.model.ModelGraph; +import org.mastodon.mamut.model.Spot; +import org.mastodon.model.tag.ObjTagMap; +import org.mastodon.model.tag.ObjTags; +import org.mastodon.model.tag.TagSetStructure; import org.mastodon.tracking.mamut.detection.DetectionQualityFeature; import mpicbg.spim.data.SpimDataException; @@ -81,4 +90,61 @@ public void test() throws IOException, SpimDataException assertEquals( "Imported incorrect value for quality.", expectedQ[ id ], quality.value( s ), 1e-3 ); } ); } + + @Test + public void testImportWithRadiusTagsLinks() + { + final URL urlCSV = CSVImporterTest.class.getResource( "TestCSVImportTagParentIdRadius.csv" ); + assertNotNull( urlCSV ); + + final Model model = new Model(); + + final String csvFilePath = urlCSV.getPath(); + final CSVImporter importer = CSVImporter.create() + .model( model ) + .csvFilePath( csvFilePath ) + .radius( 3. ) + .xColumnName( "POSITION_X" ) + .yColumnName( "POSITION_Y" ) + .zColumnName( "POSITION_Z" ) + .frameColumnName( "FRAME" ) + .idColumnName( "ID" ) + .tagColumnName( "TAG" ) + .parentIdColumnName( "PARENT_ID" ) + .radiusColumnName( "RADIUS" ) + .get(); + if ( !importer.checkInput() || !importer.process() ) + fail( importer.getErrorMessage() ); + + ModelGraph graph = model.getGraph(); + assertEquals( "Incorrect number of spots imported.", 5, graph.vertices().size() ); + assertEquals( "Incorrect number of links imported.", 2, graph.edges().size() ); + + Iterator< Spot > it = graph.vertices().iterator(); + it.next(); + it.next(); + it.next(); + it.next(); + Spot spot4 = it.next(); + List< TagSetStructure.TagSet > tagSets = model.getTagSetModel().getTagSetStructure().getTagSets(); + TagSetStructure.TagSet tagSet = tagSets.get( 0 ); + TagSetStructure.Tag tag1 = tagSet.getTags().get( 0 ); + TagSetStructure.Tag tag2 = tagSet.getTags().get( 1 ); + ObjTags< Spot > vertexTags = model.getTagSetModel().getVertexTags(); + ObjTagMap< Spot, TagSetStructure.Tag > spotToTagMap = vertexTags.tags( tagSet ); + + assertEquals( "4", spot4.getLabel() ); + assertEquals( 1, spot4.incomingEdges().size() ); + assertEquals( 0, spot4.outgoingEdges().size() ); + assertEquals( 4d, spot4.getBoundingSphereRadiusSquared(), 0d ); + assertArrayEquals( new double[] { 36d, 12d, 9d }, spot4.positionAsDoubleArray(), 0d ); + assertEquals( 2, spot4.getTimepoint() ); + assertEquals( 1, tagSets.size() ); + assertEquals( "Imported Tags", tagSet.getName() ); + assertEquals( 2, tagSet.getTags().size() ); + assertEquals( "tag1", tag1.label() ); + assertEquals( "tag2", tag2.label() ); + TagSetStructure.Tag actualTag = spotToTagMap.get( spot4 ); + assertEquals( tag2.label(), actualTag.label() ); + } } diff --git a/src/test/resources/org/mastodon/mamut/io/csv/TestCSVImportTagParentIdRadius.csv b/src/test/resources/org/mastodon/mamut/io/csv/TestCSVImportTagParentIdRadius.csv new file mode 100644 index 0000000..2fa20c4 --- /dev/null +++ b/src/test/resources/org/mastodon/mamut/io/csv/TestCSVImportTagParentIdRadius.csv @@ -0,0 +1,6 @@ +ID;QUALITY;POSITION_X;POSITION_Y;POSITION_Z;FRAME;RADIUS;MEDIAN_INTENSITY;TAG;PARENT_ID +0; 50.943; 11.000; 11.000; 5.000; 0; 5.73; 51; tag1; -1 +1; 206.877; 30.000; 31.000; 6.000; 0; 4.; 119; tag1; -1 +2; 130.664; 36.000; 12.000; 7.000; 0; 2; 197; tag2; -1 +3; 206.877; 30.000; 31.000; 8.000; 1; 4.; 119; tag2; 2 +4; 130.664; 36.000; 12.000; 9.000; 2; 2; 197; tag2; 3