From ad962ac912384e2a4c295d21c9ce4f2cef9dbcd1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 30 May 2024 15:29:59 +0200 Subject: [PATCH 1/8] Update README.md - add link to public doc --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3093f3b84..001c32006 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ ![Mastodon logo](doc/Mastodon-logo-512x512.png) +Read the user and developer documentation [here](https://mastodon.readthedocs.io/). + # Mastodon – a large-scale tracking and track-editing framework for large, multi-view images. Modern microscopy technologies such as light sheet microscopy allows live sample *in toto* 3D imaging with high spatial and temporal resolution. Such images will be 3D over time, possibly multi-channels and multi-view. Computational analysis of these images promises new insights in cellular, developmental and stem cells biology. However, a single image can amount to several terabytes, and in turn, the automated or semi-automated analysis of these large images can generate a vast amount of annotations. The challenges of big data are then met twice: first by dealing with a very large image, and second with generating large annotations from this image. They will make interacting and analyzing the data especially difficult. From 82448d2c233386946969a2d7d5b6761a4d3da461 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 28 Mar 2024 16:33:52 +0100 Subject: [PATCH 2/8] Add new method getFirstSpot to TreeUtils * Convenience method to retrieve the first spot of a branch spot --- src/main/java/org/mastodon/util/TreeUtils.java | 15 +++++++++++++++ .../java/org/mastodon/util/TreeUtilsTest.java | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/org/mastodon/util/TreeUtils.java b/src/main/java/org/mastodon/util/TreeUtils.java index 0a8734354..c7b7a511f 100644 --- a/src/main/java/org/mastodon/util/TreeUtils.java +++ b/src/main/java/org/mastodon/util/TreeUtils.java @@ -39,6 +39,7 @@ import org.mastodon.graph.Vertex; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.Spot; +import org.mastodon.mamut.model.branch.BranchSpot; public class TreeUtils { @@ -255,4 +256,18 @@ public static int getMaxTimepoint( final Model model ) max = Math.max( max, spot.getTimepoint() ); return max; } + + /** + * Gets the first {@link Spot} within the given {@link BranchSpot}. + * @param model the {@link Model} to which the {@link BranchSpot} belongs + * @param branchSpot the {@link BranchSpot} to query + * @return the first {@link Spot} + */ + public static Spot getFirstSpot( final Model model, final BranchSpot branchSpot ) + { + Spot ref = model.getGraph().vertexRef(); + Spot first = model.getBranchGraph().getFirstLinkedVertex( branchSpot, ref ); + model.getGraph().releaseRef( ref ); + return first; + } } diff --git a/src/test/java/org/mastodon/util/TreeUtilsTest.java b/src/test/java/org/mastodon/util/TreeUtilsTest.java index 803f991f7..b6407bbe4 100644 --- a/src/test/java/org/mastodon/util/TreeUtilsTest.java +++ b/src/test/java/org/mastodon/util/TreeUtilsTest.java @@ -191,4 +191,14 @@ public void testGetMaxTimepoint() assertEquals( 3, TreeUtils.getMaxTimepoint( new ExampleGraph1().getModel() ) ); assertEquals( 7, TreeUtils.getMaxTimepoint( new ExampleGraph2().getModel() ) ); } + + @Test + public void testGetFirstSpot() + { + ExampleGraph1 exampleGraph1 = new ExampleGraph1(); + assertEquals( exampleGraph1.spot0, TreeUtils.getFirstSpot( exampleGraph1.getModel(), exampleGraph1.branchSpotA ) ); + + ExampleGraph2 exampleGraph2 = new ExampleGraph2(); + assertEquals( exampleGraph2.spot5, TreeUtils.getFirstSpot( exampleGraph2.getModel(), exampleGraph2.branchSpotD ) ); + } } From cb30695923dc0f8df47152d7f22595ada341bfee Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 28 Mar 2024 16:31:52 +0100 Subject: [PATCH 3/8] Add 2 new methods to TagSetUtils * getTagSetNames, which return the names of the tag sets in the model * getTagValue(), which returns the value of a branchSpot within a specified tag set * unit tests for the 2 new methods are contained --- .../java/org/mastodon/util/TagSetUtils.java | 31 ++++++++++++++++ .../org/mastodon/util/TagSetUtilsTest.java | 36 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/main/java/org/mastodon/util/TagSetUtils.java b/src/main/java/org/mastodon/util/TagSetUtils.java index 1952be552..9820880c3 100644 --- a/src/main/java/org/mastodon/util/TagSetUtils.java +++ b/src/main/java/org/mastodon/util/TagSetUtils.java @@ -28,7 +28,9 @@ */ package org.mastodon.util; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -36,6 +38,7 @@ import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.ModelGraph; import org.mastodon.mamut.model.Spot; +import org.mastodon.mamut.model.branch.BranchSpot; import org.mastodon.model.tag.ObjTagMap; import org.mastodon.model.tag.TagSetModel; import org.mastodon.model.tag.TagSetStructure; @@ -351,4 +354,32 @@ public static TagSetStructure.Tag findTag( final TagSetStructure.TagSet tagSet, return tag; throw new NoSuchElementException( "Did not find a tag with the given label: " + tagLabel ); } + + /** + * Returns the names of all tag sets in the model. + * @param model the model to get the tag-set model from. + * @return the names of all tag sets in the model. + */ + public static List< String > getTagSetNames( final Model model ) + { + List< String > tagSetNames = new ArrayList<>(); + model.getTagSetModel().getTagSetStructure().getTagSets().forEach( tagSet -> tagSetNames.add( tagSet.getName() ) ); + return tagSetNames; + } + + /** + * Gets the tag label of the first spot in the given branchSpot within the given tagSet. + * @param model the model to which the branch belongs + * @param branchSpot the branch spot + * @param tagSet the tag set + * @return the tag label + */ + public static String getTagLabel( final Model model, final BranchSpot branchSpot, final TagSetStructure.TagSet tagSet ) + { + if ( model == null || branchSpot == null || tagSet == null ) + return null; + Spot first = TreeUtils.getFirstSpot( model, branchSpot ); + TagSetStructure.Tag tag = TagSetUtils.getBranchTag( model, tagSet, first ); + return tag == null ? null : tag.label(); + } } diff --git a/src/test/java/org/mastodon/util/TagSetUtilsTest.java b/src/test/java/org/mastodon/util/TagSetUtilsTest.java index 48f0a1421..b8688c36d 100644 --- a/src/test/java/org/mastodon/util/TagSetUtilsTest.java +++ b/src/test/java/org/mastodon/util/TagSetUtilsTest.java @@ -38,9 +38,13 @@ import java.awt.Color; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class TagSetUtilsTest @@ -110,4 +114,36 @@ public void tagSpotAndOutgoingEdges() // 3 links are tagged: spot0 -> spot1, spot2 -> spot3, spot2 -> spot11 assertEquals( 3, model.getTagSetModel().getEdgeTags().getTaggedWith( tag0 ).size() ); } + + @Test + public void testGetTagSetNames() + { + ExampleGraph1 exampleGraph1 = new ExampleGraph1(); + String tagSetName1 = "TagSet1"; + String tagSetName2 = "TagSet2"; + String tagSetName3 = "TagSet2"; + Collection< Pair< String, Integer > > emptyTagsAndColors = Collections.emptyList(); + TagSetUtils.addNewTagSetToModel( exampleGraph1.getModel(), tagSetName1, emptyTagsAndColors ); + TagSetUtils.addNewTagSetToModel( exampleGraph1.getModel(), tagSetName2, emptyTagsAndColors ); + TagSetUtils.addNewTagSetToModel( exampleGraph1.getModel(), tagSetName3, emptyTagsAndColors ); + Collection< String > tagSetNames = TagSetUtils.getTagSetNames( exampleGraph1.getModel() ); + List< String > expected = Arrays.asList( tagSetName1, tagSetName2, tagSetName3 ); + assertEquals( expected, tagSetNames ); + } + + @Test + public void testGetTagLabel() + { + ExampleGraph2 exampleGraph2 = new ExampleGraph2(); + String tagSetName = "TagSet"; + Pair< String, Integer > tag0 = Pair.of( "Tag", 0 ); + Collection< Pair< String, Integer > > tagAndColor = Collections.singletonList( tag0 ); + TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( exampleGraph2.getModel(), tagSetName, tagAndColor ); + TagSetStructure.Tag tag = tagSet.getTags().get( 0 ); + TagSetUtils.tagBranch( exampleGraph2.getModel(), tagSet, tag, exampleGraph2.spot5 ); + assertEquals( tag.label(), TagSetUtils.getTagLabel( exampleGraph2.getModel(), exampleGraph2.branchSpotD, tagSet ) ); + assertNull( TagSetUtils.getTagLabel( null, exampleGraph2.branchSpotD, tagSet ) ); + assertNull( TagSetUtils.getTagLabel( exampleGraph2.getModel(), null, tagSet ) ); + assertNull( TagSetUtils.getTagLabel( exampleGraph2.getModel(), exampleGraph2.branchSpotD, null ) ); + } } From c5a186bb516fb91002c145d34bc884c3c3450fd5 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 17 May 2024 16:19:16 +0200 Subject: [PATCH 4/8] Add Spot ref as parameter to TagSetUtils.getTagLabel() and TreeUtils.getFirstSpot() methods Previously the methods created refs that the caller could not release --- src/main/java/org/mastodon/util/TagSetUtils.java | 4 ++-- src/main/java/org/mastodon/util/TreeUtils.java | 7 ++----- src/test/java/org/mastodon/util/TagSetUtilsTest.java | 10 ++++++---- src/test/java/org/mastodon/util/TreeUtilsTest.java | 8 ++++++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/mastodon/util/TagSetUtils.java b/src/main/java/org/mastodon/util/TagSetUtils.java index 9820880c3..0afd96d3e 100644 --- a/src/main/java/org/mastodon/util/TagSetUtils.java +++ b/src/main/java/org/mastodon/util/TagSetUtils.java @@ -374,11 +374,11 @@ public static List< String > getTagSetNames( final Model model ) * @param tagSet the tag set * @return the tag label */ - public static String getTagLabel( final Model model, final BranchSpot branchSpot, final TagSetStructure.TagSet tagSet ) + public static String getTagLabel( final Model model, final BranchSpot branchSpot, final TagSetStructure.TagSet tagSet, final Spot ref ) { if ( model == null || branchSpot == null || tagSet == null ) return null; - Spot first = TreeUtils.getFirstSpot( model, branchSpot ); + Spot first = TreeUtils.getFirstSpot( model, branchSpot, ref ); TagSetStructure.Tag tag = TagSetUtils.getBranchTag( model, tagSet, first ); return tag == null ? null : tag.label(); } diff --git a/src/main/java/org/mastodon/util/TreeUtils.java b/src/main/java/org/mastodon/util/TreeUtils.java index c7b7a511f..b1c6ae44b 100644 --- a/src/main/java/org/mastodon/util/TreeUtils.java +++ b/src/main/java/org/mastodon/util/TreeUtils.java @@ -263,11 +263,8 @@ public static int getMaxTimepoint( final Model model ) * @param branchSpot the {@link BranchSpot} to query * @return the first {@link Spot} */ - public static Spot getFirstSpot( final Model model, final BranchSpot branchSpot ) + public static Spot getFirstSpot( final Model model, final BranchSpot branchSpot, final Spot ref ) { - Spot ref = model.getGraph().vertexRef(); - Spot first = model.getBranchGraph().getFirstLinkedVertex( branchSpot, ref ); - model.getGraph().releaseRef( ref ); - return first; + return model.getBranchGraph().getFirstLinkedVertex( branchSpot, ref ); } } diff --git a/src/test/java/org/mastodon/util/TagSetUtilsTest.java b/src/test/java/org/mastodon/util/TagSetUtilsTest.java index b8688c36d..34c30b7b6 100644 --- a/src/test/java/org/mastodon/util/TagSetUtilsTest.java +++ b/src/test/java/org/mastodon/util/TagSetUtilsTest.java @@ -141,9 +141,11 @@ public void testGetTagLabel() TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( exampleGraph2.getModel(), tagSetName, tagAndColor ); TagSetStructure.Tag tag = tagSet.getTags().get( 0 ); TagSetUtils.tagBranch( exampleGraph2.getModel(), tagSet, tag, exampleGraph2.spot5 ); - assertEquals( tag.label(), TagSetUtils.getTagLabel( exampleGraph2.getModel(), exampleGraph2.branchSpotD, tagSet ) ); - assertNull( TagSetUtils.getTagLabel( null, exampleGraph2.branchSpotD, tagSet ) ); - assertNull( TagSetUtils.getTagLabel( exampleGraph2.getModel(), null, tagSet ) ); - assertNull( TagSetUtils.getTagLabel( exampleGraph2.getModel(), exampleGraph2.branchSpotD, null ) ); + Spot ref = exampleGraph2.getModel().getGraph().vertexRef(); + assertEquals( tag.label(), TagSetUtils.getTagLabel( exampleGraph2.getModel(), exampleGraph2.branchSpotD, tagSet, ref ) ); + assertNull( TagSetUtils.getTagLabel( null, exampleGraph2.branchSpotD, tagSet, ref ) ); + assertNull( TagSetUtils.getTagLabel( exampleGraph2.getModel(), null, tagSet, ref ) ); + assertNull( TagSetUtils.getTagLabel( exampleGraph2.getModel(), exampleGraph2.branchSpotD, null, ref ) ); + exampleGraph2.getModel().getGraph().releaseRef( ref ); } } diff --git a/src/test/java/org/mastodon/util/TreeUtilsTest.java b/src/test/java/org/mastodon/util/TreeUtilsTest.java index b6407bbe4..fbaf9df3c 100644 --- a/src/test/java/org/mastodon/util/TreeUtilsTest.java +++ b/src/test/java/org/mastodon/util/TreeUtilsTest.java @@ -196,9 +196,13 @@ public void testGetMaxTimepoint() public void testGetFirstSpot() { ExampleGraph1 exampleGraph1 = new ExampleGraph1(); - assertEquals( exampleGraph1.spot0, TreeUtils.getFirstSpot( exampleGraph1.getModel(), exampleGraph1.branchSpotA ) ); + Spot ref = exampleGraph1.getModel().getGraph().vertexRef(); + assertEquals( exampleGraph1.spot0, TreeUtils.getFirstSpot( exampleGraph1.getModel(), exampleGraph1.branchSpotA, ref ) ); + exampleGraph1.getModel().getGraph().releaseRef( ref ); ExampleGraph2 exampleGraph2 = new ExampleGraph2(); - assertEquals( exampleGraph2.spot5, TreeUtils.getFirstSpot( exampleGraph2.getModel(), exampleGraph2.branchSpotD ) ); + ref = exampleGraph2.getModel().getGraph().vertexRef(); + assertEquals( exampleGraph2.spot5, TreeUtils.getFirstSpot( exampleGraph2.getModel(), exampleGraph2.branchSpotD, ref ) ); + exampleGraph2.getModel().getGraph().releaseRef( ref ); } } From 541be19b95ba8852e3e0129591d5070b29d99d30 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 24 May 2024 16:49:09 +0200 Subject: [PATCH 5/8] Import the 'fix image path' command from mastodon-tomancak. --- .../java/org/mastodon/mamut/MainWindow.java | 2 + .../org/mastodon/mamut/MamutMenuBuilder.java | 1 + .../mastodon/mamut/io/DatasetPathDialog.java | 411 ++++++++++++++++++ .../org/mastodon/mamut/io/ProjectActions.java | 29 +- 4 files changed, 441 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/io/DatasetPathDialog.java diff --git a/src/main/java/org/mastodon/mamut/MainWindow.java b/src/main/java/org/mastodon/mamut/MainWindow.java index f5d5ada30..887cc6bb1 100644 --- a/src/main/java/org/mastodon/mamut/MainWindow.java +++ b/src/main/java/org/mastodon/mamut/MainWindow.java @@ -320,6 +320,8 @@ public static void addMenus( final ViewMenu menu, final ActionMap actionMap ) item( ProjectActions.SAVE_PROJECT ), item( ProjectActions.SAVE_PROJECT_AS ), separator(), + item( ProjectActions.FIX_DATASET_PATH ), + separator(), // item( ProjectActions.IMPORT_TGMM ), // item( ProjectActions.IMPORT_SIMI ), // item( ProjectActions.IMPORT_MAMUT ), diff --git a/src/main/java/org/mastodon/mamut/MamutMenuBuilder.java b/src/main/java/org/mastodon/mamut/MamutMenuBuilder.java index 7ed8063f1..be2d461ac 100644 --- a/src/main/java/org/mastodon/mamut/MamutMenuBuilder.java +++ b/src/main/java/org/mastodon/mamut/MamutMenuBuilder.java @@ -59,6 +59,7 @@ public class MamutMenuBuilder extends ViewMenuBuilder menuTexts.put( ProjectActions.LOAD_PROJECT, "Load Project" ); menuTexts.put( ProjectActions.SAVE_PROJECT, "Save Project" ); menuTexts.put( ProjectActions.SAVE_PROJECT_AS, "Save Project As..." ); + menuTexts.put( ProjectActions.FIX_DATASET_PATH, "Fix Image Path" ); menuTexts.put( ProjectActions.IMPORT_TGMM, "Import TGMM tracks" ); menuTexts.put( ProjectActions.IMPORT_SIMI, "Import Simi BioCell tracks" ); menuTexts.put( ProjectActions.IMPORT_MAMUT, "Import MaMuT project" ); diff --git a/src/main/java/org/mastodon/mamut/io/DatasetPathDialog.java b/src/main/java/org/mastodon/mamut/io/DatasetPathDialog.java new file mode 100644 index 000000000..f49b02042 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/DatasetPathDialog.java @@ -0,0 +1,411 @@ +/*- + * #%L + * mastodon-tomancak + * %% + * Copyright (C) 2018 - 2024 Tobias Pietzsch + * %% + * 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.io; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.SpinnerNumberModel; +import javax.swing.WindowConstants; + +import org.mastodon.mamut.ProjectModel; +import org.mastodon.mamut.io.project.MamutProject; +import org.mastodon.ui.util.ExtensionFileFilter; +import org.mastodon.ui.util.FileChooser; + +public class DatasetPathDialog extends JDialog +{ + + private static final long serialVersionUID = 1L; + + private final Path projectRootWoMastodonFile; + + public DatasetPathDialog( final Frame owner, final ProjectModel appModel ) + { + super( owner, "Edit Dataset Path...", true ); + + final MamutProject project = appModel.getProject(); + final boolean projectInContainerFile = project.getProjectRoot().isFile(); + projectRootWoMastodonFile = projectInContainerFile ? project.getProjectRoot().toPath().getParent() : project.getProjectRoot().toPath(); + + final JPanel content = new JPanel(); + content.setLayout( new GridBagLayout() ); + content.setBorder( BorderFactory.createEmptyBorder( 30, 20, 20, 20 ) ); + + final GridBagConstraints c = new GridBagConstraints(); + c.gridy = 0; + c.gridx = 0; + c.anchor = GridBagConstraints.LINE_START; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0.0; + content.add( new JLabel( "Current project path: " ), c ); + + c.gridx = 1; + final JLabel rootPathTextField = new JLabel( tellProjectPath( !project.isDatasetXmlPathRelative() ) ); + content.add( rootPathTextField, c ); + + ++c.gridy; + c.gridx = 0; + content.add( new JLabel( "Current BDV dataset path: " ), c ); + + String initialPathValue = tellXmlFilePath( convertFromWinOrLeaveAsIs( project.getDatasetXmlFile().toPath() ), + !project.isDatasetXmlPathRelative() ); + if ( projectInContainerFile && project.isDatasetXmlPathRelative() && initialPathValue.startsWith( ".." ) ) + { + // this is hacky, it removes the leading "../" or "..\" from the + // (for sure!) relative path, which + // was here to "get out of" the .mastodon container file, and which + // also confuses the Java Path functions... + initialPathValue = initialPathValue.substring( 3 ); + } + final JTextField xmlPathTextField = new JTextField( initialPathValue ); + c.gridx = 1; + c.weightx = 1.0; + content.add( xmlPathTextField, c ); + xmlPathTextField.setColumns( 50 ); + + final JButton browseButton = new JButton( "Browse" ); + c.gridx = 2; + c.weightx = 0.0; + content.add( browseButton, c ); + + ++c.gridy; + c.gridx = 0; + content.add( new JLabel( "Store as absolute path: " ), c ); + final JCheckBox storeAbsoluteCheckBox = new JCheckBox(); + storeAbsoluteCheckBox.setSelected( !project.isDatasetXmlPathRelative() ); + + c.gridx = 1; + content.add( storeAbsoluteCheckBox, c ); + + c.gridx = 2; + final JButton testButton = new JButton( "Test Path" ); + content.add( testButton, c ); + // + final Color normalBgColor = xmlPathTextField.getBackground(); + testButton.addChangeListener( l -> { + if ( testButton.getModel().isPressed() ) + { + final File f = new File( tellXmlFilePath( Paths.get( xmlPathTextField.getText() ), true ) ); + xmlPathTextField.setBackground( f.isFile() ? Color.GREEN : Color.RED ); + } + else + { + xmlPathTextField.setBackground( normalBgColor ); + } + } ); + + final JPanel infoLine = new JPanel(); + infoLine.setLayout( new BoxLayout( infoLine, BoxLayout.LINE_AXIS ) ); + infoLine.add( Box.createHorizontalGlue() ); + infoLine.add( new JLabel( "Save the project eventually to make the changes permanent." ) ); + + final JPanel buttons = new JPanel(); + final JButton dummy = new JButton( "I want dummy image data instead" ); + final JButton cancel = new JButton( "Cancel" ); + final JButton ok = new JButton( "OK" ); + buttons.setLayout( new BoxLayout( buttons, BoxLayout.LINE_AXIS ) ); + buttons.add( dummy ); + buttons.add( Box.createHorizontalGlue() ); + buttons.add( cancel ); + buttons.add( ok ); + + getContentPane().add( content, BorderLayout.NORTH ); + getContentPane().add( infoLine, BorderLayout.CENTER ); + getContentPane().add( buttons, BorderLayout.SOUTH ); + + class Browse implements ActionListener + { + private final JTextField path; + + private final String dialogTitle; + + public Browse( final JTextField path, final String dialogTitle ) + { + this.path = path; + this.dialogTitle = dialogTitle; + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + final File file = FileChooser.chooseFile( + true, + DatasetPathDialog.this, + tellXmlFilePath( Paths.get( path.getText() ), true ), + new ExtensionFileFilter( "xml" ), + dialogTitle, + FileChooser.DialogType.LOAD, + FileChooser.SelectionMode.FILES_ONLY ); + if ( file != null ) + path.setText( tellXmlFilePath( file.toPath(), storeAbsoluteCheckBox.isSelected() ) ); + } + } + browseButton.addActionListener( new Browse( xmlPathTextField, "Select BDV XML file" ) ); + + storeAbsoluteCheckBox.addActionListener( e -> { + rootPathTextField.setText( tellProjectPath( storeAbsoluteCheckBox.isSelected() ) ); + xmlPathTextField.setText( tellXmlFilePath( Paths.get( xmlPathTextField.getText() ), storeAbsoluteCheckBox.isSelected() ) ); + } ); + + ok.addActionListener( e -> { + final String path = xmlPathTextField.getText(); + final boolean relative = !storeAbsoluteCheckBox.isSelected(); + + // always give the absolute path! -- the underlying spim_data + // library "relativyfies" + // the path on its own (provided the set..Xml..Relative() is set to + // true) + final File xmlFilePath = new File( tellXmlFilePath( Paths.get( path ), true ) ); + project.setDatasetXmlFile( xmlFilePath ); + project.setDatasetXmlPathRelative( relative ); + System.out.println( "Storing BDV xml path as " + xmlFilePath + " (should be relative: " + relative + ")" ); + close(); + } ); + + cancel.addActionListener( e -> close() ); + + dummy.addActionListener( e -> { + final DummyImageDataParams params = new DummyImageDataParams( owner, appModel ); + if ( !params.wasOkClosed ) + return; + + rootPathTextField.setText( tellProjectPath( true ) ); + xmlPathTextField.setText( "x=" + params.xSize + + " y=" + params.ySize + " z=" + params.zSize + + " sx=1 sy=1 sz=1 t=" + params.timePoints + ".dummy" ); + storeAbsoluteCheckBox.setSelected( false ); + storeAbsoluteCheckBox.setEnabled( false ); + browseButton.setEnabled( false ); + testButton.setEnabled( false ); + } ); + + addWindowListener( new WindowAdapter() + { + @Override + public void windowClosing( final WindowEvent e ) + { + close(); + } + } ); + + final ActionMap am = getRootPane().getActionMap(); + final InputMap im = getRootPane().getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); + final Object hideKey = new Object(); + final Action hideAction = new AbstractAction() + { + @Override + public void actionPerformed( final ActionEvent e ) + { + close(); + } + + private static final long serialVersionUID = 1L; + }; + im.put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), hideKey ); + am.put( hideKey, hideAction ); + + pack(); + setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); + } + + private void close() + { + setVisible( false ); + dispose(); + } + + private static Path convertFromWinOrLeaveAsIs( final Path relativePath ) + { + return Paths.get( relativePath.toString().replace( "\\", "/" ) ); + } + + private String tellXmlFilePath( final Path xmlFilePath, final boolean tellAsAbsolutePath ) + { + if ( tellAsAbsolutePath ) + { + if ( xmlFilePath.isAbsolute() ) + return xmlFilePath.toString(); + else + return projectRootWoMastodonFile.resolve( xmlFilePath ).toString(); + } + else + { + // should create relative paths + if ( xmlFilePath.isAbsolute() ) + return projectRootWoMastodonFile.relativize( xmlFilePath ).toString(); + else + return xmlFilePath.toString(); + } + } + + private String tellProjectPath( final boolean tellAsAbsolutePath ) + { + return tellAsAbsolutePath ? "(this path is not considered now)" : projectRootWoMastodonFile.toString(); + } + + private static class DummyImageDataParams extends JDialog + { + + private static final long serialVersionUID = 1L; + + public DummyImageDataParams( final Frame owner, final ProjectModel appModel ) + { + super( owner, "Adjust Dummy Dataset Parameters", true ); + + final JPanel content = new JPanel(); + content.setLayout( new GridBagLayout() ); + content.setBorder( BorderFactory.createEmptyBorder( 10, 10, 10, 10 ) ); + + final GridBagConstraints c = new GridBagConstraints(); + c.anchor = GridBagConstraints.LINE_START; + c.fill = GridBagConstraints.HORIZONTAL; + c.gridy = 0; + + final JSpinner xSpinner = new JSpinner( new SpinnerNumberModel( xSize, 10, 10000, 100 ) ); + final JSpinner ySpinner = new JSpinner( new SpinnerNumberModel( ySize, 10, 10000, 100 ) ); + final JSpinner zSpinner = new JSpinner( new SpinnerNumberModel( zSize, 10, 10000, 100 ) ); + final JSpinner tpSpinner = new JSpinner( new SpinnerNumberModel( timePoints, 10, 10000, 100 ) ); + + c.gridx = 0; + content.add( new JLabel( "Size in pixels in X: " ), c ); + c.gridx = 1; + content.add( xSpinner, c ); + + c.gridy++; + c.gridx = 0; + content.add( new JLabel( "Size in pixels in Y: " ), c ); + c.gridx = 1; + content.add( ySpinner, c ); + + c.gridy++; + c.gridx = 0; + content.add( new JLabel( "Size in pixels in Z: " ), c ); + c.gridx = 1; + content.add( zSpinner, c ); + + c.gridy++; + c.gridx = 0; + content.add( new JLabel( "Number of time points: " ), c ); + c.gridx = 1; + content.add( tpSpinner, c ); + + c.gridy++; + c.gridx = 0; + final JButton fromSpots = new JButton( "From spots" ); + if ( appModel == null ) + { + fromSpots.setEnabled( false ); + } + else + { + fromSpots.addActionListener( l -> { + final double[] max = new double[ 3 ]; + final double[] pos = new double[ 3 ]; + appModel.getModel() + .getSpatioTemporalIndex() + .forEach( s -> { + s.localize( pos ); + if ( pos[ 0 ] > max[ 0 ] ) + max[ 0 ] = pos[ 0 ]; + if ( pos[ 1 ] > max[ 1 ] ) + max[ 1 ] = pos[ 1 ]; + if ( pos[ 2 ] > max[ 2 ] ) + max[ 2 ] = pos[ 2 ]; + } ); + xSpinner.setValue( ( int ) Math.floor( 1.1 * max[ 0 ] ) ); + ySpinner.setValue( ( int ) Math.floor( 1.1 * max[ 1 ] ) ); + zSpinner.setValue( ( int ) Math.floor( 1.1 * max[ 2 ] ) ); + tpSpinner.setValue( appModel.getMaxTimepoint() + 1 ); + } ); + } + content.add( fromSpots, c ); + // + c.gridx = 1; + final JButton ok = new JButton( "OK" ); + ok.addActionListener( l -> { + xSize = ( int ) ( xSpinner.getValue() ); + ySize = ( int ) ( ySpinner.getValue() ); + zSize = ( int ) ( zSpinner.getValue() ); + timePoints = ( int ) ( tpSpinner.getValue() ); + wasOkClosed = true; + close(); + } ); + content.add( ok, c ); + + getContentPane().add( content ); + pack(); + setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); + setVisible( true ); + } + + private void close() + { + setVisible( false ); + dispose(); + } + + int xSize = 1000; + + int ySize = 1000; + + int zSize = 1000; + + int timePoints = 1000; + + boolean wasOkClosed = false; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/ProjectActions.java b/src/main/java/org/mastodon/mamut/io/ProjectActions.java index 97e923328..b60035e6a 100644 --- a/src/main/java/org/mastodon/mamut/io/ProjectActions.java +++ b/src/main/java/org/mastodon/mamut/io/ProjectActions.java @@ -34,8 +34,10 @@ import javax.swing.JOptionPane; +import org.mastodon.app.MastodonIcons; import org.mastodon.mamut.KeyConfigScopes; import org.mastodon.mamut.ProjectModel; +import org.mastodon.mamut.io.project.MamutImagePlusProject; import org.mastodon.mamut.launcher.LauncherUtil; import org.mastodon.ui.keymap.KeyConfigContexts; import org.scijava.Context; @@ -60,6 +62,7 @@ public class ProjectActions public static final String IMPORT_SIMI = "import simi"; public static final String IMPORT_MAMUT = "import mamut"; public static final String EXPORT_MAMUT = "export mamut"; + public static final String FIX_DATASET_PATH = "fix project image path"; static final String[] CREATE_PROJECT_KEYS = new String[] { "not mapped" }; static final String[] CREATE_PROJECT_FROM_URL_KEYS = new String[] { "not mapped" }; @@ -70,6 +73,7 @@ public class ProjectActions static final String[] IMPORT_SIMI_KEYS = new String[] { "not mapped" }; static final String[] IMPORT_MAMUT_KEYS = new String[] { "not mapped" }; static final String[] EXPORT_MAMUT_KEYS = new String[] { "not mapped" }; + static final String[] FIX_DATASET_PATH_KEYS = { "not mapped" }; /** * Install the global actions for creating, loading or importing a new @@ -115,15 +119,34 @@ public static void installAppActions( final Actions actions, final ProjectModel final RunnableAction importTgmmAction = new RunnableAction( IMPORT_TGMM, () -> ProjectImporter.importTgmmDataWithDialog( appModel, parentComponent ) ); final RunnableAction importSimiAction = new RunnableAction( IMPORT_TGMM, () -> ProjectImporter.importSimiDataWithDialog( appModel, parentComponent ) ); final RunnableAction exportMamutAction = new RunnableAction( EXPORT_MAMUT, () -> ProjectExporter.exportMamut( appModel, parentComponent ) ); + final RunnableAction fixDatasetPathAction = new RunnableAction( FIX_DATASET_PATH, () -> tweakDatasetPath( appModel, parentComponent ) ); actions.namedAction( saveProjectAction, SAVE_PROJECT_KEYS ); actions.namedAction( saveProjectAsAction, SAVE_PROJECT_AS_KEYS ); actions.namedAction( importTgmmAction, IMPORT_TGMM_KEYS ); actions.namedAction( importSimiAction, IMPORT_SIMI_KEYS ); actions.namedAction( exportMamutAction, EXPORT_MAMUT_KEYS ); + actions.namedAction( fixDatasetPathAction, FIX_DATASET_PATH_KEYS ); } - private static Runnable runInNewThread( Runnable o ) + private static void tweakDatasetPath( final ProjectModel appModel, final Frame parentComponent ) + { + if ( appModel.getProject() instanceof MamutImagePlusProject ) + { + JOptionPane.showMessageDialog( + null, + "The current project is based on an \n" + + "ImagePlus as image data source. \n" + + "Its dataset path cannot be edited.", + "Cannot edit dataset path", + JOptionPane.WARNING_MESSAGE, + MastodonIcons.MASTODON_ICON_MEDIUM ); + return; + } + new DatasetPathDialog( parentComponent, appModel ).setVisible( true ); + } + + private static Runnable runInNewThread( final Runnable o ) { return () -> { new Thread( () -> { @@ -131,7 +154,7 @@ private static Runnable runInNewThread( Runnable o ) { o.run(); } - catch ( Throwable t ) + catch ( final Throwable t ) { t.printStackTrace(); } @@ -164,6 +187,8 @@ public void getCommandDescriptions( final CommandDescriptions descriptions ) "Import tracks from a Simi Biocell .sbd into the current project." ); descriptions.add( IMPORT_MAMUT, IMPORT_MAMUT_KEYS, "Import a MaMuT project." ); descriptions.add( EXPORT_MAMUT, EXPORT_MAMUT_KEYS, "Export current project as a MaMuT project." ); + descriptions.add( FIX_DATASET_PATH, FIX_DATASET_PATH_KEYS, + "Shows a dialog to modify a new path to the image data and whether it is relative or absolute." ); } } From ef02997b313da7254d51549f90b004dabaaec77f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 24 May 2024 17:05:58 +0200 Subject: [PATCH 6/8] Center the fix image dialog. --- src/main/java/org/mastodon/mamut/io/DatasetPathDialog.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/io/DatasetPathDialog.java b/src/main/java/org/mastodon/mamut/io/DatasetPathDialog.java index f49b02042..a0f496d4d 100644 --- a/src/main/java/org/mastodon/mamut/io/DatasetPathDialog.java +++ b/src/main/java/org/mastodon/mamut/io/DatasetPathDialog.java @@ -260,6 +260,7 @@ public void actionPerformed( final ActionEvent e ) pack(); setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); + setLocationRelativeTo( null ); } private void close() @@ -389,6 +390,7 @@ public DummyImageDataParams( final Frame owner, final ProjectModel appModel ) getContentPane().add( content ); pack(); setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); + setLocationRelativeTo( owner ); setVisible( true ); } From b93f8b21fa0e6ecfc809476a79babf17813e8d55 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 5 Jun 2024 12:09:33 +0200 Subject: [PATCH 7/8] Make TreeUtils.getMinTimepoint() return 0 in case of an empty model. * It was before Integer.MAX_VALUE, which was confusing to be a min time point * Update javadocs and unit test accordingly --- src/main/java/org/mastodon/util/TreeUtils.java | 6 +++++- src/test/java/org/mastodon/util/TreeUtilsTest.java | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/util/TreeUtils.java b/src/main/java/org/mastodon/util/TreeUtils.java index b1c6ae44b..14149a5ac 100644 --- a/src/main/java/org/mastodon/util/TreeUtils.java +++ b/src/main/java/org/mastodon/util/TreeUtils.java @@ -232,12 +232,16 @@ private static < V extends Vertex, E extends Edge< V > > RefSet< V > filterRo } /** - * Gets the minimum timepoint in the given {@link Model} at which at least one {@link Spot} exists in the Model. + * Gets the minimum time point in the given {@link Model} at which at least one {@link Spot} exists in the Model. + *
+ * If the model is empty, returns 0. * @param model the {@link Model} * @return the timepoint */ public static int getMinTimepoint( final Model model ) { + if ( model.getGraph().vertices().isEmpty() ) + return 0; int minTimepoint = Integer.MAX_VALUE; for ( final Spot spot : model.getGraph().vertices() ) minTimepoint = Math.min( minTimepoint, spot.getTimepoint() ); diff --git a/src/test/java/org/mastodon/util/TreeUtilsTest.java b/src/test/java/org/mastodon/util/TreeUtilsTest.java index fbaf9df3c..aefbdec85 100644 --- a/src/test/java/org/mastodon/util/TreeUtilsTest.java +++ b/src/test/java/org/mastodon/util/TreeUtilsTest.java @@ -41,6 +41,7 @@ import org.mastodon.collection.RefSet; import org.mastodon.mamut.feature.branch.exampleGraph.ExampleGraph1; import org.mastodon.mamut.feature.branch.exampleGraph.ExampleGraph2; +import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.ModelGraph; import org.mastodon.mamut.model.Spot; @@ -182,7 +183,7 @@ public void testGetMinTimepoint() { assertEquals( 0, TreeUtils.getMinTimepoint( new ExampleGraph1().getModel() ) ); assertEquals( 0, TreeUtils.getMinTimepoint( new ExampleGraph2().getModel() ) ); - + assertEquals( 0, TreeUtils.getMinTimepoint( new Model() ) ); } @Test From 19ea45dca507d6f3fcae9b1803fd17ff6c3da597 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 5 Jun 2024 12:10:21 +0200 Subject: [PATCH 8/8] Make it transparent in the docs that TreeUtils.getMaxTimepoint() return 0 in case of an empty model. * Update javadocs and unit test accordingly --- src/main/java/org/mastodon/util/TreeUtils.java | 4 +++- src/test/java/org/mastodon/util/TreeUtilsTest.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/util/TreeUtils.java b/src/main/java/org/mastodon/util/TreeUtils.java index 14149a5ac..f046320f6 100644 --- a/src/main/java/org/mastodon/util/TreeUtils.java +++ b/src/main/java/org/mastodon/util/TreeUtils.java @@ -249,7 +249,9 @@ public static int getMinTimepoint( final Model model ) } /** - * Gets the maximum timepoint in the given {@link Model} at which at least one {@link Spot} exists in the Model. + * Gets the maximum time point in the given {@link Model} at which at least one {@link Spot} exists in the Model. + *
+ * If the model is empty, returns 0. * @param model the {@link Model} * @return the timepoint */ diff --git a/src/test/java/org/mastodon/util/TreeUtilsTest.java b/src/test/java/org/mastodon/util/TreeUtilsTest.java index aefbdec85..b3784ae0e 100644 --- a/src/test/java/org/mastodon/util/TreeUtilsTest.java +++ b/src/test/java/org/mastodon/util/TreeUtilsTest.java @@ -191,6 +191,7 @@ public void testGetMaxTimepoint() { assertEquals( 3, TreeUtils.getMaxTimepoint( new ExampleGraph1().getModel() ) ); assertEquals( 7, TreeUtils.getMaxTimepoint( new ExampleGraph2().getModel() ) ); + assertEquals( 0, TreeUtils.getMaxTimepoint( new Model() ) ); } @Test