diff --git a/.gitignore b/.gitignore index b9bb0f54..dbb66312 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,10 @@ hs_err_pid* /bin/ /target/ +# ignore files generated during test +/src/test/resources/bigwarp/url/imgDir3d/img3d0000.tif +/src/test/resources/bigwarp/url/imgDir3d/img3d0001.tif +/src/test/resources/bigwarp/url/imgDir3d/img3d0002.tif +/src/test/resources/bigwarp/url/imgDir3d/img3d0003.tif +/src/test/resources/bigwarp/url/img.tif +/src/test/resources/bigwarp/url/img2d.png diff --git a/pom.xml b/pom.xml index d6c40c6e..c5897d9c 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ sc.fiji bigwarp_fiji - 8.0.1-SNAPSHOT + 9.0.0-SNAPSHOT BigWarp plugin for Fiji A tool for manual pointwise deformable registration using bigdataviewer. @@ -118,16 +118,25 @@ BigWarp plugin for Fiji. **/resources/*.xml + 6.2.0 + 4.0.1 + 1.48 1.4.1 + 3.0.3 sign,deploy-to-scijava - 3.0.0 - 3.2.4 + 3.0.2 + 3.2.6 + 2.0.1 + 4.0.1 + 4.0.0 7.0.0 - 1.0.1 + 1.2.0 + 5.3.1 + 1.0.1 10.4.8 @@ -145,6 +154,10 @@ net.imagej ij + + net.imagej + imagej + @@ -236,13 +249,27 @@ org.janelia.saalfeldlab n5-imglib2 + + org.janelia.saalfeldlab + n5-aws-s3 + + + org.janelia.saalfeldlab + n5-google-cloud + + + org.janelia.saalfeldlab + n5-viewer_fiji + + + org.janelia.saalfeldlab + n5-zarr + org.janelia.saalfeldlab n5-universe ${n5-universe.version} - - ome @@ -306,6 +333,10 @@ alphanumeric-comparator ${alphanumeric-comparator.version} + + com.formdev + flatlaf + @@ -313,5 +344,45 @@ junit test + + xmlunit + xmlunit + 1.5 + + + + + + default + + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/SerializationTest.java + + + + + + + + uiTests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + + diff --git a/src/main/java/bdv/gui/AutosaveOptionsPanel.java b/src/main/java/bdv/gui/AutosaveOptionsPanel.java new file mode 100644 index 00000000..01107601 --- /dev/null +++ b/src/main/java/bdv/gui/AutosaveOptionsPanel.java @@ -0,0 +1,184 @@ +package bdv.gui; + +import bigwarp.BigWarp; +import bigwarp.BigWarpAutoSaver; +import java.awt.Container; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.File; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JSpinner.DefaultEditor; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +public class AutosaveOptionsPanel extends JPanel +{ + private static final long serialVersionUID = 2449704984531905538L; + + private final BigWarp< ? > bw; + + private final SpinnerNumberModel savePeriodModel; + private final JSpinner autoSavePeriodSpinner; + private final JCheckBox doAutoSaveBox; + private final JTextField autoSaveFolderText; + + private int lastAutoSaveFreq = 5; + private boolean updating = false; + + public AutosaveOptionsPanel( final BigWarp< ? > bw, final Container content ) + { + super( new GridBagLayout() ); + this.bw = bw; + + doAutoSaveBox = new JCheckBox( "Auto-save landmarks" ); + + final JLabel autoSavePeriodLabel = new JLabel( "Frequency (minutes)" ); + autoSavePeriodSpinner = new JSpinner(); + savePeriodModel = new SpinnerNumberModel( 0, 0, 0, 1 ); + getAutoSavePeriodSpinner().setModel( savePeriodModel ); + ((DefaultEditor)getAutoSavePeriodSpinner().getEditor()).getTextField().setEditable( false ); + ((DefaultEditor)getAutoSavePeriodSpinner().getEditor()).getTextField().setEnabled( false ); + getAutoSavePeriodSpinner().addChangeListener( new ChangeListener() + { + @Override + public void stateChanged( ChangeEvent e ) + { + if ( getDoAutoSaveBox().isSelected() && !updating ) + { + long periodMillis = ((Integer) savePeriodModel.getValue()).longValue() * 60000; + BigWarpAutoSaver autoSaver = bw.getAutoSaver(); + if ( autoSaver != null ) + autoSaver.stop(); + + bw.setAutoSaver( new BigWarpAutoSaver( bw, periodMillis )); + } + } + } ); + + getDoAutoSaveBox().addItemListener( new ItemListener() + { + @Override + public void itemStateChanged( ItemEvent e ) + { + bw.stopAutosave(); + if ( getDoAutoSaveBox().isSelected() ) + { + updating = true; + ((DefaultEditor)getAutoSavePeriodSpinner().getEditor()).getTextField().setEditable( true ); + ((DefaultEditor)getAutoSavePeriodSpinner().getEditor()).getTextField().setEnabled( true ); + savePeriodModel.setMinimum( 1 ); + savePeriodModel.setMaximum( 5000 ); + savePeriodModel.setValue( lastAutoSaveFreq ); + + long periodMillis = ((Integer) savePeriodModel.getValue()).longValue() * 60000; + bw.setAutoSaver( new BigWarpAutoSaver( bw, periodMillis )); + updating = false; + } + else + { + lastAutoSaveFreq = ((Integer) savePeriodModel.getValue()); + savePeriodModel.setMinimum( 0 ); + savePeriodModel.setMaximum( 0 ); + savePeriodModel.setValue( 0 ); + ((DefaultEditor)getAutoSavePeriodSpinner().getEditor()).getTextField().setEditable( false ); + ((DefaultEditor)getAutoSavePeriodSpinner().getEditor()).getTextField().setEnabled( false ); + } + } + } ); + + final JLabel destDirLabel = new JLabel( "Directory" ); + final File startingFolder = bw.getBigwarpSettingsFolder(); + autoSaveFolderText = new JTextField(); + getAutoSaveFolderText().setText( startingFolder.getAbsolutePath() ); + + final JButton browseBtn = new JButton( "Browse" ); + browseBtn.addActionListener( e -> { + + final JFileChooser fileChooser = new JFileChooser(); + fileChooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); + fileChooser.setCurrentDirectory( startingFolder ); + + final int ret = fileChooser.showOpenDialog( content ); + if ( ret == JFileChooser.APPROVE_OPTION ) + { + final File folder = fileChooser.getSelectedFile(); + getAutoSaveFolderText().setText( folder.getAbsolutePath() ); + bw.getAutoSaver().setAutosaveFolder( folder ); + } + } ); + + final GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 0; + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.weightx = 1.0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets( 5, 5, 5, 5 ); + + add( getDoAutoSaveBox(), gbc ); + + gbc.weightx = 1.0; + gbc.gridx = 2; + gbc.gridwidth = 1; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.LINE_END; + add( autoSavePeriodLabel, gbc ); + + gbc.gridx = 3; + gbc.gridwidth = 1; + gbc.weightx = 1.0; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.fill = GridBagConstraints.HORIZONTAL; + add( getAutoSavePeriodSpinner(), gbc ); + + gbc.gridy = 2; + gbc.gridx = 0; + gbc.gridwidth = 1; + gbc.weightx = 0.0; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.fill = GridBagConstraints.NONE; + add( destDirLabel, gbc ); + + gbc.gridy = 2; + gbc.gridx = 1; + gbc.gridwidth = 3; + gbc.weightx = 1.0; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.fill = GridBagConstraints.HORIZONTAL; + add( getAutoSaveFolderText(), gbc ); + + gbc.gridx = 4; + gbc.weightx = 0.0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + add( browseBtn, gbc ); + } + + public JSpinner getAutoSavePeriodSpinner() + { + return autoSavePeriodSpinner; + } + + public JCheckBox getDoAutoSaveBox() + { + return doAutoSaveBox; + } + + public JTextField getAutoSaveFolderText() + { + return autoSaveFolderText; + } + +} diff --git a/src/main/java/bdv/gui/BigWarpInitDialog.java b/src/main/java/bdv/gui/BigWarpInitDialog.java new file mode 100644 index 00000000..72e7829c --- /dev/null +++ b/src/main/java/bdv/gui/BigWarpInitDialog.java @@ -0,0 +1,1092 @@ +package bdv.gui; + +import java.awt.Container; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.JTree; +import javax.swing.Timer; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import org.janelia.saalfeldlab.n5.bdv.N5ViewerTreeCellRenderer; +import org.janelia.saalfeldlab.n5.ij.N5Importer.N5BasePathFun; +import org.janelia.saalfeldlab.n5.ij.N5Importer.N5ViewerReaderFun; +import org.janelia.saalfeldlab.n5.metadata.N5ViewerMultichannelMetadata; +import org.janelia.saalfeldlab.n5.metadata.imagej.ImagePlusLegacyMetadataParser; +import org.janelia.saalfeldlab.n5.ui.DataSelection; +import org.janelia.saalfeldlab.n5.ui.DatasetSelectorDialog; +import org.janelia.saalfeldlab.n5.ui.N5SwingTreeNode; +import org.janelia.saalfeldlab.n5.universe.metadata.N5CosemMetadataParser; +import org.janelia.saalfeldlab.n5.universe.metadata.N5CosemMultiScaleMetadata; +import org.janelia.saalfeldlab.n5.universe.metadata.N5GenericSingleScaleMetadataParser; +import org.janelia.saalfeldlab.n5.universe.metadata.N5Metadata; +import org.janelia.saalfeldlab.n5.universe.metadata.N5MetadataParser; +import org.janelia.saalfeldlab.n5.universe.metadata.N5SingleScaleMetadataParser; +import org.janelia.saalfeldlab.n5.universe.metadata.N5ViewerMultiscaleMetadataParser; +import org.janelia.saalfeldlab.n5.universe.metadata.canonical.CanonicalMetadataParser; +import org.jdom2.JDOMException; + +import com.formdev.flatlaf.util.UIScale; + +import bdv.gui.sourceList.BigWarpSourceListPanel; +import bdv.gui.sourceList.BigWarpSourceTableModel; +import bdv.gui.sourceList.BigWarpSourceTableModel.SourceRow; +import bdv.gui.sourceList.BigWarpSourceTableModel.SourceType; +import bdv.ij.util.ProgressWriterIJ; +import bdv.viewer.Source; +import bigwarp.BigWarp; +import bigwarp.BigWarpData; +import bigwarp.BigWarpInit; +import bigwarp.source.SourceInfo; +import bigwarp.transforms.NgffTransformations; +import bigwarp.transforms.metadata.N5TransformMetadata; +import bigwarp.transforms.metadata.N5TransformMetadataParser; +import bigwarp.transforms.metadata.N5TransformTreeCellRenderer; +import ij.IJ; +import ij.ImagePlus; +import ij.Macro; +import ij.Prefs; +import ij.WindowManager; +import ij.plugin.frame.Recorder; +import mpicbg.spim.data.SpimDataException; +import net.imagej.Dataset; +import net.imagej.DatasetService; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.type.NativeType; + +public class BigWarpInitDialog extends JFrame +{ + private static final long serialVersionUID = -2914972130819029899L; + + public static String listSeparator = ","; + + private boolean imageJOpen; + private DatasetService datasetService; + + private String initialPath; + private JTextField projectPathTxt, containerPathText, transformPathText; + private JLabel messageLabel; + private JButton okBtn, cancelBtn; + private JPanel listPanel; + private JTable sourceTable; + private JButton browseProjectButton, browseBtn, addN5Button, addN5TransformButton, browseTransformButton; + private BigWarpSourceTableModel sourceTableModel; + private JComboBox imagePlusDropdown; + private JButton addImageButton, addPathButton, addTransformButton; + private DatasetSelectorDialog selectionDialog, transformSelectionDialog; + + private String lastOpenedContainer = ""; + private String lastBrowsePath = null; + private ExecutorService exec; + + private Consumer okayCallback; + private Consumer cancelCallback; + private Consumer< String > imagePathUpdateCallback, transformPathUpdateCallback, projectPathUpdateCallback; + + public static final int DEFAULT_OUTER_PAD = 8; + public static final int DEFAULT_BUTTON_PAD = 3; + public static final int DEFAULT_MID_PAD = 5; + + private static final String commandName = "BigWarp"; + private static final String projectKey = "project"; + private static final String imagesKey = "images"; + private static final String movingKey = "moving"; + private static final String transformsKey = "transforms"; + + public static final String ImageJPrefix = "imagej://"; + + private String projectLandmarkPath; + private String imageList; + private String movingList; + private String transformList; + + private boolean initialRecorderState; + + + + public BigWarpInitDialog( final String title ) + { + this( title, null ); + } + + public BigWarpInitDialog( final String title, final DatasetService datasetService ) + { + super( title ); + this.datasetService = datasetService; + initialPath = ""; + imageJOpen = IJ.getInstance() != null; + + buildN5SelectionDialog(); + final Container content = getContentPane(); + content.add( createContent() ); + pack(); + + initializeImagePlusSources(); + + cancelCallback = x -> { + setVisible( false ); + dispose(); + Recorder.record = initialRecorderState; + }; + + okayCallback = x -> { + macroRecord(); + runBigWarp(); + Recorder.record = initialRecorderState; + setVisible( false ); + }; + + imagePathUpdateCallback = ( p ) -> { + addPath(); + }; + + transformPathUpdateCallback = ( p ) -> { + addTransform(); + }; + } + + + public void setInitialRecorderState( final boolean initialRecorderState ) + { + this.initialRecorderState = initialRecorderState; + } + + public static < T extends NativeType > BigWarp runBigWarp( final String projectLandmarkPath, final String[] images, final String[] moving, final String[] transforms ) + { + final BigWarpData< T > data = BigWarpInit.initData(); + final boolean haveProjectLandmarkArg = projectLandmarkPath != null && !projectLandmarkPath.isEmpty(); + final boolean haveProject = haveProjectLandmarkArg && projectLandmarkPath.endsWith(".json"); + final boolean haveLandmarks = haveProjectLandmarkArg && projectLandmarkPath.endsWith(".csv"); + + final int nThreads = IJ.getInstance() != null ? Prefs.getThreads() : 1; + final BigWarpViewerOptions bwOpts = ( BigWarpViewerOptions ) BigWarpViewerOptions.options().numRenderingThreads( nThreads ); + + if( !haveProject ) + { + int id = 0; + final int N = images.length; + for( int i = 0; i < N; i++ ) + { + // TODO better messages for exceptions? + try + { + final LinkedHashMap< Source< T >, SourceInfo > infos = BigWarpInit.createSources( data, images[ i ], id, moving[ i ].equals( "true" ) ); + + RealTransform transform = null; + final Supplier transformSupplier; + if( transforms != null && transforms.length > i ) + { + final String transformUrl = transforms[ i ]; + if( transformUrl!= null && !transformUrl.isEmpty() ) + { + transform = NgffTransformations.open( transformUrl ); + transformSupplier = () -> transformUrl; + } + else { + transformSupplier = null; + } + } + else + { + transformSupplier = null; + } + + // add performs a null check on transform + BigWarpInit.add( data, infos, transform, transformSupplier ); + + id += infos.size(); + } + catch ( final URISyntaxException e ) + { + e.printStackTrace(); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + catch ( final SpimDataException e ) + { + e.printStackTrace(); + } + } + } + + BigWarp bw = null; + try + { + bwOpts.is2D(BigWarp.detectNumDims( data.sources ) == 2); + data.applyTransformations(); + + bw = new BigWarp<>( data, bwOpts, new ProgressWriterIJ() ); + if( haveProject ) + bw.loadSettings( projectLandmarkPath, true ); + else if( haveLandmarks ) + bw.loadLandmarks(projectLandmarkPath); + } + catch ( final SpimDataException e ) + { + e.printStackTrace(); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + catch ( final JDOMException e ) + { + e.printStackTrace(); + } + + return bw; + } + + public > void runBigWarp() + { + if (Recorder.record) + { + Recorder.setCommand(commandName); + macroRecord(); + Recorder.saveCommand(); + } + + final BigWarpData< T > data = BigWarpInit.initData(); + + projectLandmarkPath = projectLandmarkPath == null ? projectPathTxt.getText() : projectLandmarkPath; + final boolean haveProjectLandmarkArg = projectLandmarkPath != null && !projectLandmarkPath.isEmpty(); + final boolean haveProject = haveProjectLandmarkArg && projectLandmarkPath.endsWith(".json"); + final boolean haveLandmarks = haveProjectLandmarkArg && projectLandmarkPath.endsWith(".csv"); + + if( !haveProject ) + { + int id = 0; + final int N = sourceTable.getRowCount(); + for( int i = 0; i < N; i++ ) + { + final SourceRow tableRow = sourceTableModel.get( i ); + if( tableRow.type.equals( SourceType.IMAGEPLUS ) ) + { + // strip off prefix if present + final ImagePlus imp = WindowManager.getImage( tableRow.srcName.replaceAll( "^"+ImageJPrefix, "" ) ); + final LinkedHashMap< Source< T >, SourceInfo > infos = BigWarpInit.createSources( data, imp, id, 0, tableRow.moving ); + BigWarpInit.add( data, infos, tableRow.getTransform(), tableRow.getTransformUri() ); + id += infos.size(); + } + else if( tableRow.type.equals( SourceType.DATASET )) + { + final Dataset dataset = datasetService.getDatasets().stream() + .filter( x -> x.getSource().equals( tableRow.srcName ) ) + .findFirst().get(); + final LinkedHashMap< Source< T >, SourceInfo > infos = BigWarpInit.createSources( data, dataset, id, tableRow.moving ); + BigWarpInit.add( data, infos, tableRow.getTransform(), tableRow.getTransformUri() ); + id += infos.size(); + } + else + { + // deal with exceptions differently? + try + { + final LinkedHashMap< Source< T >, SourceInfo > infos = BigWarpInit.createSources( data, tableRow.srcName, id, tableRow.moving ); + BigWarpInit.add( data, infos, tableRow.getTransform(), tableRow.getTransformUri() ); + id += infos.size(); + } + catch ( final URISyntaxException e ) + { + e.printStackTrace(); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + catch ( final SpimDataException e ) + { + e.printStackTrace(); + } + } + } + } + + BigWarp bw; + try + { + data.applyTransformations(); + bw = new BigWarp<>( data, new ProgressWriterIJ() ); + if( haveProject ) + bw.loadSettings( projectLandmarkPath, true ); + else if( haveLandmarks ) + bw.loadLandmarks(projectLandmarkPath); + } + catch ( final SpimDataException e ) + { + e.printStackTrace(); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + catch ( final JDOMException e ) + { + e.printStackTrace(); + } + + } + + public JPanel createContent() + { + final int OUTER_PAD = DEFAULT_OUTER_PAD; + final int BUTTON_PAD = DEFAULT_BUTTON_PAD; + final int MID_PAD = DEFAULT_MID_PAD; + + final int frameSizeX = UIScale.scale( 600 ); + final JPanel panel = new JPanel(false); + panel.setPreferredSize( new Dimension( frameSizeX, UIScale.scale( 335 ) )); // ~ 16:9 + panel.setLayout(new GridBagLayout()); + + final GridBagConstraints ctxt = new GridBagConstraints(); + ctxt.gridx = 0; + ctxt.gridy = 0; + ctxt.gridwidth = 1; + ctxt.gridheight = 1; + ctxt.weightx = 0.0; + ctxt.weighty = 0.0; + ctxt.anchor = GridBagConstraints.LINE_END; + ctxt.fill = GridBagConstraints.NONE; + ctxt.insets = new Insets(OUTER_PAD, OUTER_PAD, MID_PAD, BUTTON_PAD); + panel.add( new JLabel("BigWarp project or landmarks:"), ctxt ); + + final GridBagConstraints gbcBar = new GridBagConstraints(); + gbcBar.gridx = 1; + gbcBar.gridy = 0; + gbcBar.gridwidth = 6; + gbcBar.gridheight = 1; + gbcBar.weightx = 1.0; + gbcBar.weighty = 0.0; + gbcBar.fill = GridBagConstraints.HORIZONTAL; + gbcBar.insets = new Insets(OUTER_PAD, OUTER_PAD, MID_PAD, BUTTON_PAD); + + projectPathTxt = new JTextField(); + projectPathTxt.setPreferredSize( new Dimension( frameSizeX / 3, projectPathTxt.getPreferredSize().height ) ); + panel.add(projectPathTxt, gbcBar); + + // gbc bars below are width 4 + gbcBar.gridwidth = 4; + + final GridBagConstraints cProjBrowse = new GridBagConstraints(); + cProjBrowse.gridx = 7; + cProjBrowse.gridy = 0; + cProjBrowse.gridwidth = 1; + cProjBrowse.weightx = 0.0; + cProjBrowse.fill = GridBagConstraints.HORIZONTAL; + cProjBrowse.insets = new Insets(OUTER_PAD, BUTTON_PAD, MID_PAD, BUTTON_PAD); + browseProjectButton = new JButton("Browse"); + browseProjectButton.addActionListener( e -> { browseProjectDialog(); } ); + panel.add(browseProjectButton, cProjBrowse); + + + // Add open imagej image + ctxt.gridy = 1; + panel.add( new JLabel("Add open image:"), ctxt ); + + final GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 1; + gbc.gridwidth = 3; + gbc.weightx = 1.0; + gbc.weighty = 0.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(OUTER_PAD, BUTTON_PAD, MID_PAD, BUTTON_PAD); + gbc.anchor = GridBagConstraints.LINE_END; + imagePlusDropdown = new JComboBox<>( new String[] { "" } ); + panel.add( imagePlusDropdown, gbc ); + updateImagePlusDropdown(); + + final GridBagConstraints cadd = new GridBagConstraints(); + cadd.gridx = 5; + cadd.gridy = 1; + cadd.gridwidth = 1; + cadd.weightx = 0.0; + cadd.fill = GridBagConstraints.NONE; + cadd.anchor = GridBagConstraints.LINE_START; + cadd.insets = new Insets(OUTER_PAD, BUTTON_PAD, MID_PAD, BUTTON_PAD); + + cadd.gridy = 1; + addImageButton = new JButton("+"); + panel.add( addImageButton, cadd ); + addImageButton.addActionListener( e -> { addImage(); }); + + ctxt.gridy = 2; + final JLabel addFileLabel = new JLabel( "Add image file/folder:"); + panel.add(addFileLabel, ctxt); + + gbcBar.gridy = 2; + containerPathText = new JTextField(); + containerPathText.setText( initialPath ); + containerPathText.setPreferredSize( new Dimension( frameSizeX / 3, containerPathText.getPreferredSize().height ) ); +// containerPathText.addActionListener( e -> openContainer( n5Fun, () -> getN5RootPath(), pathFun ) ); + panel.add(containerPathText, gbcBar); + + cadd.gridy = 2; + addPathButton = new JButton("+"); + addPathButton.addActionListener( e -> addPath() ); + panel.add(addPathButton, cadd); + + final GridBagConstraints cbrowse = new GridBagConstraints(); + cbrowse.gridx = 6; + cbrowse.gridy = 2; + cbrowse.gridwidth = 1; + cbrowse.weightx = 0.0; + cbrowse.fill = GridBagConstraints.HORIZONTAL; + cbrowse.insets = new Insets(OUTER_PAD, BUTTON_PAD, MID_PAD, BUTTON_PAD); + browseBtn = new JButton("Browse"); + browseBtn.addActionListener( e -> { browseImageDialog(); } ); + panel.add(browseBtn, cbrowse); + + final GridBagConstraints cn5 = new GridBagConstraints(); + cn5.gridx = 7; + cn5.gridy = 2; + cn5.gridwidth = 1; + cn5.weightx = 0.0; + cn5.fill = GridBagConstraints.HORIZONTAL; + cn5.insets = new Insets(OUTER_PAD, BUTTON_PAD, MID_PAD, BUTTON_PAD); + addN5Button = new JButton( "H5/N5/Zarr" ); + panel.add( addN5Button, cn5 ); + + addN5Button.addActionListener( e -> { + + selectionDialog = new DatasetSelectorDialog( new N5ViewerReaderFun(), new N5BasePathFun(), + lastOpenedContainer, + BigWarpInit.GROUP_PARSERS, + BigWarpInit.PARSERS); + + selectionDialog.setLoaderExecutor( exec ); + selectionDialog.setTreeRenderer(new N5ViewerTreeCellRenderer(false)); + + selectionDialog.setContainerPathUpdateCallback( x -> { + if ( x != null ) + lastOpenedContainer = x; + } ); + + // figure this out +// selectionDialog.setCancelCallback( x -> { +// // set back recorder state if canceled +// Recorder.record = initialRecorderState; +// } ); + + selectionDialog.setVirtualOption( false ); + selectionDialog.setCropOption( false ); + + selectionDialog.run( this::n5DialogCallback ); + }); + + // add transforms + ctxt.gridy = 3; + panel.add( new JLabel("Add transformation:"), ctxt ); + + transformPathText = new JTextField(); + transformPathText.setPreferredSize( new Dimension( frameSizeX / 3, transformPathText.getPreferredSize().height ) ); + gbcBar.gridy = 3; + panel.add( transformPathText, gbcBar ); + + addTransformButton = new JButton( "+" ); + addTransformButton.addActionListener( e -> addTransform() ); + cadd.gridy = 3; + panel.add( addTransformButton, cadd ); + + browseTransformButton = new JButton("Browse"); + browseTransformButton.addActionListener( e -> { browseTransformDialog(); } ); + cbrowse.gridy = 3; + panel.add( browseTransformButton, cbrowse ); + + cn5.gridy = 3; + addN5TransformButton = new JButton( "H5/N5/Zarr" ); + panel.add( addN5TransformButton, cn5 ); + + addN5TransformButton.addActionListener( e -> { + if (sourceTable.getSelectedRow() < 0) + IJ.showMessage("Please highlight the row you would like to transform."); + else + { + + final N5MetadataParser[] tformParsers = new N5MetadataParser[]{ new N5TransformMetadataParser() }; + + transformSelectionDialog = new DatasetSelectorDialog( new N5ViewerReaderFun(), new N5BasePathFun(), + lastOpenedContainer, new N5MetadataParser[] {}, tformParsers ); + + transformSelectionDialog.setLoaderExecutor( exec ); + transformSelectionDialog.setTreeRenderer( new N5TransformTreeCellRenderer( true ) ); + transformSelectionDialog.setContainerPathUpdateCallback( x -> { + if ( x != null ) + lastOpenedContainer = x; + } ); + + transformSelectionDialog.run(this::n5DialogTransformCallback); + + // remove any existing selection listeners + final JTree tree = transformSelectionDialog.getJTree(); + for ( final TreeSelectionListener l : tree.getTreeSelectionListeners()) + tree.removeTreeSelectionListener(l); + + // add a new listener for transform metadata + tree.addTreeSelectionListener(new N5TransformTreeSelectionListener(tree.getSelectionModel())); + } + }); + + // source list + final GridBagConstraints clist = new GridBagConstraints(); + clist.gridx = 0; + clist.gridy = 4; + clist.gridwidth = 8; + clist.gridheight = 3; + clist.weightx = 1.0; + clist.weighty = 1.0; + clist.fill = GridBagConstraints.BOTH; + clist.insets = new Insets(OUTER_PAD, BUTTON_PAD, MID_PAD, BUTTON_PAD); + + sourceTableModel = new BigWarpSourceTableModel( t -> { + final String val = NgffTransformations.detectTransforms(t); + if( val == null ) + showMessage(1000, "No transformation found"); + else + showMessage(1000, "Found transformation"); + + return val; + }); + + final BigWarpSourceListPanel srcListPanel = new BigWarpSourceListPanel( sourceTableModel ); + sourceTableModel.setContainer( srcListPanel ); + sourceTable = srcListPanel.getJTable(); + sourceTable.putClientProperty("terminateEditOnFocusLost", true); + panel.add( srcListPanel, clist ); + + // bottom button section + final GridBagConstraints cbot = new GridBagConstraints(); + cbot.gridx = 0; + cbot.gridy = 7; + cbot.gridwidth = 4; + cbot.gridheight = 1; + cbot.weightx = 1.0; + cbot.weighty = 0.0; + cbot.fill = GridBagConstraints.HORIZONTAL; + cbot.anchor = GridBagConstraints.WEST; + cbot.insets = new Insets(OUTER_PAD, OUTER_PAD, OUTER_PAD, OUTER_PAD); + + messageLabel = new JLabel(""); + messageLabel.setVisible(true); + panel.add(messageLabel, cbot); + + okBtn = new JButton("OK"); + okBtn.addActionListener( e -> okayCallback.accept( sourceTableModel )); + cbot.gridx = 6; + cbot.weightx = 0.0; + cbot.gridwidth = 1; + cbot.ipadx = (int)(40); + cbot.fill = GridBagConstraints.HORIZONTAL; + cbot.anchor = GridBagConstraints.EAST; + cbot.insets = new Insets(MID_PAD, OUTER_PAD, OUTER_PAD, BUTTON_PAD); + panel.add(okBtn, cbot); + + cancelBtn = new JButton("Cancel"); + cancelBtn.addActionListener( e -> cancelCallback.accept( sourceTableModel )); + cbot.gridx = 7; + cbot.ipadx = 0; + cbot.gridwidth = 1; + cbot.fill = GridBagConstraints.HORIZONTAL; + cbot.anchor = GridBagConstraints.EAST; + cbot.insets = new Insets(MID_PAD, BUTTON_PAD, OUTER_PAD, OUTER_PAD); + panel.add(cancelBtn, cbot); + + return panel; + } + + public void buildN5SelectionDialog() + { + exec = Executors.newFixedThreadPool( Prefs.getThreads() ); + + + /* + * The Dialogs need to be created anew by the action listener + */ + +// selectionDialog = new DatasetSelectorDialog( new N5ViewerReaderFun(), new N5BasePathFun(), +// lastOpenedContainer, +// n5vGroupParsers, +// n5Parsers); +// +// selectionDialog.setLoaderExecutor( exec ); +// selectionDialog.setTreeRenderer(new N5ViewerTreeCellRenderer(false)); +// +// selectionDialog.setContainerPathUpdateCallback( x -> { +// if ( x != null ) +// lastOpenedContainer = x; +// } ); +// +// // figure this out +//// selectionDialog.setCancelCallback( x -> { +//// // set back recorder state if canceled +//// Recorder.record = initialRecorderState; +//// } ); +// +// selectionDialog.setVirtualOption( false ); +// selectionDialog.setCropOption( false ); + + +// // transform +// +// final N5MetadataParser[] tformParsers = new N5MetadataParser[]{ new N5TransformMetadataParser() }; +// +// transformSelectionDialog = new DatasetSelectorDialog( new N5ViewerReaderFun(), new N5BasePathFun(), +// lastOpenedContainer, new N5MetadataParser[] {}, tformParsers ); +// +// transformSelectionDialog.setLoaderExecutor( exec ); +// transformSelectionDialog.setTreeRenderer( new N5TransformTreeCellRenderer( true ) ); +// transformSelectionDialog.setContainerPathUpdateCallback( x -> { +// if ( x != null ) +// lastOpenedContainer = x; +// } ); + + } + + public void n5DialogCallback( final DataSelection selection ) + { + final String n5RootPath = selectionDialog.getN5RootPath(); + for( final N5Metadata m : selection.metadata ) + sourceTableModel.add( n5RootPath + "?" + m.getPath() ); + + repaint(); + } + + public void n5DialogTransformCallback( final DataSelection selection ) + { + final String n5RootPath = transformSelectionDialog.getN5RootPath(); + final int i = sourceTable.getSelectedRow(); + if( selection.metadata.size() > 0 ) + sourceTableModel.setTransform(i, n5RootPath + "?" + selection.metadata.get(0).getPath() ); + + repaint(); + } + + protected void addImage() + { + if ( !imageJOpen && datasetService == null) + return; + + final String title = (String)(imagePlusDropdown.getSelectedItem()); + if (!addDataset(title, false)) + addImagePlus(title); + + this.repaint(); + } + + protected boolean addDataset( final String datasetSource, final boolean moving ) + { + if( datasetService.getDatasets().stream().filter( x -> x.getSource().equals(datasetSource)).count() > 0 ) + { + sourceTableModel.addDataset( datasetSource, moving ); + return true; + } + else + return false; + } + + protected void addImagePlus( final String title ) + { + addImagePlus( title, true ); + } + + protected void addImagePlus( final String title, final boolean moving ) + { + if ( IJ.getInstance() == null ) + return; + + final ImagePlus imp = WindowManager.getImage( title ); + + // TODO consider giving the user information if + // an image is not added, and / or updating the dropdown menu periodically + if( !title.isEmpty() && imp != null ) + { + sourceTableModel.addImagePlus( ImageJPrefix + title, moving ); + repaint(); + } + } + + protected void addPath() + { + final String path = containerPathText.getText(); + if( !path.isEmpty() ) + { + sourceTableModel.add( path ); + repaint(); + } + } + + protected void addTransform() + { + final String path = transformPathText.getText(); + final int row = sourceTable.getSelectedRow(); + if( !path.isEmpty() && row >= 0 ) + { + sourceTableModel.setTransform( row, path ); + repaint(); + } + } + + protected void updateImagePlusDropdown() + { + if( !imageJOpen && datasetService == null ) + return; + + // add both images from dataset service and ij1 window manager but avoid duplicates + final ArrayList titleList = new ArrayList<>(); + if( datasetService != null ) + { + for( final Dataset d : datasetService.getDatasets() ) + titleList.add(d.getSource()); + } + + // don't need any open windows if we're using N5 + final int[] ids = WindowManager.getIDList(); + + // Find any open images + final int N = ids == null ? 0 : ids.length; + for ( int i = 0; i < N; ++i ) + { + final String t = ( WindowManager.getImage( ids[ i ] )).getTitle(); + if( !titleList.contains(t)) + titleList.add(t); + } + + final String[] titles = new String[titleList.size()]; + for (int i = 0; i < titleList.size(); i++) + titles[i] = titleList.get(i); + + imagePlusDropdown.setModel( new DefaultComboBoxModel<>( titles )); + } + + /** + * Adds first two image plus images to the source list automatically. + */ + public void initializeImagePlusSources() + { + final int N = imagePlusDropdown.getModel().getSize(); + if ( N > 0 ) + { + if( datasetService == null ) + addImagePlus( ( String ) imagePlusDropdown.getItemAt( 0 ), true ); + else + addDataset( ( String ) imagePlusDropdown.getItemAt( 0 ), true ); + } + + if ( N > 1 ) + { + if( datasetService == null ) + addImagePlus( ( String ) imagePlusDropdown.getItemAt( 1 ), false ); + else + addDataset( ( String ) imagePlusDropdown.getItemAt( 1 ), false ); + } + } + + public static BigWarpInitDialog createAndShow() + { + return createAndShow( null ); + } + + public static BigWarpInitDialog createAndShow( final DatasetService datasets ) + { + // Create and set up the window. + final BigWarpInitDialog frame = new BigWarpInitDialog( "BigWarp", datasets ); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + frame.setVisible( true ); + return frame; + } + + private String browseDialogGeneral( final int mode, final FileFilter filefilter ) + { + + final JFileChooser fileChooser = new JFileChooser(); + /* + * Need to allow files so h5 containers can be opened, and directories + * so that filesystem n5's and zarrs can be opened. + */ + fileChooser.setFileSelectionMode( mode ); + if( filefilter == null ) + { + fileChooser.setFileFilter( filefilter ); + } + + if ( lastBrowsePath != null && !lastBrowsePath.isEmpty() ) + fileChooser.setCurrentDirectory( new File( lastBrowsePath ) ); + else if ( initialPath != null && !initialPath.isEmpty() ) + fileChooser.setCurrentDirectory( new File( initialPath ) ); + else if ( imageJOpen ) + { + File f = null; + + final String currDir = IJ.getDirectory( "current" ); + final String homeDir = IJ.getDirectory( "home" ); + if ( currDir != null ) + f = new File( currDir ); + else if ( homeDir != null ) + f = new File( homeDir ); + + fileChooser.setCurrentDirectory( f ); + } + + final int ret = fileChooser.showOpenDialog( this ); + if ( ret != JFileChooser.APPROVE_OPTION ) + return null; + + final String path = fileChooser.getSelectedFile().getAbsolutePath(); + lastBrowsePath = path; + + return path; + } + + public void setImagePathUpdateCallback( final Consumer< String > callback ) + { + this.imagePathUpdateCallback = callback; + } + + public void setTransformPathUpdateCallback( final Consumer< String > callback ) + { + this.transformPathUpdateCallback = callback; + } + + public void setProjectPathUpdateCallback( final Consumer< String > callback ) + { + this.projectPathUpdateCallback = callback; + } + + private String browseProjectDialog() + { + final String s = browseDialogGeneral( JFileChooser.FILES_ONLY, new FileNameExtensionFilter( "JSON file", "json" ) ); + projectPathTxt.setText( s ); + if ( projectPathUpdateCallback != null ) + projectPathUpdateCallback.accept( s ); + + return s; + } + + private String browseImageDialog() + { + final String s = browseDialogGeneral( JFileChooser.FILES_AND_DIRECTORIES, null ); + containerPathText.setText( s ); + if ( imagePathUpdateCallback != null ) + imagePathUpdateCallback.accept( s ); + + return s; + } + + private String browseTransformDialog() + { + final String s = browseDialogGeneral( JFileChooser.FILES_AND_DIRECTORIES, null ); + transformPathText.setText( s ); + if ( transformPathUpdateCallback != null ) + transformPathUpdateCallback.accept( s ); + + return s; + } + + public void setParameters( final String projectLandmarkPath, final String images, final String moving, final String transforms ) { + this.projectLandmarkPath = projectLandmarkPath; + this.imageList = images; + this.movingList = moving; + this.transformList = transforms; + } + + public void updateTableFromParameters() + { + for( int i = 0; i < sourceTableModel.getRowCount(); i++ ) + sourceTableModel.remove( i ); + + final String[] imageParams = imageList.split( listSeparator, -1 ); + final String[] movingParams = movingList.split( listSeparator, -1 ); + final String[] transformParams = transformList.split( listSeparator, -1 ); + + final int N = imageParams.length; + if( movingParams.length != N || transformParams.length != N ) + { + System.err.println("Parameter arrays must have identical lengths"); + return; + } + + for( int i = 0; i < N; i++ ) + { + sourceTableModel.add( imageParams[ i ], movingParams[ i ].trim().equals( "true" ) ); + sourceTableModel.setTransform( i, transformParams[ i ] ); + } + } + + public void updateParametersFromTable() + { + // make source list + final StringBuffer imageList = new StringBuffer(); + final StringBuffer movingList = new StringBuffer(); + final StringBuffer transformList = new StringBuffer(); + + final int N = sourceTable.getRowCount(); + for( int i = 0; i < N; i++ ) + { + imageList.append( sourceTableModel.get( i ).srcName ); + movingList.append( sourceTableModel.get( i ).moving ); + transformList.append( sourceTableModel.get( i ).transformUrl ); + if( i < N -1 ) + { + imageList.append( listSeparator ); + movingList.append( listSeparator ); + transformList.append( listSeparator ); + } + } + + this.imageList = imageList.toString(); + this.movingList = movingList.toString(); + this.transformList = transformList.toString(); + } + + public String macroRecord() + { + if( !Recorder.record ) + return ""; + + updateParametersFromTable(); +// return String.format( "images=[%s], moving=[%s], transformations=[%s]", +// imageList.toString(), movingList.toString(), transformList.toString() ); + + Recorder.resetCommandOptions(); + Recorder.recordOption(imagesKey, imageList.toString()); + Recorder.recordOption(movingKey, movingList.toString()); + + if( transformList != null ) + Recorder.recordOption(transformsKey, transformList.toString()); + + return Recorder.getCommandOptions(); + } + + protected void showMessage( int timeMillis, String message ) + { + messageLabel.setText(message); + final Timer timer = new Timer( timeMillis, new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + messageLabel.setText(""); + } + }); + timer.setRepeats(false); + timer.start(); + } + + public static void runMacro( final String args ) + { + final String project = Macro.getValue( args, projectKey, "" ); + + final String[] images = Macro.getValue( args, imagesKey, "" ).split( ",", -1 ); + final String[] moving = Macro.getValue( args, movingKey, "" ).split( ",", -1 ); + final String[] transforms = Macro.getValue( args, transformsKey, "" ).split( ",", -1 ); + + if( !project.isEmpty()) + { + runBigWarp( project, null, null, null ); + } + else + { + if( images.length == 0 || moving.length == 0 ) + { + System.err.println( "images and moving keys required" ); + return; + } + // TODO fix transforms + runBigWarp( null, images, moving, transforms ); + } + +// System.out.println( "BigWarpInitDialog runMacro"); +// System.out.println( args ); +// final HashMap< String, String > keyVals = BigWarpUtils.parseMacroArguments( args ); +// final Set< String > keys = keyVals.keySet(); + +// if( keys.contains("project")) +// { +// runBigWarp( keyVals.get( "project" ), null, null, null ); +// } +// else +// { +// if( !keys.contains( "images" ) || !keys.contains( "moving" )) +// { +// System.out.println( "images and moving keys required" ); +// return; +// } +// +// final String[] images = keyVals.get( "images" ).split( ",", -1 ); +// final String[] moving = keyVals.get( "moving" ).split( ",", -1 ); +// +//// final Boolean[] moving = Arrays.stream( keyVals.get( "moving" ).split( ",", -1 ) ).map( x -> { +//// return x.equals( "true" ); +//// } ).toArray( Boolean[]::new ); +// +//// final String transforms; +//// if( keys.contains( "transforms" ) ) +//// transforms = keyVals.get( "transforms" ); +//// else +//// transforms = ""; +// +// // TOD fix transforms +// runBigWarp( null, images, moving, null ); +// } + } + + /** + * Removes selected nodes that do not have transformation metadata. + */ + public static class N5TransformTreeSelectionListener implements TreeSelectionListener { + + private TreeSelectionModel selectionModel; + + public N5TransformTreeSelectionListener(final TreeSelectionModel selectionModel) { + + this.selectionModel = selectionModel; + } + + @Override + public void valueChanged(final TreeSelectionEvent sel) { + + int i = 0; + for (final TreePath path : sel.getPaths()) { + if (!sel.isAddedPath(i)) + continue; + + final Object last = path.getLastPathComponent(); + if (last instanceof N5SwingTreeNode) { + final N5SwingTreeNode node = ((N5SwingTreeNode)last); + if (node.getMetadata() == null || !(node.getMetadata() instanceof N5TransformMetadata)) { + selectionModel.removeSelectionPath(path); + } + } + i++; + } + } + } + +} diff --git a/src/main/java/bdv/gui/BigWarpLandmarkPanel.java b/src/main/java/bdv/gui/BigWarpLandmarkPanel.java index 961af7f1..1cfcc96b 100644 --- a/src/main/java/bdv/gui/BigWarpLandmarkPanel.java +++ b/src/main/java/bdv/gui/BigWarpLandmarkPanel.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -37,6 +37,7 @@ import javax.swing.KeyStroke; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,23 +45,23 @@ import bigwarp.landmarks.LandmarkTableModel; public class BigWarpLandmarkPanel extends JPanel { - + private static final long serialVersionUID = 8470689265638231579L; protected LandmarkTableModel tableModel; protected JTable table; - + public final Logger logger = LoggerFactory.getLogger( BigWarpLandmarkPanel.class ); public BigWarpLandmarkPanel( LandmarkTableModel tableModel ) { - + super(new GridLayout(1,0)); setTableModel( tableModel ); - + genJTable(); - + //Create the scroll pane and add the table to it. - JScrollPane scrollPane = new JScrollPane(table); + final JScrollPane scrollPane = new JScrollPane(table); //Add the scroll pane to this panel. add(scrollPane); @@ -69,7 +70,11 @@ public BigWarpLandmarkPanel( LandmarkTableModel tableModel ) { public LandmarkTableModel getTableModel() { return tableModel; } - + + public int numDimensions() { + return tableModel.getNumdims(); + } + public void genJTable() { table = new JTableChecking( getTableModel() ); @@ -92,7 +97,7 @@ public void genJTable() /* * Add a listener to update the next row the tableModel will edit * based on the selected row. - * + * * Specifically, when the user changes the selected row of the table, the * listener checks whether any of those rows are "incomplete." * If so, the first row in the selection that does not have a moving image @@ -107,7 +112,7 @@ public void valueChanged( ListSelectionEvent e ) logger.trace( "table selection changed" ); boolean setMoving = false; boolean setFixed = false; - int row = table.getSelectedRow(); + final int row = table.getSelectedRow(); // if no rows are selected, the next edit should add a new row if( row < 0 ) @@ -136,32 +141,33 @@ public void valueChanged( ListSelectionEvent e ) } }); } - + public void setTableModel( LandmarkTableModel tableModel ) { this.tableModel = tableModel; genJTable(); } - + public JTable getJTable() { return table; } - + /** * A JTable implementation that prevents keybindings from being propagated * while editing cells. This prevents hotkeys from being activated. * */ - public class JTableChecking extends JTable + public static class JTableChecking extends JTable { private static final long serialVersionUID = 1437406384583869710L; - public JTableChecking( LandmarkTableModel tableModel ) + public JTableChecking( AbstractTableModel tableModel ) { super( tableModel ); } + @Override protected boolean processKeyBinding( KeyStroke ks, KeyEvent e, int condition, boolean pressed ) @@ -175,7 +181,7 @@ protected boolean processKeyBinding( } } - public class TextFieldCell extends JTextField + public static class TextFieldCell extends JTextField { private static final long serialVersionUID = -4327183047476876882L; @@ -195,7 +201,7 @@ public void focusGained( FocusEvent e ) @Override public void focusLost( FocusEvent e ) { - CellEditor cellEditor = table.getCellEditor(); + final CellEditor cellEditor = table.getCellEditor(); if ( cellEditor != null ) { if ( cellEditor.getCellEditorValue() != null ) @@ -211,7 +217,7 @@ public void focusLost( FocusEvent e ) } } - public class TextFieldCellEditor extends DefaultCellEditor + public static class TextFieldCellEditor extends DefaultCellEditor { private static final long serialVersionUID = 9185738725311357320L; @@ -239,7 +245,7 @@ public boolean stopCellEditing() public Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int row, int column ) { - TextFieldCell tf = (TextFieldCell) super.getTableCellEditorComponent( table, + final TextFieldCell tf = (TextFieldCell) super.getTableCellEditorComponent( table, value, isSelected, row, column ); if ( value != null ) { @@ -267,7 +273,7 @@ else if (columnClass.equals(Byte.class)) else if (columnClass.equals(String.class)) return textField.getText(); } - catch (NumberFormatException ex) { + catch (final NumberFormatException ex) { } diff --git a/src/main/java/bdv/gui/BigWarpViewerFrame.java b/src/main/java/bdv/gui/BigWarpViewerFrame.java index e7dcba3b..e892e9cc 100644 --- a/src/main/java/bdv/gui/BigWarpViewerFrame.java +++ b/src/main/java/bdv/gui/BigWarpViewerFrame.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -45,18 +45,20 @@ import bdv.tools.brightness.ConverterSetup; import bdv.ui.BdvDefaultCards; import bdv.ui.CardPanel; +import bdv.ui.appearance.AppearanceManager; import bdv.ui.splitpanel.SplitPanel; import bdv.viewer.BigWarpViewerPanel; import bdv.viewer.BigWarpViewerSettings; import bdv.viewer.ConverterSetups; import bdv.viewer.SourceAndConverter; import bigwarp.BigWarp; +import bigwarp.ui.keymap.KeymapManager; public class BigWarpViewerFrame extends JFrame { protected final BigWarpViewerPanel viewer; - + private final InputActionBindings keybindings; private final TriggerBehaviourBindings triggerbindings; @@ -67,8 +69,14 @@ public class BigWarpViewerFrame extends JFrame private CardPanel cards; + private final Behaviours transformBehaviours; + private final ConverterSetups setups; + private final KeymapManager keymapManager; + + private final AppearanceManager appearanceManager; + private static final long serialVersionUID = -7630931733043185034L; public BigWarpViewerFrame( @@ -79,13 +87,14 @@ public BigWarpViewerFrame( final BigWarpViewerSettings viewerSettings, final CacheControl cache, final String title, - final boolean isMoving, - final int[] movingIndexList, - final int[] targetIndexList ) + final boolean isMoving ) { - this( bw, width, height, sources, converterSetups, viewerSettings, cache, BigWarpViewerOptions.options(), title, isMoving, movingIndexList, targetIndexList ); + this( bw, width, height, sources, converterSetups, viewerSettings, cache, + new KeymapManager( BigWarp.configDir ), + new AppearanceManager( BigWarp.configDir ), + BigWarpViewerOptions.options(), title, isMoving ); } - + public BigWarpViewerFrame( BigWarp bw, final int width, final int height, @@ -93,33 +102,34 @@ public BigWarpViewerFrame( final List< ConverterSetup > converterSetups, final BigWarpViewerSettings viewerSettings, final CacheControl cache, + final KeymapManager keymapManager, + final AppearanceManager appearanceManager, final BigWarpViewerOptions optional, final String title, - final boolean isMoving, - final int[] movingIndexList, - final int[] targetIndexList ) + final boolean isMoving ) { super( title, AWTUtils.getSuitableGraphicsConfiguration( AWTUtils.RGB_COLOR_MODEL ) ); this.bw = bw; - viewer = new BigWarpViewerPanel( sources, viewerSettings, cache, optional.size( width / 2, height ), isMoving, movingIndexList, targetIndexList ); + this.keymapManager = keymapManager; + this.appearanceManager = appearanceManager; + + viewer = new BigWarpViewerPanel( bw.getData(), viewerSettings, cache, optional.size( width / 2, height ), isMoving ); setups = new ConverterSetups( viewer.state() ); setups.listeners().add( s -> viewer.requestRepaint() ); - if ( converterSetups.size() != sources.size() ) + if ( converterSetups.size() != bw.getData().sources.size() ) System.err.println( "WARNING! Constructing BigWarp with converterSetups.size() that is not the same as sources.size()." ); - final int numSetups = Math.min( converterSetups.size(), sources.size() ); + final int numSetups = Math.min( converterSetups.size(), bw.getData().sources.size() ); for ( int i = 0; i < numSetups; ++i ) { - final SourceAndConverter< ? > source = sources.get( i ); + final SourceAndConverter< ? > source = bw.getData().sources.get( i ); final ConverterSetup setup = converterSetups.get( i ); if ( setup != null ) setups.put( source, setup ); } - if ( !isMoving ) - { - viewer.state().setCurrentSource( viewer.state().getSources().get( bw.getData().targetSourceIndices[ 0 ] )); - } + if ( !isMoving && bw.getData().numTargetSources() > 0 ) + viewer.state().setCurrentSource( bw.getData().getTargetSource( 0 ) ); keybindings = new InputActionBindings(); triggerbindings = new TriggerBehaviourBindings(); @@ -154,20 +164,28 @@ public void valueChanged( ListSelectionEvent e ) } }); - SwingUtilities.replaceUIActionMap( getRootPane(), keybindings.getConcatenatedActionMap() ); - SwingUtilities.replaceUIInputMap( getRootPane(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); + SwingUtilities.replaceUIActionMap( viewer, keybindings.getConcatenatedActionMap() ); + SwingUtilities.replaceUIInputMap( viewer, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); final MouseAndKeyHandler mouseAndKeyHandler = new MouseAndKeyHandler(); mouseAndKeyHandler.setInputMap( triggerbindings.getConcatenatedInputTriggerMap() ); mouseAndKeyHandler.setBehaviourMap( triggerbindings.getConcatenatedBehaviourMap() ); + mouseAndKeyHandler.setKeypressManager( optional.values.getKeyPressedManager(), viewer.getDisplayComponent() ); viewer.getDisplay().addHandler( mouseAndKeyHandler ); + transformBehaviours = new Behaviours( optional.values.getInputTriggerConfig(), "bigwarp", "navigation" ); // TODO: should be a field? + updateTransformBehaviors( optional ); + } + + public void updateTransformBehaviors(BigWarpViewerOptions optional) { final Behaviours transformBehaviours = new Behaviours( optional.values.getInputTriggerConfig(), "bdv" ); transformBehaviours.install( triggerbindings, "transform" ); final TransformEventHandler tfHandler = viewer.getTransformEventHandler(); tfHandler.install( transformBehaviours ); + + viewer.getDisplay().setTransformEventHandler(tfHandler); } public boolean isMoving() @@ -206,12 +224,17 @@ public void collapseCardPanel() getSplitPanel().setCollapsed( true ); viewer.requestFocusInWindow(); } - + public InputActionBindings getKeybindings() { return keybindings; } + public TriggerBehaviourBindings getTriggerBindings() { + + return triggerbindings; + } + public void setTransformEnabled( final boolean enabled ) { viewer.setTransformEnabled( enabled ); @@ -220,4 +243,15 @@ public void setTransformEnabled( final boolean enabled ) else triggerbindings.addInputTriggerMap( "block_transform", new InputTriggerMap(), "transform" ); } + + /** + * Get {@code Behaviours} hook where TransformEventHandler behaviours are installed. + * This is installed in {@link #getTriggerBindings} with the id "transform". + * The hook can be used to update the keymap and install additional behaviours. + */ + public Behaviours getTransformBehaviours() + { + return transformBehaviours; + } + } diff --git a/src/main/java/bdv/gui/BigWarpViewerOptions.java b/src/main/java/bdv/gui/BigWarpViewerOptions.java index 9574e296..338a0194 100644 --- a/src/main/java/bdv/gui/BigWarpViewerOptions.java +++ b/src/main/java/bdv/gui/BigWarpViewerOptions.java @@ -23,7 +23,8 @@ import bdv.viewer.ViewerOptions; import bdv.viewer.ViewerPanel; -import bdv.viewer.animate.MessageOverlayAnimator; +import bigwarp.ui.keymap.KeymapManager; + import org.scijava.ui.behaviour.io.InputTriggerConfig; public class BigWarpViewerOptions extends ViewerOptions @@ -40,6 +41,11 @@ public static BigWarpViewerOptions options() return new BigWarpViewerOptions(); } + public static BigWarpViewerOptions options( final boolean is2D ) + { + return options().is2D( is2D ); + } + @Override public BigWarpViewerOptions inputTriggerConfig( final InputTriggerConfig c ) { @@ -54,6 +60,12 @@ public BigWarpViewerOptions is2D( final boolean is2D ) return this; } + public BigWarpViewerOptions bwKeymapManager( final KeymapManager keymapManager ) + { + bwValues.keymapManager = keymapManager; + return this; + } + public BwValues getValues() { return bwValues; @@ -76,6 +88,8 @@ public BigWarpViewerOptions size( final int width, final int height ) public BigWarpViewerOptions copy() { BigWarpViewerOptions out = new BigWarpViewerOptions(); + out.bwKeymapManager( bwValues.keymapManager ); + out. width( values.getWidth() ). height( values.getHeight() ). @@ -88,9 +102,8 @@ public BigWarpViewerOptions copy() is2D( values.is2D() ). transformEventHandlerFactory( values.getTransformEventHandlerFactory() ). accumulateProjectorFactory( values.getAccumulateProjectorFactory() ). - inputTriggerConfig( values.getInputTriggerConfig() ). + inputTriggerConfig( bwValues.getInputTriggerConfig() ). shareKeyPressedEvents( values.getKeyPressedManager() ). - keymapManager( values.getKeymapManager() ). appearanceManager( values.getAppearanceManager() ); return out; } @@ -104,6 +117,18 @@ public ViewerOptions getViewerOptions( final boolean isMoving ) public static class BwValues { + private KeymapManager keymapManager = null; private BigWarpMessageAnimator messageAnimator = new BigWarpMessageAnimator( 1500, 0.01, 0.1 ); + private InputTriggerConfig inputTriggerConfig = null; + + public KeymapManager getKeymapManager() + { + return keymapManager; + } + + public InputTriggerConfig getInputTriggerConfig() + { + return inputTriggerConfig; + } } } diff --git a/src/main/java/bdv/gui/BigwarpLandmarkSelectionPanel.java b/src/main/java/bdv/gui/BigwarpLandmarkSelectionPanel.java index 4c61ae62..63d46382 100644 --- a/src/main/java/bdv/gui/BigwarpLandmarkSelectionPanel.java +++ b/src/main/java/bdv/gui/BigwarpLandmarkSelectionPanel.java @@ -45,7 +45,7 @@ import bdv.ij.ApplyBigwarpPlugin.WriteDestinationOptions; import bdv.viewer.Interpolation; import bdv.viewer.SourceAndConverter; -import bigwarp.BigWarp.BigWarpData; +import bigwarp.BigWarpData; import net.imglib2.Interval; public class BigwarpLandmarkSelectionPanel extends JPanel @@ -138,12 +138,8 @@ public BigwarpLandmarkSelectionPanel( @Override public void actionPerformed( ActionEvent e ) { - System.out.println("ok"); filterPoints( matchedPtNames, outputIntervalList, selectionTable ); - System.out.println( matchedPtNames ); - System.out.println( outputIntervalList ); - ApplyBigwarpPlugin.runExport( data, sources, fieldOfViewOption, outputIntervalList, matchedPtNames, interp, offsetIn, resolution, isVirtual, nThreads, @@ -158,7 +154,6 @@ public void actionPerformed( ActionEvent e ) @Override public void actionPerformed( ActionEvent e ) { - //System.out.println("cancel"); frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); } }); diff --git a/src/main/java/bdv/gui/ExportDisplacementFieldFrame.java b/src/main/java/bdv/gui/ExportDisplacementFieldFrame.java new file mode 100644 index 00000000..ddb00f12 --- /dev/null +++ b/src/main/java/bdv/gui/ExportDisplacementFieldFrame.java @@ -0,0 +1,782 @@ +package bdv.gui; + +import java.awt.Container; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.io.File; +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JFormattedTextField; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; + +import com.formdev.flatlaf.util.UIScale; + +import bdv.ij.ApplyBigwarpPlugin; +import bdv.ij.BigWarpToDeformationFieldPlugIn; +import bdv.ij.BigWarpToDeformationFieldPlugIn.DeformationFieldExportParameters; +import bdv.viewer.Source; +import bigwarp.BigWarp; +import bigwarp.BigWarpData; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.transforms.BigWarpTransform; +import ij.IJ; +import ij.Macro; +import ij.plugin.frame.Recorder; + +public class ExportDisplacementFieldFrame extends JFrame +{ + private static final long serialVersionUID = -6179153725489981013L; + + // formats + public static final String FMT_NGFF = "NGFF"; + public static final String FMT_SLICER = "Slicer"; + + // macro recording + public static final String commandName = "Big Warp to Displacement field"; + protected static final String landmarksKey = "landmarks"; + protected static final String splitAffineKey = "split_affine"; + protected static final String typeKey = "type"; + protected static final String directionKey = "direction"; + protected static final String inverseToleranceKey = "inverseTolerance"; + protected static final String inverseMaxIterationsKey = "inverseMaxIterations"; + + protected static final String virtualKey = "virtual"; + protected static final String threadsKey = "threads"; + protected static final String formatKey = "format"; + protected static final String sizeKey = "pixel_size"; + protected static final String spacingKey = "pixel_spacing"; + protected static final String minKey = "min"; + protected static final String unitKey = "unit"; + protected static final String n5RootKey = "n5_root"; + protected static final String n5DatasetKey = "n5_dataset"; + protected static final String n5BlockSizeKey = "n5_block_size"; + protected static final String n5CompressionKey = "n5_compression"; + + private String lastBrowsePath = null; + private String initialPath = null; + + // no need at the moment for these callbacks to consume parameters, but will leave it this way + private Consumer okayCallback; + private Consumer cancelCallback; + + private BigWarpData< ? > data; + private BigWarpTransform bwTransform; + private LandmarkTableModel ltm; + + private boolean imageJOpen; + private boolean initialRecorderState; + + private boolean n5DatasetChanged; + private DocumentListener docListener; + + private JPanel contentPanel; + private JPanel invPanel; + + private JTextField landmarkPathTxt; + private JButton browseLandmarksButton; + private JTextField n5RootTxt; + private JButton browseN5Button; + private JTextField n5DatasetTxt; + private JTextField n5BlockSizeTxt; + private JComboBox< String > n5CompressionDropdown; + private JCheckBox splitAffineCheckBox; + private JCheckBox virtualCheckBox; + private JComboBox< String > typeComboBox; + private JComboBox< String > directionComboBox; + private JSpinner nThreadsField; + private JComboBox< String > formatComboBox; + private JButton okBtn; + private JButton cancelBtn; + + // inverse options + private JSpinner invMaxIterationsSpinner; + private JSpinner invToleranceSpinner; + + private FieldOfViewPanel fovPanel; + + public ExportDisplacementFieldFrame( BigWarp bw ) + { + this( bw.getData(), bw.getBwTransform(), bw.getLandmarkPanel().getTableModel()); + } + + public ExportDisplacementFieldFrame( BigWarpData data, BigWarpTransform bwTransform, LandmarkTableModel ltm ) + { + super( "Export transformation" ); + initialPath = ""; + imageJOpen = IJ.getInstance() != null; + + this.data = data; + this.bwTransform = bwTransform; + this.ltm = ltm; + + cancelCallback = x -> { + dispose(); + setVisible( false ); + Recorder.record = initialRecorderState; + }; + + okayCallback = x -> { + macroRecord(); + run(); + Recorder.record = initialRecorderState; + dispose(); + setVisible( false ); + }; + + // attach to the n5Dataset text field, keep track of whether user changes it + // once user change occurs, default values from direction dropdown no longer affect it + docListener = new DocumentListener() { + @Override + public void changedUpdate( DocumentEvent e ) { } + + @Override + public void insertUpdate( DocumentEvent e ) { n5DatasetChanged = true; } + + @Override + public void removeUpdate( DocumentEvent e ) { n5DatasetChanged = true; } + }; + } + + public static void createAndShow() + { + createAndShow( null, null, null ); + } + + public static void createAndShow( final BigWarp< ? > bw ) + { + ExportDisplacementFieldFrame frame = new ExportDisplacementFieldFrame( bw ); + if ( bw == null ) + frame = new ExportDisplacementFieldFrame( null, null, null ); + else + frame = new ExportDisplacementFieldFrame( bw ); + + frame.createContent(); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + frame.pack(); + frame.setVisible( true ); + } + + public static void createAndShow( BigWarpData< ? > data, BigWarpTransform bwTransform, LandmarkTableModel ltm ) + { + final ExportDisplacementFieldFrame frame = new ExportDisplacementFieldFrame( data, bwTransform, ltm ); + frame.createContent(); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + frame.pack(); + frame.setVisible( true ); + } + + public void createContent() + { + final boolean isNonlinear = bwTransform.isNonlinear(); + + n5DatasetChanged = false; + final int frameSizeX = UIScale.scale( 600 ); + + final JPanel panel = basicPanel(); + + invPanel = inverseOptionsPanel( frameSizeX ); + invPanel.setBorder( BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), + BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + "Inverse options" ), + BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); + + + final JPanel n5Panel = n5OptionsPanel( frameSizeX ); + n5Panel.setBorder( BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), + BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + "N5 options" ), + BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); + + // field of view panel + String unit = "pixel"; + if( data != null ) + { + final Source< ? > src = data.getMovingSource( 0 ).getSpimSource(); + unit = src.getVoxelDimensions().unit(); + } + + if( isNonlinear ) + { + fovPanel = new FieldOfViewPanel( data, ltm, bwTransform, unit, 150, + new double[] { 0, 0, 0 }, + new double[] { 1, 1, 1 }, + new long[] { 256, 256, 128 } + ); + + fovPanel.setBorder( BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), + BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + "Field of view" ), + BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); + } + + contentPanel = new JPanel(); + contentPanel.setLayout( new GridBagLayout() ); + + final GridBagConstraints cGbc = new GridBagConstraints(); + cGbc.gridx = 0; + cGbc.gridwidth = 4; + cGbc.fill = GridBagConstraints.HORIZONTAL; + + cGbc.gridy = 0; + contentPanel.add( panel, cGbc ); + cGbc.gridy = 1; + contentPanel.add( invPanel, cGbc ); + invPanel.setEnabled( false ); + invPanel.setVisible( false ); + cGbc.gridy = 2; + contentPanel.add( n5Panel, cGbc ); + cGbc.gridy = 3; + if( isNonlinear ) + contentPanel.add( fovPanel, cGbc ); + + // bottom button section + final GridBagConstraints cbot = new GridBagConstraints(); + cbot.gridx = 0; + cbot.gridy = 4; + cbot.gridwidth = 1; + cbot.gridheight = 1; + cbot.weightx = 1.0; + cbot.weighty = 0.0; + cbot.fill = GridBagConstraints.NONE; + + okBtn = new JButton( "OK" ); + okBtn.setPreferredSize( new Dimension( okBtn.getPreferredSize().width, okBtn.getPreferredSize().height ) ); + okBtn.addActionListener( e -> okayCallback.accept( getParams() ) ); + cbot.gridx = 2; + cbot.gridwidth = 1; + cbot.insets = new Insets( 20, ( int ) ( frameSizeX * 0.8 ), 20, 2 ); + cbot.anchor = GridBagConstraints.LINE_END; + contentPanel.add( okBtn, cbot ); + + cancelBtn = new JButton( "Cancel" ); + cancelBtn.setPreferredSize( new Dimension( cancelBtn.getPreferredSize().width, cancelBtn.getPreferredSize().height ) ); + cancelBtn.addActionListener( e -> cancelCallback.accept( null ) ); + cbot.gridx = 3; + cbot.anchor = GridBagConstraints.LINE_START; + cbot.ipadx = 0; + cbot.insets = new Insets( 2, 2, 2, 2 ); + contentPanel.add( cancelBtn, cbot ); + + final Container content = getContentPane(); + content.add( contentPanel ); + + if( isNonlinear ) + { + if( data != null ) + { + // set default resolution to target image resolution + final double[] res = ApplyBigwarpPlugin.getResolution( data, ApplyBigwarpPlugin.TARGET, null ); + fovPanel.setSpacing(res); + fovPanel.updateFieldsFromReference(); + } + else + fovPanel.updateFieldsFromImageJReference(); + } + + addDefaultN5DatasetAction(); + } + + public JPanel basicPanel() + { + final int OUTER_PAD = BigWarpInitDialog.DEFAULT_OUTER_PAD; + final int BUTTON_PAD = BigWarpInitDialog.DEFAULT_BUTTON_PAD; + final int MID_PAD = BigWarpInitDialog.DEFAULT_MID_PAD; + final Insets defaultInsets = new Insets( OUTER_PAD, BUTTON_PAD, MID_PAD, BUTTON_PAD ); + + final int szX = UIScale.scale( 600 ); + + final JPanel panel = new JPanel( false ); + panel.setLayout( new GridBagLayout() ); + + final GridBagConstraints ctxt = new GridBagConstraints(); + ctxt.gridx = 0; + ctxt.gridy = 0; + ctxt.gridwidth = 1; + ctxt.gridheight = 1; + ctxt.weightx = 0.0; + ctxt.weighty = 0.0; + ctxt.anchor = GridBagConstraints.LINE_END; + ctxt.fill = GridBagConstraints.NONE; + ctxt.insets = defaultInsets; + + final GridBagConstraints gbcBar = new GridBagConstraints(); + gbcBar.gridx = 1; + gbcBar.gridy = 0; + gbcBar.gridwidth = 6; + gbcBar.gridheight = 1; + gbcBar.weightx = 1.0; + gbcBar.weighty = 0.0; + gbcBar.fill = GridBagConstraints.HORIZONTAL; + gbcBar.insets = defaultInsets; + + final GridBagConstraints cProjBrowse = new GridBagConstraints(); + cProjBrowse.gridx = 7; + cProjBrowse.gridy = 0; + cProjBrowse.gridwidth = 1; + cProjBrowse.weightx = 0.0; + cProjBrowse.fill = GridBagConstraints.HORIZONTAL; + cProjBrowse.insets = defaultInsets; + + // Don't ask for landmarks if running from a bigwarp instance + if( bwTransform == null ) + { + panel.add( new JLabel( "Landmarks:" ), ctxt ); + + landmarkPathTxt = new JTextField(); + landmarkPathTxt.setPreferredSize( new Dimension( szX / 3, landmarkPathTxt.getPreferredSize().height ) ); + panel.add( landmarkPathTxt, gbcBar ); + + browseLandmarksButton = new JButton( "Browse" ); + browseLandmarksButton.addActionListener( e -> { + browseLandmarksDialog(); + } ); + panel.add( browseLandmarksButton, cProjBrowse ); + } + + ctxt.gridx = 0; + ctxt.gridy = 1; + ctxt.anchor = GridBagConstraints.LINE_END; + panel.add( new JLabel( "Type:" ), ctxt ); + + final GridBagConstraints gbcCheck = new GridBagConstraints(); + gbcCheck.gridx = 1; + gbcCheck.gridy = 1; + gbcCheck.insets = defaultInsets; + gbcCheck.anchor = GridBagConstraints.LINE_START; + typeComboBox = new JComboBox< String >( new String[] { + BigWarpToDeformationFieldPlugIn.flattenOption, + BigWarpToDeformationFieldPlugIn.sequenceOption } ); + panel.add( typeComboBox, gbcCheck ); + + // want some more padding for direction + ctxt.gridx = 3; + ctxt.insets = new Insets( OUTER_PAD, 10*BUTTON_PAD, MID_PAD, BUTTON_PAD ); + panel.add( new JLabel( "Direction:" ), ctxt ); + ctxt.insets = defaultInsets; + + gbcCheck.gridx = 4; + directionComboBox = new JComboBox< String >( new String[] { + BigWarpToDeformationFieldPlugIn.INVERSE_OPTIONS.FORWARD.toString(), + BigWarpToDeformationFieldPlugIn.INVERSE_OPTIONS.INVERSE.toString(), + BigWarpToDeformationFieldPlugIn.INVERSE_OPTIONS.BOTH.toString() } ); + panel.add( directionComboBox, gbcCheck ); + + ctxt.gridx = 5; + ctxt.anchor = GridBagConstraints.LINE_END; + panel.add( new JLabel( "Split affine:" ), ctxt ); + + gbcCheck.gridx = 6; + splitAffineCheckBox = new JCheckBox(); + panel.add( splitAffineCheckBox, gbcCheck ); + + // second row + ctxt.gridx = 0; + ctxt.gridy = 2; + ctxt.anchor = GridBagConstraints.LINE_END; + panel.add( new JLabel( "Threads:" ), ctxt ); + + gbcCheck.gridx = 1; + gbcCheck.gridy = 2; + gbcCheck.fill = GridBagConstraints.HORIZONTAL; + nThreadsField = new JSpinner( new SpinnerNumberModel( 1, 1, 9999, 1 ) ); + panel.add( nThreadsField, gbcCheck ); + + ctxt.gridx = 3; + panel.add( new JLabel( "Format:" ), ctxt ); + gbcCheck.gridx = 4; +// formatComboBox = new JComboBox< String >( new String[] { FMT_NGFF, FMT_SLICER } ); + formatComboBox = new JComboBox< String >( new String[] { FMT_NGFF } ); + panel.add( formatComboBox, gbcCheck ); + + ctxt.gridx = 5; + ctxt.anchor = GridBagConstraints.LINE_END; + ctxt.insets = new Insets( OUTER_PAD, BUTTON_PAD, MID_PAD, BUTTON_PAD ); + ctxt.weightx = 0.1; + panel.add( new JLabel( "Virtual:" ), ctxt ); + + gbcCheck.gridx = 6; + gbcCheck.weightx = 0.1; + virtualCheckBox = new JCheckBox(); + panel.add( virtualCheckBox, gbcCheck ); + + return panel; + } + + public JPanel inverseOptionsPanel( final int frameSizeX ) + { + final int OUTER_PAD = BigWarpInitDialog.DEFAULT_OUTER_PAD; + final int BUTTON_PAD = BigWarpInitDialog.DEFAULT_BUTTON_PAD; + final int MID_PAD = BigWarpInitDialog.DEFAULT_MID_PAD; + + final JPanel panel = new JPanel(); + panel.setLayout( new GridBagLayout() ); + + final GridBagConstraints ctxt = new GridBagConstraints(); + ctxt.gridx = 0; + ctxt.gridy = 0; + ctxt.gridwidth = 1; + ctxt.gridheight = 1; + ctxt.weightx = 0.0; + ctxt.weighty = 0.0; + ctxt.anchor = GridBagConstraints.LINE_END; + ctxt.fill = GridBagConstraints.NONE; + ctxt.insets = new Insets( OUTER_PAD, OUTER_PAD, MID_PAD, BUTTON_PAD ); + + final GridBagConstraints gbcBar = new GridBagConstraints(); + gbcBar.gridx = 1; + gbcBar.gridy = 0; + gbcBar.gridwidth = 1; + gbcBar.gridheight = 1; + gbcBar.weightx = 1.0; + gbcBar.weighty = 0.0; + gbcBar.fill = GridBagConstraints.HORIZONTAL; + gbcBar.insets = new Insets( OUTER_PAD, OUTER_PAD, MID_PAD, BUTTON_PAD ); + + ctxt.gridx = 0; + ctxt.anchor = GridBagConstraints.LINE_END; + panel.add( new JLabel( "Tolerance:" ), ctxt ); + + gbcBar.gridx = 1; + gbcBar.fill = GridBagConstraints.HORIZONTAL; + invToleranceSpinner = new JSpinner( new SpinnerNumberModel( 0.5, 1e-9, 999999, 0.01 ) ); + + final JSpinner.NumberEditor editor = new JSpinner.NumberEditor(invToleranceSpinner, "###,###.######"); + final JFormattedTextField textField = editor.getTextField(); + textField.setColumns(12); + invToleranceSpinner.setEditor(editor); + + panel.add( invToleranceSpinner, gbcBar ); + + ctxt.gridx = 3; + ctxt.anchor = GridBagConstraints.LINE_END; + panel.add( new JLabel( "Max iterations:" ), ctxt ); + + gbcBar.gridx = 4; + gbcBar.fill = GridBagConstraints.HORIZONTAL; + invMaxIterationsSpinner = new JSpinner( new SpinnerNumberModel( 200, 1, 999999, 1 ) ); + panel.add( invMaxIterationsSpinner, gbcBar ); + + return panel; + } + + public JPanel n5OptionsPanel( final int frameSizeX ) + { + final int OUTER_PAD = BigWarpInitDialog.DEFAULT_OUTER_PAD; + final int BUTTON_PAD = BigWarpInitDialog.DEFAULT_BUTTON_PAD; + final int MID_PAD = BigWarpInitDialog.DEFAULT_MID_PAD; + + final JPanel panel = new JPanel(); + panel.setLayout( new GridBagLayout() ); + + final GridBagConstraints ctxt = new GridBagConstraints(); + ctxt.gridx = 0; + ctxt.gridy = 0; + ctxt.gridwidth = 1; + ctxt.gridheight = 1; + ctxt.weightx = 0.0; + ctxt.weighty = 0.0; + ctxt.anchor = GridBagConstraints.LINE_END; + ctxt.fill = GridBagConstraints.NONE; + ctxt.insets = new Insets( OUTER_PAD, OUTER_PAD, MID_PAD, BUTTON_PAD ); + panel.add( new JLabel( "Root folder:" ), ctxt ); + + final GridBagConstraints gbcBar = new GridBagConstraints(); + gbcBar.gridx = 1; + gbcBar.gridy = 0; + gbcBar.gridwidth = 6; + gbcBar.gridheight = 1; + gbcBar.weightx = 1.0; + gbcBar.weighty = 0.0; + gbcBar.fill = GridBagConstraints.HORIZONTAL; + gbcBar.insets = new Insets( OUTER_PAD, OUTER_PAD, MID_PAD, BUTTON_PAD ); + + n5RootTxt = new JTextField(); + n5RootTxt.setPreferredSize( new Dimension( frameSizeX / 3, n5RootTxt.getPreferredSize().height ) ); + panel.add( n5RootTxt, gbcBar ); + + final GridBagConstraints cProjBrowse = new GridBagConstraints(); + cProjBrowse.gridx = 7; + cProjBrowse.gridy = 0; + cProjBrowse.gridwidth = 1; + cProjBrowse.weightx = 0.0; + cProjBrowse.fill = GridBagConstraints.HORIZONTAL; + cProjBrowse.insets = new Insets( OUTER_PAD, BUTTON_PAD, MID_PAD, BUTTON_PAD ); + browseN5Button = new JButton( "Browse" ); + browseN5Button.addActionListener( e -> { + browseN5Root(); + } ); + panel.add( browseN5Button, cProjBrowse ); + + ctxt.gridy = 1; + panel.add( new JLabel( "Dataset:" ), ctxt ); + + gbcBar.gridy = 1; + n5DatasetTxt = new JTextField(); + n5DatasetTxt.setPreferredSize( new Dimension( frameSizeX / 3, n5DatasetTxt.getPreferredSize().height ) ); + n5DatasetTxt.setText( "dfield" ); + n5DatasetTxt.getDocument().addDocumentListener( docListener ); + + + panel.add( n5DatasetTxt, gbcBar ); + + ctxt.gridy = 2; + panel.add( new JLabel( "Block size:" ), ctxt ); + + gbcBar.gridy = 2; + n5BlockSizeTxt = new JTextField(); + n5BlockSizeTxt.setPreferredSize( new Dimension( frameSizeX / 3, n5BlockSizeTxt.getPreferredSize().height ) ); + n5BlockSizeTxt.setText( "64" ); + panel.add( n5BlockSizeTxt, gbcBar ); + + ctxt.gridy = 3; + panel.add( new JLabel( "Compression" ), ctxt ); + + gbcBar.gridy = 3; + gbcBar.fill = GridBagConstraints.NONE; + gbcBar.anchor = GridBagConstraints.LINE_START; + n5CompressionDropdown = new JComboBox< String >( BigWarpToDeformationFieldPlugIn.compressionOptions ); + panel.add( n5CompressionDropdown, gbcBar ); + + return panel; + } + + private void addDefaultN5DatasetAction() + { + directionComboBox.addActionListener( e -> { + if( n5DatasetChanged ) { + return; + } + + final String item = (String)directionComboBox.getSelectedItem(); + if( item.equals( BigWarpToDeformationFieldPlugIn.INVERSE_OPTIONS.FORWARD.toString() )) + { + n5DatasetTxt.getDocument().removeDocumentListener( docListener ); + n5DatasetTxt.setText( "dfield" ); + n5DatasetTxt.getDocument().addDocumentListener( docListener ); + + SwingUtilities.invokeLater( () -> { + invPanel.setEnabled( false ); + invPanel.setVisible( false ); + contentPanel.revalidate(); + contentPanel.repaint(); + pack(); + }); + } + else if( item.equals( BigWarpToDeformationFieldPlugIn.INVERSE_OPTIONS.INVERSE.toString() )) + { + n5DatasetTxt.getDocument().removeDocumentListener( docListener ); + n5DatasetTxt.setText( "invdfield" ); + n5DatasetTxt.getDocument().addDocumentListener( docListener ); + SwingUtilities.invokeLater( () -> { + invPanel.setEnabled( true ); + invPanel.setVisible( true ); + contentPanel.revalidate(); + contentPanel.repaint(); + pack(); + }); + } + else if( item.equals( BigWarpToDeformationFieldPlugIn.INVERSE_OPTIONS.BOTH.toString() )) + { + n5DatasetTxt.getDocument().removeDocumentListener( docListener ); + n5DatasetTxt.setText( "transform" ); + n5DatasetTxt.getDocument().addDocumentListener( docListener ); + SwingUtilities.invokeLater( () -> { + invPanel.setEnabled( true ); + invPanel.setVisible( true ); + contentPanel.revalidate(); + contentPanel.repaint(); + pack(); + }); + } + }); + } + + private String browseLandmarksDialog() + { + final String s = browseDialogGeneral( JFileChooser.FILES_ONLY, new FileNameExtensionFilter( "csv file", "csv" ) ); + landmarkPathTxt.setText( s ); + + return s; + } + + private String browseN5Root() + { + final String s = browseDialogGeneral( JFileChooser.FILES_AND_DIRECTORIES, null ); + n5RootTxt.setText( s ); + return s; + } + + private String browseDialogGeneral( final int mode, final FileFilter filefilter ) + { + + final JFileChooser fileChooser = new JFileChooser(); + /* + * Need to allow files so h5 containers can be opened, and directories + * so that filesystem n5's and zarrs can be opened. + */ + fileChooser.setFileSelectionMode( mode ); + if( filefilter == null ) + { + fileChooser.setFileFilter( filefilter ); + } + + if ( lastBrowsePath != null && !lastBrowsePath.isEmpty() ) + fileChooser.setCurrentDirectory( new File( lastBrowsePath ) ); + else if ( initialPath != null && !initialPath.isEmpty() ) + fileChooser.setCurrentDirectory( new File( initialPath ) ); + else if ( imageJOpen ) + { + File f = null; + + final String currDir = IJ.getDirectory( "current" ); + final String homeDir = IJ.getDirectory( "home" ); + if ( currDir != null ) + f = new File( currDir ); + else if ( homeDir != null ) + f = new File( homeDir ); + + fileChooser.setCurrentDirectory( f ); + } + + final int ret = fileChooser.showOpenDialog( this ); + if ( ret != JFileChooser.APPROVE_OPTION ) + return null; + + final String path = fileChooser.getSelectedFile().getAbsolutePath(); + lastBrowsePath = path; + + return path; + } + + public DeformationFieldExportParameters getParams() + { + final String n5BlockSizeString = n5BlockSizeTxt.getText(); + final int[] blockSize = n5BlockSizeString.isEmpty() ? null : + Arrays.stream( n5BlockSizeString.split( "," ) ).mapToInt( Integer::parseInt ).toArray(); + + return new DeformationFieldExportParameters( + landmarkPathTxt == null ? "" : landmarkPathTxt.getText(), + splitAffineCheckBox.isSelected(), + (String)typeComboBox.getSelectedItem(), + (String)directionComboBox.getSelectedItem(), + (Double)invToleranceSpinner.getValue(), + (Integer)invMaxIterationsSpinner.getValue(), + virtualCheckBox.isSelected(), + (Integer)nThreadsField.getValue(), + (String)formatComboBox.getSelectedItem(), + fovPanel == null ? null : fovPanel.getPixelSize(), + fovPanel == null ? null : fovPanel.getSpacing(), + fovPanel == null ? null : fovPanel.getMin(), + fovPanel == null ? null : fovPanel.getUnit(), + n5RootTxt.getText(), + n5DatasetTxt.getText(), + blockSize, + BigWarpToDeformationFieldPlugIn.getCompression( (String)n5CompressionDropdown.getSelectedItem() ) ); + } + + public void run() + { + BigWarpToDeformationFieldPlugIn.runFromParameters( getParams(), data, ltm, bwTransform ); + } + + public String macroRecord() + { + if( !Recorder.record ) + return ""; + + Recorder.setCommand( commandName ); + final String szString = Arrays.stream( fovPanel.getPixelSize() ).mapToObj( Long::toString ).collect( Collectors.joining( "," ) ); + final String spacingString = Arrays.stream( fovPanel.getSpacing() ).mapToObj( Double::toString ).collect( Collectors.joining( "," ) ); + final String minString = Arrays.stream( fovPanel.getMin() ).mapToObj( Double::toString ).collect( Collectors.joining( "," ) ); + + Recorder.resetCommandOptions(); + Recorder.recordOption( landmarksKey, landmarkPathTxt.getText().trim() ); + Recorder.recordOption( splitAffineKey ); + Recorder.recordOption( virtualKey ); + Recorder.recordOption( typeKey, ( String ) typeComboBox.getSelectedItem() ); + Recorder.recordOption( threadsKey, Integer.toString( ( Integer ) nThreadsField.getValue() ) ); + Recorder.recordOption( sizeKey, szString ); + Recorder.recordOption( spacingKey, spacingString ); + Recorder.recordOption( minKey, minString ); + Recorder.recordOption( unitKey, fovPanel.getUnit() ); + + if( !n5RootTxt.getText().isEmpty() ) + { + Recorder.recordOption( n5RootKey, n5RootTxt.getText().trim() ); + Recorder.recordOption( n5DatasetKey, n5DatasetTxt.getText().trim() ); + Recorder.recordOption( n5BlockSizeKey, n5BlockSizeTxt.getText().trim() ); + Recorder.recordOption( n5CompressionKey, ( String ) n5CompressionDropdown.getSelectedItem() ); + } + + Recorder.saveCommand(); + return Recorder.getCommandOptions(); + } + + public static void runMacro( String args ) + { + final String landmarks = Macro.getValue( args, landmarksKey, "" ); + final String type = Macro.getValue( args, typeKey, "" ); + final String direction = Macro.getValue( args, directionKey, "" ); + final double tolerance = Double.valueOf( Macro.getValue( args, inverseToleranceKey, "" )); + final int maxIters = Integer.valueOf( Macro.getValue( args, inverseMaxIterationsKey, "" )); + + final boolean splitAffine = args.contains(" " + splitAffineKey ); + final boolean openAsVirtual = args.contains(" " + virtualKey); + final int threads = Integer.valueOf( Macro.getValue( args, threadsKey, "1" )); + final String format = Macro.getValue( args, formatKey, FMT_NGFF ); + + final double[] min = Arrays.stream( Macro.getValue( args, minKey, "" ).split( "," ) ).mapToDouble( Double::valueOf ).toArray(); + final double[] spacing = Arrays.stream( Macro.getValue( args, spacingKey, "" ).split( "," ) ).mapToDouble( Double::valueOf ).toArray(); + final long[] pixSize = Arrays.stream( Macro.getValue( args, sizeKey, "" ).split( "," ) ).mapToLong( Long::valueOf ).toArray(); + final String unit = Macro.getValue( args, typeKey, "pixel" ); + + final String n5Root = Macro.getValue( args, n5RootKey, "" ); + final String n5Dataset = Macro.getValue( args, n5DatasetKey, "" ); + final String n5BlockSizeString = Macro.getValue( args, n5BlockSizeKey, "" ); + final String n5Compression = Macro.getValue( args, n5CompressionKey, "" ); + + final int[] blockSize = n5BlockSizeString.isEmpty() ? null : + Arrays.stream( n5BlockSizeString.split( "," ) ).mapToInt( Integer::parseInt ).toArray(); + + final DeformationFieldExportParameters params = new DeformationFieldExportParameters( + landmarks, splitAffine, type, + direction, tolerance, maxIters, + openAsVirtual, threads, format, + pixSize, spacing, min, unit, + n5Root, + n5Dataset, + blockSize, + BigWarpToDeformationFieldPlugIn.getCompression( n5Compression ) ); + + BigWarpToDeformationFieldPlugIn.runFromParameters( params, null, null, null ); + } + +} \ No newline at end of file diff --git a/src/main/java/bdv/gui/FieldOfViewPanel.java b/src/main/java/bdv/gui/FieldOfViewPanel.java new file mode 100644 index 00000000..60e49e72 --- /dev/null +++ b/src/main/java/bdv/gui/FieldOfViewPanel.java @@ -0,0 +1,513 @@ +package bdv.gui; + +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Arrays; +import java.util.List; + +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import com.formdev.flatlaf.util.UIScale; + +import bdv.ij.ApplyBigwarpPlugin; +import bigwarp.BigWarpData; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.transforms.BigWarpTransform; +import ij.IJ; +import ij.ImagePlus; +import ij.WindowManager; +import net.imglib2.Interval; +import net.imglib2.realtransform.BoundingBoxEstimation; + +public class FieldOfViewPanel extends JPanel +{ + private static final long serialVersionUID = 1719652204751351335L; + + private static final int DEFAULT_OUTER_PAD = 8; + private static final int DEFAULT_BUTTON_PAD = 3; + private static final int DEFAULT_MID_PAD = 5; + + private BigWarpData data; + private LandmarkTableModel ltm; + private BigWarpTransform bwTransform; + + private String unit; + private int ndims; + private int textFieldWidth; + + private double[] initMin; + private double[] initSpacing; + private long[] initPixsize; + + private double[] min; + private double[] spacing; + private double[] size; + private long[] pixSize; + + private JComboBox referenceComboBox; + private JTextField unitField; + private JLabel sizeLabel; + + private ImprovedFormattedTextField[] minFields; + private ImprovedFormattedTextField[] sizeFields; + private ImprovedFormattedTextField[] spacingFields; + private ImprovedFormattedTextField[] pixelFields; + + public FieldOfViewPanel( final LandmarkTableModel ltm, final BigWarpTransform bwTransform, final String unit, final int textFieldWidth, + final double[] initMin, final double[] initSpacing, final long[] initPixsize ) + { + this( null, ltm, bwTransform, unit, textFieldWidth, initMin, initSpacing, initPixsize ); + } + + public FieldOfViewPanel( final BigWarpData data, final LandmarkTableModel ltm, final BigWarpTransform bwTransform, final String unit, final int textFieldWidth, + final double[] initMin, final double[] initSpacing, final long[] initPixsize ) + { + super(); + + this.data = data; + this.ltm = ltm; + this.bwTransform = bwTransform; + + this.ndims = ltm != null ? ltm.getNumdims() : 3; + this.unit = unit; + this.textFieldWidth = textFieldWidth; + + this.initMin = initMin; + this.initSpacing = initSpacing; + this.initPixsize = initPixsize; + + this.min = new double[ ndims ]; + System.arraycopy( initMin, 0, min, 0, ndims ); + + this.spacing = new double[ ndims ]; + System.arraycopy( initSpacing, 0, spacing, 0, ndims ); + + this.pixSize = new long[ ndims ]; + System.arraycopy( initPixsize, 0, pixSize, 0, ndims ); + + this.size = new double[ ndims ]; + for ( int i = 0; i < ndims; i++ ) + size[ i ] = spacing[ i ] * pixSize[ i ]; + + create(); + } + + public double[] getMin() + { + return min; + } + + /** + * Sets the minimum value, does not trigger callbacks. + * + * @param newMin the new min val + */ + public void setMin( final double[] newMin ) + { + final int N = newMin.length > min.length ? min.length : newMin.length; + for ( int i = 0; i < N; i++ ) + { + minFields[ i ].setValue( new Double( newMin[ i ] ), false ); + min[ i ] = newMin[ i ]; + } + } + + public double[] getSpacing() + { + return spacing; + } + + /** + * Sets the spacing, does not trigger callbacks + * + * @param newSpacing the new spacing + */ + public void setSpacing( final double[] newSpacing ) + { + final int N = newSpacing.length > spacing.length ? spacing.length : newSpacing.length; + for ( int i = 0; i < N; i++ ) + { + spacingFields[ i ].setValue( new Double( newSpacing[ i ] ), false ); + spacing[ i ] = newSpacing[ i ]; + } + } + + public double[] getPhysicalSize() + { + return size; + } + + public long[] getPixelSize() + { + return pixSize; + } + + /** + * Sets the pixel size, does not trigger callbacks. + * + * @param newSize the new discrete image size (in pixels) + */ + public void setPixelSize( final long[] newSize ) + { + final int N = newSize.length > pixSize.length ? pixSize.length : newSize.length; + for ( int i = 0; i < N; i++ ) + { + pixelFields[i].setValue( new Long(newSize[i]), false ); + pixSize[ i ] = newSize[ i ]; + } + } + + public String getUnit() + { + return unitField.getText(); + } + + public void setUnit( String unit ) + { + unitField.setText( unit ); + updateSizeLabel(); + } + + private void updateSizeLabel() + { + sizeLabel.setText( String.format( "size (%s)", unitField.getText() ) ); + } + + public void create() + { + setLayout(new GridBagLayout()); + + final GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.CENTER; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets( DEFAULT_OUTER_PAD, DEFAULT_OUTER_PAD, DEFAULT_MID_PAD, DEFAULT_BUTTON_PAD ); + + int j = 1; + if( data != null ) + { + add( new JLabel( "reference:" ), gbc ); + + final String[] fovOpts = new String[]{ + ApplyBigwarpPlugin.SPECIFIED, + ApplyBigwarpPlugin.TARGET, + ApplyBigwarpPlugin.MOVING_WARPED, + ApplyBigwarpPlugin.LANDMARK_POINTS }; + referenceComboBox = new JComboBox<>( fovOpts ); + referenceComboBox.setSelectedItem( ApplyBigwarpPlugin.MOVING_WARPED ); + referenceComboBox.addActionListener( e -> { + updateFieldsFromReference(); + }); + + gbc.gridx = 1; + add( referenceComboBox, gbc ); + + j = 2; + } + else if( IJ.getInstance() != null ) + { + add( new JLabel( "reference:" ), gbc ); + + final String[] impTitles = getImagePlusTitles(); + int numImp = 0; + if( impTitles != null ) + numImp = impTitles.length; + + final String[] fovOpts = new String[ numImp + 1 ]; + fovOpts[ 0 ] = ApplyBigwarpPlugin.SPECIFIED; + for( int i = 0; i < numImp; i++ ) + fovOpts[ i + 1 ] = impTitles[ i ]; + + referenceComboBox = new JComboBox<>( fovOpts ); + if( impTitles.length > 0 ) + referenceComboBox.setSelectedIndex( 1 ); + + referenceComboBox.addActionListener( e -> { + updateFieldsFromImageJReference(); + }); + + gbc.gridx = 1; + add( referenceComboBox, gbc ); + + j = 2; + } + + final JLabel unitLabel = new JLabel( "units:" ); + gbc.gridx = 2; + gbc.anchor = GridBagConstraints.LINE_END; + add( unitLabel, gbc ); + + gbc.gridx = 3; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.CENTER; + unitField = new JTextField("pixel"); + unitField.getDocument().addDocumentListener( new DocumentListener() { + + @Override + public void changedUpdate( DocumentEvent e ) { } + + @Override + public void insertUpdate( DocumentEvent e ) + { + updateSizeLabel(); + } + + @Override + public void removeUpdate( DocumentEvent e ) + { + updateSizeLabel(); + } + }); + add( unitField, gbc ); + + final JLabel minLabel = new JLabel( String.format("min (%s)", unit )); + sizeLabel = new JLabel( String.format( "size (%s)", unit ) ); + final JLabel spacingLabel = new JLabel( String.format( "spacing (%s/px)", unit )); + final JLabel pixelLabel = new JLabel( "size (px)" ); + + gbc.gridy++; + gbc.gridx = 1; + add( minLabel, gbc ); + gbc.gridx = 2; + add( sizeLabel, gbc ); + gbc.gridx = 3; + add( spacingLabel, gbc ); + gbc.gridx = 4; + add( pixelLabel, gbc ); + + final JLabel yLabel = new JLabel("y"); + + gbc.gridx = 0; + gbc.gridy++; + gbc.anchor = GridBagConstraints.LINE_END; + + final JLabel xLabel = new JLabel("x"); + add( xLabel, gbc ); + + gbc.gridy++; + add( yLabel, gbc ); + + if( ndims >= 3 ) + { + gbc.gridy++; + final JLabel zLabel = new JLabel("z"); + add( zLabel, gbc ); + } + + /* + * TODO investigate focus traversal, see: + * https://docs.oracle.com/javase/tutorial/uiswing/misc/focus.html#customFocusTraversal + */ + + minFields = new ImprovedFormattedTextField[ ndims ]; + sizeFields = new ImprovedFormattedTextField[ ndims ]; + spacingFields = new ImprovedFormattedTextField[ ndims ]; + pixelFields = new ImprovedFormattedTextField[ ndims ]; + gbc.fill = GridBagConstraints.HORIZONTAL; + + final int textWidthScaled = UIScale.scale( textFieldWidth ); + final int textHeight = UIScale.scale( 20 ); + final Dimension textFieldSize = new Dimension( textWidthScaled, textHeight ); + final DecimalFormat decimalFormat = new DecimalFormat(); + decimalFormat.setMaximumFractionDigits( 8 ); + + // add fields + for( int i = 0; i < ndims; i++ ) + { + gbc.gridy = i + j; + + gbc.gridx = 1; + minFields[ i ] = new ImprovedFormattedTextField( decimalFormat ); + minFields[ i ].setPreferredSize( textFieldSize ); + minFields[ i ].setHorizontalAlignment( JTextField.RIGHT ); + minFields[ i ].setValueNoCallback( new Double( initMin[i]) ); + add( minFields[ i ], gbc ); + + gbc.gridx = 2; + sizeFields[ i ] = new ImprovedFormattedTextField( decimalFormat ); + sizeFields[ i ].setPreferredSize( textFieldSize ); + sizeFields[ i ].setHorizontalAlignment( JTextField.RIGHT ); + sizeFields[ i ].setValueNoCallback( new Double( initSpacing[ i ] * initPixsize[ i ] ) ); + add( sizeFields[ i ], gbc ); + + gbc.gridx = 3; + spacingFields[ i ] = new ImprovedFormattedTextField( decimalFormat ); + spacingFields[ i ].setPreferredSize( textFieldSize ); + spacingFields[ i ].setHorizontalAlignment( JTextField.RIGHT ); + spacingFields[ i ].setValueNoCallback( new Double( initSpacing[ i ] ) ); + + add( spacingFields[ i ], gbc ); + + gbc.gridx = 4; + pixelFields[ i ] = new ImprovedFormattedTextField( NumberFormat.getIntegerInstance()); + pixelFields[ i ].setPreferredSize( textFieldSize ); + pixelFields[ i ].setHorizontalAlignment( JTextField.RIGHT ); + pixelFields[ i ].setValueNoCallback( new Long( initPixsize[ i ] ) ); + add( pixelFields[ i ], gbc ); + + // set callbacks for fields + // what fields update others when modified + final int idx = i; + minFields[ i ].setCallback( () -> { +// System.out.println("min callback"); + try { + min[ idx ] = Double.parseDouble( minFields[ idx ].getText() ); + } catch (final NumberFormatException e) {} + }); + + sizeFields[ i ].setCallback( () -> { +// System.out.println("size callback"); + try { + size[ idx ] = Double.parseDouble( sizeFields[ idx ].getText() ); + updatePixelsFromSize( idx ); + } catch (final NumberFormatException e) {} + }); + + spacingFields[ i ].setCallback( () -> { +// System.out.println("spacing callback"); + try { + spacing[ idx ] = Double.parseDouble( spacingFields[ idx ].getText() ); +// updatePixelsFromSpacing( idx ); + updateSize( idx ); + } catch (final NumberFormatException e) {} + }); + + pixelFields[ i ].setCallback( () -> { +// System.out.println("pix sz callback"); + try { + pixSize[ idx ] = Long.parseLong( pixelFields[ idx ].getText() ); + updateSize( idx ); + } catch (final NumberFormatException e) {} +// updateSpacingFromPixels( idx ); // an alternative update + }); + } + } + + protected void updatePixelsFromSize( int i ) + { + pixSize[ i ] = ( long ) Math.ceil( size[ i ] / spacing[ i ] ); + SwingUtilities.invokeLater( () -> { pixelFields[ i ].setValueNoCallback( new Double( pixSize[ i ] ) ); }); + } + + protected void updatePixelsFromSpacing( int i ) + { + pixSize[ i ] = ( long ) Math.floor( size[ i ] / spacing[ i ] ); + SwingUtilities.invokeLater( () -> { pixelFields[ i ].setValueNoCallback( new Long( pixSize[ i ] ) ); }); + } + + protected void updateSpacingFromPixels( int i ) + { + spacing[ i ] = size[ i ] / pixSize[ i ]; + SwingUtilities.invokeLater( () -> { spacingFields[ i ].setValueNoCallback( new Double( spacing[ i ] ) ); }); + } + + protected void updateSize( int i ) + { + size[ i ] = spacing[ i ] * pixSize[ i ]; + SwingUtilities.invokeLater( () -> { sizeFields[ i ].setValueNoCallback( new Double( size[ i ] ) ); }); + } + + protected void updateFieldsFromReference() + { + if ( data == null || bwTransform == null ) + return; + + final String referenceOption = ( String ) referenceComboBox.getSelectedItem(); + if ( referenceOption.equals( ApplyBigwarpPlugin.SPECIFIED ) ) + return; + + final double[] res = ApplyBigwarpPlugin.getResolution( data, referenceOption, null ); + if ( res != null ) + setSpacing( res ); + + final List< Interval > itvl = ApplyBigwarpPlugin.getPixelInterval( data, ltm, bwTransform.getTransformation( false ), + referenceOption, "", new BoundingBoxEstimation(), null, null, getSpacing() ); + + final double[] offset = ApplyBigwarpPlugin.getPixelOffset( referenceOption, null, res, itvl.get( 0 ) ); + + setPixelSize( itvl.get( 0 ).dimensionsAsLongArray() ); + setMin( offset ); + setUnit( ApplyBigwarpPlugin.getUnit( data, referenceOption )); + + for ( int i = 0; i < size.length; i++ ) + updateSize( i ); + } + + protected String[] getImagePlusTitles() + { + if( IJ.getInstance() != null ) + { + return WindowManager.getImageTitles(); + } + else + return null; + } + + protected void updateFieldsFromImageJReference() + { + final String referenceOption = (String)referenceComboBox.getSelectedItem(); + if( referenceOption.equals( ApplyBigwarpPlugin.SPECIFIED )) + return; + + if( IJ.getInstance() != null ) + { + final ImagePlus refImp = WindowManager.getImage( (String)referenceComboBox.getSelectedItem() ); + setSpacing( new double[] { + refImp.getCalibration().pixelWidth, + refImp.getCalibration().pixelHeight, + refImp.getCalibration().pixelDepth, + }); + + setMin( new double[] { + refImp.getCalibration().xOrigin, + refImp.getCalibration().yOrigin, + refImp.getCalibration().zOrigin, + }); + + setPixelSize( new long[] { + refImp.getWidth(), + refImp.getHeight(), + refImp.getNSlices() + }); + + setUnit( refImp.getCalibration().getUnit() ); + } + } + +// public static void main( String[] args ) throws IOException, URISyntaxException, SpimDataException +// { +// final JFrame frame = new JFrame( "fov" ); +// BigWarpData data = new BigWarpData(); +// +// int id = 0; +// BigWarpInit.add( data, BigWarpInit.createSources( data, "/home/john/tmp/mri-stack_mm.tif", id++, true ) ); +// BigWarpInit.add( data, BigWarpInit.createSources( data, "/home/john/tmp/mri-stack_mm.tif", id++, false ) ); +// +//// LandmarkTableModel ltm = LandmarkTableModel.loadFromCsv( new File("/home/john/tmp/mri-stack-landmarks.csv"), false ); +// LandmarkTableModel ltm = LandmarkTableModel.loadFromCsv( new File("/home/john/tmp/mri-stack-mm-landmarks.csv"), false ); +// BigWarpTransform bwTransform = new BigWarpTransform( ltm, BigWarpTransform.TPS ); +// +// final FieldOfViewPanel panel = new FieldOfViewPanel( data, ltm, bwTransform, "mm", 150, +// new double[] { 0, 0, 0 }, new double[] { 1, 1, 1 }, new long[] { 300, 200, 100 } ); +// +// frame.add( panel ); +// frame.pack(); +// frame.setVisible( true ); +// } + +} diff --git a/src/main/java/bdv/gui/ImprovedFormattedTextField.java b/src/main/java/bdv/gui/ImprovedFormattedTextField.java new file mode 100644 index 00000000..790338ee --- /dev/null +++ b/src/main/java/bdv/gui/ImprovedFormattedTextField.java @@ -0,0 +1,308 @@ +package bdv.gui; + +import javax.swing.JFormattedTextField; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.Color; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.KeyEvent; +import java.text.Format; +import java.text.ParseException; +import java.text.AttributedCharacterIterator; +import java.text.FieldPosition; +import java.text.ParsePosition; + +/** + *

+ * Extension of {@code JFormattedTextField} which solves some of the usability + * issues + *

+ * + *

+ * from + * https://stackoverflow.com/questions/1313390/is-there-any-way-to-accept-only-numeric-values-in-a-jtextfield?answertab=scoredesc#tab-top + *

+ */ +public class ImprovedFormattedTextField extends JFormattedTextField +{ + private static final Color ERROR_BACKGROUND_COLOR = new Color( 255, 215, 215 ); + + private static final Color ERROR_FOREGROUND_COLOR = null; + + private Color fBackground, fForeground; + + private Runnable updateCallback; + + private boolean runCallback = true; + + /** + * Create a new {@code ImprovedFormattedTextField} instance which will use + * {@code aFormat} for the validation of the user input. + * + * @param aFormat + * The format. May not be {@code null} + */ + public ImprovedFormattedTextField( Format aFormat ) + { + // use a ParseAllFormat as we do not want to accept user input which is + // partially valid + super( new ParseAllFormat( aFormat ) ); + setFocusLostBehavior( JFormattedTextField.COMMIT_OR_REVERT ); + updateBackgroundOnEachUpdate(); + // improve the caret behavior + // see also + // http://tips4java.wordpress.com/2010/02/21/formatted-text-field-tips/ + addFocusListener( new MousePositionCorrectorListener() ); + } + + /** + * Create a new {@code ImprovedFormattedTextField} instance which will use + * {@code aFormat} for the validation of the user input. The field will be + * initialized with {@code aValue}. + * + * @param aFormat + * The format. May not be {@code null} + * @param aValue + * The initial value + */ + public ImprovedFormattedTextField( Format aFormat, Object aValue ) + { + this( aFormat ); + setValue( aValue ); + } + + public void setCallback(final Runnable updateCallback) { + + this.updateCallback = updateCallback; + } + + private void updateBackgroundOnEachUpdate() + { + getDocument().addDocumentListener( new DocumentListener() + { + @Override + public void insertUpdate( DocumentEvent e ) + { + updateBackground(); + if( runCallback && updateCallback != null ) + updateCallback.run(); + } + + @Override + public void removeUpdate( DocumentEvent e ) + { + updateBackground(); + if( runCallback && updateCallback != null ) + updateCallback.run(); + } + + @Override + public void changedUpdate( DocumentEvent e ) + { + updateBackground(); + if( runCallback && updateCallback != null ) + updateCallback.run(); + } + } ); + } + + /** + * Update the background color depending on the valid state of the current + * input. This provides visual feedback to the user + */ + private void updateBackground() + { + final boolean valid = validContent(); + if ( ERROR_BACKGROUND_COLOR != null ) + { + setBackground( valid ? fBackground : ERROR_BACKGROUND_COLOR ); + } + if ( ERROR_FOREGROUND_COLOR != null ) + { + setForeground( valid ? fForeground : ERROR_FOREGROUND_COLOR ); + } + } + + @Override + public void updateUI() + { + super.updateUI(); + fBackground = getBackground(); + fForeground = getForeground(); + } + + private boolean validContent() + { + final AbstractFormatter formatter = getFormatter(); + if ( formatter != null ) + { + try + { + formatter.stringToValue( getText() ); + return true; + } + catch ( final ParseException e ) + { + return false; + } + } + return true; + } + + public void setValue( Object value, boolean callback ) + { + boolean validValue = true; + // before setting the value, parse it by using the format + try + { + final AbstractFormatter formatter = getFormatter(); + if ( formatter != null ) + { + formatter.valueToString( value ); + } + } + catch ( final ParseException e ) + { + validValue = false; + updateBackground(); + } + // only set the value when valid + if ( validValue ) + { + final int old_caret_position = getCaretPosition(); + + // TODO synchronize? + final boolean before = runCallback; + runCallback = callback; + super.setValue( value ); + runCallback = before; + + setCaretPosition( Math.min( old_caret_position, getText().length() ) ); + } + } + + public void setValueNoCallback( Object value ) + { + setValue( value, false ); + } + + @Override + public void setValue( Object value ) + { + setValue( value, true ); + } + + @Override + protected boolean processKeyBinding( KeyStroke ks, KeyEvent e, int condition, boolean pressed ) + { + // do not let the formatted text field consume the enters. This allows + // to trigger an OK button by + // pressing enter from within the formatted text field + if ( validContent() ) + { + return super.processKeyBinding( ks, e, condition, pressed ) && ks != KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ); + } + else + { + return super.processKeyBinding( ks, e, condition, pressed ); + } + } + + private static class MousePositionCorrectorListener extends FocusAdapter + { + @Override + public void focusGained( FocusEvent e ) + { + /* + * After a formatted text field gains focus, it replaces its text + * with its current value, formatted appropriately of course. It + * does this after any focus listeners are notified. We want to make + * sure that the caret is placed in the correct position rather than + * the dumb default that is before the 1st character ! + */ + final JTextField field = ( JTextField ) e.getSource(); + final int dot = field.getCaret().getDot(); + final int mark = field.getCaret().getMark(); + if ( field.isEnabled() && field.isEditable() ) + { + SwingUtilities.invokeLater( new Runnable() + { + @Override + public void run() + { + // Only set the caret if the textfield hasn't got a + // selection on it + if ( dot == mark ) + { + field.getCaret().setDot( dot ); + } + } + } ); + } + } + } + + /** + *

+ * Decorator for a {@link Format Format} which only accepts values which can + * be completely parsed by the delegate format. If the value can only be + * partially parsed, the decorator will refuse to parse the value. + *

+ */ + public static class ParseAllFormat extends Format + { + private final Format fDelegate; + + /** + * Decorate aDelegate to make sure if parser everything or + * nothing + * + * @param aDelegate + * The delegate format + */ + public ParseAllFormat( Format aDelegate ) + { + fDelegate = aDelegate; + } + + @Override + public StringBuffer format( Object obj, StringBuffer toAppendTo, FieldPosition pos ) + { + return fDelegate.format( obj, toAppendTo, pos ); + } + + @Override + public AttributedCharacterIterator formatToCharacterIterator( Object obj ) + { + return fDelegate.formatToCharacterIterator( obj ); + } + + @Override + public Object parseObject( String source, ParsePosition pos ) + { + final int initialIndex = pos.getIndex(); + final Object result = fDelegate.parseObject( source, pos ); + if ( result != null && pos.getIndex() < source.length() ) + { + final int errorIndex = pos.getIndex(); + pos.setIndex( initialIndex ); + pos.setErrorIndex( errorIndex ); + return null; + } + return result; + } + + @Override + public Object parseObject( String source ) throws ParseException + { + // no need to delegate the call, super will call the parseObject( + // source, pos ) method + return super.parseObject( source ); + } + } + +} diff --git a/src/main/java/bdv/gui/MaskOptionsPanel.java b/src/main/java/bdv/gui/MaskOptionsPanel.java new file mode 100644 index 00000000..9117f2de --- /dev/null +++ b/src/main/java/bdv/gui/MaskOptionsPanel.java @@ -0,0 +1,209 @@ +package bdv.gui; + +import bigwarp.BigWarp; +import bigwarp.source.PlateauSphericalMaskRealRandomAccessible.FalloffShape; +import bigwarp.source.PlateauSphericalMaskSource; +import bigwarp.transforms.BigWarpTransform; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import bdv.ui.convertersetupeditor.MaskBoundsRangePanel; + +public class MaskOptionsPanel extends JPanel +{ + private static final long serialVersionUID = -8614381106547838575L; + + private static final String FALLOFF_HELP_TEXT = "Controls the shape of the mask in the transition region."; + private static final String AUTO_ESTIMATE_HELP_TEXT = "If selected, the mask location and size will be dynamically updated as you add landmarks."; + private static final String SHOW_MASK_OVERLAY_HELP_TEXT = "Toggles the visibility of the mask overlay"; + private static final String INTERPOLATION_HELP_TEXT = "Controls whether a mask is applied to the transformation and how the transformation changes " + + "in the mask transition region.\n" + + "If your transformation has lots of rotation, try selecting \"ROTATION\" or \"SIMILARITY\"."; + + public static final String[] maskTypes = new String[] { + BigWarpTransform.NO_MASK_INTERP, + BigWarpTransform.MASK_INTERP, + BigWarpTransform.ROT_MASK_INTERP, + BigWarpTransform.SIM_MASK_INTERP, + }; + + private final BigWarp< ? > bw; + + private final JCheckBox autoEstimateMaskButton; + private final JCheckBox showMaskOverlayButton; + + private final JLabel falloffTypeLabel; + private final JComboBox< FalloffShape > falloffTypeDropdown; + + private final JLabel maskTypeLabel; + private final JComboBox< String > maskTypeDropdown; + + private final MaskBoundsRangePanel maskRangePanel; + + private ActionListener falloffListener; + + public MaskOptionsPanel( BigWarp bw ) + { + super( new GridBagLayout() ); + this.bw = bw; + + setBorder( BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), + BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + "Mask options" ), + BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); + + autoEstimateMaskButton = new JCheckBox( "Auto-estimate mask", true ); + autoEstimateMaskButton.setToolTipText( AUTO_ESTIMATE_HELP_TEXT ); + + showMaskOverlayButton = new JCheckBox( "Show mask overlay", true ); + showMaskOverlayButton.setToolTipText( SHOW_MASK_OVERLAY_HELP_TEXT ); + + + falloffTypeLabel = new JLabel( "Mask falloff"); + falloffTypeLabel.setToolTipText( FALLOFF_HELP_TEXT ); + falloffTypeDropdown = new JComboBox<>( FalloffShape.values() ); + falloffTypeDropdown.setToolTipText( FALLOFF_HELP_TEXT ); + + maskTypeLabel = new JLabel( "Mask interpolation"); + maskTypeLabel.setToolTipText( INTERPOLATION_HELP_TEXT ); + maskTypeDropdown = new JComboBox<>( maskTypes ); + maskTypeDropdown.setToolTipText( INTERPOLATION_HELP_TEXT ); + + maskRangePanel = new MaskBoundsRangePanel(bw); + + // layout + final GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets( 5, 5, 5, 5 ); + add( maskTypeLabel, gbc ); + + gbc.gridx = 2; + gbc.weightx = 0.0; + gbc.anchor = GridBagConstraints.LINE_START; + add( maskTypeDropdown, gbc ); + + gbc.gridx = 0; + gbc.gridy = 1; + gbc.anchor = GridBagConstraints.LINE_END; + add( falloffTypeLabel, gbc ); + + gbc.gridx = 2; + gbc.anchor = GridBagConstraints.LINE_START; + add( falloffTypeDropdown, gbc ); + + gbc.gridx = 0; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.LINE_END; + add( autoEstimateMaskButton, gbc ); + + gbc.gridx = 2; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.LINE_START; + add( showMaskOverlayButton, gbc ); + + gbc.gridx = 0; + gbc.gridy = 3; + gbc.anchor = GridBagConstraints.LINE_START; + add( new JLabel("Imported mask intensity range:"), gbc ); + + gbc.gridy = 4; + gbc.gridwidth = 3; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + add( maskRangePanel, gbc ); + } + + public void addActions() + { + autoEstimateMaskButton.addActionListener( e -> { + if ( autoEstimateMaskButton.isSelected() ) + bw.autoEstimateMask(); + } ); + + showMaskOverlayButton.addActionListener( e -> { + bw.setMaskOverlayVisibility( showMaskOverlayButton.isSelected() && isMask() ); + } ); + + maskTypeDropdown.addActionListener( e -> { + bw.updateTransformMask(); + } ); + } + + public void setMask( PlateauSphericalMaskSource maskSource ) + { + if( falloffListener == null ) + falloffTypeDropdown.removeActionListener( falloffListener ); + + falloffTypeDropdown.addActionListener( new ActionListener() { + @Override + public void actionPerformed( ActionEvent e ) + { + maskSource.getRandomAccessible().setFalloffShape( (FalloffShape)falloffTypeDropdown.getSelectedItem() ); + bw.getViewerFrameP().getViewerPanel().requestRepaint(); + bw.getViewerFrameQ().getViewerPanel().requestRepaint(); + } + }); + + } + + public JCheckBox getAutoEstimateMaskButton() + { + return autoEstimateMaskButton; + } + + public JCheckBox getShowMaskOverlayButton() + { + return showMaskOverlayButton; + } + + public JComboBox< String > getMaskTypeDropdown() + { + return maskTypeDropdown; + } + + public JComboBox< FalloffShape > getMaskFalloffTypeDropdown() + { + return falloffTypeDropdown; + } + + public MaskBoundsRangePanel getMaskRangeSlider() + { + return maskRangePanel; + } + + public String getType() + { + return ( String ) maskTypeDropdown.getSelectedItem(); + } + + /* + * @return true if a mask is applied to the transformation + */ + public boolean isMask() + { + return maskTypeDropdown.getSelectedIndex() > 0; + } + + public boolean showMaskOverlay() + { + return showMaskOverlayButton.isSelected(); + } +} diff --git a/src/main/java/bdv/gui/MaskedSourceEditorMouseListener.java b/src/main/java/bdv/gui/MaskedSourceEditorMouseListener.java new file mode 100644 index 00000000..dba48386 --- /dev/null +++ b/src/main/java/bdv/gui/MaskedSourceEditorMouseListener.java @@ -0,0 +1,213 @@ +package bdv.gui; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.util.ArrayList; +import java.util.List; + +import bdv.util.Affine3DHelpers; +import bdv.viewer.BigWarpViewerPanel; +import bdv.viewer.overlay.BigWarpMaskSphereOverlay; +import bigwarp.BigWarp; +import bigwarp.source.PlateauSphericalMaskRealRandomAccessible; +import bigwarp.transforms.AbstractTransformSolver; +import bigwarp.transforms.MaskedSimRotTransformSolver; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineTransform3D; + +public class MaskedSourceEditorMouseListener implements MouseListener, MouseMotionListener, MouseWheelListener +{ + protected PlateauSphericalMaskRealRandomAccessible mask; + protected BigWarpViewerPanel viewer; + protected List overlays; + + private boolean active; + private boolean dragged = false; + + private RealPoint p; + private RealPoint c; + private RealPoint pressPt; + + private BigWarp bw; + + private static final double fastSpeed = 10.0; + private static final double slowSpeed = 0.1; + + public MaskedSourceEditorMouseListener( int nd, BigWarp bw, BigWarpViewerPanel viewer ) + { + this.bw = bw; + this.viewer = viewer; + + this.viewer.getDisplay().addMouseListener( this ); + this.viewer.getDisplay().addMouseWheelListener( this ); + this.viewer.getDisplay().addMouseMotionListener( this ); + + overlays = new ArrayList(); + overlays.add( bw.getViewerFrameP().getViewerPanel().getMaskOverlay() ); + overlays.add( bw.getViewerFrameQ().getViewerPanel().getMaskOverlay() ); + + p = new RealPoint( 3 ); + c = new RealPoint( 3 ); + pressPt = new RealPoint( 3 ); + active = false; + } + + public void setActive( boolean active ) + { + this.active = active; + bw.getViewerFrameP().setTransformEnabled( !active ); + bw.getViewerFrameQ().setTransformEnabled( !active ); + + final String msg = active ? "Mask Edit On" : "Mask Edit Off"; + bw.getViewerFrameP().getViewerPanel().showMessage( msg ); + bw.getViewerFrameQ().getViewerPanel().showMessage( msg ); + } + + public void toggleActive( ) + { + setActive( !active ); + } + + public void setMask( PlateauSphericalMaskRealRandomAccessible mask ) + { + this.mask = mask; + } + + public PlateauSphericalMaskRealRandomAccessible getMask() + { + return mask; + } + + @Override + public void mouseClicked( MouseEvent e ) { } + + @Override + public void mouseEntered( MouseEvent e ) { } + + @Override + public void mouseExited( MouseEvent e ) { } + + @Override + public void mousePressed( MouseEvent e ) + { + if( !active ) + return; + + dragged = false; + } + + @Override + public void mouseMoved( MouseEvent e ) { } + + @Override + public void mouseDragged( MouseEvent e ) + { + if( !active ) + return; + + // store starting center at start of drag + if( !dragged ) + { +// c.setPosition( mask.getCenter() ); + mask.getCenter().localize( c ); + viewer.getGlobalMouseCoordinates( pressPt ); + dragged = true; + } + + viewer.getGlobalMouseCoordinates( p ); + bw.setAutoEstimateMask( false ); + + if( e.isControlDown() ) + { + mask.getCenter().localize( c ); + final double d = PlateauSphericalMaskRealRandomAccessible.squaredDistance( p, c ); + synchronized ( mask ) + { + mask.setSquaredRadius( d ); + } + } + else if( e.isShiftDown() ) + { + mask.getCenter().localize( c ); + final double d = Math.sqrt( PlateauSphericalMaskRealRandomAccessible.squaredDistance( p, c )); + synchronized ( mask ) + { + mask.setSigma( d - Math.sqrt( mask.getSquaredRadius()) ); + } + } + else + { + // p - pressPt inside the setPosition is the delta + // c is the original center (before dragging started) + // the code below sets the mask center to (c + delta) + for( int i = 0; i < p.numDimensions(); i++ ) + p.setPosition( c.getDoublePosition(i) + p.getDoublePosition(i) - pressPt.getDoublePosition(i), i); + + synchronized ( mask ) + { + mask.setCenter(p); + } + + AbstractTransformSolver< ? > solver = bw.getBwTransform().getSolver(); + if( solver instanceof MaskedSimRotTransformSolver ) + { + ((MaskedSimRotTransformSolver)solver).setCenter( p ); + } + } + + bw.getViewerFrameP().getViewerPanel().requestRepaint(); + bw.getViewerFrameQ().getViewerPanel().requestRepaint(); + } + + @Override + public void mouseReleased( MouseEvent e ) { + + if( !active ) + return; + + if( e.isControlDown() || e.isShiftDown() ) + return; + + if( !dragged ) + { + viewer.getGlobalMouseCoordinates( pressPt ); + synchronized ( mask ) + { + mask.setCenter( pressPt ); + } + + bw.setAutoEstimateMask( false ); + bw.getViewerFrameP().getViewerPanel().requestRepaint(); + bw.getViewerFrameQ().getViewerPanel().requestRepaint(); + } + else + dragged = false; + } + + @Override + public void mouseWheelMoved( MouseWheelEvent e ) + { + if( !active ) + return; + + bw.setAutoEstimateMask( false ); + final AffineTransform3D transform = viewer.state().getViewerTransform(); + final double scale = (1.0 / (Affine3DHelpers.extractScale(transform, 0) + 1e-9 ) + 1e-6 ); + final int sign = e.getWheelRotation(); + + if( e.isShiftDown() ) + mask.incSquaredSigma( sign * scale * scale * fastSpeed * fastSpeed ); + else if ( e.isControlDown() ) + mask.incSquaredSigma( sign * scale * scale * slowSpeed * slowSpeed ); + else + mask.incSquaredSigma( sign * scale * scale ); + + bw.getViewerFrameP().getViewerPanel().requestRepaint(); + bw.getViewerFrameQ().getViewerPanel().requestRepaint(); + } + + +} diff --git a/src/main/java/bdv/gui/TransformTypePanel.java b/src/main/java/bdv/gui/TransformTypePanel.java new file mode 100644 index 00000000..93ce1160 --- /dev/null +++ b/src/main/java/bdv/gui/TransformTypePanel.java @@ -0,0 +1,111 @@ +package bdv.gui; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import bigwarp.BigWarp; +import bigwarp.transforms.BigWarpTransform; + +public class TransformTypePanel extends JPanel +{ + private static final long serialVersionUID = 3285885870885172257L; + + private static final String TRANSFORM_TYPE_HELP_TEXT = "Select the type of transformation."; + + public static final String[] TRANSFORM_TYPE_STRINGS = new String[] { + BigWarpTransform.TPS, BigWarpTransform.AFFINE, + BigWarpTransform.SIMILARITY, BigWarpTransform.ROTATION, BigWarpTransform.TRANSLATION }; + + private final BigWarp< ? > bw; + + private final JLabel transformTypeLabel; + private final JComboBox< String > transformTypeDropdown; + + private boolean active; + + public TransformTypePanel( BigWarp bw ) + { + super( new GridBagLayout() ); + this.bw = bw; + active = true; + + setBorder( BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), + BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + "Transformation options" ), + BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); + + transformTypeLabel = new JLabel( "Transform type"); + transformTypeLabel.setToolTipText( TRANSFORM_TYPE_HELP_TEXT ); + transformTypeDropdown = new JComboBox<>( TRANSFORM_TYPE_STRINGS ); + getTransformTypeDropdown().setToolTipText( TRANSFORM_TYPE_HELP_TEXT ); + getTransformTypeDropdown().addActionListener( new ActionListener() { + @Override + public void actionPerformed( ActionEvent e ) + { + if( active ) + { + final String type = (String)transformTypeDropdown.getSelectedItem(); + bw.setTransformType( type ); + bw.updateTransformTypeDialog( type ); + } + } + }); + + // layout + final GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets( 5, 5, 5, 5 ); + add( transformTypeLabel, gbc ); + + gbc.gridx = 2; + gbc.weightx = 0.0; + gbc.anchor = GridBagConstraints.LINE_START; + add( getTransformTypeDropdown(), gbc ); + + } + + public JComboBox< String > getTransformTypeDropdown() + { + return transformTypeDropdown; + } + + public void setType( String type ) + { + transformTypeDropdown.setSelectedItem( type ); + } + + /** + * After calling deactivate, updates to this panel won't affect Bigwarp. + */ + public void deactivate() + { + active = false; + } + + /** + * After calling activate, updates to this panel will affect Bigwarp. + */ + public void activate() + { + active = true; + } + +} diff --git a/src/main/java/bdv/gui/TransformTypeSelectDialog.java b/src/main/java/bdv/gui/TransformTypeSelectDialog.java index fae8b02e..c662b450 100644 --- a/src/main/java/bdv/gui/TransformTypeSelectDialog.java +++ b/src/main/java/bdv/gui/TransformTypeSelectDialog.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -34,30 +34,43 @@ import javax.swing.JRadioButton; import bigwarp.BigWarp; +import bigwarp.transforms.BigWarpTransform; public class TransformTypeSelectDialog extends JDialog { private static final long serialVersionUID = 1L; - + + @Deprecated public static final String TPS = "Thin Plate Spline"; + @Deprecated + public static final String MASKEDTPS = "Masked Thin Plate Spline"; + @Deprecated + public static final String MASKEDSIMTPS = "Masked Similarity + Thin Plate Spline"; + @Deprecated public static final String AFFINE = "Affine"; + @Deprecated public static final String SIMILARITY = "Similarity"; + @Deprecated public static final String ROTATION = "Rotation"; + @Deprecated public static final String TRANSLATION = "Translation"; - + private final BigWarp< ? > bw; private String transformType; + private final ButtonGroup group; private final JRadioButton tpsButton; private final JRadioButton affineButton; private final JRadioButton similarityButton; private final JRadioButton rotationButton; private final JRadioButton translationButton; + private boolean active; + /** * Instantiates and displays a JFrame that enables * the selection of the transformation type. - * + * * @param owner the parent frame * @param bw a bigwarp instance */ @@ -66,16 +79,17 @@ public TransformTypeSelectDialog( final Frame owner, final BigWarp< ? > bw ) super( owner, "Transform Type select", false ); this.bw = bw; + active = true; this.setLayout( new BorderLayout() ); transformType = bw.getTransformType(); - tpsButton = new JRadioButton( TPS ); - affineButton = new JRadioButton( AFFINE ); - similarityButton = new JRadioButton( SIMILARITY ); - rotationButton = new JRadioButton( ROTATION ); - translationButton = new JRadioButton( TRANSLATION ); - - ButtonGroup group = new ButtonGroup(); + tpsButton = new JRadioButton( BigWarpTransform.TPS ); + affineButton = new JRadioButton( BigWarpTransform.AFFINE ); + similarityButton = new JRadioButton( BigWarpTransform.SIMILARITY ); + rotationButton = new JRadioButton( BigWarpTransform.ROTATION ); + translationButton = new JRadioButton( BigWarpTransform.TRANSLATION ); + + group = new ButtonGroup(); group.add( tpsButton ); group.add( affineButton ); group.add( similarityButton ); @@ -89,14 +103,14 @@ public TransformTypeSelectDialog( final Frame owner, final BigWarp< ? > bw ) addActionListender( similarityButton ); addActionListender( rotationButton ); addActionListender( translationButton ); - - JPanel radioPanel = new JPanel( new GridLayout(0, 1)); + + final JPanel radioPanel = new JPanel( new GridLayout(0, 1)); radioPanel.add( tpsButton ); radioPanel.add( affineButton ); radioPanel.add( similarityButton ); radioPanel.add( rotationButton ); radioPanel.add( translationButton ); - + radioPanel.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), BorderFactory.createCompoundBorder( @@ -105,7 +119,8 @@ public TransformTypeSelectDialog( final Frame owner, final BigWarp< ? > bw ) "Transform type" ), BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); - add( radioPanel, BorderLayout.LINE_START ); + add( radioPanel, BorderLayout.PAGE_START ); + pack(); } @@ -113,19 +128,19 @@ private void updateButtonGroup() { switch( transformType ) { - case TPS: + case BigWarpTransform.TPS: tpsButton.setSelected( true ); break; - case AFFINE: + case BigWarpTransform.AFFINE: affineButton.setSelected( true ); break; - case SIMILARITY: + case BigWarpTransform.SIMILARITY: similarityButton.setSelected( true ); break; - case ROTATION: + case BigWarpTransform.ROTATION: rotationButton.setSelected( true ); break; - case TRANSLATION: + case BigWarpTransform.TRANSLATION: translationButton.setSelected( true ); break; } @@ -136,11 +151,30 @@ public void addActionListender( final JRadioButton button ) button.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - bw.setTransformType( button.getText() ); + if( active ) + { + final String type = button.getText(); + bw.setTransformType( type ); + bw.updateTransformTypePanel( type ); + } } }); } + public void addFalloffActionListender( final JRadioButton button ) + { + button.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + bw.getTransformPlateauMaskSource().getRandomAccessible().setFalloffShape( button.getText() ); + bw.getViewerFrameP().getViewerPanel().requestRepaint(); + bw.getViewerFrameQ().getViewerPanel().requestRepaint(); + } + } ); + } + public void setTransformType( String transformType ) { this.transformType = transformType; @@ -148,4 +182,21 @@ public void setTransformType( String transformType ) this.validate(); this.repaint(); } + + /** + * After calling deactivate, updates to this panel won't affect Bigwarp. + */ + public void deactivate() + { + active = false; + } + + /** + * After calling activate, updates to this panel will affect Bigwarp. + */ + public void activate() + { + active = true; + } + } diff --git a/src/main/java/bdv/gui/sourceList/BigWarpSourceListPanel.java b/src/main/java/bdv/gui/sourceList/BigWarpSourceListPanel.java new file mode 100644 index 00000000..97b7bc11 --- /dev/null +++ b/src/main/java/bdv/gui/sourceList/BigWarpSourceListPanel.java @@ -0,0 +1,95 @@ +/*- + * #%L + * BigWarp plugin for Fiji. + * %% + * Copyright (C) 2015 - 2022 Howard Hughes Medical Institute. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package bdv.gui.sourceList; + +import java.awt.Dimension; +import java.awt.GridLayout; + +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import bdv.gui.BigWarpLandmarkPanel.JTableChecking; +import bdv.gui.BigWarpLandmarkPanel.TextFieldCell; +import bdv.gui.BigWarpLandmarkPanel.TextFieldCellEditor; +import bdv.gui.sourceList.BigWarpSourceTableModel.ButtonEditor; +import bdv.gui.sourceList.BigWarpSourceTableModel.ButtonRenderer; + +public class BigWarpSourceListPanel extends JPanel +{ + private static final long serialVersionUID = 2370565900502584680L; + + protected BigWarpSourceTableModel tableModel; + + protected JTableChecking table; + + public final Logger logger = LoggerFactory.getLogger( BigWarpSourceListPanel.class ); + + public BigWarpSourceListPanel( BigWarpSourceTableModel tableModel ) + { + super( new GridLayout( 1, 0 ) ); + + // set table model re-generates the table + setTableModel( tableModel ); + + final JScrollPane scrollPane = new JScrollPane( table ); + add( scrollPane ); + } + + public BigWarpSourceTableModel getTableModel() + { + return tableModel; + } + + public void genJTable() + { + table = new JTableChecking( tableModel ); + table.setPreferredScrollableViewportSize( new Dimension( 500, 70 ) ); + table.setFillsViewportHeight( true ); + table.setShowVerticalLines( false ); + + table.setDefaultEditor( String.class, + new TextFieldCellEditor( new TextFieldCell(table), String.class )); + + table.getColumn( " " ).setCellRenderer( new ButtonRenderer() ); + table.getColumn( " " ).setCellEditor( new ButtonEditor( new JCheckBox(), tableModel ) ); + table.getColumn( " " ).setPreferredWidth( 24 ); + table.getColumn( " " ).setWidth( 24 ); + } + + public void setTableModel( BigWarpSourceTableModel tableModel ) + { + this.tableModel = tableModel; + genJTable(); + } + + public JTable getJTable() + { + return table; + } + + +} diff --git a/src/main/java/bdv/gui/sourceList/BigWarpSourceTableModel.java b/src/main/java/bdv/gui/sourceList/BigWarpSourceTableModel.java new file mode 100644 index 00000000..3890f08a --- /dev/null +++ b/src/main/java/bdv/gui/sourceList/BigWarpSourceTableModel.java @@ -0,0 +1,373 @@ +package bdv.gui.sourceList; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.swing.DefaultCellEditor; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JTable; +import javax.swing.UIManager; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellRenderer; + +import bigwarp.transforms.NgffTransformations; +import net.imglib2.realtransform.RealTransform; + +public class BigWarpSourceTableModel extends AbstractTableModel +{ + + private static final long serialVersionUID = 5923947651732788341L; + + public static enum SourceType { IMAGEPLUS, DATASET, URL }; + protected final static String[] colNames = new String[] { "Name", "Moving", "Transform", " " }; + + protected final String[] columnNames; + protected final ArrayList sources; + protected final ArrayList rmRowButtons; + + protected static int imageColIdx = 0; + protected static int movingColIdx = 1; + protected static int transformColIdx = 2; + protected static int removeColIdx = 3; + + protected Function transformChangedCallback; + + private Component container; + + public BigWarpSourceTableModel() + { + this(null); + } + + public BigWarpSourceTableModel(final Function transformChangedCallback ) + { + super(); + columnNames = colNames; + sources = new ArrayList<>(); + rmRowButtons = new ArrayList<>(); + this.transformChangedCallback = transformChangedCallback; + } + + /** + * Set the {@link Component} to repaint when a row is removed. + * + * @param container the component containing this table + */ + public void setContainer( Component container ) + { + this.container = container; + } + + public SourceRow get( int i ) + { + return sources.get( i ); + } + + @Override + public String getColumnName( int col ){ + return columnNames[col]; + } + + @Override + public int getColumnCount() + { + return columnNames.length; + } + + @Override + public int getRowCount() + { + return sources.size(); + } + + @Override + public Object getValueAt( int r, int c ) + { + if( c == 3 ) + return rmRowButtons.get( r ); + else + return sources.get( r ).get( c ); + } + + @Override + public Class getColumnClass( int col ){ + if ( col == 1 ) + return Boolean.class; + else if ( col == 3 ) + return JButton.class; + else + return String.class; + } + + @Override + public boolean isCellEditable( int row, int col ) + { + return true; + } + + @Override + public void setValueAt(Object value, int row, int col) + { + if( col == movingColIdx ) + sources.get( row ).moving = (Boolean)value; + else if( col == imageColIdx ) + sources.get( row ).srcName = (String)value; + else if( col == transformColIdx ) + setTransform( row, (String)value); + } + + public void setTransform(final int row, final String value) { + + if (transformChangedCallback != null) { + final String res = transformChangedCallback.apply(value); + if (res != null) + sources.get(row).transformUrl = res; + else + sources.get(row).transformUrl = value; + } else + sources.get(row).transformUrl = value; + } + + public void add( String srcName, boolean moving, SourceType type ) + { + final RemoveRowButton rmButton = new RemoveRowButton( sources.size() ); + rmRowButtons.add( rmButton ); + sources.add( new SourceRow( srcName, moving, "", type )); + } + + public void add( String srcName, boolean moving ) + { + add( srcName, moving, SourceType.URL ); + } + + public void add( String srcName ) + { + add( srcName, false ); + } + + public void addImagePlus( String srcName ) + { + addImagePlus( srcName, false ); + } + + public void addImagePlus( String srcName, boolean isMoving ) + { + add( srcName, isMoving, SourceType.IMAGEPLUS ); + } + + public void addDataset( String srcName ) + { + addImagePlus( srcName, false ); + } + + public void addDataset( String srcName, boolean isMoving ) + { + add( srcName, isMoving, SourceType.DATASET ); + } + + public boolean remove( int i ) + { + if( i >= sources.size() ) + return false; + + sources.remove( i ); + rmRowButtons.remove( i ); + updateRmButtonIndexes(); + + if( container != null ) + container.repaint(); + + return true; + } + + private void updateRmButtonIndexes() + { + for( int i = 0; i < rmRowButtons.size(); i++ ) + rmRowButtons.get( i ).setRow( i ); + } + + public static class SourceRow + { + public String srcName; + public boolean moving; + public String transformUrl; + + public SourceType type; + + public SourceRow( String srcName, boolean moving, String transformUrl, SourceType type ) + { + this.srcName = srcName; + this.moving = moving; + this.transformUrl = transformUrl; + this.type = type; + } + + public SourceRow( String srcName, boolean moving, String transformName ) + { + this( srcName, moving, transformName, SourceType.URL ); + } + + public Object get( int c ) + { + if( c == 0 ) + return srcName; + else if( c == 1 ) + return moving; + else if ( c == 2 ) + return transformUrl; + else + return null; + } + + public RealTransform getTransform() + { + RealTransform transform = null; + if( transformUrl!= null && !transformUrl.isEmpty() ) + transform = NgffTransformations.open( transformUrl ); + + return transform; + } + + public Supplier getTransformUri() + { + if( transformUrl!= null && !transformUrl.isEmpty() ) + return () -> transformUrl; + + return null; + } + } + + protected static class RemoveRowButton extends JButton { + private int row; + public RemoveRowButton( int row ) + { + super( "remove" ); + setRow( row ); + } + + public int getRow() + { + return row; + } + + public void setRow(int row) + { + this.row = row; + } + } + + /** + * From + * http://www.java2s.com/Code/Java/Swing-Components/ButtonTableExample.htm + */ + protected static class ButtonRenderer extends JButton implements TableCellRenderer + { + public ButtonRenderer() + { + setOpaque( true ); + } + + @Override + public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column ) + { + if ( isSelected ) + { + setForeground( table.getSelectionForeground() ); + setBackground( table.getSelectionBackground() ); + } + else + { + setForeground( table.getForeground() ); + setBackground( UIManager.getColor( "Button.background" ) ); + } + setText( ( value == null ) ? "" : ((RemoveRowButton)value).getText()); + return this; + } + } + + /** + * From + * http://www.java2s.com/Code/Java/Swing-Components/ButtonTableExample.htm + */ + protected static class ButtonEditor extends DefaultCellEditor + { + protected JButton button; + + private String label; + + private RemoveRowButton thisButton; + + private BigWarpSourceTableModel model; + + private boolean isPushed; + + public ButtonEditor( JCheckBox checkBox, BigWarpSourceTableModel model ) + { + super( checkBox ); + checkBox.setText( "-" ); + this.model = model; + + button = new JButton(); + button.setOpaque( true ); + button.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + fireEditingStopped(); + } + } ); + } + + @Override + public Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int row, int column ) + { + if ( isSelected ) + { + button.setForeground( table.getSelectionForeground() ); + button.setBackground( table.getSelectionBackground() ); + } + else + { + button.setForeground( table.getForeground() ); + button.setBackground( table.getBackground() ); + } + thisButton = ((RemoveRowButton)value); + label = ( value == null ) ? "" : thisButton.getText(); + button.setText( label ); + isPushed = true; + return button; + } + + @Override + public Object getCellEditorValue() + { + if ( isPushed ) + { + model.remove( thisButton.getRow() ); + } + isPushed = false; + return new String( label ); + } + + @Override + public boolean stopCellEditing() + { + isPushed = false; + return super.stopCellEditing(); + } + + @Override + protected void fireEditingStopped() + { + super.fireEditingStopped(); + } + } + +} diff --git a/src/main/java/bdv/ij/ApplyBigwarpPlugin.java b/src/main/java/bdv/ij/ApplyBigwarpPlugin.java index c7eca5fd..99ab6bc3 100644 --- a/src/main/java/bdv/ij/ApplyBigwarpPlugin.java +++ b/src/main/java/bdv/ij/ApplyBigwarpPlugin.java @@ -46,14 +46,13 @@ import org.janelia.saalfeldlab.n5.universe.metadata.N5CosemMetadataParser; import bdv.export.ProgressWriter; -import bdv.gui.TransformTypeSelectDialog; import bdv.ij.util.ProgressWriterIJ; import bdv.img.WarpedSource; import bdv.viewer.Interpolation; import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; import bigwarp.BigWarp; -import bigwarp.BigWarp.BigWarpData; +import bigwarp.BigWarpData; import bigwarp.BigWarpExporter; import bigwarp.BigWarpInit; import bigwarp.landmarks.LandmarkTableModel; @@ -167,17 +166,17 @@ public static String getUnit( final BigWarpData bwData, final String resolutionOption ) { String unit = "pix"; - final boolean doTargetImagesExist = bwData.targetSourceIndices != null && bwData.targetSourceIndices.length > 0; + final boolean doTargetImagesExist = bwData.numTargetSources() > 0; if( resolutionOption.equals( MOVING ) || resolutionOption.equals( MOVING_WARPED ) || !doTargetImagesExist ) { - final VoxelDimensions mvgVoxDims = bwData.sources.get( bwData.movingSourceIndices[0] ).getSpimSource().getVoxelDimensions(); + final VoxelDimensions mvgVoxDims = bwData.getMovingSource( 0 ).getSpimSource().getVoxelDimensions(); if( mvgVoxDims != null ) unit = mvgVoxDims.unit(); } else { // use target units even if - final VoxelDimensions tgtVoxDims = bwData.sources.get( bwData.targetSourceIndices[0] ).getSpimSource().getVoxelDimensions(); + final VoxelDimensions tgtVoxDims = bwData.getTargetSource( 0 ).getSpimSource().getVoxelDimensions(); if( tgtVoxDims != null ) unit = tgtVoxDims.unit(); } @@ -194,21 +193,18 @@ public static double[] getResolution( if( resolutionOption.equals( TARGET )) { - if( bwData.targetSourceIndices.length <= 0 ) + if( bwData.numTargetSources() <= 0 ) return null; - final Source< ? > spimSource = bwData.sources.get( - bwData.targetSourceIndices[ 0 ]).getSpimSource(); - + final Source< ? > spimSource = bwData.getTargetSource(0 ).getSpimSource(); return getResolution( spimSource, resolutionOption, resolutionSpec ); } - else if( resolutionOption.equals( MOVING )) + else if( resolutionOption.equals( MOVING ) ) { - if( bwData.targetSourceIndices.length <= 0 ) + if( bwData.numTargetSources() <= 0 ) return null; - final Source< ? > spimSource = bwData.sources.get( - bwData.movingSourceIndices[ 0 ]).getSpimSource(); + final Source< ? > spimSource = bwData.getMovingSource( 0 ).getSpimSource(); return getResolution( spimSource, resolutionOption, resolutionSpec ); } else if( resolutionOption.equals( SPECIFIED )) @@ -343,8 +339,9 @@ public static Interval getPixelInterval( { final double[] inputres = resolutionFromSource( source ); + final int N = outputResolution.length <= rai.numDimensions() ? outputResolution.length : rai.numDimensions(); final long[] max = new long[ rai.numDimensions() ]; - for( int d = 0; d < rai.numDimensions(); d++ ) + for( int d = 0; d < N; d++ ) { max[ d ] = (long)Math.ceil( ( inputres[ d ] * rai.dimension( d )) / outputResolution[ d ]); } @@ -353,6 +350,13 @@ public static Interval getPixelInterval( } else if( fieldOfViewOption.equals( MOVING_WARPED )) { + final FinalInterval interval = new FinalInterval( + Intervals.minAsLongArray( rai ), + Intervals.maxAsLongArray( rai )); + + if( transform == null ) + return interval; + final double[] movingRes = resolutionFromSource( source ); final int ndims = transform.numSourceDimensions(); final AffineTransform movingPixelToPhysical = new AffineTransform( ndims ); @@ -372,10 +376,6 @@ else if( fieldOfViewOption.equals( MOVING_WARPED )) seq.add( transform.inverse() ); seq.add( outputResolution2Pixel.inverse() ); - final FinalInterval interval = new FinalInterval( - Intervals.minAsLongArray( rai ), - Intervals.maxAsLongArray( rai )); - return bboxEst.estimatePixelInterval(seq, interval); // return BigWarpExporter.estimateBounds( seq, interval ); } @@ -450,26 +450,26 @@ public static List getPixelInterval( final double[] outputResolution) { if (fieldOfViewOption.equals(TARGET)) { - if (bwData.targetSourceIndices.length <= 0) { + if (bwData.numTargetSources() <= 0) { System.err.println("Requested target fov but target image is missing."); return null; } return Stream.of( getPixelInterval( - bwData.sources.get(bwData.targetSourceIndices[0]).getSpimSource(), + bwData.getTargetSource( 0 ).getSpimSource(), landmarks, transform, fieldOfViewOption, bboxEst, outputResolution)) .collect(Collectors.toList()); } else if (fieldOfViewOption.equals(MOVING_WARPED)) { return Stream.of( getPixelInterval( - bwData.sources.get(bwData.movingSourceIndices[0]).getSpimSource(), + bwData.getMovingSource( 0 ).getSpimSource(), landmarks, transform, fieldOfViewOption, bboxEst, outputResolution)) .collect(Collectors.toList()); } else if (fieldOfViewOption.equals(UNION_TARGET_MOVING)) { return Stream.of( getPixelInterval( - bwData.sources.get(bwData.movingSourceIndices[0]).getSpimSource(), + bwData.getMovingSource( 0 ).getSpimSource(), landmarks, transform, fieldOfViewOption, bboxEst, outputResolution)) .collect(Collectors.toList()); } else if (fieldOfViewOption.equals(SPECIFIED_PIXEL)) { @@ -820,18 +820,21 @@ public static List apply( final boolean wait, final WriteDestinationOptions writeOpts) { - final int numChannels = bwData.movingSourceIndices.length; - final int[] movingSourceIndexList = bwData.movingSourceIndices; +// int numChannels = bwData.movingSourceIndexList.size(); + final int numChannels = bwData.numMovingSources(); final List< SourceAndConverter< T >> sourcesxfm = BigWarp.wrapSourcesAsTransformed( - bwData.sources, + bwData.sourceInfos, landmarks.getNumdims(), bwData ); final InvertibleRealTransform invXfm = new BigWarpTransform( landmarks, tranformTypeOption ).getTransformation(); for ( int i = 0; i < numChannels; i++ ) { - ((WarpedSource< ? >) (sourcesxfm.get( movingSourceIndexList[ i ]).getSpimSource())).updateTransform( invXfm ); - ((WarpedSource< ? >) (sourcesxfm.get( movingSourceIndexList[ i ]).getSpimSource())).setIsTransformed( true ); + final SourceAndConverter< T > originalMovingSource = bwData.getMovingSource( i ); + final int originalIdx = bwData.sources.indexOf( originalMovingSource ); + + ((WarpedSource< ? >) (sourcesxfm.get( originalIdx).getSpimSource())).updateTransform( invXfm ); + ((WarpedSource< ? >) (sourcesxfm.get( originalIdx).getSpimSource())).setIsTransformed( true ); } final ProgressWriter progressWriter = new ProgressWriterIJ(); @@ -982,10 +985,11 @@ public static & NumericType> void runN5Export( pixelRenderToPhysical.concatenate( offsetTransform ); // render and write - final int N = data.movingSourceIndices.length; + final int N = data.numMovingSources(); for ( int i = 0; i < N; i++ ) { - final int movingSourceIndex = data.movingSourceIndices[ i ]; + final SourceAndConverter< S > originalMovingSource = data.getMovingSource( i ); + final int movingSourceIndex = data.sources.indexOf( originalMovingSource ); @SuppressWarnings( "unchecked" ) final RealRandomAccessible< T > raiRaw = ( RealRandomAccessible< T > )sources.get( movingSourceIndex ).getSpimSource().getInterpolatedSource( 0, 0, interp ); @@ -994,7 +998,7 @@ public static & NumericType> void runN5Export( final IntervalView< T > img = Views.interval( Views.raster( rai ), Intervals.zeroMin( outputInterval ) ); - final String srcName = data.sources.get( data.movingSourceIndices[ i ]).getSpimSource().getName(); + final String srcName = originalMovingSource.getSpimSource().getName(); String destDataset = dataset; if( N > 1 ) @@ -1067,12 +1071,12 @@ public void run( final String arg ) gd.addChoice( "Transform type", new String[] { - TransformTypeSelectDialog.TPS, - TransformTypeSelectDialog.AFFINE, - TransformTypeSelectDialog.SIMILARITY, - TransformTypeSelectDialog.ROTATION, - TransformTypeSelectDialog.TRANSLATION }, - TransformTypeSelectDialog.TPS); + BigWarpTransform.TPS, + BigWarpTransform.AFFINE, + BigWarpTransform.SIMILARITY, + BigWarpTransform.ROTATION, + BigWarpTransform.TRANSLATION }, + BigWarpTransform.TPS); gd.addMessage( "Field of view and resolution:" ); gd.addChoice( "Resolution", diff --git a/src/main/java/bdv/ij/BigWarpBdvCommand.java b/src/main/java/bdv/ij/BigWarpBdvCommand.java index 34cd2c6e..ed6e406f 100755 --- a/src/main/java/bdv/ij/BigWarpBdvCommand.java +++ b/src/main/java/bdv/ij/BigWarpBdvCommand.java @@ -23,6 +23,7 @@ import bdv.ij.util.ProgressWriterIJ; import bigwarp.BigWarp; +import bigwarp.BigWarpData; import bigwarp.BigWarpInit; import mpicbg.spim.data.SpimData; import mpicbg.spim.data.SpimDataException; @@ -61,8 +62,8 @@ public void run() final SpimData fixedSpimData = new XmlIoSpimData().load( fixedImageXml.getAbsolutePath() ); final SpimData movingSpimData = new XmlIoSpimData().load( movingImageXml.getAbsolutePath() ); new RepeatingReleasedEventsFixer().install(); - final BigWarp.BigWarpData< ? > bigWarpData = BigWarpInit.createBigWarpData( movingSpimData, fixedSpimData ); - bw = new BigWarp( bigWarpData, "Big Warp", new ProgressWriterIJ() ); + final BigWarpData< ? > bigWarpData = BigWarpInit.createBigWarpData( movingSpimData, fixedSpimData ); + bw = new BigWarp( bigWarpData, new ProgressWriterIJ() ); bw.getViewerFrameP().getViewerPanel().requestRepaint(); bw.getViewerFrameQ().getViewerPanel().requestRepaint(); bw.getLandmarkFrame().repaint(); diff --git a/src/main/java/bdv/ij/BigWarpCommand.java b/src/main/java/bdv/ij/BigWarpCommand.java new file mode 100644 index 00000000..6fe1a074 --- /dev/null +++ b/src/main/java/bdv/ij/BigWarpCommand.java @@ -0,0 +1,100 @@ +package bdv.ij; + +import java.io.IOException; + +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +import bdv.gui.BigWarpInitDialog; +import ij.Macro; +import ij.plugin.PlugIn; +import ij.plugin.frame.Recorder; +import net.imagej.Dataset; +import net.imagej.DatasetService; +import net.imagej.ImageJ; + +@Plugin(type = Command.class, menuPath = "Plugins>BigDataViewer>Big Warp Command" ) +public class BigWarpCommand implements Command, PlugIn +{ + private boolean initialRecorderState; + + @Parameter + private DatasetService datasetService; + + public BigWarpCommand() + { + initialRecorderState = Recorder.record; + Recorder.record = false; + } + + @Override + public void run( String args ) + { + Recorder.record = initialRecorderState; + final String macroOptions = Macro.getOptions(); + String options = args; + if ( options == null || options.isEmpty() ) + options = macroOptions; + + final boolean isMacro = (options != null && !options.isEmpty()); + if ( isMacro ) + BigWarpInitDialog.runMacro( macroOptions ); + else + { +// if( datasetService != null ) +// { +// System.out.println( "dset service exists"); +// for( final Dataset d : datasetService.getDatasets() ) +// System.out.println( d.getName()); +// } + + final BigWarpInitDialog dialog = BigWarpInitDialog.createAndShow( datasetService ); + // dialog sets recorder to its initial state on cancel or execution + dialog.setInitialRecorderState( initialRecorderState ); + } + } + + @Override + public void run() + { + run(null); + } + + public static void main( String[] a ) throws IOException + { +// String options = "images=mri-stack.tif,mri-stack.tif moving=true,false transforms=,"; +// +// String images = Macro.getValue(options, "images", ""); +// String moving = Macro.getValue(options, "moving", ""); +// String transforms = Macro.getValue(options, "transforms", ""); +// +// System.out.println( images ); +// System.out.println( moving ); +// System.out.println( transforms ); + + final ImageJ ij2 = new ImageJ(); + ij2.ui().showUI(); + +// final Object im1 = ij2.io().open( "/home/john/tmp/mri-stack.tif" ); +// final Object im2 = ij2.io().open( "/home/john/tmp/t1-head.tif" ); +//// +//// Object im1 = ij2.io().open( "/groups/saalfeld/home/bogovicj/tmp/mri-stack.tif" ); +//// Object im2 = ij2.io().open( "/groups/saalfeld/home/bogovicj/tmp/t1-head.tif" ); +//// +// ij2.ui().show( im1 ); +// ij2.ui().show( im2 ); + + + final Object im1 = ij2.io().open( "/home/john/tmp/boats.tif" ); + ij2.ui().show( im1 ); + + +// String args = "images=[a, b, c], isMoving=[true, true, false], transforms=[,,]"; +// +// String imagesList = null; +// String isMovingList = null; +// String transformsList = null; + } + +} diff --git a/src/main/java/bdv/ij/BigWarpImagePlusPlugIn.java b/src/main/java/bdv/ij/BigWarpImagePlusPlugIn.java index 2e804074..c8a0badd 100755 --- a/src/main/java/bdv/ij/BigWarpImagePlusPlugIn.java +++ b/src/main/java/bdv/ij/BigWarpImagePlusPlugIn.java @@ -27,7 +27,7 @@ import bdv.ij.util.ProgressWriterIJ; import bigwarp.BigWarp; -import bigwarp.BigWarp.BigWarpData; +import bigwarp.BigWarpData; import bigwarp.BigWarpInit; import fiji.util.gui.GenericDialogPlus; import ij.IJ; @@ -170,7 +170,7 @@ public void run( final String arg ) try { new RepeatingReleasedEventsFixer().install(); - final BigWarp bw = new BigWarp<>( bigwarpdata, "Big Warp", new ProgressWriterIJ() ); + final BigWarp bw = new BigWarp<>( bigwarpdata, new ProgressWriterIJ() ); if( landmarkPath != null && !landmarkPath.isEmpty()) { diff --git a/src/main/java/bdv/ij/BigWarpToDeformationFieldPlugIn.java b/src/main/java/bdv/ij/BigWarpToDeformationFieldPlugIn.java index a10a2256..1bceb401 100644 --- a/src/main/java/bdv/ij/BigWarpToDeformationFieldPlugIn.java +++ b/src/main/java/bdv/ij/BigWarpToDeformationFieldPlugIn.java @@ -26,16 +26,21 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import javax.swing.JFrame; import org.janelia.saalfeldlab.n5.Compression; import org.janelia.saalfeldlab.n5.GzipCompression; import org.janelia.saalfeldlab.n5.Lz4Compression; -import org.janelia.saalfeldlab.n5.N5Exception; import org.janelia.saalfeldlab.n5.N5Writer; import org.janelia.saalfeldlab.n5.RawCompression; import org.janelia.saalfeldlab.n5.XzCompression; @@ -43,36 +48,69 @@ import org.janelia.saalfeldlab.n5.ij.N5Exporter; import org.janelia.saalfeldlab.n5.imglib2.N5DisplacementField; import org.janelia.saalfeldlab.n5.universe.N5Factory; - -import bdv.gui.TransformTypeSelectDialog; -import bdv.viewer.SourceAndConverter; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.AffineCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.CoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.DisplacementFieldCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.ReferencedCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.SequenceCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.TranslationCoordinateTransform; + +import bdv.gui.ExportDisplacementFieldFrame; +import bdv.img.WarpedSource; +import bdv.viewer.Source; +import bigwarp.BigWarp; +import bigwarp.BigWarpData; import bigwarp.BigWarpExporter; import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.source.SourceInfo; import bigwarp.transforms.BigWarpTransform; +import bigwarp.transforms.NgffTransformations; +import bigwarp.transforms.SlicerTransformations; import fiji.util.gui.GenericDialogPlus; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; +import ij.Macro; import ij.WindowManager; import ij.plugin.PlugIn; +import ij.plugin.frame.Recorder; import jitk.spline.ThinPlateR2LogRSplineKernelTransform; -import mpicbg.spim.data.sequence.VoxelDimensions; +import mpicbg.models.AffineModel2D; +import mpicbg.models.AffineModel3D; +import mpicbg.models.InvertibleCoordinateTransform; +import mpicbg.models.RigidModel2D; +import mpicbg.models.RigidModel3D; +import mpicbg.models.SimilarityModel2D; +import mpicbg.models.SimilarityModel3D; +import mpicbg.models.TranslationModel2D; +import mpicbg.models.TranslationModel3D; import net.imglib2.Cursor; import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealPoint; +import net.imglib2.converter.Converters; +import net.imglib2.converter.RealFloatConverter; +import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.img.imageplus.FloatImagePlus; import net.imglib2.img.imageplus.ImagePlusImgs; import net.imglib2.iterator.IntervalIterator; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.TaskExecutors; import net.imglib2.realtransform.AffineGet; import net.imglib2.realtransform.AffineTransform2D; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.DisplacementFieldTransform; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.InvertibleRealTransformSequence; +import net.imglib2.realtransform.InvertibleWrapped2DIntermediate3D; import net.imglib2.realtransform.RealTransform; -import net.imglib2.realtransform.Scale2D; -import net.imglib2.realtransform.Scale3D; +import net.imglib2.realtransform.RealTransformSequence; +import net.imglib2.realtransform.ScaleAndTranslation; import net.imglib2.realtransform.ThinplateSplineTransform; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.DoubleType; import net.imglib2.type.numeric.real.FloatType; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; @@ -80,7 +118,13 @@ import net.imglib2.view.composite.GenericComposite; /** - * ImageJ plugin to convert the thin plate spline to a deformation field. + * ImageJ plugin to convert the thin plate spline to a displacement field. + *

+ * If the ignoreAffine option is true, the resulting displacement field will + * not contain the affine part of the transformation. In this case, the total + * transformation is the displacement field first, followed by the affine part + * of the transformation. + * * * @author John Bogovic <bogovicj@janelia.hhmi.org> * @author Tobias Pietzsch <tobias.pietzsch@gmail.com> @@ -88,6 +132,8 @@ */ public class BigWarpToDeformationFieldPlugIn implements PlugIn { + public static enum INVERSE_OPTIONS { FORWARD, INVERSE, BOTH }; + public static final String[] compressionOptions = new String[] { N5Exporter.RAW_COMPRESSION, N5Exporter.GZIP_COMPRESSION, @@ -95,224 +141,731 @@ public class BigWarpToDeformationFieldPlugIn implements PlugIn N5Exporter.XZ_COMPRESSION, N5Exporter.BLOSC_COMPRESSION }; + public static final String flattenOption = "Flat"; + public static final String sequenceOption = "Sequence"; + public static void main( final String[] args ) { new ImageJ(); -// IJ.run("Boats (356K)"); - final ImagePlus imp = IJ.openImage( "/groups/saalfeld/home/bogovicj/tmp/mri-stack.tif" ); -// ImagePlus imp = IJ.openImage( "/groups/saalfeld/home/bogovicj/tmp/mri-stack_p2p2p4.tif" ); -// WindowManager.getActiveWindow(); + new Recorder(); + Recorder.record = true; + +// IJ.run("Boats (356K)"); +// ImagePlus imp = IJ.openImage( "/groups/saalfeld/home/bogovicj/tmp/mri-stack.tif" ); + final ImagePlus imp = IJ.openImage( "/home/john/tmp/mri-stack.tif" ); +// ImagePlus imp = IJ.openImage( "/home/john/tmp/mri-stack_mm.tif" ); imp.show(); new BigWarpToDeformationFieldPlugIn().run( null ); } - public void runFromBigWarpInstance( - final LandmarkTableModel landmarkModel, - final List> sources, - final int[] targetSourceIndexList ) + public void runFromBigWarpInstance( final BigWarp bw ) { - final ImageJ ij = IJ.getInstance(); - if ( ij == null ) - return; - - - final DeformationFieldExportParameters params = DeformationFieldExportParameters.fromDialog( false, false ); - if( params == null ) - return; +// ImageJ ij = IJ.getInstance(); +// if ( ij == null ) +// return; +// + ExportDisplacementFieldFrame.createAndShow( bw ); + } - final RandomAccessibleInterval< ? > tgtInterval = sources.get( targetSourceIndexList[ 0 ] ).getSpimSource().getSource( 0, 0 ); +// /** +// * @deprecated not necessary access thedesired source this way anymore, use {@link #runFromBigWarpInstance(LandmarkTableModel, SourceAndConverter)} +// * on the result of {@link bigwarp.BigWarpData#getTargetSource(int)} +// */ +// @Deprecated +// public void runFromBigWarpInstanceOld( +// final BigWarpData data, +// final LandmarkTableModel landmarkModel, +// final List> sources, +// final List targetSourceIndexList ) +// { +// runFromBigWarpInstanceOld( data, landmarkModel, data.getTargetSource( 0 ) ); +// } + +// public void runFromBigWarpInstanceOld( +// final BigWarpData data, final LandmarkTableModel landmarkModel, final SourceAndConverter< T > sourceAndConverter ) +// { +// ImageJ ij = IJ.getInstance(); +// if ( ij == null ) +// return; +// +// final DeformationFieldExportParameters params = DeformationFieldExportParameters.fromDialog( false, false ); +// if( params == null ) +// return; +// +// final RandomAccessibleInterval< ? > tgtInterval = sourceAndConverter.getSpimSource().getSource( 0, 0 ); +// +// int ndims = landmarkModel.getNumdims(); +// long[] dims = tgtInterval.dimensionsAsLongArray(); +// +// double[] spacing = new double[ 3 ]; +// double[] offset = new double[ 3 ]; +// String unit = "pix"; +// VoxelDimensions voxelDim = sourceAndConverter.getSpimSource().getVoxelDimensions(); +// voxelDim.dimensions( spacing ); +// +// if( params.spacing != null ) +// spacing = params.spacing; +// +// if( params.offset != null ) +// offset = params.offset; +// +// if( params.size != null ) +// dims = params.size; +// +// if( params.n5Base.isEmpty() ) +// { +//// toImagePlus( data, landmarkModel, null, params.ignoreAffine, params.flatten(), params.virtual, dims, spacing, params.nThreads ); +// if ( params.inverseOption.equals( INVERSE_OPTIONS.BOTH.toString() ) ) +// { +// toImagePlus( data, landmarkModel, null, params.ignoreAffine, params.flatten(), false, params.virtual, params.size, params.spacing, params.nThreads ); +// toImagePlus( data, landmarkModel, null, params.ignoreAffine, params.flatten(), true, params.virtual, params.size, params.spacing, params.nThreads ); +// } +// else +// { +// final boolean inverse = params.inverseOption.equals( INVERSE_OPTIONS.INVERSE.toString() ); +// toImagePlus( data, landmarkModel, null, params.ignoreAffine, params.flatten(), inverse, params.virtual, params.size, params.spacing, params.nThreads ); +// } +// } +// else +// { +// try +// { +// final boolean inverse = params.inverseOption.equals( INVERSE_OPTIONS.INVERSE.toString() ); +// writeN5( params.n5Base, params.n5Dataset, landmarkModel, null, data, dims, spacing, offset, unit, params.blockSize, params.compression, params.nThreads, params.ignoreAffine, params.flatten(), inverse ); +// } +// catch ( IOException e ) +// { +// e.printStackTrace(); +// } +// } +// } + + public static void runFromParameters( final DeformationFieldExportParameters params, final BigWarpData data, final LandmarkTableModel landmarkModel, final BigWarpTransform bwTransform ) + { + final String unit = "pixel"; + LandmarkTableModel ltm; - final int ndims = landmarkModel.getNumdims(); - long[] dims; - if ( ndims <= 2 ) + if( landmarkModel == null ) { - dims = new long[ 3 ]; - dims[ 0 ] = tgtInterval.dimension( 0 ); - dims[ 1 ] = tgtInterval.dimension( 1 ); - dims[ 2 ] = 2; + if( params.landmarkPath == null || params.landmarkPath.isEmpty() ) + { + IJ.showMessage( "Must provide landmark file." ); // TODO message differently + return; + } + + // load landmarks + try + { + ltm = LandmarkTableModel.loadFromCsv( new File( params.landmarkPath ), false ); + } + catch ( final IOException e ) + { + e.printStackTrace(); + return; + } } else - { - dims = new long[ 4 ]; - dims[ 0 ] = tgtInterval.dimension( 0 ); - dims[ 1 ] = tgtInterval.dimension( 1 ); - dims[ 2 ] = 3; - dims[ 3 ] = tgtInterval.dimension( 2 ); - } + ltm = landmarkModel; - double[] spacing = new double[ 3 ]; - final VoxelDimensions voxelDim = sources.get( targetSourceIndexList[ 0 ] ).getSpimSource().getVoxelDimensions(); - voxelDim.dimensions( spacing ); + final int ndims = ltm.getNumdims(); + double[] spacing = new double[ ndims ]; + double[] offset = new double[ ndims ]; + long[] dims = new long[ ndims ]; + if ( params.spacing != null ) - if( params.spacing != null ) spacing = params.spacing; - if( params.size != null ) + if ( params.offset != null ) + offset = params.offset; + + if ( params.size != null ) dims = params.size; - if( params.n5Base.isEmpty() ) + ImagePlus imp = null; + if ( params.n5Base.isEmpty() ) { - toImagePlus( landmarkModel, params.ignoreAffine, dims, spacing, params.nThreads ); + if( !bwTransform.isNonlinear() ) // is linear + { + IJ.showMessage("unsupported at the moment"); + } + else if ( params.inverseOption.equals( INVERSE_OPTIONS.BOTH.toString() ) ) + { + imp = toImagePlus( data, ltm, bwTransform, params.ignoreAffine, params.flatten(), false, params.virtual, params.size, params.spacing, params.nThreads ); + imp = toImagePlus( data, ltm, bwTransform, params.ignoreAffine, params.flatten(), true, params.virtual, params.size, params.spacing, params.nThreads ); + } + else + { + final boolean inverse = params.inverseOption.equals( INVERSE_OPTIONS.INVERSE.toString() ); + imp = toImagePlus( data, ltm, bwTransform, params.ignoreAffine, params.flatten(), inverse, params.virtual, params.size, params.spacing, params.nThreads ); + } + if( imp != null ) + imp.show(); } else { - try + if( !bwTransform.isNonlinear() ) // is linear { - writeN5( params.n5Base, landmarkModel, dims, spacing, params.blockSize, params.compression, params.nThreads ); + writeAffineN5(params.n5Base, params.n5Dataset, data, bwTransform ); } - catch ( final N5Exception e ) + else if ( params.inverseOption.equals( INVERSE_OPTIONS.BOTH.toString() ) ) { - e.printStackTrace(); + writeN5( params.n5Base, params.n5Dataset + "/dfield", ltm, bwTransform, data, dims, spacing, offset, unit, params.blockSize, params.compression, params.nThreads, params.format, params.ignoreAffine, params.flatten(), + false, params.inverseTolerance, params.inverseMaxIterations ); + writeN5( params.n5Base, params.n5Dataset + "/invdfield", ltm, bwTransform, data, dims, spacing, offset, unit, params.blockSize, params.compression, params.nThreads, params.format, params.ignoreAffine, params.flatten(), + true, params.inverseTolerance, params.inverseMaxIterations ); + } + else + { + final boolean inverse = params.inverseOption.equals( INVERSE_OPTIONS.INVERSE.toString() ); + writeN5( params.n5Base, params.n5Dataset, ltm, bwTransform, data, dims, spacing, offset, unit, params.blockSize, params.compression, params.nThreads, params.format, params.ignoreAffine, params.flatten(), + inverse, params.inverseTolerance, params.inverseMaxIterations ); } } } @Override - public void run( final String arg ) + public void run( final String args ) { if ( IJ.versionLessThan( "1.40" ) ) return; - final DeformationFieldExportParameters params = DeformationFieldExportParameters.fromDialog( true, true ); - final int nd = params.size.length; + final String macroOptions = Macro.getOptions(); + String options = args; + if( options == null || options.isEmpty()) + options = macroOptions; - // load - final LandmarkTableModel ltm = new LandmarkTableModel( nd ); - try - { - ltm.load( new File( params.landmarkPath ) ); - } - catch ( final IOException e ) - { - e.printStackTrace(); - return; - } + final boolean isMacro = (options != null && !options.isEmpty()); - if( params.n5Base.isEmpty() ) - { - toImagePlus( ltm, params.ignoreAffine, params.size, params.spacing, params.nThreads ); - } + if( isMacro ) + ExportDisplacementFieldFrame.runMacro( macroOptions ); else - { - try - { - writeN5( params.n5Base, params.n5Dataset, ltm, params.size, params.spacing, params.blockSize, params.compression, params.nThreads ); - } - catch ( final N5Exception e ) - { - e.printStackTrace(); - } - } + ExportDisplacementFieldFrame.createAndShow(); } public static ImagePlus toImagePlus( + final BigWarpData data, final LandmarkTableModel ltm, - final boolean ignoreAffine, + final BigWarpTransform bwTransform, + final boolean splitAffine, + final boolean flatten, + final boolean inverse, + final boolean virtual, final long[] dims, final double[] spacing, final int nThreads ) { - final BigWarpTransform bwXfm = new BigWarpTransform( ltm, TransformTypeSelectDialog.TPS ); - final RealTransform tps = getTpsAffineToggle( bwXfm, ignoreAffine ); + final double[] offset = new double[ spacing.length ]; + return toImagePlus( data, ltm, bwTransform, splitAffine, flatten, inverse, virtual, dims, spacing, offset, nThreads ); + } - AffineGet pixelToPhysical = null; - if( spacing.length == 2) + /** + * + * @param data the {@link BigWarpData} + * @param ltm the {@link LandmarkTableModel} + * @param bwTransform the {@link BigWarpTransform} + * @param splitAffine omit the affine part of the transformation + * @param flatten include any fixed transformation + * @param inverse output the inverse transformation + * @param virtual output of virtual image + * @param dims the dimensions of the output {@link ImagePlus}'s spatial dimensions + * @param spacing the pixel spacing of the output field + * @param offset the physical offset (origin) of the output field + * @param nThreads number of threads for copying + * @return the image plus representing the displacement field + */ + public static ImagePlus toImagePlus( + final BigWarpData data, + final LandmarkTableModel ltm, + final BigWarpTransform bwTransform, + final boolean splitAffine, + final boolean flatten, + final boolean inverse, + final boolean virtual, + final long[] dims, + final double[] spacing, + final double[] offset, + final int nThreads ) + { + BigWarpTransform bwXfm; + if( bwTransform == null ) + bwXfm = new BigWarpTransform( ltm, BigWarpTransform.TPS ); + else + bwXfm = bwTransform; + + final InvertibleRealTransform fwdTransform = getTransformation( data, bwXfm, flatten, splitAffine ); + final InvertibleRealTransform startingTransform = inverse ? fwdTransform.inverse() : fwdTransform; + + final InvertibleRealTransform transform; + final int nd = ltm.getNumdims(); + long[] ipDims = null; + if ( nd == 2 ) { - pixelToPhysical = new Scale2D( spacing ); + ipDims = new long[ 4 ]; + ipDims[ 0 ] = dims[ 0 ]; + ipDims[ 1 ] = dims[ 1 ]; + ipDims[ 2 ] = 2; + ipDims[ 3 ] = 1; + + if (bwXfm.isMasked()) + transform = new InvertibleWrapped2DIntermediate3D(startingTransform); + else + transform = startingTransform; } - else if( spacing.length == 3) + else if ( nd == 3 ) { - pixelToPhysical = new Scale3D( spacing ); + ipDims = new long[ 4 ]; + ipDims[ 0 ] = dims[ 0 ]; + ipDims[ 1 ] = dims[ 1 ]; + ipDims[ 2 ] = 3; + ipDims[ 3 ] = dims[ 2 ]; + + transform = startingTransform; } else - { return null; + + final RandomAccessibleInterval< DoubleType > dfieldVirt = DisplacementFieldTransform.createDisplacementField( transform, new FinalInterval( dims ), spacing, offset ); + + ImagePlus dfieldIp; + if( virtual ) + { + final RealFloatConverter conv = new RealFloatConverter<>(); + final RandomAccessibleInterval< FloatType > dfieldF = Views.moveAxis( + Converters.convert2( dfieldVirt, conv, FloatType::new ), + 0, 2 ); + dfieldIp = ImageJFunctions.wrap( dfieldF, "" ); // title gets set below } + else + { + final FloatImagePlus< FloatType > dfield = ImagePlusImgs.floats( ipDims ); - final FloatImagePlus< FloatType > dfield = convertToDeformationField( dims, tps, pixelToPhysical, nThreads ); + // make the "vector" axis the first dimension + // 2d displacement fields will have an extraneous singleton z-dimension + final RandomAccessibleInterval< FloatType > dfieldImpPerm = Views.dropSingletonDimensions( Views.moveAxis( dfield, 2, 0 )); + LoopBuilder.setImages( dfieldVirt, dfieldImpPerm ).multiThreaded( TaskExecutors.fixedThreadPool( nThreads ) ).forEachPixel( (x,y) -> { y.setReal(x.get()); }); + dfieldIp = dfield.getImagePlus(); + } String title = "bigwarp dfield"; - if ( ignoreAffine ) + if ( splitAffine ) title += " (no affine)"; - final ImagePlus dfieldIp = dfield.getImagePlus(); dfieldIp.setTitle( title ); dfieldIp.getCalibration().pixelWidth = spacing[ 0 ]; dfieldIp.getCalibration().pixelHeight = spacing[ 1 ]; + dfieldIp.getCalibration().xOrigin = offset[ 0 ]; + dfieldIp.getCalibration().yOrigin = offset[ 1 ]; + if( spacing.length > 2 ) + { dfieldIp.getCalibration().pixelDepth = spacing[ 2 ]; + dfieldIp.getCalibration().zOrigin = offset[ 2 ]; + } - dfieldIp.show(); return dfieldIp; } + /** + * Returns the transformation to be converted to a displacement field. + * + * @param data the bigwarp data storing the fixed transformations + * @param transform the current transformation + * @param concatPreTransforms concatenate the current with fixed transformation + * @param ignoreAffine whether the output should include the affine part of the transformation + * @return the transformation + */ + public static InvertibleRealTransform getTransformation( final BigWarpData data, final BigWarpTransform transform, final boolean concatPreTransforms, + final boolean ignoreAffine ) + { + final InvertibleRealTransform tps = transform.getTransformation( false ); + + AffineGet affine = null; + if( ignoreAffine ) + affine = transform.affinePartOfTps(); + + if ( !ignoreAffine && ( data == null || !concatPreTransforms ) ) + { + return tps; + } + + InvertibleRealTransform preTransform = null; + if( data != null && concatPreTransforms ) + { + for ( final Entry< Integer, SourceInfo > entry : data.sourceInfos.entrySet() ) + { + if ( entry.getValue().getTransform() != null ) + { + final RealTransform tform = entry.getValue().getTransform(); + if( tform instanceof InvertibleRealTransform ) + { + preTransform = ( InvertibleRealTransform ) tform; + } + else + { + final WrappedIterativeInvertibleRealTransform< RealTransform > ixfm = new WrappedIterativeInvertibleRealTransform<>( tform ); + // use same parameters as the passed transform, hopefully that works + // alternatively, I could wrap the sequence in the iterative invertible transform - there are tradeoffs here + // TODO consider the tradeoffs + ixfm.getOptimzer().setMaxIters( transform.getInverseMaxIterations() ); + ixfm.getOptimzer().setTolerance( transform.getInverseTolerance() ); + preTransform = ixfm; + } + break; + } + } + } + + final InvertibleRealTransform startingTransform; + if ( preTransform != null || affine != null ) + { + final InvertibleRealTransformSequence seq = new InvertibleRealTransformSequence(); + seq.add( tps ); + + if( affine != null ) + seq.add( affine.inverse() ); + + if( preTransform != null ) + seq.add( preTransform ); + + startingTransform = seq; + } + else + startingTransform = tps; + + return startingTransform; + } + + + + public static void writeAffineN5( + final String n5BasePath, + final String n5Dataset, + final BigWarpData data, + final BigWarpTransform bwTransform ) + { + final String mvgSpaceName = data != null && data.numMovingSources() > 0 ? data.getMovingSource( 0 ).getSpimSource().getName() : "moving"; + final String tgtSpaceName = data != null && data.numTargetSources() > 0 ? data.getTargetSource( 0 ).getSpimSource().getName() : "target"; + final String input= mvgSpaceName; + final String output= tgtSpaceName; + final String name = input + " to " + output; + + CoordinateTransform ct = null; + final InvertibleCoordinateTransform tform = bwTransform.getCoordinateTransform(); + switch( bwTransform.getTransformType()) { + case BigWarpTransform.TRANSLATION: + if (tform instanceof TranslationModel2D) + ct = new TranslationCoordinateTransform(name, input, output, ((TranslationModel2D)tform).getTranslation()); + else if (tform instanceof TranslationModel3D) + ct = new TranslationCoordinateTransform(name, input, output, ((TranslationModel3D)tform).getTranslation()); + break; + case BigWarpTransform.SIMILARITY: + double[] simparams; + if (tform instanceof SimilarityModel2D) + { + simparams = bwTransform.toImglib2((SimilarityModel2D)tform).getRowPackedCopy(); + ct = new AffineCoordinateTransform(name, input, output, simparams); + } + else if (tform instanceof SimilarityModel3D) + { + simparams = bwTransform.toImglib2((SimilarityModel3D)tform).getRowPackedCopy(); + ct = new AffineCoordinateTransform(name, input, output, simparams); + } + break; + case BigWarpTransform.ROTATION: + double[] rotparams; + if (tform instanceof RigidModel2D) + { + rotparams = bwTransform.toImglib2((RigidModel2D)tform).getRowPackedCopy(); + ct = new AffineCoordinateTransform(name, input, output, rotparams); + } + else if (tform instanceof RigidModel3D) + { + rotparams = bwTransform.toImglib2((RigidModel3D)tform).getRowPackedCopy(); + ct = new AffineCoordinateTransform(name, input, output, rotparams); + } + break; + case BigWarpTransform.AFFINE: + double[] affparams; + if (tform instanceof AffineModel2D) + { + affparams = bwTransform.toImglib2((AffineModel2D)tform).getRowPackedCopy(); + ct = new AffineCoordinateTransform(name, input, output, affparams); + } + else if (tform instanceof AffineModel3D) + { + affparams = bwTransform.toImglib2((AffineModel3D)tform).getRowPackedCopy(); + ct = new AffineCoordinateTransform(name, input, output, affparams); + } + break; + } + + if( ct == null ) + return; + + + + final String dataset = (n5Dataset == null) ? "" : n5Dataset; + final N5Factory factory = new N5Factory().gsonBuilder( NgffTransformations.gsonBuilder() ); + final N5Writer n5 = factory.openWriter( n5BasePath ); + NgffTransformations.addCoordinateTransformations(n5, dataset, ct); + + // also add to root + NgffTransformations.addCoordinateTransformations(n5, "", ct); + } + public static void writeN5( final String n5BasePath, final LandmarkTableModel ltm, + final BigWarpTransform bwTransform, + final BigWarpData data, final long[] dims, final double[] spacing, + final double[] offset, + final String unit, final int[] spatialBlockSize, final Compression compression, - final int nThreads ) + final int nThreads, + final String format, + final boolean flatten, + final boolean inverse, + final double invTolerance, + final int invMaxIters ) throws IOException { - writeN5( n5BasePath, "dfield", ltm, dims, spacing, spatialBlockSize, compression, nThreads ); + writeN5( n5BasePath, N5DisplacementField.FORWARD_ATTR, ltm, bwTransform, data, dims, spacing, offset, unit, spatialBlockSize, compression, nThreads, format, flatten, inverse, invTolerance, invMaxIters ); } - public static void writeN5( final String n5BasePath, final String n5Dataset, + public static void writeN5( + final String n5BasePath, + final String n5Dataset, final LandmarkTableModel ltm, + final BigWarpTransform bwTransform, + final BigWarpData data, final long[] dims, final double[] spacing, + final double[] offset, + final String unit, final int[] spatialBlockSize, final Compression compression, - final int nThreads ) + final int nThreads, + final String format, + final boolean flatten, + final boolean inverse, + final double invTolerance, + final int invMaxIters ) throws IOException { - final BigWarpTransform bwXfm = new BigWarpTransform( ltm, TransformTypeSelectDialog.TPS ); - final RealTransform tpsTotal = getTpsAffineToggle( bwXfm, false ); + writeN5( n5BasePath, n5Dataset, ltm, bwTransform, data, dims, spacing, offset, unit, spatialBlockSize, compression, nThreads, format, false, flatten, inverse, invTolerance, invMaxIters ); + } + + @SuppressWarnings("rawtypes") + public static void writeN5( + final String n5BasePath, + final String n5Dataset, + final LandmarkTableModel ltm, + final BigWarpTransform bwTransform, + final BigWarpData data, + final long[] dims, + final double[] spacing, + final double[] offset, + final String unit, + final int[] spatialBlockSizeArg, + final Compression compression, + final int nThreads, + final String format, + final boolean splitAffine, + final boolean flatten, + final boolean inverse, + final double invTolerance, + final int invMaxIters ) + { + final String dataset = ( n5Dataset == null || n5Dataset.isEmpty() ) ? N5DisplacementField.FORWARD_ATTR : n5Dataset; + + final String mvgSpaceName = getMovingName(data); + final String tgtSpaceName = getTargetName(data); + final String inputSpace; + final String outputSpace; + if( inverse ) + { + inputSpace = mvgSpaceName; + outputSpace = tgtSpaceName; + } + else + { + inputSpace = tgtSpaceName; + outputSpace = mvgSpaceName; + } + + final BigWarpTransform bwXfm; + if( bwTransform == null ) + bwXfm = new BigWarpTransform( ltm, BigWarpTransform.TPS ); + else + bwXfm = bwTransform; - AffineGet pixelToPhysical = null; - if( spacing.length == 2 ) - pixelToPhysical = new Scale2D( spacing ); - else if( spacing.length == 3 ) - pixelToPhysical = new Scale3D( spacing ); + bwXfm.setInverseMaxIterations( invMaxIters ); + bwXfm.setInverseTolerance( invTolerance ); - final FloatImagePlus< FloatType > dfieldRaw = convertToDeformationField( - dims, tpsTotal, pixelToPhysical, nThreads ); + final InvertibleRealTransform fwdTransform = getTransformation( data, bwXfm, flatten, splitAffine ); + final InvertibleRealTransform totalTransform = inverse ? fwdTransform.inverse() : fwdTransform; + final InvertibleRealTransform transform; - // this works for both 2d and 3d, it turn out - final RandomAccessibleInterval< FloatType > dfield = - Views.permute( - Views.permute( dfieldRaw, - 0, 2 ), - 1, 2 ); + if (totalTransform.numSourceDimensions() == 2 && bwXfm.isMasked()) + transform = new InvertibleWrapped2DIntermediate3D(totalTransform); + else + transform = totalTransform; + final int[] spatialBlockSize = fillBlockSize( spatialBlockSizeArg, ltm.getNumdims() ); final int[] blockSize = new int[ spatialBlockSize.length + 1 ]; blockSize[ 0 ] = spatialBlockSize.length; - for( int i = 0; i < spatialBlockSize.length; i++ ) + System.arraycopy( spatialBlockSize, 0, blockSize, 1, spatialBlockSize.length ); + + final N5Factory factory = new N5Factory().gsonBuilder( NgffTransformations.gsonBuilder() ); + final N5Writer n5 = factory.openWriter( n5BasePath ); + + + // TODO generalize + // get first transformUri for a moving source + ReferencedCoordinateTransform refCt = null; + if( !flatten ) + { + for ( final Entry e : data.sourceInfos.entrySet() ) + { + final SourceInfo i = e.getValue(); + if( i.isMoving() && i.getTransformUri() != null && !i.getTransformUri().isEmpty()) + { + refCt = new ReferencedCoordinateTransform( i.getTransformUri() ); + break; + } + } + } + + + final RandomAccessibleInterval< DoubleType > dfield; + if( splitAffine ) + { + // the affine part + final AffineGet affine = bwXfm.affinePartOfTps(); + final AffineCoordinateTransform ngffAffine = new AffineCoordinateTransform( affine.getRowPackedCopy() ); + + // the variable transform has the affine part removed here + dfield = DisplacementFieldTransform.createDisplacementField( transform, new FinalInterval( dims ), spacing, offset ); + + if( format.equals( ExportDisplacementFieldFrame.FMT_SLICER )) + { + final ThreadPoolExecutor exec = new ThreadPoolExecutor( nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue() ); + SlicerTransformations.saveDisplacementField( n5, dataset, dfield, blockSize, compression, exec ); + SlicerTransformations.saveAffine( n5, dataset, affine ); + } + else + { + final DisplacementFieldCoordinateTransform dfieldTform = NgffTransformations.save( n5, dataset, dfield, inputSpace, outputSpace, spacing, offset, unit, blockSize, compression, nThreads ); + + // the transform sequence needs to have a reference to whatever transform was imported, if requested + final CoordinateTransform[] ctList = refCt == null ? new CoordinateTransform[]{ dfieldTform, ngffAffine } : new CoordinateTransform[]{ dfieldTform, ngffAffine, refCt }; + + // the total transform + final SequenceCoordinateTransform totalTform = new SequenceCoordinateTransform( inputSpace, outputSpace, ctList ); + + NgffTransformations.addCoordinateTransformations( n5, "/", totalTform ); + } + } + else { - blockSize[ i + 1 ] = spatialBlockSize[ i ]; + dfield = DisplacementFieldTransform.createDisplacementField( transform, new FinalInterval( dims ), spacing, offset ); + + if( format.equals( ExportDisplacementFieldFrame.FMT_SLICER )) + { + final ThreadPoolExecutor exec = new ThreadPoolExecutor( nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue() ); + SlicerTransformations.saveDisplacementField( n5, dataset, dfield, blockSize, compression, exec ); + + // for slicer, this affine represents the pixel to physical transformation + SlicerTransformations.saveAffine( n5, dataset, new ScaleAndTranslation( spacing, offset ) ); + } + else + { + final DisplacementFieldCoordinateTransform dfieldTform = NgffTransformations.save( n5, dataset, dfield, inputSpace, outputSpace, spacing, offset, unit, blockSize, compression, nThreads ); + + final CoordinateTransform ngffTform; + if( refCt == null ) + ngffTform = dfieldTform; + else + ngffTform = new SequenceCoordinateTransform( refCt.getInput(), dfieldTform.getOutput(), new CoordinateTransform[]{ dfieldTform, refCt }); + + + NgffTransformations.addCoordinateTransformations( n5, "/", ngffTform ); + } } - final N5Writer n5 = new N5Factory().openWriter( n5BasePath ); - N5DisplacementField.save( n5, n5Dataset, null, dfield, spacing, blockSize, compression ); n5.close(); } - private static RealTransform getTpsAffineToggle( final BigWarpTransform bwXfm, final boolean ignoreAffine ) + private static String getMovingName( final BigWarpData data ) { + if( data != null && data.numMovingSources() > 0 ) + { + final Source src = data.getMovingSource( 0 ).getSpimSource(); + if( src instanceof WarpedSource ) + return ((WarpedSource)src).getOriginalName(); + else + return src.getName(); + } + return "moving"; + } + + private static String getTargetName( final BigWarpData data ) { + if( data != null && data.numTargetSources() > 0 ) + { + final Source src = data.getTargetSource( 0 ).getSpimSource(); + if( src instanceof WarpedSource ) + return ((WarpedSource)src).getOriginalName(); + else + return src.getName(); + } + return "target"; + } + + private static int[] fillBlockSize(final int[] blockSize, final int N) { - if( ignoreAffine ) + if( blockSize.length >= N ) + return blockSize; + else + { + final int[] out = new int[ N ]; + final int j = blockSize.length - 1; + for ( int i = 0; i < N; i++ ) + { + if ( i < blockSize.length ) + out[ i ] = blockSize[ i ]; + else + out[ i ] = blockSize[ j ]; + } + return out; + } + } + + private static RealTransform getTpsAffineToggle( final BigWarpTransform bwXfm, final boolean splitAffine ) + { + if ( splitAffine ) { final ThinPlateR2LogRSplineKernelTransform tps = bwXfm.getTpsBase(); return new ThinplateSplineTransform( - new ThinPlateR2LogRSplineKernelTransform( tps.getSourceLandmarks(), null, null, tps.getKnotWeights() )); + new ThinPlateR2LogRSplineKernelTransform( tps.getSourceLandmarks(), null, null, tps.getKnotWeights() ) ); } else - return bwXfm.getTransformation(); + return bwXfm.getTransformation( false ); } + /** + * Get the affine part of a thin-plate spline transform. The affine will be 2D (3D) + * if the transformation is 2D (3D). + * + * @param tps the {@link ThinPlateR2LogRSplineKernelTransform} instance + * @return the {@link AffineGet} instance. + * + * @deprecated Use {@link BigWarpTransform} method instead + */ + @Deprecated() public static AffineGet toAffine( final ThinPlateR2LogRSplineKernelTransform tps ) { final double[] affineFlat = toFlatAffine( tps ); @@ -379,22 +932,21 @@ public static long[] dimensionsFromImagePlus( final ImagePlus ref_imp ) long[] dims; if( ref_imp.getNSlices() < 2 ) { - dims = new long[ 3 ]; + dims = new long[ 2 ]; dims[ 0 ] = ref_imp.getWidth(); dims[ 1 ] = ref_imp.getHeight(); - dims[ 2 ] = 2; } else { - dims = new long[ 4 ]; + dims = new long[ 3 ]; dims[ 0 ] = ref_imp.getWidth(); dims[ 1 ] = ref_imp.getHeight(); - dims[ 2 ] = 3; - dims[ 3 ] = ref_imp.getNSlices(); + dims[ 2 ] = ref_imp.getNSlices(); } return dims; } + @Deprecated public static FloatImagePlus< FloatType > convertToDeformationField( final long[] dims, final RealTransform transform, @@ -458,6 +1010,7 @@ public static boolean areTransformsTheSame( final RealTransform xfm1, final Real * the {@link RandomAccessibleInterval} into which the * displacement field will be written */ + @Deprecated public static < T extends RealType< T > > void fromRealTransform( final RealTransform transform, final AffineGet pixelToPhysical, @@ -509,6 +1062,7 @@ public static < T extends RealType< T > > void fromRealTransform( * @param nThreads * the number of threads */ + @Deprecated public static < T extends RealType< T > > void fromRealTransform( final RealTransform transform, final AffineGet pixelToPhysical, final RandomAccessibleInterval< T > deformationField, @@ -532,7 +1086,7 @@ public static < T extends RealType< T > > void fromRealTransform( final RealTran dim2split = 2; } - final long del = ( long )( N / nThreads ); + final long del = ( N / nThreads ); splitPoints[ 0 ] = 0; splitPoints[ nThreads ] = deformationField.dimension( dim2split ); for( int i = 1; i < nThreads; i++ ) @@ -606,7 +1160,7 @@ public Boolean call() } } - private static Compression getCompression( final String compressionArg ) + public static Compression getCompression( final String compressionArg ) { switch (compressionArg) { case N5Exporter.GZIP_COMPRESSION: @@ -629,26 +1183,44 @@ private static Compression getCompression( final String compressionArg ) * and can prompt the user for these parameters. * */ - private static class DeformationFieldExportParameters + public static class DeformationFieldExportParameters { public final String landmarkPath; public final boolean ignoreAffine; + public final String option; + public final String inverseOption; + public final boolean virtual; public final int nThreads; + public final String format; + public final long[] size; public final double[] spacing; + public final double[] offset; + public final String unit; public final String n5Base; public final String n5Dataset; public final Compression compression; public final int[] blockSize; + public final double inverseTolerance; + public final int inverseMaxIterations; + public DeformationFieldExportParameters( final String landmarkPath, final boolean ignoreAffine, + final String option, + final String inverseOption, + final double inverseTolerance, + final int inverseMaxIterations, + final boolean virtual, final int nThreads, + final String format, final long[] size, final double[] spacing, + final double[] offset, + final String unit, final String n5Base, final String n5Dataset, final int[] blockSize, @@ -656,10 +1228,20 @@ public DeformationFieldExportParameters( { this.landmarkPath = landmarkPath; this.ignoreAffine = ignoreAffine; + this.option = option; + + this.inverseOption = inverseOption; + this.inverseTolerance = inverseTolerance; + this.inverseMaxIterations = inverseMaxIterations; + + this.virtual = virtual; this.nThreads = nThreads; + this.format = format; this.size = size; this.spacing = spacing; + this.offset = offset; + this.unit = unit; this.n5Base = n5Base; this.n5Dataset = n5Dataset; @@ -667,6 +1249,11 @@ public DeformationFieldExportParameters( this.compression = compression; } + public boolean flatten() + { + return option.equals( flattenOption ); + } + public static DeformationFieldExportParameters fromDialog( final boolean promptLandmarks, final boolean promptReference ) @@ -678,8 +1265,17 @@ public static DeformationFieldExportParameters fromDialog( gd.addFileField( "landmarks_image_file", "" ); } - gd.addCheckbox( "Ignore affine part", false ); + gd.addCheckbox( "Split affine part", false ); + + final String[] choices = new String[] { flattenOption, sequenceOption }; + gd.addChoice( "type", choices, flattenOption ); + + final String[] invChoices = new String[] { INVERSE_OPTIONS.FORWARD.toString(), INVERSE_OPTIONS.INVERSE.toString(), INVERSE_OPTIONS.BOTH.toString() }; + gd.addChoice( "direction", invChoices, INVERSE_OPTIONS.FORWARD.toString() ); + + gd.addCheckbox( "virtual", false ); gd.addNumericField( "threads", 1, 0 ); + gd.addStringField( "format", ExportDisplacementFieldFrame.FMT_NGFF ); gd.addMessage( "Size and spacing" ); final int[] ids = WindowManager.getIDList(); @@ -697,10 +1293,12 @@ public static DeformationFieldExportParameters fromDialog( } gd.addStringField( "output size", ""); gd.addStringField( "output spacing", ""); + gd.addStringField( "output offset", ""); + gd.addStringField( "output unit", "pixel"); gd.addMessage( "Leave n5 path empty to export as ImagePlus" ); gd.addDirectoryOrFileField( "n5 root path", "" ); - gd.addStringField( "n5 dataset", ""); + gd.addStringField( "n5 dataset", N5DisplacementField.FORWARD_ATTR ); gd.addStringField( "n5 block size", "32,32,32"); gd.addChoice( "n5 compression", compressionOptions, N5Exporter.GZIP_COMPRESSION ); gd.showDialog(); @@ -713,7 +1311,11 @@ public static DeformationFieldExportParameters fromDialog( landmarkPath = gd.getNextString(); final boolean ignoreAffine = gd.getNextBoolean(); + final String option = gd.getNextChoice(); + final String direction = gd.getNextChoice(); + final boolean virtual = gd.getNextBoolean(); final int nThreads = ( int ) gd.getNextNumber(); + final String format = gd.getNextString(); ImagePlus ref_imp = null; if( promptReference ) @@ -725,6 +1327,8 @@ public static DeformationFieldExportParameters fromDialog( final String sizeString = gd.getNextString(); final String spacingString = gd.getNextString(); + final String offsetString = gd.getNextString(); + final String unitString = gd.getNextString(); final String n5Base = gd.getNextString(); final String n5Dataset = gd.getNextString(); @@ -737,6 +1341,8 @@ public static DeformationFieldExportParameters fromDialog( final long[] size; final double[] spacing; + final double[] offset; + String unit = "pixel"; if( ref_imp == null ) { if( !sizeString.isEmpty()) @@ -748,6 +1354,14 @@ public static DeformationFieldExportParameters fromDialog( spacing = Arrays.stream( spacingString.split( "," ) ).mapToDouble( Double::parseDouble ).toArray(); else spacing = null; + + if( !offsetString.isEmpty() ) + offset = Arrays.stream( offsetString.split( "," ) ).mapToDouble( Double::parseDouble ).toArray(); + else + offset = null; + + if( !unitString.isEmpty() ) + unit = unitString; } else { @@ -757,11 +1371,18 @@ public static DeformationFieldExportParameters fromDialog( // account for physical units of reference image spacing = new double[ nd ]; + offset = new double[ nd ]; spacing[ 0 ] = ref_imp.getCalibration().pixelWidth; spacing[ 1 ] = ref_imp.getCalibration().pixelHeight; + offset[ 0 ] = ref_imp.getCalibration().xOrigin; + offset[ 1 ] = ref_imp.getCalibration().yOrigin; + if ( nd > 2 ) + { spacing[ 2 ] = ref_imp.getCalibration().pixelDepth; + offset[ 2 ] = ref_imp.getCalibration().zOrigin; + } size = BigWarpToDeformationFieldPlugIn.dimensionsFromImagePlus( ref_imp ); } @@ -769,14 +1390,27 @@ public static DeformationFieldExportParameters fromDialog( return new DeformationFieldExportParameters( landmarkPath, ignoreAffine, + option, + direction, 0.5, 200, + virtual, nThreads, + format, size, spacing, + offset, + unit, n5Base, n5Dataset, blockSize, compression ); } + + public JFrame makeDialog() + { + final ExportDisplacementFieldFrame frame = new ExportDisplacementFieldFrame( null ); + return frame; + } + } } diff --git a/src/main/java/bdv/ij/BigWarpTransformExportPlugin.java b/src/main/java/bdv/ij/BigWarpTransformExportPlugin.java new file mode 100644 index 00000000..157808ec --- /dev/null +++ b/src/main/java/bdv/ij/BigWarpTransformExportPlugin.java @@ -0,0 +1,101 @@ +package bdv.ij; + +import java.io.File; +import java.io.IOException; + +import bigwarp.BigWarp; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.transforms.BigWarpTransform; +import bigwarp.transforms.NgffTransformations; +import bigwarp.transforms.WrappedCoordinateTransform; +import fiji.util.gui.GenericDialogPlus; +import ij.ImageJ; +import ij.plugin.PlugIn; +import mpicbg.models.Model; +import net.imglib2.realtransform.AffineGet; + +public class BigWarpTransformExportPlugin implements PlugIn +{ + private boolean promptLandmarks = true; + + private BigWarpTransform bwTransform; + + public static void main( final String[] args ) + { + new ImageJ(); + new BigWarpTransformExportPlugin().run( null ); + } + + public void runFromBigWarpInstance( final BigWarp< ? > bw ) + { + promptLandmarks = false; + bwTransform = bw.getBwTransform(); + run( null ); + } + + @Override + public void run( final String arg ) + { + // TODO deal with macro recordability + + final GenericDialogPlus gd = new GenericDialogPlus( "Export BigWarp Transformation" ); + gd.addMessage( "Transformation export:" ); + if ( promptLandmarks ) + { + gd.addFileField( "landmarks_file", "" ); + } + + gd.addChoice( "Transform type", + new String[] { + BigWarpTransform.AFFINE, + BigWarpTransform.SIMILARITY, + BigWarpTransform.ROTATION, + BigWarpTransform.TRANSLATION }, + BigWarpTransform.AFFINE ); + + gd.addFileField( "Output json file or n5", "" ); + gd.addStringField( "N5 dataset", "" ); + gd.addStringField( "N5 attribute name", "" ); + + gd.showDialog(); + if ( gd.wasCanceled() ) + return; + + String landmarksPath = null; + if ( promptLandmarks ) + { + landmarksPath = gd.getNextString(); + } + + final String transformTypeOption = gd.getNextChoice(); + final String fileOrN5Root = gd.getNextString(); + final String n5Dataset = gd.getNextString(); + final String n5Attr = gd.getNextString(); + + if ( bwTransform == null ) + { + try + { + final LandmarkTableModel ltm = LandmarkTableModel.loadFromCsv( new File( landmarksPath ), false ); + bwTransform = new BigWarpTransform( ltm, transformTypeOption ); + } + catch ( final IOException e ) + { + e.printStackTrace(); + return; + } + } + + String url = fileOrN5Root; + if ( !n5Dataset.isEmpty() ) + url = url + "?" + n5Dataset; + + if ( !n5Attr.isEmpty() ) + url = url + "#" + n5Attr; + + final WrappedCoordinateTransform wct = ( WrappedCoordinateTransform ) bwTransform.getTransformation( false ); + final AffineGet affine = bwTransform.toImglib2( ( Model< ? > ) wct.getTransform() ); + NgffTransformations.save( url, NgffTransformations.createAffine( affine ) ); + } + +} diff --git a/src/main/java/bdv/img/RealTransformedSource.java b/src/main/java/bdv/img/RealTransformedSource.java new file mode 100644 index 00000000..1701f84c --- /dev/null +++ b/src/main/java/bdv/img/RealTransformedSource.java @@ -0,0 +1,266 @@ +/*- + * #%L + * BigWarp plugin for Fiji. + * %% + * Copyright (C) 2015 - 2022 Howard Hughes Medical Institute. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package bdv.img; + +import java.util.function.Supplier; + +import bdv.viewer.Interpolation; +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.render.DefaultMipmapOrdering; +import bdv.viewer.render.MipmapOrdering; +import mpicbg.spim.data.sequence.VoxelDimensions; +import net.imglib2.Interval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealRandomAccessible; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.BoundingBoxEstimation; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.RealTransformRealRandomAccessible; +import net.imglib2.realtransform.RealTransformSequence; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; +import net.imglib2.view.Views; + +public class RealTransformedSource < T > implements Source< T >, MipmapOrdering +{ + + public static < T > SourceAndConverter< T > wrap( final SourceAndConverter< T > wrap, final String name, int ndims ) + { + return new SourceAndConverter< T >( + new RealTransformedSource< T >( wrap.getSpimSource(), name ), + wrap.getConverter(), + wrap.asVolatile() == null ? null : wrap( wrap.asVolatile(), name, ndims ) ); + } + + /** + * The wrapped {@link Source}. + */ + private final Source< T > source; + + private final String name; + + /** + * This is either the {@link #source} itself, if it implements + * {@link MipmapOrdering}, or a {@link DefaultMipmapOrdering}. + */ + private final MipmapOrdering sourceMipmapOrdering; + + private InvertibleRealTransform xfm; + + private Interval[] boundingIntervalsPerLevel; + + private boolean isTransformed; + + private final Supplier< Boolean > boundingBoxCullingSupplier; + + private BoundingBoxEstimation bboxEst; + + public RealTransformedSource( final Source< T > source, final String name ) + { + this( source, name, null ); + } + + public RealTransformedSource( final Source< T > source, final String name, + final Supplier< Boolean > doBoundingBoxCulling ) + { + this.source = source; + this.name = name; + this.isTransformed = false; + this.boundingBoxCullingSupplier = doBoundingBoxCulling; + this.xfm = null; + + bboxEst = new BoundingBoxEstimation( BoundingBoxEstimation.Method.FACES, 5 ); + boundingIntervalsPerLevel = new Interval[source.getNumMipmapLevels()]; + + sourceMipmapOrdering = MipmapOrdering.class.isInstance( source ) ? + ( MipmapOrdering ) source : new DefaultMipmapOrdering( source ); + } + + @Override + public boolean isPresent( final int t ) + { + return source.isPresent( t ); + } + + @Override + public boolean doBoundingBoxCulling() + { + if( boundingBoxCullingSupplier != null ) + return boundingBoxCullingSupplier.get(); + else + return ( !isTransformed ) && ( source.doBoundingBoxCulling() ); + } + + public void updateTransform( RealTransform xfm ) + { + if( xfm instanceof InvertibleRealTransform ) + this.xfm = (InvertibleRealTransform)xfm; + else + this.xfm = new WrappedIterativeInvertibleRealTransform<>(xfm); + + updateBoundingIntervals(); + } + + protected void updateBoundingIntervals() + { + for( int i = 0; i < getNumMipmapLevels(); i++ ) + { + boundingIntervalsPerLevel[i] = estimateBoundingInterval(0, i); + } + } + + public void setIsTransformed( boolean isTransformed ) + { + this.isTransformed = isTransformed; + } + + public void setBoundingBoxEstimator( final BoundingBoxEstimation bboxEst ) + { + this.bboxEst = bboxEst; + } + + public boolean isTransformed( ) + { + return isTransformed; + } + + public Source< T > getWrappedSource() + { + return source; + } + + @Override + public RandomAccessibleInterval< T > getSource( final int t, final int level ) + { +// if( isTransformed ) +// { +// return Views.interval( +// Views.raster( getInterpolatedSource( t, level, Interpolation.NEARESTNEIGHBOR ) ), +// boundingIntervalsPerLevel[level] ); +// } +// return source.getSource( t, level ); + + if( isTransformed ) + { + final RealTransformRealRandomAccessible interpSrc = (RealTransformRealRandomAccessible)getInterpolatedSource( t, level, Interpolation.NEARESTNEIGHBOR ); + + final AffineTransform3D transform = new AffineTransform3D(); + source.getSourceTransform( t, level, transform ); + final RealTransformSequence totalInverseTransform = new RealTransformSequence(); + totalInverseTransform.add( transform.inverse() ); + totalInverseTransform.add( transform.inverse() ); + totalInverseTransform.add( transform ); + + return Views.interval( Views.raster(interpSrc), boundingIntervalsPerLevel[level] ); + } + else + return source.getSource( t, level ); + } + + private Interval estimateBoundingInterval( final int t, final int level ) + { + if( xfm == null ) + { + return source.getSource( t, level ); + } + else + { + // getSource can be called by multiple threads, so need ensure application of + // the transform is thread safe here by copying + return bboxEst.estimatePixelInterval( xfm.copy().inverse(), source.getSource( t, level ) ); + } + } + + @Override + public RealRandomAccessible< T > getInterpolatedSource( final int t, final int level, final Interpolation method ) + { + + final RealRandomAccessible realSrc = source.getInterpolatedSource( t, level, method ); + if( isTransformed && xfm != null ) + { + final AffineTransform3D transform = new AffineTransform3D(); + source.getSourceTransform( t, level, transform ); + + final RealTransformSequence totalTransform = new RealTransformSequence(); + totalTransform.add( transform.inverse() ); + totalTransform.add( xfm ); + totalTransform.add( transform ); + + return new RealTransformRealRandomAccessible< T, RealTransform >( realSrc, xfm ); + } + else + { + return realSrc; + } + } + + @Override + public synchronized void getSourceTransform( final int t, final int level, final AffineTransform3D transform ) + { + if( isTransformed ) + transform.identity(); + else + source.getSourceTransform( t, level, transform ); + } + + public RealTransform getTransform() + { + return xfm; + } + + @Override + public T getType() + { + return source.getType(); + } + + @Override + public String getName() + { + return source.getName() + "_" + name; + } + + public String getOriginalName() + { + return getWrappedSource().getName(); + } + + @Override + public VoxelDimensions getVoxelDimensions() + { + return source.getVoxelDimensions(); + } + + @Override + public int getNumMipmapLevels() + { + return source.getNumMipmapLevels(); + } + + @Override + public synchronized MipmapHints getMipmapHints( final AffineTransform3D screenTransform, final int timepoint, final int previousTimepoint ) + { + return sourceMipmapOrdering.getMipmapHints( screenTransform, timepoint, previousTimepoint ); + } + +} diff --git a/src/main/java/bdv/img/WarpedSource.java b/src/main/java/bdv/img/WarpedSource.java index bd70dd1c..296d804d 100644 --- a/src/main/java/bdv/img/WarpedSource.java +++ b/src/main/java/bdv/img/WarpedSource.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -28,16 +28,18 @@ import bdv.viewer.SourceAndConverter; import bdv.viewer.render.DefaultMipmapOrdering; import bdv.viewer.render.MipmapOrdering; -import bigwarp.BigWarpExporter; import mpicbg.spim.data.sequence.VoxelDimensions; import net.imglib2.Interval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealRandomAccessible; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.realtransform.BoundingBoxEstimation; +import net.imglib2.realtransform.InvertibleRealTransform; import net.imglib2.realtransform.RealTransform; import net.imglib2.realtransform.RealTransformRealRandomAccessible; +import net.imglib2.realtransform.RealTransformSequence; import net.imglib2.realtransform.RealViews; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; import net.imglib2.view.Views; public class WarpedSource < T > implements Source< T >, MipmapOrdering @@ -64,10 +66,12 @@ public static < T > SourceAndConverter< T > wrap( final SourceAndConverter< T > */ private final MipmapOrdering sourceMipmapOrdering; - private RealTransform xfm; + private InvertibleRealTransform xfm; + + private Interval[] boundingIntervalsPerLevel; private boolean isTransformed; - + private final Supplier< Boolean > boundingBoxCullingSupplier; private BoundingBoxEstimation bboxEst; @@ -84,9 +88,11 @@ public WarpedSource( final Source< T > source, final String name, this.name = name; this.isTransformed = false; this.boundingBoxCullingSupplier = doBoundingBoxCulling; - this.xfm = null; + bboxEst = new BoundingBoxEstimation( BoundingBoxEstimation.Method.FACES, 5 ); + boundingIntervalsPerLevel = new Interval[source.getNumMipmapLevels()]; + sourceMipmapOrdering = MipmapOrdering.class.isInstance( source ) ? ( MipmapOrdering ) source : new DefaultMipmapOrdering( source ); } @@ -96,7 +102,7 @@ public boolean isPresent( final int t ) { return source.isPresent( t ); } - + @Override public boolean doBoundingBoxCulling() { @@ -108,14 +114,27 @@ public boolean doBoundingBoxCulling() public void updateTransform( RealTransform xfm ) { - this.xfm = xfm; + if( xfm instanceof InvertibleRealTransform ) + this.xfm = (InvertibleRealTransform)xfm; + else + this.xfm = new WrappedIterativeInvertibleRealTransform<>(xfm); + + updateBoundingIntervals(); + } + + protected void updateBoundingIntervals() + { + for( int i = 0; i < getNumMipmapLevels(); i++ ) + { + boundingIntervalsPerLevel[i] = estimateBoundingInterval(0, i); + } } - + public void setIsTransformed( boolean isTransformed ) { this.isTransformed = isTransformed; } - + public void setBoundingBoxEstimator( final BoundingBoxEstimation bboxEst ) { this.bboxEst = bboxEst; @@ -138,44 +157,32 @@ public RandomAccessibleInterval< T > getSource( final int t, final int level ) { return Views.interval( Views.raster( getInterpolatedSource( t, level, Interpolation.NEARESTNEIGHBOR ) ), - estimateBoundingInterval( t, level )); + boundingIntervalsPerLevel[level] ); + } return source.getSource( t, level ); } private Interval estimateBoundingInterval( final int t, final int level ) { - return bboxEst.estimatePixelInterval( xfm, source.getSource( t, level ) ); + if( xfm == null ) + { + return source.getSource( t, level ); + } + else + { + // getSource can be called by multiple threads, so need ensure application of + // the transform is thread safe here by copying + return bboxEst.estimatePixelInterval( xfm.copy().inverse(), source.getSource( t, level ) ); + } } @Override public RealRandomAccessible< T > getInterpolatedSource( final int t, final int level, final Interpolation method ) { -// RealRandomAccessible realSrc = source.getInterpolatedSource( t, level, method ); -// if( isTransformed && xfm != null ) -// { -// final AffineTransform3D transform = new AffineTransform3D(); -// source.getSourceTransform( t, level, transform ); -// -// RealTransformSequence totalTransform = new RealTransformSequence(); -//// totalTransform.add( transform ); -//// totalTransform.add( xfm ); -//// totalTransform.add( transform.inverse() ); -// -// totalTransform.add( transform.inverse() ); -// totalTransform.add( xfm ); -// totalTransform.add( transform ); -// -// return new RealTransformRealRandomAccessible< T, RealTransform >( realSrc, xfm ); -// } -// else -// { -// return realSrc; -// } - - final RealRandomAccessible< T > sourceRealAccessible = source.getInterpolatedSource( t, level, method ); - if( isTransformed ) + final RealRandomAccessible realSrc = source.getInterpolatedSource( t, level, method ); + if( isTransformed && xfm != null ) { final AffineTransform3D transform = new AffineTransform3D(); source.getSourceTransform( t, level, transform ); @@ -184,11 +191,11 @@ public RealRandomAccessible< T > getInterpolatedSource( final int t, final int l if( xfm == null ) return srcRaTransformed; else - return new RealTransformRealRandomAccessible< T, RealTransform >( srcRaTransformed, xfm ); + return new RealTransformRealRandomAccessible< T, RealTransform >( srcRaTransformed, xfm); } else { - return sourceRealAccessible; + return realSrc; } } @@ -218,6 +225,11 @@ public String getName() return source.getName() + "_" + name; } + public String getOriginalName() + { + return getWrappedSource().getName(); + } + @Override public VoxelDimensions getVoxelDimensions() { diff --git a/src/main/java/bdv/ui/convertersetupeditor/MaskBoundsRangePanel.java b/src/main/java/bdv/ui/convertersetupeditor/MaskBoundsRangePanel.java new file mode 100644 index 00000000..e617fa48 --- /dev/null +++ b/src/main/java/bdv/ui/convertersetupeditor/MaskBoundsRangePanel.java @@ -0,0 +1,29 @@ +package bdv.ui.convertersetupeditor; + +import bdv.util.BoundedRange; +import bigwarp.BigWarp; + +public class MaskBoundsRangePanel extends BoundedRangePanel { + + private static final long serialVersionUID = -4065636040399818543L; + + public MaskBoundsRangePanel(BigWarp bw ) { + this(bw, new BoundedRange(0, 1, 0, 1)); + } + + public MaskBoundsRangePanel( final BigWarp bw, final BoundedRange range ) { + super( range ); + setConsistent( true ); + setup( bw ); + } + + protected void setup( BigWarp bw ) { + changeListeners().add(new ChangeListener() { + @Override + public void boundedRangeChanged() { + bw.getBwTransform().setMaskIntensityBounds( getRange().getMin(), getRange().getMax()); + } + }); + } + +} diff --git a/src/main/java/bdv/viewer/BigWarpDragOverlay.java b/src/main/java/bdv/viewer/BigWarpDragOverlay.java index 5af16790..b6979a07 100644 --- a/src/main/java/bdv/viewer/BigWarpDragOverlay.java +++ b/src/main/java/bdv/viewer/BigWarpDragOverlay.java @@ -81,13 +81,11 @@ public void paint( final Graphics2D g ) { arad = viewer.getSettings().getSpotSize(); baseColor = viewer.getSettings().getSpotColor(); - - // System.out.println("BigWarpDragOverlay - PAINT" ); + if( inProgress ) { viewer.state().getViewerTransform( transform ); - //System.out.println("BigWarpDragOverlay - PAINT IN PROGRESS" ); transform.apply( movingPoint, movingPointScreen ); transform.apply( targetPoint, targetPointScreen ); diff --git a/src/main/java/bdv/viewer/BigWarpLandmarkFrame.java b/src/main/java/bdv/viewer/BigWarpLandmarkFrame.java index 52116094..b54e90b9 100644 --- a/src/main/java/bdv/viewer/BigWarpLandmarkFrame.java +++ b/src/main/java/bdv/viewer/BigWarpLandmarkFrame.java @@ -31,9 +31,11 @@ import javax.swing.WindowConstants; import org.scijava.ui.behaviour.util.InputActionBindings; +import org.scijava.ui.behaviour.util.TriggerBehaviourBindings; import bdv.gui.BigWarpLandmarkPanel; import bigwarp.BigWarp; +import bigwarp.ui.keymap.KeymapManager; public class BigWarpLandmarkFrame extends JFrame { @@ -43,16 +45,21 @@ public class BigWarpLandmarkFrame extends JFrame { private BigWarpLandmarkPanel lmPanel; + private final KeymapManager keymapManager; + private final InputActionBindings keybindings; - public BigWarpLandmarkFrame( String name, BigWarpLandmarkPanel panel, BigWarp bw ) + private final TriggerBehaviourBindings triggerbindings; + + public BigWarpLandmarkFrame( String name, BigWarpLandmarkPanel panel, BigWarp< ? > bw, KeymapManager keymapManager ) { super( name, AWTUtils.getSuitableGraphicsConfiguration( AWTUtils.RGB_COLOR_MODEL ) ); this.bw = bw; + this.keymapManager = keymapManager; setLandmarkPanel( panel ); keybindings = new InputActionBindings(); - + triggerbindings = new TriggerBehaviourBindings(); // do nothing because the closeAll method in bigWarp is responsible for calling dispose and cleaning up setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE ); @@ -63,11 +70,11 @@ public void windowClosing( final WindowEvent e ) BigWarpLandmarkFrame.this.bw.closeAll(); } } ); - + SwingUtilities.replaceUIActionMap( lmPanel.getJTable(), keybindings.getConcatenatedActionMap() ); - SwingUtilities.replaceUIInputMap( lmPanel.getJTable(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); + SwingUtilities.replaceUIInputMap( lmPanel.getJTable(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); } - + public void setLandmarkPanel( BigWarpLandmarkPanel panel ) { this.lmPanel = panel; @@ -81,5 +88,4 @@ public InputActionBindings getKeybindings() return keybindings; } - } diff --git a/src/main/java/bdv/viewer/BigWarpOverlay.java b/src/main/java/bdv/viewer/BigWarpOverlay.java index 23ce4368..2ab164aa 100644 --- a/src/main/java/bdv/viewer/BigWarpOverlay.java +++ b/src/main/java/bdv/viewer/BigWarpOverlay.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -40,70 +40,51 @@ public class BigWarpOverlay { private BigWarpViewerPanel viewer; - - protected JTable table; - protected LandmarkTableModel landmarkModel; + private BigWarpLandmarkPanel landmarkPanel; protected RealTransform estimatedXfm; - + protected boolean isTransformed = false; private int hoveredIndex; protected final boolean isMoving; - protected final boolean is3d; - + protected final double[] spot; - protected final double[] viewerCoords; + protected final double[] viewerCoords; /** The transform for the viewer current viewpoint. */ private final AffineTransform3D transform = new AffineTransform3D(); - - public BigWarpOverlay( final BigWarpViewerPanel viewer, BigWarpLandmarkPanel landmarkpanel ) - { - this.viewer = viewer; - this.table = landmarkpanel.getJTable(); - this.landmarkModel = landmarkpanel.getTableModel(); - if( landmarkModel.getNumdims() == 3 ) - is3d = true; - else - is3d = false; + public BigWarpOverlay( final BigWarpViewerPanel viewer, BigWarpLandmarkPanel landmarkPanel ) + { + this.viewer = viewer; + setLandmarkPanel(landmarkPanel); isMoving = viewer.getIsMoving(); spot = new double[ 3 ]; viewerCoords = new double[ 3 ]; } - public int getHoveredIndex() { return hoveredIndex; } - public void setHoveredIndex( int hoveredIndex ) { this.hoveredIndex = hoveredIndex; } + public void setLandmarkPanel( final BigWarpLandmarkPanel landmarkPanel ) + { + this.landmarkPanel = landmarkPanel; + } public void paint( final Graphics2D g ) { - // Save graphic device original settings - final Composite originalComposite = g.getComposite(); - final Stroke originalStroke = g.getStroke(); - final Color originalColor = g.getColor(); - - // get selected points - int[] selectedRows = table.getSelectedRows(); - Arrays.sort( selectedRows ); - boolean[] isSelected = new boolean[ landmarkModel.getRowCount() ]; - for( int i : selectedRows ) - isSelected[ i ] = true; - /* * Draw spots. @@ -111,17 +92,34 @@ public void paint( final Graphics2D g ) if ( viewer.getSettings().areLandmarksVisible() ) { + final LandmarkTableModel landmarkModel = landmarkPanel.getTableModel(); + final JTable table = landmarkPanel.getJTable(); + final boolean is3d = landmarkPanel.numDimensions() == 3; + + // Save graphic device original settings + final Composite originalComposite = g.getComposite(); + final Stroke originalStroke = g.getStroke(); + final Color originalColor = g.getColor(); + + // get selected points + final int[] selectedRows = table.getSelectedRows(); + Arrays.sort( selectedRows ); + final boolean[] isSelected = new boolean[ landmarkModel.getRowCount() ]; + for( final int i : selectedRows ) + isSelected[ i ] = true; + + final BasicStroke hlStroke = new BasicStroke( (int)viewer.getSettings().strokeWeight ); - final BasicStroke selStroke = new BasicStroke( (int)viewer.getSettings().strokeWeight / 2 ); + // final BasicStroke selStroke = new BasicStroke( (int)viewer.getSettings().strokeWeight / 2 ); - final double radiusRatio = ( Double ) viewer.getSettings().get( + final double radiusRatio = ( Double ) viewer.getSettings().get( BigWarpViewerSettings.KEY_SPOT_RADIUS_RATIO ); - + final double radius = viewer.getSettings().getSpotSize(); Stroke stroke; stroke = BigWarpViewerSettings.NORMAL_STROKE; - + FontMetrics fm = null; Font font = null; int fonthgt = 0; @@ -143,7 +141,7 @@ public void paint( final Graphics2D g ) textBoxColorHl = new Color( color.getRed(), color.getGreen(), color.getBlue(), 255 ); textBoxColor = new Color( color.getRed(), color.getGreen(), color.getBlue(), 128 ); - } final Color desaturatedColor = null; + } final int nRows = landmarkModel.getRowCount(); for( int index = 0; index < nRows; index++ ) @@ -195,13 +193,13 @@ public void paint( final Graphics2D g ) else arad = rad; - double srad = arad + strokeW; + final double srad = arad + strokeW; // vary size - g.fillOval( ( int ) ( viewerCoords[ 0 ] - arad ), - ( int ) ( viewerCoords[ 1 ] - arad ), + g.fillOval( ( int ) ( viewerCoords[ 0 ] - arad ), + ( int ) ( viewerCoords[ 1 ] - arad ), ( int ) ( 2 * arad + 1 ), ( int ) ( 2 * arad + 1) ); - + if( isSelected[ index ] ) { g.setStroke( hlStroke ); @@ -228,31 +226,31 @@ public void paint( final Graphics2D g ) { final int tx = ( int ) ( viewerCoords[ 0 ] + arad + 5 ); final int ty = ( int ) viewerCoords[ 1 ]; - - String name = landmarkModel.getNames().get(index); - int strwidth = fm.stringWidth( name ); - + + final String name = landmarkModel.getNames().get(index); + final int strwidth = fm.stringWidth( name ); + if( hoveredIndex == index ) g.setColor( textBoxColorHl ); else g.setColor( textBoxColor ); - + g.fillRect( tx - 1, ty - fonthgt + 2, strwidth + 2, fonthgt); - + g.setColor( Color.BLACK ); g.setFont( font ); g.drawString( name, tx, ty ); - + } } - } + + // Restore graphic device original settings + g.setComposite( originalComposite ); + g.setStroke( originalStroke ); + g.setColor( originalColor ); } - // Restore graphic device original settings - g.setComposite( originalComposite ); - g.setStroke( originalStroke ); - g.setColor( originalColor ); } @@ -267,17 +265,17 @@ public void setViewerState( final ViewerState state ) */ state.getViewerTransform( transform ); } - + public void setEstimatedTransform( final RealTransform estimatedXfm ) { this.estimatedXfm = estimatedXfm; } - + public boolean getIsTransformed() { return isTransformed; } - + public void setIsTransformed( boolean isTransformed ) { this.isTransformed = isTransformed; diff --git a/src/main/java/bdv/viewer/BigWarpViewerPanel.java b/src/main/java/bdv/viewer/BigWarpViewerPanel.java index 1bb9b2cf..de353058 100644 --- a/src/main/java/bdv/viewer/BigWarpViewerPanel.java +++ b/src/main/java/bdv/viewer/BigWarpViewerPanel.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -28,6 +28,10 @@ import bdv.util.Prefs; import bdv.viewer.animate.RotationAnimator; import bdv.viewer.animate.SimilarityTransformAnimator3D; +import bdv.viewer.overlay.BigWarpMaskSphereOverlay; +import bigwarp.BigWarp; +import bigwarp.BigWarpData; +import bigwarp.source.SourceInfo; import bigwarp.util.Rotation2DHelpers; import java.awt.Graphics; import java.awt.Graphics2D; @@ -45,12 +49,16 @@ public class BigWarpViewerPanel extends ViewerPanel public static final int TARGET_GROUP_INDEX = 1; + protected final BigWarpData bwData; + protected BigWarpViewerSettings viewerSettings; protected BigWarpOverlay overlay; protected BigWarpDragOverlay dragOverlay; + protected BigWarpMaskSphereOverlay maskOverlay; + protected boolean isMoving; protected boolean updateOnDrag; @@ -59,10 +67,6 @@ public class BigWarpViewerPanel extends ViewerPanel protected int ndims; - final protected int[] movingSourceIndexList; - - final protected int[] targetSourceIndexList; - protected boolean boxOverlayVisible = true; protected boolean textOverlayVisible = true; @@ -74,35 +78,33 @@ public class BigWarpViewerPanel extends ViewerPanel ViewerOptions options; - public BigWarpViewerPanel( final List< SourceAndConverter< ? > > sources, final BigWarpViewerSettings viewerSettings, final CacheControl cache, boolean isMoving, - int[] movingSourceIndexList, int[] targetSourceIndexList ) + @SuppressWarnings("rawtypes") + public BigWarpViewerPanel( final BigWarpData bwData , final BigWarpViewerSettings viewerSettings, final CacheControl cache, boolean isMoving ) { - this( sources, viewerSettings, cache, BigWarpViewerOptions.options(), isMoving, movingSourceIndexList, targetSourceIndexList ); + this( bwData, viewerSettings, cache, BigWarpViewerOptions.options(), isMoving ); } - public BigWarpViewerPanel( final List< SourceAndConverter< ? > > sources, final BigWarpViewerSettings viewerSettings, final CacheControl cache, final BigWarpViewerOptions optional, boolean isMoving, - int[] movingSourceIndexList, int[] targetSourceIndexList ) + @SuppressWarnings("unchecked") + public BigWarpViewerPanel( final BigWarpData bwData, final BigWarpViewerSettings viewerSettings, final CacheControl cache, final BigWarpViewerOptions optional, boolean isMoving ) { - super( sources, 1, cache, optional.getViewerOptions( isMoving ) ); + // TODO compiler complains if the first argument is 'final BigWarpData bwData' + super( bwData.sources, 1, cache, optional.getViewerOptions( isMoving ) ); + this.bwData = bwData; this.viewerSettings = viewerSettings; this.isMoving = isMoving; this.updateOnDrag = !isMoving; // update on drag only for the fixed // image by default - this.movingSourceIndexList = movingSourceIndexList; - this.targetSourceIndexList = targetSourceIndexList; - getDisplay().overlays().add( g -> { if ( null != overlay ) { overlay.setViewerState( state() ); overlay.paint( ( Graphics2D ) g ); } if ( dragOverlay != null ) { - //dragOverlay.setViewerState( state ); dragOverlay.paint( ( Graphics2D ) g ); } } ); - updateGrouping(); + //updateGrouping(); } @Override @@ -114,29 +116,29 @@ public ViewerOptions.Values getOptionValues() public void precomputeRotations2d( final AffineTransform3D initialViewTransform ) { orthoTransforms = new ArrayList<>(); - AffineTransform3D rot = new AffineTransform3D(); + final AffineTransform3D rot = new AffineTransform3D(); rot.rotate( 2, -Math.PI / 2 ); AffineTransform3D xfm = initialViewTransform; orthoTransforms.add( xfm ); for( int i = 1; i < 4; i++ ) { - AffineTransform3D newXfm = xfm.copy(); + final AffineTransform3D newXfm = xfm.copy(); newXfm.rotate( 2, -Math.PI/2); orthoTransforms.add( newXfm ); xfm = newXfm; } } - public void toggleTextOverlayVisible() - { - textOverlayVisible = !textOverlayVisible; - } - - public void toggleBoxOverlayVisible() - { - boxOverlayVisible = !boxOverlayVisible; - } +// public void toggleTextOverlayVisible() +// { +// textOverlayVisible = !textOverlayVisible; +// } +// +// public void toggleBoxOverlayVisible() +// { +// boxOverlayVisible = !boxOverlayVisible; +// } public void setHoveredIndex( int index ) { @@ -154,23 +156,30 @@ public void setHoveredIndex( int index ) /** * Makes the first group contain all the moving images and the second group * contain all the fixed images + *

+ * @deprecated use {@link BigWarp#createMovingTargetGroups} * * @return the number sources in the moving group */ + @Deprecated public int updateGrouping() { final SynchronizedViewerState state = state(); synchronized ( state ) { - // TODO: work backwards to find out whether movingSourceIndexList - // and targetSourceIndexList are required, or whether a - // List> can be used directly final List< SourceAndConverter< ? > > moving = new ArrayList<>(); - for ( int i : movingSourceIndexList ) - moving.add( state.getSources().get( i ) ); final List< SourceAndConverter< ? > > target = new ArrayList<>(); - for ( int i : targetSourceIndexList ) - target.add( state.getSources().get( i ) ); + + int idx = 0; + for ( final SourceInfo sourceInfo : bwData.sourceInfos.values() ) + { + if (sourceInfo.isMoving()) { + moving.add( state.getSources().get( idx ) ); + } else { + target.add( state.getSources().get( idx ) ); + } + idx++; + } state.clearGroups(); @@ -202,7 +211,12 @@ public int updateGrouping() public boolean isInFixedImageSpace() { - return !isMoving || ( ( WarpedSource< ? > ) ( state().getSources().get( movingSourceIndexList[ 0 ] ).getSpimSource() ) ).isTransformed(); + if( bwData.numMovingSources() < 1 ) + return true; + else + { + return !isMoving || ( ( WarpedSource< ? > ) ( ( bwData.getMovingSource( 0 )).getSpimSource() ) ).isTransformed(); + } } public boolean doUpdateOnDrag() @@ -236,10 +250,26 @@ public void addDragOverlay( BigWarpDragOverlay dragOverlay ){ this.dragOverlay = dragOverlay; } + public void addOverlay( OverlayRenderer overlay ) + { + super.getDisplay().overlays().add( overlay ); + } + public BigWarpDragOverlay getDragOverlay(){ return dragOverlay; } + public BigWarpMaskSphereOverlay getMaskOverlay() + { + return maskOverlay; + } + + public void setMaskOverlay( final BigWarpMaskSphereOverlay maskOverlay ) + { + this.maskOverlay = maskOverlay; + addOverlay( maskOverlay ); + } + public boolean getIsMoving() { return isMoving; @@ -288,7 +318,7 @@ public synchronized void rotateView2d( boolean isClockwise ) newTransform = Rotation2DHelpers.targetViewerTransform2d( transform , isClockwise ); break; } - catch(Exception e) + catch(final Exception e) { if( isClockwise ) transform.rotate( 2, -0.1 ); @@ -297,7 +327,7 @@ public synchronized void rotateView2d( boolean isClockwise ) } } - double[] qNew = new double[ 4 ]; + final double[] qNew = new double[ 4 ]; Affine3DHelpers.extractRotation( newTransform, qNew ); setTransformAnimator( new RotationAnimator(transform, centerX, centerY, qNew, 300 ) ); } @@ -369,12 +399,12 @@ public void drawOverlays( final Graphics g ) final boolean prefsShowTextOverlay = Prefs.showTextOverlay(); final boolean prefsShowMultibox = Prefs.showMultibox(); - Prefs.showTextOverlay( textOverlayVisible ); - Prefs.showMultibox( boxOverlayVisible ); +// Prefs.showTextOverlay( textOverlayVisible ); +// Prefs.showMultibox( boxOverlayVisible ); super.drawOverlays( g ); - // restore Prefs settings - Prefs.showTextOverlay( prefsShowTextOverlay ); - Prefs.showMultibox( prefsShowMultibox ); +// // restore Prefs settings +// Prefs.showTextOverlay( prefsShowTextOverlay ); +// Prefs.showMultibox( prefsShowMultibox ); } } diff --git a/src/main/java/bdv/viewer/BigWarpViewerSettings.java b/src/main/java/bdv/viewer/BigWarpViewerSettings.java index 3413a7c2..99f077b3 100644 --- a/src/main/java/bdv/viewer/BigWarpViewerSettings.java +++ b/src/main/java/bdv/viewer/BigWarpViewerSettings.java @@ -120,8 +120,6 @@ public Boolean areLandmarksVisible(){ public void togglePointsVisible(){ displaySettings.put( KEY_SPOTS_VISIBLE, !((Boolean)displaySettings.get( KEY_SPOTS_VISIBLE )).booleanValue()); - - //System.out.println(((Boolean)displaySettings.get( KEY_DISPLAY_SPOT_NAMES ))); } public void setSpotColor( Color c ) diff --git a/src/main/java/bdv/viewer/LandmarkPointMenu.java b/src/main/java/bdv/viewer/LandmarkPointMenu.java index 8ca7b5e3..49965081 100644 --- a/src/main/java/bdv/viewer/LandmarkPointMenu.java +++ b/src/main/java/bdv/viewer/LandmarkPointMenu.java @@ -23,10 +23,10 @@ import java.awt.Point; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.util.Arrays; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; @@ -35,6 +35,7 @@ import bdv.gui.BigWarpLandmarkPanel; import bigwarp.BigWarp; +import bigwarp.BigWarpActions; import bigwarp.landmarks.LandmarkTableModel; public class LandmarkPointMenu extends JPopupMenu @@ -43,46 +44,45 @@ public class LandmarkPointMenu extends JPopupMenu public static final boolean MOVING = true; public static final boolean FIXED = false; - public static final String CLEAR_MOVING = "table clear moving"; - public static final String CLEAR_FIXED = "table clear fixed"; - public static final String CLEAR_SELECTED_MOVING = "table clear selected moving"; - public static final String CLEAR_SELECTED_FIXED = "table clear selected fixed"; - - public static final String DELETE = "table delete"; - public static final String DELETE_SELECTED = "table delete selected "; - - public static final String ACTIVATE = "table activate"; - public static final String ACTIVATE_SELECTED = "table activate selected "; +// public static final String CLEAR_MOVING = "table clear moving"; +// public static final String CLEAR_FIXED = "table clear fixed"; +// public static final String CLEAR_SELECTED_MOVING = "table clear selected moving"; +// public static final String CLEAR_SELECTED_FIXED = "table clear selected fixed"; +// +// public static final String DELETE = "table delete"; +// public static final String DELETE_SELECTED = "table delete selected "; +// +// public static final String ACTIVATE_SELECTED = "table activate selected"; +// public static final String DEACTIVATE_SELECTED = "table deactivate selected "; private static final long serialVersionUID = -3676180390835767585L; protected BigWarpLandmarkPanel landmarkPanel; - protected BigWarp bw; - -// public ClearHandler clearMoving; -// public ClearHandler clearFixed; + protected BigWarp< ? > bw; + public ClearSelectedHandler clearSelectedMoving; public ClearSelectedHandler clearSelectedFixed; -// public DeleteHandler deleteHandler; public DeleteSelectedHandler deleteSelectedHandler; - public ActivateSelectedHandler activateAllHandler; - public DeactivateSelectedHandler deactivateAllHandler; - + public ActivateSelectedHandler activateSelectedHandler; + public DeactivateSelectedHandler deactivateSelectedHandler; + + public AddToSelection addAboveHandler; + public AddToSelection addAllAboveHandler; + public AddToSelection addBelowHandler; + public AddToSelection addAllBelowHandler; + protected MouseListener popupListener; -// protected JMenuItem deleteSingleItem; protected JMenuItem deleteAllItem; protected JMenuItem activateAllItem; protected JMenuItem deactivateAllItem; -// protected JMenuItem clearMovingItem; -// protected JMenuItem clearFixedItem; protected JMenuItem clearSelectedMovingItem; protected JMenuItem clearSelectedFixedItem; private Point clickPt; - public LandmarkPointMenu( BigWarp bw ) + public LandmarkPointMenu( BigWarp< ? > bw ) { this( bw.getLandmarkPanel() ); this.bw = bw; @@ -92,30 +92,23 @@ public LandmarkPointMenu( BigWarpLandmarkPanel landmarkPanel ) { this.landmarkPanel = landmarkPanel; -// deleteHandler = new DeleteHandler( DELETE ); - deleteSelectedHandler = new DeleteSelectedHandler( DELETE_SELECTED ); - activateAllHandler = new ActivateSelectedHandler( ACTIVATE ); - deactivateAllHandler = new DeactivateSelectedHandler( ACTIVATE_SELECTED ); + deleteSelectedHandler = new DeleteSelectedHandler( BigWarpActions.DELETE_SELECTED ); + activateSelectedHandler = new ActivateSelectedHandler( BigWarpActions.ACTIVATE_SELECTED ); + deactivateSelectedHandler = new DeactivateSelectedHandler( BigWarpActions.DEACTIVATE_SELECTED ); -// clearMoving = new ClearHandler( CLEAR_MOVING, MOVING ); -// clearFixed = new ClearHandler( CLEAR_FIXED, FIXED ); - clearSelectedMoving = new ClearSelectedHandler( CLEAR_SELECTED_MOVING, MOVING ); - clearSelectedFixed = new ClearSelectedHandler( CLEAR_SELECTED_FIXED, FIXED ); + clearSelectedMoving = new ClearSelectedHandler( BigWarpActions.CLEAR_SELECTED_MOVING, MOVING ); + clearSelectedFixed = new ClearSelectedHandler( BigWarpActions.CLEAR_SELECTED_FIXED, FIXED ); - popupListener = new PopupListener(); + addAboveHandler = new AddToSelection( BigWarpActions.LANDMARK_SELECT_ABOVE, true, false ); + addAllAboveHandler = new AddToSelection( BigWarpActions.LANDMARK_SELECT_ALL_ABOVE, true, true ); + addBelowHandler = new AddToSelection( BigWarpActions.LANDMARK_SELECT_BELOW, false, false ); + addAllBelowHandler = new AddToSelection( BigWarpActions.LANDMARK_SELECT_ALL_BELOW, false, true ); -// deleteSingleItem = new JMenuItem( "Delete" ); -// deleteSingleItem.addActionListener( deleteHandler ); + popupListener = new PopupListener(); deleteAllItem = new JMenuItem( "Delete" ); deleteAllItem.addActionListener( deleteSelectedHandler ); -// clearMovingItem = new JMenuItem( "Clear moving point" ); -// clearMovingItem.addActionListener( clearMoving ); -// -// clearFixedItem = new JMenuItem( "Clear fixed point" ); -// clearFixedItem.addActionListener( clearFixed ); - clearSelectedMovingItem = new JMenuItem( "Clear moving" ); clearSelectedMovingItem.addActionListener( clearSelectedMoving ); @@ -123,17 +116,14 @@ public LandmarkPointMenu( BigWarpLandmarkPanel landmarkPanel ) clearSelectedFixedItem.addActionListener( clearSelectedFixed ); activateAllItem = new JMenuItem( "Activate" ); - activateAllItem.addActionListener( activateAllHandler ); + activateAllItem.addActionListener( activateSelectedHandler ); deactivateAllItem = new JMenuItem( "Deactivate" ); - deactivateAllItem.addActionListener( deactivateAllHandler ); + deactivateAllItem.addActionListener( deactivateSelectedHandler ); -// this.add( deleteSingleItem ); this.add( deleteAllItem ); this.addSeparator(); -// this.add( clearMovingItem ); -// this.add( clearFixedItem ); this.add( clearSelectedMovingItem ); this.add( clearSelectedFixedItem ); @@ -305,7 +295,8 @@ public ActivateSelectedHandler( String name ) @Override public void actionPerformed(ActionEvent e) { - int[] selectedRows = landmarkPanel.getJTable().getSelectedRows(); + final int[] selectedRows = landmarkPanel.getJTable().getSelectedRows(); + Arrays.sort( selectedRows ); // do in reverse order so that the index for( int i = selectedRows.length - 1; i >= 0; i-- ) @@ -333,7 +324,8 @@ public DeactivateSelectedHandler( String name ) @Override public void actionPerformed(ActionEvent e) { - int[] selectedRows = landmarkPanel.getJTable().getSelectedRows(); + final int[] selectedRows = landmarkPanel.getJTable().getSelectedRows(); + Arrays.sort( selectedRows ); // do in reverse order so that the index for( int i = selectedRows.length - 1; i >= 0; i-- ) @@ -348,4 +340,48 @@ public void actionPerformed(ActionEvent e) landmarkPanel.repaint(); } } + + public class AddToSelection extends AbstractNamedAction + { + private static final long serialVersionUID = -904756750247052099L; + + private final boolean before; + private final boolean all; + + public AddToSelection( final String name, final boolean before, final boolean all ) + { + super( name ); + this.before = before; + this.all = all; + } + + @Override + public void actionPerformed( ActionEvent e ) + { + final int[] selectedRows = landmarkPanel.getJTable().getSelectedRows(); + if( selectedRows.length == 0 ) + return; + + Arrays.sort( selectedRows ); + + int i; + int j; + if ( before ) + { + j = selectedRows[ 0 ]; + i = all ? 0 : j - 1; + } else + { + i = selectedRows[ selectedRows.length - 1 ]; + j = all ? landmarkPanel.getJTable().getRowCount() - 1 : i + 1; + } + landmarkPanel.getJTable().getSelectionModel().addSelectionInterval( i, j ); + + if ( bw != null ) + bw.restimateTransformation(); + + landmarkPanel.repaint(); + } + } + } diff --git a/src/main/java/bdv/viewer/overlay/BigWarpMaskSphereOverlay.java b/src/main/java/bdv/viewer/overlay/BigWarpMaskSphereOverlay.java new file mode 100644 index 00000000..0776abd7 --- /dev/null +++ b/src/main/java/bdv/viewer/overlay/BigWarpMaskSphereOverlay.java @@ -0,0 +1,171 @@ +package bdv.viewer.overlay; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; + +import bdv.util.Affine3DHelpers; +import bdv.viewer.BigWarpViewerPanel; +import bdv.viewer.OverlayRenderer; +import net.imglib2.RealLocalizable; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineTransform3D; + +public class BigWarpMaskSphereOverlay implements OverlayRenderer +{ + + final static protected BasicStroke stroke = new BasicStroke( 1 ); + final protected BigWarpViewerPanel viewer; + + protected double[] radii; + protected final double[] viewerCoords; + protected double minWidth = 0.1; + + protected final Color[] colors; + final protected AffineTransform3D viewerTransform; + + protected double[] center; + protected int width, height; + protected boolean is3d; + protected boolean visible = false; + + public BigWarpMaskSphereOverlay( final BigWarpViewerPanel viewer, boolean is3d ) + { + this( viewer, new double[]{0,0}, new Color[]{ Color.ORANGE, Color.YELLOW }, is3d ); + } + + public BigWarpMaskSphereOverlay( final BigWarpViewerPanel viewer, final Color[] colors, boolean is3d ) + { + this( viewer, new double[]{0, 0}, colors, is3d ); + } + + public BigWarpMaskSphereOverlay( final BigWarpViewerPanel viewer, final double[] radii, final Color[] colors, boolean is3d ) + { + this.viewer = viewer; + this.radii = radii; + this.colors = colors; + viewerCoords = new double[ 3 ]; + center = new double[ 3 ]; + this.is3d = is3d; + viewerTransform = new AffineTransform3D(); + } + + public double[] getCenter() + { + return center; + } + + public double[] getRadii() + { + return radii; + } + + public void setCenter( final double[] center ) + { + this.center = center; + } + + public void setCenter( final RealLocalizable c ) + { + c.localize( center ); + } + + public void setCenter( final RealPoint center ) + { + center.localize( this.center ); + } + + public void setRadii( double[] radii ) + { + this.radii = radii; + } + + public void setInnerRadius( double inner ) + { + final double del = radii[1] - radii[0]; + radii[0] = inner; + radii[1] = inner + del; + } + + public void setOuterRadiusDelta( double outerDelta ) + { + radii[1] = radii[0] + outerDelta; + } + + public void setColor( final Color color, final int i ) + { + colors[ i ] = color; + } + + public void setColors( final Color[] colors ) + { + System.arraycopy( colors, 0, this.colors, 0, Math.min( colors.length, this.colors.length ) ); + } + + public void setVisible( final boolean visible ) + { + this.visible = visible; + viewer.requestRepaint(); + } + + public void toggleVisible() + { + setVisible( !visible ); + } + + @Override + public void drawOverlays( final Graphics g ) + { + if ( visible ) + { + final Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + g2d.setComposite( AlphaComposite.SrcOver ); + + viewer.state().getViewerTransform( viewerTransform ); // synchronized + final double scale = Affine3DHelpers.extractScale( viewerTransform, 0 ); + viewerTransform.apply( center, viewerCoords ); + + final double zv; + if( is3d ) + zv = viewerCoords[ 2 ]; + else + zv = 0; + + final double dz2 = zv * zv; + for ( int i = 0; i < radii.length; ++i ) + { + final double rad = radii[i]; + final double scaledRadius = scale * rad; + + if ( viewerCoords[0] + scaledRadius > 0 && viewerCoords[0] - scaledRadius < width && + viewerCoords[1] + scaledRadius > 0 && viewerCoords[1] - scaledRadius < height ) + { + final double arad; + if( is3d ) + arad = Math.sqrt( scaledRadius * scaledRadius - dz2 ); + else + arad = scaledRadius; + + final int rarad = (int)Math.round( arad ); + + g2d.setColor( colors[ i ] ); + g2d.setStroke( stroke ); + g2d.drawOval( (int)viewerCoords[0] - rarad, (int)viewerCoords[1] - rarad, + (2 * rarad + 1), 2 * rarad + 1 ); + } + } + } + } + + @Override + public void setCanvasSize( final int width, final int height ) + { + this.width = width; + this.height = height; + } + +} diff --git a/src/main/java/bdv/viewer/overlay/BigWarpSourceOverlayRenderer.java b/src/main/java/bdv/viewer/overlay/BigWarpSourceOverlayRenderer.java index a94157e4..8971569f 100644 --- a/src/main/java/bdv/viewer/overlay/BigWarpSourceOverlayRenderer.java +++ b/src/main/java/bdv/viewer/overlay/BigWarpSourceOverlayRenderer.java @@ -24,23 +24,60 @@ import java.awt.Font; import java.awt.Graphics2D; +import bdv.img.WarpedSource; +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.ViewerState; + public class BigWarpSourceOverlayRenderer extends SourceInfoOverlayRenderer { + private boolean indicateTransformed = true; + + // are any visible source in this viewer transformed + private boolean anyTransformed = false; @Override public synchronized void paint( final Graphics2D g ) { - g.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) ); + super.paint( g ); + if( indicateTransformed && anyTransformed ) + { + g.setFont( new Font( "Monospaced", Font.BOLD, 16 ) ); + int tformedWidth = g.getFontMetrics().stringWidth( "TRANSFORMED" ); + g.drawString( "TRANSFORMED", + ( int ) ( g.getClipBounds().getWidth() - tformedWidth ) / 2, + ( int ) g.getClipBounds().getHeight() - 16 ); + } + } - int actual_width = g.getFontMetrics().stringWidth( sourceName ); - g.drawString( sourceName, ( int ) g.getClipBounds().getWidth() - actual_width - 10, 12 ); + /** + * Update data to show in the overlay. + * + * Checks whether any sources in this viewer are transformed in order to indicate that fact. + */ + @Override + public synchronized void setViewerState( final ViewerState state ) + { + super.setViewerState( state ); - if( !groupName.isEmpty() ) + anyTransformed = false; + for( SourceAndConverter vs : state.getVisibleSources()) { - String groupStringBracket = "[ " + groupName + " ]"; - int actual_width_group = g.getFontMetrics().stringWidth( groupStringBracket ); - g.drawString( groupStringBracket, - ( int ) g.getClipBounds().getWidth() - actual_width - actual_width_group - 20, 12 ); + Source< ? > src = vs.getSpimSource(); + if( src instanceof WarpedSource ) + { + WarpedSource ws = (WarpedSource)src; + if( ws.isTransformed() ) + { + anyTransformed = true; + break; + } + } } } + + public void setIndicateTransformed( final boolean indicateTransformed ) + { + this.indicateTransformed = indicateTransformed; + } } diff --git a/src/main/java/bigwarp/BigWarp.java b/src/main/java/bigwarp/BigWarp.java index 52e80812..e80ed662 100755 --- a/src/main/java/bigwarp/BigWarp.java +++ b/src/main/java/bigwarp/BigWarp.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -21,13 +21,11 @@ */ package bigwarp; -import bdv.viewer.ConverterSetups; -import bdv.viewer.DisplayMode; -import bdv.viewer.TransformListener; -import bdv.viewer.ViewerState; +import bdv.TransformState; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; +import java.awt.Dimension; import java.awt.FileDialog; import java.awt.KeyEventPostProcessor; import java.awt.KeyboardFocusManager; @@ -37,14 +35,16 @@ import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.io.File; +import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.net.URISyntaxException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -66,15 +66,14 @@ import javax.swing.Timer; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; +import javax.swing.filechooser.FileFilter; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; -import mpicbg.spim.data.SpimData; -import mpicbg.spim.data.XmlIoSpimData; -import mpicbg.spim.data.registration.ViewTransformAffine; - import org.janelia.saalfeldlab.n5.Compression; import org.janelia.saalfeldlab.n5.ij.N5Exporter; +import org.janelia.utility.geom.BoundingSphereRitter; +import org.janelia.utility.geom.Sphere; import org.janelia.utility.ui.RepeatingReleasedEventsFixer; import org.jdom2.Document; import org.jdom2.Element; @@ -83,11 +82,14 @@ import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.util.Actions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.formdev.flatlaf.util.UIScale; +import com.google.gson.stream.JsonReader; + import bdv.BigDataViewer; -import bdv.cache.CacheControl; import bdv.export.ProgressWriter; import bdv.export.ProgressWriterConsole; import bdv.gui.BigWarpLandmarkPanel; @@ -95,7 +97,10 @@ import bdv.gui.BigWarpViewerFrame; import bdv.gui.BigWarpViewerOptions; import bdv.gui.BigwarpLandmarkSelectionPanel; +import bdv.gui.ExportDisplacementFieldFrame; import bdv.gui.LandmarkKeyboardProcessor; +import bdv.gui.MaskOptionsPanel; +import bdv.gui.MaskedSourceEditorMouseListener; import bdv.gui.TransformTypeSelectDialog; import bdv.ij.ApplyBigwarpPlugin; import bdv.ij.ApplyBigwarpPlugin.WriteDestinationOptions; @@ -103,44 +108,65 @@ import bdv.ij.util.ProgressWriterIJ; import bdv.img.WarpedSource; import bdv.tools.InitializeViewerState; +import bdv.tools.PreferencesDialog; import bdv.tools.VisibilityAndGroupingDialog; import bdv.tools.bookmarks.Bookmarks; import bdv.tools.bookmarks.BookmarksEditor; -import bdv.tools.brightness.BrightnessDialog; import bdv.tools.brightness.ConverterSetup; import bdv.tools.brightness.SetupAssignments; +import bdv.ui.appearance.AppearanceManager; +import bdv.ui.appearance.AppearanceSettingsPage; +import bdv.ui.keymap.Keymap; +import bdv.ui.keymap.KeymapSettingsPage; +import bdv.util.BoundedRange; import bdv.util.Bounds; +import bdv.viewer.AbstractViewerPanel.AlignPlane; import bdv.viewer.BigWarpDragOverlay; import bdv.viewer.BigWarpLandmarkFrame; import bdv.viewer.BigWarpOverlay; import bdv.viewer.BigWarpViewerPanel; import bdv.viewer.BigWarpViewerSettings; +import bdv.viewer.ConverterSetups; +import bdv.viewer.DisplayMode; import bdv.viewer.Interpolation; import bdv.viewer.LandmarkPointMenu; import bdv.viewer.MultiBoxOverlay2d; import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; +import bdv.viewer.SourceGroup; import bdv.viewer.SynchronizedViewerState; +import bdv.viewer.TransformListener; import bdv.viewer.ViewerPanel; -import bdv.viewer.VisibilityAndGrouping; +import bdv.viewer.ViewerState; import bdv.viewer.WarpNavigationActions; import bdv.viewer.animate.SimilarityModel3D; import bdv.viewer.animate.TranslationAnimator; +import bdv.viewer.overlay.BigWarpMaskSphereOverlay; import bdv.viewer.overlay.BigWarpSourceOverlayRenderer; import bdv.viewer.overlay.MultiBoxOverlayRenderer; import bigwarp.landmarks.LandmarkTableModel; -import bigwarp.loader.ImagePlusLoader; -import bigwarp.loader.ImagePlusLoader.ColorSettings; import bigwarp.source.GridSource; import bigwarp.source.JacobianDeterminantSource; +import bigwarp.source.PlateauSphericalMaskRealRandomAccessible; +import bigwarp.source.PlateauSphericalMaskRealRandomAccessible.FalloffShape; +import bigwarp.source.PlateauSphericalMaskSource; +import bigwarp.source.SourceInfo; import bigwarp.source.WarpMagnitudeSource; +import bigwarp.transforms.AbstractTransformSolver; import bigwarp.transforms.BigWarpTransform; +import bigwarp.transforms.MaskedSimRotTransformSolver; import bigwarp.transforms.WrappedCoordinateTransform; +import bigwarp.transforms.io.TransformWriterJson; +import bigwarp.ui.keymap.KeymapManager; +import bigwarp.ui.keymap.NavigationKeys; import bigwarp.util.BigWarpUtils; +import dev.dirs.ProjectDirectories; import fiji.util.gui.GenericDialogPlus; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; + +import java.util.LinkedHashMap; import jitk.spline.ThinPlateR2LogRSplineKernelTransform; import jitk.spline.XfmUtils; import mpicbg.models.AbstractModel; @@ -155,7 +181,10 @@ import mpicbg.models.SimilarityModel2D; import mpicbg.models.TranslationModel2D; import mpicbg.models.TranslationModel3D; +import mpicbg.spim.data.SpimData; import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.XmlIoSpimData; +import mpicbg.spim.data.registration.ViewTransformAffine; import net.imglib2.Interval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealPoint; @@ -163,18 +192,23 @@ import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.realtransform.BoundingBoxEstimation; import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.InvertibleWrapped2DTransformAs3D; +import net.imglib2.realtransform.RealTransform; import net.imglib2.realtransform.ThinplateSplineTransform; -import net.imglib2.realtransform.Wrapped2DTransformAs3D; +import net.imglib2.realtransform.inverse.RealTransformFiniteDerivatives; import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.DoubleType; import net.imglib2.type.numeric.real.FloatType; public class BigWarp< T > { + public static String configDir = ProjectDirectories.from( "sc", "fiji", "bigwarp" ).configDir; - protected static final int DEFAULT_WIDTH = 600; + protected static final int DEFAULT_WIDTH = 400; - protected static final int DEFAULT_HEIGHT = 400; + protected static final int DEFAULT_HEIGHT = 300; public static final int GRID_SOURCE_ID = 1696993146; @@ -182,34 +216,32 @@ public class BigWarp< T > public static final int JACDET_SOURCE_ID = 1006827158; + public static final int TRANSFORM_MASK_SOURCE_ID = 33872301; + protected BigWarpViewerOptions options; protected BigWarpData< T > data; - // descriptive names for indexing sources - protected int[] movingSourceIndexList; - - protected int[] targetSourceIndexList; - - protected List< SourceAndConverter< T > > sources; - protected final SetupAssignments setupAssignments; - - protected final BrightnessDialog brightnessDialog; - protected final WarpVisFrame warpVisDialog; protected final HelpDialog helpDialog; + private final KeymapManager keymapManager; + + private final AppearanceManager appearanceManager; + protected final SourceInfoDialog sourceInfoDialog; protected final VisibilityAndGroupingDialog activeSourcesDialogP; protected final VisibilityAndGroupingDialog activeSourcesDialogQ; + protected final PreferencesDialog preferencesDialog; + final AffineTransform3D fixedViewXfm; - private final Bookmarks bookmarks; + private Bookmarks bookmarks; protected final BookmarksEditor bookmarkEditorP; @@ -223,9 +255,9 @@ public class BigWarp< T > protected final BigWarpViewerPanel viewerQ; - protected final AffineTransform3D initialViewP; + protected AffineTransform3D initialViewP; - protected final AffineTransform3D initialViewQ; + protected AffineTransform3D initialViewQ; private JMenuItem toggleAlwaysWarpMenuP; @@ -235,7 +267,7 @@ public class BigWarp< T > protected final LandmarkPointMenu landmarkPopupMenu; - protected final BigWarpLandmarkFrame landmarkFrame; + protected BigWarpLandmarkFrame landmarkFrame; protected final BigWarpViewerSettings viewerSettings; @@ -263,17 +295,27 @@ public class BigWarp< T > protected MouseLandmarkTableListener landmarkTableListener; + protected MaskedSourceEditorMouseListener maskSourceMouseListenerP; + + protected MaskedSourceEditorMouseListener maskSourceMouseListenerQ; + protected BigWarpMessageAnimator message; protected final Set< KeyEventPostProcessor > keyEventPostProcessorSet = new HashSet< KeyEventPostProcessor >(); private final RepeatingReleasedEventsFixer repeatedKeyEventsFixer; - protected final SourceAndConverter< FloatType > gridSource; + protected GridSource gridSource; - protected final SourceAndConverter< FloatType > warpMagSource; + protected WarpMagnitudeSource warpMagSource; - protected final SourceAndConverter< FloatType > jacDetSource; + protected JacobianDeterminantSource jacDetSource; + + protected SourceAndConverter< ? extends RealType> transformMaskSource; + + protected Source< ? extends RealType> transformMask; + + protected PlateauSphericalMaskSource plateauTransformMask; protected final AbstractModel< ? >[] baseXfmList; @@ -283,10 +325,12 @@ public class BigWarp< T > private BigWarpTransform bwTransform; + protected SourceGroup mvgGrp, tgtGrp; + private BoundingBoxEstimation bboxOptions; private long keyClickMaxLength = 250; - + protected TransformTypeSelectDialog transformSelector; protected AffineTransform3D tmpTransform = new AffineTransform3D(); @@ -300,9 +344,11 @@ public class BigWarp< T > protected int baselineModelIndex; // file selection - final JFrame fileFrame; + protected JFrame fileFrame; - final FileDialog fileDialog; + protected FileDialog fileDialog; + + final JFileChooser fileChooser; protected File autoSaveDirectory; @@ -324,21 +370,42 @@ public class BigWarp< T > protected static Logger logger = LoggerFactory.getLogger( BigWarp.class ); + //TODO Caleb: John, can this be replaced by info from BigWarpData/SourceInfo url? private SpimData movingSpimData; private File movingImageXml; private CopyOnWriteArrayList< TransformListener< InvertibleRealTransform > > transformListeners = new CopyOnWriteArrayList<>( ); - final int ndims; + int ndims; - public BigWarp( final BigWarpData data, final String windowTitle, final ProgressWriter progressWriter ) throws SpimDataException + @Deprecated + public BigWarp( final BigWarpData< T > data, final String windowTitle, final ProgressWriter progressWriter ) throws SpimDataException { - this( data, windowTitle, BigWarpViewerOptions.options().is2D( detectNumDims( data.sources ) == 2 ), progressWriter ); + this( data, BigWarpViewerOptions.options().is2D( detectNumDims( data.sources ) == 2 ), progressWriter ); } - public BigWarp( final BigWarpData data, final String windowTitle, BigWarpViewerOptions options, final ProgressWriter progressWriter ) throws SpimDataException + @Deprecated + public BigWarp( final BigWarpData< T > data, final String windowTitle, BigWarpViewerOptions options, final ProgressWriter progressWriter ) throws SpimDataException { + this(data, options, progressWriter ); + } + + public BigWarp( final BigWarpData< T > data, final ProgressWriter progressWriter ) throws SpimDataException { + this( data, BigWarpViewerOptions.options().is2D( detectNumDims( data.sources ) == 2 ), progressWriter ); + } + + public BigWarp( final BigWarpData data, BigWarpViewerOptions options, final ProgressWriter progressWriter ) throws SpimDataException { + final KeymapManager optionsKeymapManager = options.getValues().getKeymapManager(); + final AppearanceManager optionsAppearanceManager = options.values.getAppearanceManager(); + keymapManager = optionsKeymapManager != null ? optionsKeymapManager : new KeymapManager( configDir ); + appearanceManager = optionsAppearanceManager != null ? optionsAppearanceManager : new AppearanceManager( configDir ); + + InputTriggerConfig inputTriggerConfig = options.values.getInputTriggerConfig(); + final Keymap keymap = this.keymapManager.getForwardSelectedKeymap(); + if ( inputTriggerConfig == null ) + inputTriggerConfig = keymap.getConfig(); + repeatedKeyEventsFixer = RepeatingReleasedEventsFixer.installAnyTime(); ij = IJ.getInstance(); @@ -361,58 +428,34 @@ public BigWarp( final BigWarpData data, final String windowTitle, BigWarpVie * Set up LandmarkTableModel, holds the data and interfaces with the * LandmarkPanel */ - landmarkModel = new LandmarkTableModel( ndims ); - landmarkModellistener = new LandmarkTableListener(); - landmarkModel.addTableModelListener( landmarkModellistener ); - addTransformListener( landmarkModel ); /* Set up landmark panel */ - landmarkPanel = new BigWarpLandmarkPanel( landmarkModel ); - landmarkPanel.setOpaque( true ); - landmarkTable = landmarkPanel.getJTable(); - landmarkTable.setDefaultRenderer( Object.class, new WarningTableCellRenderer() ); - addDefaultTableMouseListener(); - - landmarkFrame = new BigWarpLandmarkFrame( "Landmarks", landmarkPanel, this ); + setupLandmarkFrame(); baseXfmList = new AbstractModel< ? >[ 3 ]; setupWarpMagBaselineOptions( baseXfmList, ndims ); fixedViewXfm = new AffineTransform3D(); - //sources.get( targetSourceIndexList[ 0 ] ).getSpimSource().getSourceTransform( 0, 0, fixedViewXfm ); - data.sources.get( data.targetSourceIndices[ 0 ] ).getSpimSource().getSourceTransform( 0, 0, fixedViewXfm ); - - baselineModelIndex = 0; - warpMagSource = addWarpMagnitudeSource( data, ndims == 2, "WarpMagnitudeSource" ); - jacDetSource = addJacobianDeterminantSource( data, "JacobianDeterminantSource" ); - gridSource = addGridSource( data, "GridSource" ); - - this.sources = this.data.sources; - final List< ConverterSetup > converterSetups = data.converterSetups; - this.movingSourceIndexList = data.movingSourceIndices; - this.targetSourceIndexList = data.targetSourceIndices; - Arrays.sort( movingSourceIndexList ); - Arrays.sort( targetSourceIndexList ); - - sources = wrapSourcesAsTransformed( data.sources, ndims, data ); - - setGridType( GridSource.GRID_TYPE.LINE ); - viewerSettings = new BigWarpViewerSettings(); // key properties final InputTriggerConfig keyProperties = BigDataViewer.getInputTriggerConfig( options ); options = options.inputTriggerConfig( keyProperties ); + final int width = UIScale.scale( DEFAULT_WIDTH ); + final int height = UIScale.scale( DEFAULT_HEIGHT ); + + final List> srcs = (List)data.sources; + // Viewer frame for the moving image - viewerFrameP = new BigWarpViewerFrame( this, DEFAULT_WIDTH, DEFAULT_HEIGHT, (List)sources, converterSetups, viewerSettings, - data.cache, options, "Bigwarp moving image", true, movingSourceIndexList, targetSourceIndexList ); + viewerFrameP = new BigWarpViewerFrame(this, width, height, srcs, data.converterSetups, + viewerSettings, data.cache, keymapManager, appearanceManager, options, "BigWarp moving image", true); viewerP = getViewerFrameP().getViewerPanel(); // Viewer frame for the fixed image - viewerFrameQ = new BigWarpViewerFrame( this, DEFAULT_WIDTH, DEFAULT_HEIGHT, (List)sources, converterSetups, viewerSettings, - data.cache, options, "Bigwarp fixed image", false, movingSourceIndexList, targetSourceIndexList ); + viewerFrameQ = new BigWarpViewerFrame(this, width, height, srcs, data.converterSetups, + viewerSettings, data.cache, keymapManager, appearanceManager, options, "BigWarp fixed image", false); viewerQ = getViewerFrameQ().getViewerPanel(); @@ -434,7 +477,7 @@ public BigWarp( final BigWarpData data, final String windowTitle, BigWarpVie final MultiBoxOverlayRenderer overlayRenderP = new MultiBoxOverlayRenderer( DEFAULT_WIDTH, DEFAULT_HEIGHT ); final MultiBoxOverlayRenderer overlayRenderQ = new MultiBoxOverlayRenderer( DEFAULT_WIDTH, DEFAULT_HEIGHT ); - + // TODO hopefully I won't' need reflection any more final Field boxField = overlayRenderP.getClass().getDeclaredField( "box" ); boxField.setAccessible( true ); @@ -476,22 +519,6 @@ public BigWarp( final BigWarpData data, final String windowTitle, BigWarpVie activeSourcesDialogQ = new VisibilityAndGroupingDialog( viewerFrameQ, viewerQ.getVisibilityAndGrouping() ); activeSourcesDialogQ.setTitle( "visibility and grouping ( fixed )" ); - final ARGBType white = new ARGBType( ARGBType.rgba( 255, 255, 255, 255 )); - // set warp mag source to inactive at the start - viewerP.state().setSourceActive( warpMagSource, false ); - viewerQ.state().setSourceActive( warpMagSource, false ); - data.sourceColorSettings.put( warpMagSource, new ImagePlusLoader.ColorSettings( -1, 0, 15, white )); - - // set warp grid source to inactive at the start - viewerP.state().setSourceActive( gridSource, false ); - viewerQ.state().setSourceActive( gridSource, false ); - data.sourceColorSettings.put( gridSource, new ImagePlusLoader.ColorSettings( -1, 0, 255, white )); - - // set jacobian determinant source to inactive at the start - viewerP.state().setSourceActive( jacDetSource, false ); - viewerQ.state().setSourceActive( jacDetSource, false ); - data.sourceColorSettings.put( jacDetSource, new ImagePlusLoader.ColorSettings( -1, 0.0, 1.0, white )); - overlayP = new BigWarpOverlay( viewerP, landmarkPanel ); overlayQ = new BigWarpOverlay( viewerQ, landmarkPanel ); viewerP.addOverlay( overlayP ); @@ -504,7 +531,6 @@ public BigWarp( final BigWarpData data, final String windowTitle, BigWarpVie solverThread.start(); bboxOptions = new BoundingBoxEstimation( BoundingBoxEstimation.Method.FACES, 5 ); - updateSourceBoundingBoxEstimators(); dragOverlayP = new BigWarpDragOverlay( this, viewerP, solverThread ); dragOverlayQ = new BigWarpDragOverlay( this, viewerQ, solverThread ); @@ -514,24 +540,76 @@ public BigWarp( final BigWarpData data, final String windowTitle, BigWarpVie landmarkPopupMenu = new LandmarkPointMenu( this ); landmarkPopupMenu.setupListeners(); - setupAssignments = new SetupAssignments( new ArrayList<>( converterSetups ), 0, 65535 ); + setupAssignments = new SetupAssignments( new ArrayList<>( data.converterSetups ), 0, 65535 ); - brightnessDialog = new BrightnessDialog( landmarkFrame, setupAssignments ); helpDialog = new HelpDialog( landmarkFrame ); sourceInfoDialog = new SourceInfoDialog( landmarkFrame, data ); transformSelector = new TransformTypeSelectDialog( landmarkFrame, this ); // dialogs have to be constructed before action maps are made - warpVisDialog = new WarpVisFrame( viewerFrameQ, this ); + warpVisDialog = new WarpVisFrame( viewerFrameQ, this ); +// warpVisDialog.maskOptionsPanel.setMask( transformMask ); - WarpNavigationActions.installActionBindings( getViewerFrameP().getKeybindings(), viewerFrameP, keyProperties, ( ndims == 2 ) ); - BigWarpActions.installActionBindings( getViewerFrameP().getKeybindings(), this, keyProperties ); + preferencesDialog = new PreferencesDialog( landmarkFrame, keymap, new String[] { "bigwarp", "navigation", "bw-table" } ); + preferencesDialog.addPage( new AppearanceSettingsPage( "Appearance", appearanceManager ) ); + preferencesDialog.addPage( new KeymapSettingsPage( "Keymap", this.keymapManager, new KeymapManager(), this.keymapManager.getCommandDescriptions() ) ); - WarpNavigationActions.installActionBindings( getViewerFrameQ().getKeybindings(), viewerFrameQ, keyProperties, ( ndims == 2 ) ); - BigWarpActions.installActionBindings( getViewerFrameQ().getKeybindings(), this, keyProperties ); + fileChooser = new JFileChooser(); + fileChooser.setFileFilter( new FileFilter() + { + @Override + public String getDescription() + { + return "xml files"; + } - BigWarpActions.installLandmarkPanelActionBindings( landmarkFrame.getKeybindings(), this, landmarkTable, keyProperties ); + @Override + public boolean accept( final File f ) + { + if ( f.isDirectory() ) + return true; + if ( f.isFile() ) + { + final String s = f.getName(); + final int i = s.lastIndexOf( '.' ); + if ( i > 0 && i < s.length() - 1 ) + { + final String ext = s.substring( i + 1 ).toLowerCase(); + return ext.equals( "xml" ); + } + } + return false; + } + } ); + + appearanceManager.appearance().updateListeners().add( viewerFrameP::repaint ); + appearanceManager.appearance().updateListeners().add( viewerFrameQ::repaint ); + appearanceManager.appearance().updateListeners().add( landmarkFrame::repaint ); + appearanceManager.addLafComponent( fileChooser ); + SwingUtilities.invokeLater(() -> appearanceManager.updateLookAndFeel()); + + final Actions navigationActions = new Actions( inputTriggerConfig, "navigation" ); + navigationActions.install( getViewerFrameP().getKeybindings(), "navigation" ); + NavigationKeys.install( navigationActions, getViewerFrameP().getViewerPanel(), options.values.is2D() ); + navigationActions.install( getViewerFrameQ().getKeybindings(), "navigation" ); + NavigationKeys.install( navigationActions, getViewerFrameQ().getViewerPanel(), options.values.is2D() ); + + final BigWarpActions bwActions = new BigWarpActions( inputTriggerConfig, "bigwarp" ); + BigWarpActions.installViewerActions( bwActions, getViewerFrameP(), this ); + BigWarpActions.installViewerActions( bwActions, getViewerFrameQ(), this ); + final BigWarpActions tableActions = new BigWarpActions( inputTriggerConfig, "bw-table" ); + BigWarpActions.installTableActions( tableActions, getLandmarkFrame().getKeybindings(), this ); +// UnmappedNavigationActions.install( tableActions, options.values.is2D() ); + + keymap.updateListeners().add( () -> { + + bwActions.updateKeyConfig( keymap.getConfig() ); + tableActions.updateKeyConfig( keymap.getConfig() ); + + viewerFrameP.getTransformBehaviours().updateKeyConfig( keymap.getConfig() ); + viewerFrameQ.getTransformBehaviours().updateKeyConfig( keymap.getConfig() ); + } ); // this call has to come after the actions are set warpVisDialog.setActions(); @@ -542,11 +620,11 @@ public BigWarp( final BigWarpData data, final String windowTitle, BigWarpVie setUpLandmarkMenus(); /* Set the locations of frames */ - final Point viewerFramePloc = getViewerFrameP().getLocation(); - viewerFramePloc.setLocation( viewerFramePloc.x + DEFAULT_WIDTH, viewerFramePloc.y ); - getViewerFrameQ().setLocation( viewerFramePloc ); - viewerFramePloc.setLocation( viewerFramePloc.x + DEFAULT_WIDTH, viewerFramePloc.y ); - landmarkFrame.setLocation( viewerFramePloc ); + viewerFrameP.setLocation( 0, 0 ); + viewerFrameP.setSize( width, height ); + viewerFrameQ.setLocation( width, 0 ); + viewerFrameQ.setSize( width, height ); + landmarkFrame.setLocation( 2 * width, 0 ); landmarkClickListenerP = new MouseLandmarkListener( this.viewerP ); landmarkClickListenerQ = new MouseLandmarkListener( this.viewerQ ); @@ -562,40 +640,13 @@ public BigWarp( final BigWarpData data, final String windowTitle, BigWarpVie viewerP.state().getViewerTransform( initialViewP ); viewerQ.state().getViewerTransform( initialViewQ ); - viewerFrameP.setVisible( true ); - viewerFrameQ.setVisible( true ); - - landmarkFrame.pack(); - landmarkFrame.setVisible( true ); - - SwingUtilities.invokeLater( new Runnable() - { - public void run() - { - data.transferChannelSettings( viewerFrameP ); - data.transferChannelSettings( viewerFrameQ ); - - } - } ); - - // set initial transforms so data are visible - InitializeViewerState.initTransform( viewerP ); - InitializeViewerState.initTransform( viewerQ ); - checkBoxInputMaps(); - // file selection - fileFrame = new JFrame( "Select File" ); - fileDialog = new FileDialog( fileFrame ); - if ( ij == null || (IJ.getDirectory( "current" ) == null) ) lastDirectory = new File( System.getProperty( "user.home" )); else lastDirectory = new File( IJ.getDirectory( "current" )); - // default to linear interpolation - fileFrame.setVisible( false ); - // add focus listener //new BigwarpFocusListener( this ); @@ -605,6 +656,324 @@ public void run() // add landmark mode listener //addKeyEventPostProcessor( new LandmarkModeListener() ); + + baselineModelIndex = 0; + + if( data.sources.size() > 0 ) + initialize(); + + createMovingTargetGroups(); + viewerP.state().setCurrentGroup( mvgGrp ); + viewerP.state().setCurrentGroup( tgtGrp ); + + SwingUtilities.invokeLater( () -> { + viewerFrameP.setVisible( true ); + viewerFrameQ.setVisible( true ); + landmarkFrame.setVisible( true ); + + fileFrame = new JFrame( "Select File" ); + fileDialog = new FileDialog( fileFrame ); + fileFrame.setVisible( false ); + }); + } + + public void changeDimensionality(boolean is2D) { + + if (options.values.is2D() == is2D) + return; + + options.is2D( is2D ); + if( options.values.is2D() ) + ndims = 2; + else + ndims = 3; + + /* update landmark model with new dimensionality */ + landmarkModel = new LandmarkTableModel( ndims ); + landmarkModel.addTableModelListener( landmarkModellistener ); + addTransformListener( landmarkModel ); + landmarkModel.setMessage( message ); + + landmarkPanel.setTableModel(landmarkModel); + setupLandmarkFrame(); + + setupWarpMagBaselineOptions( baseXfmList, ndims ); + + final Class< ViewerPanel > c_vp = ViewerPanel.class; + try + { + final Field transformEventHandlerField = c_vp.getDeclaredField( "transformEventHandler" ); + transformEventHandlerField.setAccessible( true ); + transformEventHandlerField.set( viewerP, options.values.getTransformEventHandlerFactory().create( TransformState.from( viewerP.state()::getViewerTransform, viewerP.state()::setViewerTransform ) ) ); + transformEventHandlerField.set( viewerQ, options.values.getTransformEventHandlerFactory().create( TransformState.from( viewerQ.state()::getViewerTransform, viewerQ.state()::setViewerTransform ) ) ); + transformEventHandlerField.setAccessible( false ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + + viewerFrameP.updateTransformBehaviors( options ); + viewerFrameQ.updateTransformBehaviors( options ); + + // If the images are 2d, use a transform handler that limits + // transformations to rotations and scalings of the 2d plane ( z = 0 ) + if ( options.values.is2D() ) + { + + // final Class< ViewerPanel > c_vp = ViewerPanel.class; + try + { + final Field overlayRendererField = c_vp.getDeclaredField( "multiBoxOverlayRenderer" ); + overlayRendererField.setAccessible( true ); + + final MultiBoxOverlayRenderer overlayRenderP = new MultiBoxOverlayRenderer( DEFAULT_WIDTH, DEFAULT_HEIGHT ); + final MultiBoxOverlayRenderer overlayRenderQ = new MultiBoxOverlayRenderer( DEFAULT_WIDTH, DEFAULT_HEIGHT ); + + // TODO hopefully I won't' need reflection any more + final Field boxField = overlayRenderP.getClass().getDeclaredField( "box" ); + boxField.setAccessible( true ); + boxField.set( overlayRenderP, new MultiBoxOverlay2d() ); + boxField.set( overlayRenderQ, new MultiBoxOverlay2d() ); + boxField.setAccessible( false ); + + overlayRendererField.set( viewerP, overlayRenderP ); + overlayRendererField.set( viewerQ, overlayRenderQ ); + overlayRendererField.setAccessible( false ); + + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + } + + viewerP.setNumDim( ndims ); + viewerQ.setNumDim( ndims ); + +// overlayP.is2D( options.values.is2D() ); +// overlayQ.is2D( options.values.is2D() ); + + bwTransform = new BigWarpTransform( landmarkModel ); + bwTransform.initializeInverseParameters(data); + + transformSelector = new TransformTypeSelectDialog( landmarkFrame, this ); + + final InputTriggerConfig keyProperties = BigDataViewer.getInputTriggerConfig( options ); + WarpNavigationActions.installActionBindings( getViewerFrameP().getKeybindings(), viewerFrameP, keyProperties, ( ndims == 2 ) ); + BigWarpActions.installActionBindings( getViewerFrameP().getKeybindings(), this, keyProperties ); + + WarpNavigationActions.installActionBindings( getViewerFrameQ().getKeybindings(), viewerFrameQ, keyProperties, ( ndims == 2 ) ); + BigWarpActions.installActionBindings( getViewerFrameQ().getKeybindings(), this, keyProperties ); + + BigWarpActions.installLandmarkPanelActionBindings( landmarkFrame.getKeybindings(), this, landmarkTable, keyProperties ); + + warpVisDialog.toleranceSpinner.setValue( bwTransform.getInverseTolerance() ); + + SwingUtilities.invokeLater( () -> { + landmarkFrame.setVisible( true ); + }); + } + + public void initialize() + { + wrapMovingSources( ndims, data ); + + // starting view + if( data.numTargetSources() > 0 ) + data.getTargetSource( 0 ).getSpimSource().getSourceTransform( 0, 0, fixedViewXfm ); + +//TODO Expose adding these sources via UI + +// final ARGBType white = new ARGBType( ARGBType.rgba( 255, 255, 255, 255 )); +// +// warpMagSource = addWarpMagnitudeSource( data, ndims == 2, "Warp magnitude" ); +// jacDetSource = addJacobianDeterminantSource( data, "Jacobian determinant" ); +// gridSource = addGridSource( data, "GridSource" ); +// setGridType( GridSource.GRID_TYPE.LINE ); +// +// transformMaskSource = addTransformMaskSource( data, ndims, "Transform mask" ); +// bwTransform.setLambda( transformMask.getRandomAccessible() ); +// addMaskMouseListener(); +// +// // set warp mag source to inactive at the start +// viewerP.state().setSourceActive( warpMagSource, false ); +// viewerQ.state().setSourceActive( warpMagSource, false ); +// data.sourceColorSettings.put( warpMagSource, new ImagePlusLoader.ColorSettings( -1, 0, 15, white )); +// +// // set warp grid source to inactive at the start +// viewerP.state().setSourceActive( gridSource, false ); +// viewerQ.state().setSourceActive( gridSource, false ); +// data.sourceColorSettings.put( gridSource, new ImagePlusLoader.ColorSettings( -1, 0, 255, white )); + + // set jacobian determinant source to inactive at the start +// viewerP.state().setSourceActive( jacDetSource, false ); +// viewerQ.state().setSourceActive( jacDetSource, false ); +// data.sourceColorSettings.put( jacDetSource, new ImagePlusLoader.ColorSettings( -1, 0.0, 1.0, white )); + + synchronizeSources(); + + data.transferChannelSettings( viewerFrameP ); + data.transferChannelSettings( viewerFrameQ ); + + updateSourceBoundingBoxEstimators(); + + createMovingTargetGroups(); + viewerP.state().setCurrentGroup( mvgGrp ); + viewerP.state().setCurrentGroup( tgtGrp ); + + // set initial transforms so data are visible +// SwingUtilities.invokeLater( () -> { + + // show moving sources in the moving viewer + if( data.numMovingSources() == 1 ) + viewerP.state().setCurrentSource( data.getMovingSource( 0 ) ); + else + { + viewerP.state().setDisplayMode( DisplayMode.GROUP ); + viewerP.state().setCurrentGroup( mvgGrp ); + } + + // show fixed sources in the fixed viewer + if( data.numTargetSources() == 1 ) + viewerQ.state().setCurrentSource( data.getTargetSource( 0 ) ); + else + { + viewerQ.state().setDisplayMode( DisplayMode.GROUP ); + viewerQ.state().setCurrentGroup( tgtGrp ); + } + + InitializeViewerState.initTransform( viewerP ); + InitializeViewerState.initTransform( viewerQ ); + + // save the initial viewer transforms + initialViewP = new AffineTransform3D(); + initialViewQ = new AffineTransform3D(); + viewerP.state().getViewerTransform( initialViewP ); + viewerQ.state().getViewerTransform( initialViewQ ); +// } ); + } + + protected void setupLandmarkFrame() + { + Point loc = null; + Dimension sz = null; + if ( landmarkFrame != null ) + { + loc = landmarkFrame.getLocation(); + sz = landmarkFrame.getSize(); + + landmarkModel = null; + landmarkFrame.setVisible( false ); + landmarkFrame.dispose(); + landmarkFrame = null; + landmarkPanel = null; + + } + + landmarkModel = new LandmarkTableModel( ndims ); + landmarkModellistener = new LandmarkTableListener(); + landmarkModel.addTableModelListener( landmarkModellistener ); + addTransformListener( landmarkModel ); + + /* Set up landmark panel */ + landmarkPanel = new BigWarpLandmarkPanel( landmarkModel ); + landmarkPanel.setOpaque( true ); + landmarkTable = landmarkPanel.getJTable(); + landmarkTable.setDefaultRenderer( Object.class, new WarningTableCellRenderer() ); + addDefaultTableMouseListener(); + landmarkFrame = new BigWarpLandmarkFrame( "Landmarks", landmarkPanel, this, keymapManager ); + + if( overlayP != null ) + overlayP.setLandmarkPanel(landmarkPanel); + + if( overlayQ != null ) + overlayQ.setLandmarkPanel(landmarkPanel); + + if ( loc != null ) + landmarkFrame.setLocation( loc ); + + if ( sz != null ) + landmarkFrame.setSize( sz ); + + SwingUtilities.invokeLater( () -> { + setUpLandmarkMenus(); + landmarkFrame.pack(); + }); + } + + public void synchronizeSources() + { + final SynchronizedViewerState pState = viewerP.state(); + final SynchronizedViewerState qState = viewerQ.state(); + + final Set> activeSourcesP = new HashSet<>(pState.getActiveSources()); + final Set> activeSourcesQ = new HashSet<>(qState.getActiveSources()); + + pState.clearSources(); + qState.clearSources(); + + final ArrayList converterSetupsToRemove = new ArrayList<>(setupAssignments.getConverterSetups()); + converterSetupsToRemove.forEach( setupAssignments::removeSetup ); + + for ( int i = 0; i < data.sources.size(); i++ ) + { + final SourceAndConverter< T > sac = data.sources.get( i ); + pState.addSource( sac ); + if (activeSourcesP.contains(sac)) { + pState.setSourceActive(sac, true); + } + qState.addSource( sac ); + if (activeSourcesQ.contains(sac)) { + qState.setSourceActive(sac, true); + } + + // update the viewer converter setups too + final ConverterSetup setup = data.converterSetups.get( i ); + + viewerFrameP.getConverterSetups().put( sac, setup ); + viewerFrameQ.getConverterSetups().put( sac, setup ); + setupAssignments.addSetup( setup ); + } + } + + /** + * Create two source groups - one for moving images, + * and the other for target images, for both viewer frames. + * + * Ensure sources are synchronized with {@link #synchronizeSources()} + * before calling this method. + */ + public void createMovingTargetGroups() + { + mvgGrp = new SourceGroup(); + tgtGrp = new SourceGroup(); + + final SynchronizedViewerState pState = viewerP.state(); + pState.addGroup( mvgGrp ); + pState.addGroup( tgtGrp ); + pState.setGroupName( mvgGrp, "moving images" ); + pState.setGroupName( tgtGrp, "target images" ); + + final SynchronizedViewerState qState = viewerQ.state(); + qState.addGroup( mvgGrp ); + qState.addGroup( tgtGrp ); + qState.setGroupName( mvgGrp, "moving images" ); + qState.setGroupName( tgtGrp, "target images" ); + + for ( final SourceAndConverter< ? > sac : data.sources ) + { + if ( data.isMoving( sac ) ) + { + viewerP.state().addSourceToGroup( sac, mvgGrp ); + viewerQ.state().addSourceToGroup( sac, mvgGrp ); + } + else + { + viewerP.state().addSourceToGroup( sac, tgtGrp ); + viewerQ.state().addSourceToGroup( sac, tgtGrp ); + } + } } public int numDimensions() @@ -615,7 +984,7 @@ public int numDimensions() /** * TODO Make a PR that updates this method in InitializeViewerState in bdv-core * @deprecated Use {@link InitializeViewerState} method instead. - * + * * @param cumulativeMinCutoff the min image intensity * @param cumulativeMaxCutoff the max image intensity * @param state the viewer state @@ -624,14 +993,48 @@ public int numDimensions() @Deprecated public static void initBrightness( final double cumulativeMinCutoff, final double cumulativeMaxCutoff, final ViewerState state, final ConverterSetups converterSetups ) { - final SourceAndConverter< ? > current = state.getCurrentSource(); - if ( current == null ) - return; - final Source< ? > source = current.getSpimSource(); - final int timepoint = state.getCurrentTimepoint(); - final Bounds bounds = InitializeViewerState.estimateSourceRange( source, timepoint, cumulativeMinCutoff, cumulativeMaxCutoff ); - final ConverterSetup setup = converterSetups.getConverterSetup( current ); - setup.setDisplayRange( bounds.getMinBound(), bounds.getMaxBound() ); + final SourceAndConverter< ? > current = state.getCurrentSource(); + if ( current == null ) + return; + final Source< ? > source = current.getSpimSource(); + final int timepoint = state.getCurrentTimepoint(); + final Bounds bounds = InitializeViewerState.estimateSourceRange( source, timepoint, cumulativeMinCutoff, cumulativeMaxCutoff ); + final ConverterSetup setup = converterSetups.getConverterSetup( current ); + setup.setDisplayRange( bounds.getMinBound(), bounds.getMaxBound() ); + } + + /** + * @param sources to add; typically the output of a {#{@link BigWarpInit#createSources(BigWarpData, String, int, boolean)}} call + */ + public void addSources( LinkedHashMap, SourceInfo> sources) + { + + BigWarpInit.add( data, sources); + synchronizeSources(); + } + + public void addSource( Source< T > source, boolean moving ) + { + data.addSource( source, moving ); + synchronizeSources(); + } + + public void addSource( Source source ) + { + addSource( source, false ); + } + + public int removeSource( SourceInfo info ) + { + final int removedIdx = data.remove( info ); + synchronizeSources(); + return removedIdx; + } + + public void removeSource( int i ) + { + data.remove( i ); + synchronizeSources(); } public void addKeyEventPostProcessor( final KeyEventPostProcessor ke ) @@ -722,14 +1125,21 @@ public void setSpotColor( final Color c ) protected void setUpViewerMenu( final BigWarpViewerFrame vframe ) { // TODO setupviewermenu - final ActionMap actionMap = vframe.getKeybindings().getConcatenatedActionMap(); - final JMenuBar viewerMenuBar = new JMenuBar(); - JMenu fileMenu = new JMenu( "File" ); + final JMenu fileMenu = new JMenu( "File" ); viewerMenuBar.add( fileMenu ); + final JMenuItem loadProject = new JMenuItem( actionMap.get( BigWarpActions.LOAD_PROJECT ) ); + loadProject.setText( "Load project" ); + fileMenu.add( loadProject ); + + final JMenuItem saveProject = new JMenuItem( actionMap.get( BigWarpActions.SAVE_PROJECT ) ); + saveProject.setText( "Save project" ); + fileMenu.add( saveProject ); + + fileMenu.addSeparator(); final JMenuItem openItem = new JMenuItem( actionMap.get( BigWarpActions.LOAD_LANDMARKS ) ); openItem.setText( "Import landmarks" ); fileMenu.add( openItem ); @@ -739,23 +1149,32 @@ protected void setUpViewerMenu( final BigWarpViewerFrame vframe ) fileMenu.add( saveItem ); fileMenu.addSeparator(); - final JMenuItem miLoadSettings = new JMenuItem( actionMap.get( BigWarpActions.LOAD_SETTINGS ) ); + final JMenuItem openMask = new JMenuItem( actionMap.get( BigWarpActions.MASK_IMPORT )); + openMask.setText( "Import mask" ); + fileMenu.add( openMask ); + + final JMenuItem removeMask = new JMenuItem( actionMap.get( BigWarpActions.MASK_REMOVE )); + removeMask.setText( "Remove mask" ); + fileMenu.add( removeMask ); + + fileMenu.addSeparator(); + final JMenuItem miLoadSettings = new JMenuItem( actionMap.get( BigWarpActions.LOAD_SETTINGS )); miLoadSettings.setText( "Load settings" ); fileMenu.add( miLoadSettings ); - final JMenuItem miSaveSettings = new JMenuItem( actionMap.get( BigWarpActions.SAVE_SETTINGS ) ); + final JMenuItem miSaveSettings = new JMenuItem( actionMap.get( BigWarpActions.SAVE_SETTINGS )); miSaveSettings.setText( "Save settings" ); fileMenu.add( miSaveSettings ); if( ij != null ) { fileMenu.addSeparator(); - final JMenuItem exportToImagePlus = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_IP ) ); + final JMenuItem exportToImagePlus = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_IP )); exportToImagePlus.setText( "Export moving image" ); fileMenu.add( exportToImagePlus ); - - final JMenuItem exportWarpField = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_WARP ) ); - exportWarpField.setText( "Export warp field" ); + + final JMenuItem exportWarpField = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_WARP )); + exportWarpField.setText( "Export transformation" ); fileMenu.add( exportWarpField ); } @@ -777,9 +1196,10 @@ protected void setUpViewerMenu( final BigWarpViewerFrame vframe ) toggleAlwaysWarpMenu.setText( "Toggle warp on drag" ); settingsMenu.add( toggleAlwaysWarpMenu ); - final JMenuItem miBrightness = new JMenuItem( actionMap.get( BigWarpActions.BRIGHTNESS_SETTINGS ) ); - miBrightness.setText( "Brightness & Color" ); - settingsMenu.add( miBrightness ); + /* */ + final JMenuItem transformTypeMenu = new JMenuItem( actionMap.get( BigWarpActions.TRANSFORM_TYPE ) ); + transformTypeMenu.setText( "Transformation Options" ); + settingsMenu.add( transformTypeMenu ); /* Warp Visualization */ final JMenuItem warpVisMenu = new JMenuItem( actionMap.get( BigWarpActions.SHOW_WARPTYPE_DIALOG ) ); @@ -807,7 +1227,7 @@ protected void setupImageJExportOption() final JMenuItem exportToImagePlus = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_IP ) ); exportToImagePlus.setText( "Export moving image" ); fileMenu.add( exportToImagePlus ); - + final JMenuItem exportWarpField = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_WARP ) ); exportWarpField.setText( "Export warp field" ); fileMenu.add( exportWarpField ); @@ -822,7 +1242,7 @@ public void exportAsImagePlus( boolean virtual ) public void saveMovingImageToFile() { final JFileChooser fileChooser = new JFileChooser( getLastDirectory() ); - File proposedFile = new File( sources.get( movingSourceIndexList[ 0 ] ).getSpimSource().getName() ); + File proposedFile = new File( data.getMovingSource( 0 ).getSpimSource().getName() ); fileChooser.setSelectedFile( proposedFile ); final int returnVal = fileChooser.showSaveDialog( null ); @@ -857,7 +1277,7 @@ public File saveMovingImageXml( String proposedFilePath ) System.out.println( "bigWarp transform as affine 3d: " + bigWarpTransform.toString() ); - movingSpimData.getViewRegistrations().getViewRegistration( 0, 0 ).preconcatenateTransform( + movingSpimData.getViewRegistrations().getViewRegistration( 0, 0 ).preconcatenateTransform( new ViewTransformAffine( "Big Warp: " + bwTransform.getTransformType(), bigWarpTransform ) ); File proposedFile; @@ -881,7 +1301,7 @@ public File saveMovingImageXml( String proposedFilePath ) try { new XmlIoSpimData().save( movingSpimData, proposedFile.getAbsolutePath() ); - } catch ( SpimDataException e ) + } catch ( final SpimDataException e ) { e.printStackTrace(); } @@ -891,8 +1311,8 @@ public File saveMovingImageXml( String proposedFilePath ) public AffineTransform3D getMovingToFixedTransformAsAffineTransform3D() { - double[][] affine3DMatrix = new double[ 3 ][ 4 ]; - double[][] affine2DMatrix = new double[ 2 ][ 3 ]; + final double[][] affine3DMatrix = new double[ 3 ][ 4 ]; + final double[][] affine2DMatrix = new double[ 2 ][ 3 ]; if ( currentTransform == null ) { @@ -968,15 +1388,15 @@ public void exportAsImagePlus( boolean virtual, String path ) final GenericDialogPlus gd = new GenericDialogPlus( "Apply Big Warp transform" ); gd.addMessage( "Field of view and resolution:" ); - gd.addChoice( "Resolution", + gd.addChoice( "Resolution", new String[]{ ApplyBigwarpPlugin.TARGET, ApplyBigwarpPlugin.MOVING, ApplyBigwarpPlugin.SPECIFIED }, ApplyBigwarpPlugin.TARGET ); - gd.addChoice( "Field of view", - new String[]{ ApplyBigwarpPlugin.TARGET, + gd.addChoice( "Field of view", + new String[]{ ApplyBigwarpPlugin.TARGET, ApplyBigwarpPlugin.MOVING_WARPED, ApplyBigwarpPlugin.UNION_TARGET_MOVING, - ApplyBigwarpPlugin.LANDMARK_POINTS, + ApplyBigwarpPlugin.LANDMARK_POINTS, ApplyBigwarpPlugin.LANDMARK_POINT_CUBE_PIXEL, ApplyBigwarpPlugin.LANDMARK_POINT_CUBE_PHYSICAL, ApplyBigwarpPlugin.SPECIFIED_PIXEL, @@ -985,29 +1405,29 @@ public void exportAsImagePlus( boolean virtual, String path ) ApplyBigwarpPlugin.TARGET ); gd.addStringField( "point filter", "" ); - + gd.addMessage( "Resolution"); gd.addNumericField( "x", 1.0, 4 ); gd.addNumericField( "y", 1.0, 4 ); gd.addNumericField( "z", 1.0, 4 ); - + gd.addMessage( "Offset"); gd.addNumericField( "x", 0.0, 4 ); gd.addNumericField( "y", 0.0, 4 ); gd.addNumericField( "z", 0.0, 4 ); - + gd.addMessage( "Field of view"); gd.addNumericField( "x", -1, 0 ); gd.addNumericField( "y", -1, 0 ); gd.addNumericField( "z", -1, 0 ); - + gd.addMessage( "Other Output options"); gd.addChoice( "Interpolation", new String[]{ "Nearest Neighbor", "Linear" }, "Linear" ); - + gd.addMessage( "Virtual: fast to display,\n" + "low memory requirements,\nbut slow to navigate" ); gd.addCheckbox( "virtual?", false ); - int defaultCores = (int)Math.ceil( Runtime.getRuntime().availableProcessors()/4); + final int defaultCores = (int)Math.ceil( Runtime.getRuntime().availableProcessors()/4); gd.addNumericField( "threads", defaultCores, 0 ); gd.addMessage( "Writing options (leave empty to opena new image window)" ); @@ -1026,21 +1446,21 @@ public void exportAsImagePlus( boolean virtual, String path ) if ( gd.wasCanceled() ) return; - + final String resolutionOption = gd.getNextChoice(); final String fieldOfViewOption = gd.getNextChoice(); final String fieldOfViewPointFilter = gd.getNextString(); - + final double[] resolutionSpec = new double[ 3 ]; resolutionSpec[ 0 ] = gd.getNextNumber(); resolutionSpec[ 1 ] = gd.getNextNumber(); resolutionSpec[ 2 ] = gd.getNextNumber(); - + final double[] offsetSpec = new double[ 3 ]; offsetSpec[ 0 ] = gd.getNextNumber(); offsetSpec[ 1 ] = gd.getNextNumber(); offsetSpec[ 2 ] = gd.getNextNumber(); - + final double[] fovSpec = new double[ 3 ]; fovSpec[ 0 ] = gd.getNextNumber(); fovSpec[ 1 ] = gd.getNextNumber(); @@ -1069,7 +1489,7 @@ public void exportAsImagePlus( boolean virtual, String path ) final double[] res = ApplyBigwarpPlugin.getResolution( this.data, resolutionOption, resolutionSpec ); - final List outputIntervalList = ApplyBigwarpPlugin.getPixelInterval( this.data, + final List outputIntervalList = ApplyBigwarpPlugin.getPixelInterval( this.data, this.landmarkModel, this.currentTransform, fieldOfViewOption, fieldOfViewPointFilter, bboxOptions, fovSpec, offsetSpec, res ); @@ -1082,8 +1502,8 @@ public void exportAsImagePlus( boolean virtual, String path ) // landmark centers (because multiple images can be exported this way ) if( matchedPtNames.size() > 0 ) { - BigwarpLandmarkSelectionPanel selection = new BigwarpLandmarkSelectionPanel<>( - data, sources, fieldOfViewOption, + final BigwarpLandmarkSelectionPanel selection = new BigwarpLandmarkSelectionPanel<>( + data, data.sources, fieldOfViewOption, outputIntervalList, matchedPtNames, interp, offsetSpec, res, isVirtual, nThreads, progressWriter ); @@ -1096,26 +1516,27 @@ public void exportAsImagePlus( boolean virtual, String path ) // export async new Thread() { + @Override public void run() { progressWriter.setProgress( 0.01 ); - ApplyBigwarpPlugin.runN5Export( data, sources, fieldOfViewOption, + ApplyBigwarpPlugin.runN5Export( data, data.sources, fieldOfViewOption, outputIntervalList.get( 0 ), interp, - offsetSpec, res, unit, - progressWriter, writeOpts, + offsetSpec, res, unit, + progressWriter, writeOpts, Executors.newFixedThreadPool( nThreads ) ); progressWriter.setProgress( 1.00 ); } }.start(); } - else + else { // export final boolean show = ( writeOpts.pathOrN5Root == null || writeOpts.pathOrN5Root.isEmpty() ); - ApplyBigwarpPlugin.runExport( data, sources, fieldOfViewOption, + ApplyBigwarpPlugin.runExport( data, data.sources, fieldOfViewOption, outputIntervalList, matchedPtNames, interp, - offsetSpec, res, isVirtual, nThreads, + offsetSpec, res, isVirtual, nThreads, progressWriter, show, false, writeOpts ); } } @@ -1123,8 +1544,7 @@ public void run() public void exportWarpField() { - BigWarpToDeformationFieldPlugIn dfieldExporter = new BigWarpToDeformationFieldPlugIn(); - dfieldExporter.runFromBigWarpInstance( landmarkModel, sources, targetSourceIndexList ); + ExportDisplacementFieldFrame.createAndShow( this ); } protected void setUpLandmarkMenus() @@ -1201,7 +1621,7 @@ public BigWarpData getData() public List< SourceAndConverter< T > > getSources() { - return sources; + return data.sources; } public BigWarpLandmarkFrame getLandmarkFrame() @@ -1308,15 +1728,12 @@ public void updateRowSelection( boolean isMoving, int lastRowEdited ) } public static void updateRowSelection( - LandmarkTableModel landmarkModel, JTable table, + LandmarkTableModel landmarkModel, JTable table, boolean isMoving, int lastRowEdited ) { - logger.trace( "updateRowSelection " ); - - int i = landmarkModel.getNextRow( isMoving ); + final int i = landmarkModel.getNextRow( isMoving ); if ( i < table.getRowCount() ) { - logger.trace( " landmarkTable ( updateRowSelection ) selecting row " + i ); table.setRowSelectionInterval( i, i ); } else if( lastRowEdited >= 0 && lastRowEdited < table.getRowCount() ) table.setRowSelectionInterval( lastRowEdited, lastRowEdited ); @@ -1324,13 +1741,13 @@ public static void updateRowSelection( /** * Returns the index of the selected row, if it is unpaired, -1 otherwise - * + * * @param isMoving isMoving * @return index of the selected row */ public int getSelectedUnpairedRow( boolean isMoving ) { - int row = landmarkTable.getSelectedRow(); + final int row = landmarkTable.getSelectedRow(); if( row >= 0 && ( isMoving ? !landmarkModel.isMovingPoint( row ) : !landmarkModel.isFixedPoint( row ))) return row; @@ -1369,14 +1786,14 @@ public boolean addPoint( final double[] ptarray, final boolean isMoving ) { final boolean isWarped = ( isMoving && landmarkModel.getTransform() != null && BigWarp.this.isMovingDisplayTransformed() ); - InvertibleRealTransform transform; + InvertibleRealTransform transform; if( options.values.is2D() && currentTransform != null ) - transform = ((Wrapped2DTransformAs3D)currentTransform).getTransform(); + transform = ((InvertibleWrapped2DTransformAs3D)currentTransform).getTransform(); else transform = currentTransform; // TODO check this (current transform part) - final boolean didAdd = BigWarp.this.landmarkModel.pointEdit( -1, ptarray, false, isMoving, isWarped, true, transform ); + final boolean didAdd = BigWarp.this.landmarkModel.pointEdit( -1, ptarray, false, isMoving, isWarped, true, transform ); if ( BigWarp.this.landmarkFrame.isVisible() ) { @@ -1393,20 +1810,19 @@ protected int selectedLandmark( final double[] pt, final boolean isMoving ) /** * Returns the index of the landmark closest to the input point, * if it is within a certain distance threshold. - * + * * Updates the global variable ptBack * * @param pt the point location * @param isMoving is the point location in moving image space - * @param selectInTable also select the landmark in the table + * @param selectInTable also select the landmark in the table * @return the index of the selected landmark */ protected int selectedLandmark( final double[] pt, final boolean isMoving, final boolean selectInTable ) { - logger.trace( "clicked: " + XfmUtils.printArray( pt ) ); - - // TODO selectedLandmark - final int N = landmarkModel.getRowCount(); + // this gets called on startup for some reason, so put this check in + if( landmarkModel == null ) + return -1; // a point will be selected if you click inside the spot ( with a 5 pixel buffer ) double radsq = ( viewerSettings.getSpotSize() * viewerSettings.getSpotSize() ) + 5 ; @@ -1428,54 +1844,59 @@ protected int selectedLandmark( final double[] pt, final boolean isMoving, final int bestIdx = -1; double smallestDist = Double.MAX_VALUE; - logger.trace( " selectedLandmarkHelper dist scale: " + scale ); - logger.trace( " selectedLandmarkHelper radsq: " + radsq ); - - for ( int n = 0; n < N; n++ ) + synchronized( landmarkModel ) { - final Double[] lmpt; - if( isMoving && landmarkModel.isWarped( n ) && isMovingDisplayTransformed() ) + final int N = landmarkModel.getRowCount(); + for ( int n = 0; n < N; n++ ) { - lmpt = landmarkModel.getWarpedPoints().get( n ); - } - else if( isMoving && isMovingDisplayTransformed() ) - { - lmpt = landmarkModel.getPoints( false ).get( n ); - } - else - { - lmpt = landmarkModel.getPoints( isMoving ).get( n ); - } + final Double[] lmpt; + if( isMoving && landmarkModel.isWarped( n ) && isMovingDisplayTransformed() ) + { + lmpt = landmarkModel.getWarpedPoints().get( n ); + } + else if( isMoving && isMovingDisplayTransformed() ) + { + lmpt = landmarkModel.getPoints( false ).get( n ); + } + else + { + lmpt = landmarkModel.getPoints( isMoving ).get( n ); + } - dist = 0.0; - for ( int i = 0; i < landmarkModel.getNumdims(); i++ ) - { - dist += ( pt[ i ] - lmpt[ i ] ) * ( pt[ i ] - lmpt[ i ] ); - } + dist = 0.0; + for ( int i = 0; i < landmarkModel.getNumdims(); i++ ) + { + dist += ( pt[ i ] - lmpt[ i ] ) * ( pt[ i ] - lmpt[ i ] ); + } - dist *= ( scale * scale ); - logger.trace( " dist squared of lm index : " + n + " is " + dist ); - if ( dist < radsq && dist < smallestDist ) - { - smallestDist = dist; - bestIdx = n; + dist *= ( scale * scale ); + if ( dist < radsq && dist < smallestDist ) + { + smallestDist = dist; + bestIdx = n; + } } - } - if ( selectInTable && landmarkFrame.isVisible() ) - { - if( landmarkTable.isEditing()) + if ( selectInTable && landmarkFrame.isVisible() ) { - landmarkTable.getCellEditor().stopCellEditing(); + if( landmarkTable.isEditing()) + { + landmarkTable.getCellEditor().stopCellEditing(); + } + + landmarkTable.setEditingRow( bestIdx ); + landmarkFrame.repaint(); } - landmarkTable.setEditingRow( bestIdx ); - landmarkFrame.repaint(); } - logger.trace( "selectedLandmark: " + bestIdx ); return bestIdx; } + public void selectAllLandmarks() + { + getLandmarkPanel().getJTable().selectAll(); + } + public static double computeScaleAssumeRigid( final AffineTransform3D xfm ) { return xfm.get( 0, 0 ) + xfm.get( 0, 1 ) + xfm.get( 0, 2 ); @@ -1498,10 +1919,10 @@ protected void enableTransformHandlers() private void printSourceTransforms() { final AffineTransform3D xfm = new AffineTransform3D(); - for( SourceAndConverter sac : data.sources ) + for( final SourceAndConverter sac : data.sources ) { sac.getSpimSource().getSourceTransform(0, 0, xfm ); - double det = BigWarpUtils.det( xfm ); + final double det = BigWarpUtils.det( xfm ); System.out.println( "source xfm ( " + sac.getSpimSource().getName() + " ) : " + xfm ); System.out.println( " det = " + det ); } @@ -1521,8 +1942,20 @@ private void printViewerTransforms() System.out.println( " dotxy = " + BigWarpUtils.dotXy( xfm )); } + protected void alignActive( final AlignPlane plane ) + { + if ( viewerFrameP.isActive() ) + { + viewerFrameP.getViewerPanel().align( plane ); + } + else if ( viewerFrameQ.isActive() ) + { + viewerFrameQ.getViewerPanel().align( plane ); + } + } + /** - * Changes the view transformation of 'panelToChange' to match that of 'panelToMatch' + * Changes the view transformation of 'panelToChange' to match that of 'panelToMatch' * @param panelToChange the viewer panel whose transform will change * @param panelToMatch the viewer panel the transform will come from */ @@ -1530,7 +1963,7 @@ protected void matchWindowTransforms( final BigWarpViewerPanel panelToChange, fi { panelToChange.showMessage( "Aligning" ); panelToMatch.showMessage( "Matching alignment" ); - + // get the transform from panelToMatch final AffineTransform3D viewXfm = new AffineTransform3D(); panelToMatch.state().getViewerTransform( viewXfm ); @@ -1593,7 +2026,60 @@ else if ( viewerFrameQ.isActive() ) matchWindowTransforms( panelToChange, panelToMatch ); } - public void warpToNearest( BigWarpViewerPanel viewer ) + /** + * Centers the active viewer at a landmark whose index is an increment from the currently + * selected landmark. + * + * @param inc the increment + */ + public void jumpToLandmarkRelative( int inc ) + { + final int[] selectedRows = getLandmarkPanel().getJTable().getSelectedRows(); + + int row = 0; + if( selectedRows.length > 0 ) + row = selectedRows[ selectedRows.length - 1 ]; + + row = row + inc; // increment to get the *next* row + + // wrap to start if necessary + if( row >= getLandmarkPanel().getTableModel().getRowCount() ) + row = 0; + else if( row < 0 ) + row = getLandmarkPanel().getTableModel().getRowCount() - 1; + + // select new row + getLandmarkPanel().getJTable().setRowSelectionInterval( row, row ); + + if( getViewerFrameP().isActive() ) + { + jumpToLandmark( row, getViewerFrameP().getViewerPanel() ); + } + else + { + jumpToLandmark( row, getViewerFrameQ().getViewerPanel() ); + } + } + + public void jumpToNextLandmark() + { + jumpToLandmarkRelative( 1 ); + } + + public void jumpToPrevLandmark() + { + jumpToLandmarkRelative( -1 ); + } + + public void jumpToNearestLandmark() + { + if( getViewerFrameP().isActive() ) + jumpToNearestLandmark( getViewerFrameP().getViewerPanel() ); + else + jumpToNearestLandmark( getViewerFrameQ().getViewerPanel() ); + } + + public void jumpToNearestLandmark( BigWarpViewerPanel viewer ) { if ( inLandmarkMode ) { @@ -1601,12 +2087,26 @@ public void warpToNearest( BigWarpViewerPanel viewer ) return; } - RealPoint mousePt = new RealPoint( 3 ); // need 3d point even for 2d images + final RealPoint mousePt = new RealPoint( 3 ); // need 3d point even for 2d images viewer.getGlobalMouseCoordinates( mousePt ); - warpToLandmark( landmarkModel.getIndexNearestTo( mousePt, viewer.getIsMoving() ), viewer ); + jumpToLandmark( landmarkModel.getIndexNearestTo( mousePt, viewer.getIsMoving() ), viewer ); + } + + public void jumpToSelectedLandmark() + { + final int[] selectedRows = getLandmarkPanel().getJTable().getSelectedRows(); + + int row = 0; + if( selectedRows.length > 0 ) + row = selectedRows[ 0 ]; + + if( getViewerFrameP().isActive() ) + jumpToLandmark( row, getViewerFrameP().getViewerPanel() ); + else + jumpToLandmark( row, getViewerFrameQ().getViewerPanel() ); } - public void warpToLandmark( int row, BigWarpViewerPanel viewer ) + public void jumpToLandmark( int row, BigWarpViewerPanel viewer ) { if( inLandmarkMode ) { @@ -1614,8 +2114,14 @@ public void warpToLandmark( int row, BigWarpViewerPanel viewer ) return; } + if ( BigWarp.this.landmarkModel.getRowCount() < 1 ) + { + message.showMessage( "No landmarks found." ); + return; + } + int offset = 0; - int ndims = landmarkModel.getNumdims(); + final int ndims = landmarkModel.getNumdims(); double[] pt = null; if( viewer.getIsMoving() && viewer.getOverlay().getIsTransformed() ) { @@ -1680,6 +2186,22 @@ else if ( viewerFrameQ.isActive() ) } } + public void goToBookmarkRotation() { + + if (getViewerFrameP().isActive()) + bookmarkEditorP.initGoToBookmarkRotation(); + else if (getViewerFrameP().isActive()) + bookmarkEditorQ.initGoToBookmarkRotation(); + } + + public void setBookmark() { + + if (getViewerFrameP().isActive()) + bookmarkEditorP.initSetBookmark(); + else if (getViewerFrameQ().isActive()) + bookmarkEditorQ.initSetBookmark(); + } + public void resetView() { final RandomAccessibleInterval< ? > interval = getSources().get( 1 ).getSpimSource().getSource( 0, 0 ); @@ -1716,16 +2238,15 @@ public void togglePointVisibility() /** * Toggles whether the moving image is displayed after warping (in the same * space as the fixed image), or in its native space. - * + * * @return true of the display mode changed */ public boolean toggleMovingImageDisplay() { // If this is the first time calling the toggle, there may not be enough - // points to estimate a reasonable transformation. + // points to estimate a reasonable transformation. // return early if an re-estimation did not occur - boolean success = restimateTransformation(); - logger.trace( "toggleMovingImageDisplay, success: " + success ); + final boolean success = restimateTransformation(); if ( !success ) { message.showMessage( "Require at least 4 points to estimate a transformation" ); @@ -1744,39 +2265,94 @@ public boolean toggleMovingImageDisplay() return success; } + public void toggleMaskOverlayVisibility() + { + final BigWarpMaskSphereOverlay overlay = getViewerFrameQ().getViewerPanel().getMaskOverlay(); + if( overlay != null ) + overlay.toggleVisible(); + } + + public void setMaskOverlayVisibility( final boolean visible ) + { + final BigWarpMaskSphereOverlay overlay = getViewerFrameQ().getViewerPanel().getMaskOverlay(); + if( overlay != null ) + overlay.setVisible( visible ); + } + protected void addDefaultTableMouseListener() { landmarkTableListener = new MouseLandmarkTableListener(); landmarkPanel.getJTable().addMouseListener( landmarkTableListener ); } + protected void addMaskMouseListener() + { + final Color[] maskColors = new Color[]{ Color.ORANGE, Color.YELLOW }; + // only render mask overlay on target window + viewerQ.setMaskOverlay( new BigWarpMaskSphereOverlay( viewerQ, maskColors, numDimensions() == 3 )); + + maskSourceMouseListenerQ = new MaskedSourceEditorMouseListener( getLandmarkPanel().getTableModel().getNumdims(), this, viewerQ ); + maskSourceMouseListenerQ.setActive( false ); + maskSourceMouseListenerQ.setMask( plateauTransformMask.getRandomAccessible() ); + + plateauTransformMask.getRandomAccessible().setOverlays( Arrays.asList( viewerQ.getMaskOverlay() )); + } + public void setGridType( final GridSource.GRID_TYPE method ) { - ( ( GridSource< ? > ) gridSource.getSpimSource() ).setMethod( method ); + gridSource.setMethod( method ); + } + + @SuppressWarnings( "unchecked" ) + public static < T > void wrapMovingSources( final int ndims, final BigWarpData< T > data ) + { + int i = 0; + for ( final SourceInfo sourceInfo : data.sourceInfos.values() ) + { + if ( sourceInfo.isMoving() ) + { + final SourceAndConverter< T > newSac = ( SourceAndConverter< T > ) wrapSourceAsTransformed( sourceInfo.getSourceAndConverter(), "xfm_" + i, ndims ); + final int sourceIdx = data.sources.indexOf( sourceInfo.getSourceAndConverter() ); + sourceInfo.setSourceAndConverter( newSac ); + data.sources.set( sourceIdx, newSac ); + } + i++; + } + } + + public static < T > void wrapMovingSources( final int ndims, final BigWarpData< T > data, int id ) + { + final SourceInfo sourceInfo = data.getSourceInfo( id ); + if( sourceInfo == null ) + return; + + if ( sourceInfo.isMoving() ) + { + final SourceAndConverter< T > newSac = ( SourceAndConverter< T > ) wrapSourceAsTransformed( sourceInfo.getSourceAndConverter(), "xfm", ndims ); + final int sourceIdx = data.sources.indexOf( sourceInfo.getSourceAndConverter() ); + sourceInfo.setSourceAndConverter( newSac ); + data.sources.set( sourceIdx, newSac ); + } } - public static List< SourceAndConverter > wrapSourcesAsTransformed( final List< SourceAndConverter > sources, + @SuppressWarnings( "unchecked" ) + public static < T > List< SourceAndConverter< T > > wrapSourcesAsTransformed( final LinkedHashMap< Integer, SourceInfo > sources, final int ndims, - final BigWarpData data ) + final BigWarpData< T > data ) { final List< SourceAndConverter> wrappedSource = new ArrayList<>(); - int[] warpUsIndices = data.movingSourceIndices; - HashMap, ColorSettings> colorSettings = data.sourceColorSettings; - int i = 0; - for ( final SourceAndConvertersac : sources ) + for ( final SourceInfo sourceInfo : sources.values() ) { - int idx = Arrays.binarySearch( warpUsIndices, i ); - if ( idx >= 0 ) + if ( sourceInfo.isMoving() ) { - SourceAndConverter newSac = wrapSourceAsTransformed( sac, "xfm_" + i, ndims ); + final SourceAndConverter< T > newSac = ( SourceAndConverter< T > ) wrapSourceAsTransformed( sourceInfo.getSourceAndConverter(), "xfm_" + i, ndims ); wrappedSource.add( newSac ); - colorSettings.put( newSac, colorSettings.get( sac )); } else { - wrappedSource.add( sac ); + wrappedSource.add( ( SourceAndConverter< T > ) sourceInfo.getSourceAndConverter() ); } i++; @@ -1785,44 +2361,250 @@ public static List< SourceAndConverter > wrapSourcesAsTransformed( final } @SuppressWarnings( { "rawtypes", "unchecked" } ) - private static < T > SourceAndConverter< FloatType > addJacobianDeterminantSource( final BigWarpData< T > data, final String name ) + private static < T > JacobianDeterminantSource addJacobianDeterminantSource( final int ndims, final BigWarpData< T > data, final String name ) { - // TODO think about whether its worth it to pass a type parameter. - final JacobianDeterminantSource< FloatType > jdSource = new JacobianDeterminantSource<>( name, data, new FloatType() ); - final RealARGBColorConverter< FloatType > converter = RealARGBColorConverter.create( new FloatType(), 0, 512 ); - converter.setColor( new ARGBType( 0xffffffff ) ); - final SourceAndConverter< FloatType > soc = new SourceAndConverter<>( jdSource, converter, null ); - data.converterSetups.add( BigDataViewer.createConverterSetup( soc, JACDET_SOURCE_ID ) ); - data.sources.add( ( SourceAndConverter ) soc ); - return soc; + final JacobianDeterminantSource jdSource = new JacobianDeterminantSource<>( name, data, new FloatType() ); + final LinkedHashMap< Source, SourceInfo > infos = BigWarpInit.createSources( data, jdSource, JACDET_SOURCE_ID, false ); + final LinkedHashMap< Integer, SourceInfo > id2Info = new LinkedHashMap<>( 1 ); + id2Info.put( JACDET_SOURCE_ID, infos.get( jdSource ) ); + BigWarpInit.add( data, infos ); + + return jdSource; } @SuppressWarnings( { "rawtypes", "unchecked" } ) - private static < T > SourceAndConverter< FloatType > addWarpMagnitudeSource( final BigWarpData< T > data, final boolean is2D, final String name ) + private static < T > WarpMagnitudeSource addWarpMagnitudeSource( final BigWarpData< T > data, final boolean is2D, final String name ) { - // TODO think about whether its worth it to pass a type parameter. - final WarpMagnitudeSource< FloatType > magSource = new WarpMagnitudeSource<>( name, data, new FloatType() ); - final RealARGBColorConverter< FloatType > converter = RealARGBColorConverter.create( new FloatType(), 0, 512 ); - converter.setColor( new ARGBType( 0xffffffff ) ); - final SourceAndConverter< FloatType > soc = new SourceAndConverter<>( magSource, converter, null ); - data.converterSetups.add( BigDataViewer.createConverterSetup( soc, WARPMAG_SOURCE_ID ) ); - data.sources.add( ( SourceAndConverter ) soc ); - return soc; + final WarpMagnitudeSource magSource = new WarpMagnitudeSource<>( name, data, new FloatType() ); + final LinkedHashMap< Source, SourceInfo > infos = BigWarpInit.createSources( data, magSource, WARPMAG_SOURCE_ID, false ); + final LinkedHashMap< Integer, SourceInfo > id2Info = new LinkedHashMap<>( 1 ); + id2Info.put( WARPMAG_SOURCE_ID, infos.get( magSource ) ); + BigWarpInit.add( data, infos ); + + return magSource; } - @SuppressWarnings( { "unchecked", "rawtypes" } ) - private static < T > SourceAndConverter< FloatType > addGridSource( final BigWarpData< T > data, final String name ) + private static < T > GridSource addGridSource( final int ndims, final BigWarpData< T > data, final String name ) { // TODO think about whether its worth it to pass a type parameter. - final GridSource< FloatType > gridSource = new GridSource<>( name, data, new FloatType(), null ); - final RealARGBColorConverter< FloatType > converter = RealARGBColorConverter.create( new FloatType(), 0, 512 ); + final GridSource gridSource = new GridSource<>( name, data, new FloatType(), null ); + gridSource.setMethod( GridSource.GRID_TYPE.LINE ); + final LinkedHashMap< Source, SourceInfo > infos = BigWarpInit.createSources( data, gridSource, GRID_SOURCE_ID, true ); + final LinkedHashMap< Integer, SourceInfo > id2Info = new LinkedHashMap<>( 1 ); + id2Info.put( GRID_SOURCE_ID, infos.get( gridSource ) ); + BigWarpInit.add( data, infos ); + wrapMovingSources( ndims, data, GRID_SOURCE_ID ); + + return gridSource; + } + + /** + * Call this method when the transform mask type or options have changed. + */ + public void updateTransformMask() + { + final MaskOptionsPanel maskOpts = warpVisDialog.maskOptionsPanel; + final String type = maskOpts.getType(); + final boolean masked = maskOpts.isMask(); + + // add the transform mask if necessary + if( masked && transformMaskSource == null ) + addTransformMaskSource(); + + // update the bigwarp transform + final boolean isVirtualMask = isVirtualMask(); + getBwTransform().setMaskInterpolationType( type ); + setMaskOverlayVisibility( maskOpts.showMaskOverlay() && masked && isVirtualMask ); + + if (masked && isVirtualMask) + autoEstimateMask(); + + restimateTransformation(); + getViewerFrameQ().getViewerPanel().requestRepaint(); + getViewerFrameP().getViewerPanel().requestRepaint(); + } + + private boolean isVirtualMask() { + + return plateauTransformMask == transformMask; + } + + public void refreshTransformMask() + { + getBwTransform().setLambda( + transformMaskSource.getSpimSource().getInterpolatedSource(0, 0, Interpolation.NLINEAR)); + } + + public void setTransformMaskRange( double min, double max ) + { + getBwTransform().setMaskIntensityBounds(min, max); + warpVisDialog.maskOptionsPanel.getMaskRangeSlider().setRange( + new BoundedRange( min, max, min, max )); + } + + public void setTransformMaskType( final String maskInterpolationType ) + { + getBwTransform().setMaskInterpolationType(maskInterpolationType); + warpVisDialog.maskOptionsPanel.getMaskTypeDropdown().setSelectedItem(maskInterpolationType); + } + + public void setTransformMaskProperties( final FalloffShape falloffShape, + final double squaredRadius, double[] center ) + { + warpVisDialog.maskOptionsPanel.getMaskFalloffTypeDropdown().setSelectedItem(falloffShape); + + final PlateauSphericalMaskRealRandomAccessible mask = getTransformPlateauMaskSource().getRandomAccessible(); + mask.setFalloffShape( falloffShape ); + mask.setSquaredRadius( squaredRadius ); + mask.setCenter( center ); + } + + public void importTransformMaskSourceDialog() { + + final JFileChooser fileChooser = new JFileChooser(); + fileChooser.setFileSelectionMode( JFileChooser.FILES_AND_DIRECTORIES ); + fileChooser.setCurrentDirectory(lastDirectory); + + final int ret = fileChooser.showOpenDialog(landmarkFrame); + if ( ret == JFileChooser.APPROVE_OPTION ) { + + final File selection = fileChooser.getSelectedFile(); + importTransformMaskSource( selection.getAbsolutePath() ); + } + + } + + @SuppressWarnings( { "unchecked" } ) + public void importTransformMaskSource( final String uri ) { + + // first remove any existing mask source + final SourceInfo srcInfo = data.sourceInfos.get(TRANSFORM_MASK_SOURCE_ID); + if( srcInfo != null ) + removeSource(srcInfo); + + LinkedHashMap, SourceInfo> infos; + try { + infos = BigWarpInit.createSources(data, uri, TRANSFORM_MASK_SOURCE_ID, false); + BigWarpInit.add( data, infos, null, null ); + + infos.entrySet().stream().map( e -> { return e.getKey(); }).findFirst().ifPresent( x -> { + transformMask = (Source>)x; + }); + synchronizeSources(); + + } catch (final URISyntaxException e) { + e.printStackTrace(); + } catch (final IOException e) { + e.printStackTrace(); + } catch (final SpimDataException e) { + e.printStackTrace(); + } + + bwTransform.setLambda( transformMask.getInterpolatedSource(0, 0, Interpolation.NLINEAR)); + updateTransformMask(); + + final RealType type = transformMask.getType(); + if( !(type instanceof DoubleType ) && !(type instanceof FloatType )) + { + final double min = type.getMinValue(); + final double max = type.getMaxValue(); + bwTransform.setMaskIntensityBounds( min, max ); + warpVisDialog.maskOptionsPanel.getMaskRangeSlider().setRange(new BoundedRange( min, max, min, max )); + } + } + + /** + * Run this after loading projet to set the transform mask from a loaded source + */ + @SuppressWarnings("unchecked") + public void connectMaskSource() + { + transformMask = (Source>)data.sourceInfos.get(TRANSFORM_MASK_SOURCE_ID).getSourceAndConverter().getSpimSource(); + bwTransform.setLambda( transformMask.getInterpolatedSource(0, 0, Interpolation.NLINEAR)); + } + + public void removeMaskSource() { + + final SourceInfo srcInfo = data.sourceInfos.get(TRANSFORM_MASK_SOURCE_ID); + if( srcInfo != null ) + removeSource(srcInfo); + + transformMask = null; + transformMaskSource = null; + bwTransform.setLambda(null); + bwTransform.setMaskInterpolationType(BigWarpTransform.NO_MASK_INTERP); + warpVisDialog.maskOptionsPanel.getMaskTypeDropdown().setSelectedItem(BigWarpTransform.NO_MASK_INTERP); + + final BigWarpMaskSphereOverlay overlay = getViewerFrameQ().getViewerPanel().getMaskOverlay(); + if( overlay != null ) + overlay.setVisible(false); + + updateTransformMask(); + + restimateTransformation(); + } + + @SuppressWarnings( { "unchecked", "rawtypes", "hiding" } ) + public > SourceAndConverter< T > addTransformMaskSource() + { + if( warpVisDialog == null ) + return null; + + if( transformMask != null) + { + return ( SourceAndConverter< T > ) data.getSourceInfo( TRANSFORM_MASK_SOURCE_ID ).getSourceAndConverter(); + } + + // think about whether its worth it to pass a type parameter. or should we just stick with Floats? + final BoundingBoxEstimation bbe = new BoundingBoxEstimation(); + final AffineTransform3D affine = new AffineTransform3D(); + data.getTargetSource( 0 ).getSpimSource().getSourceTransform( 0, 0, affine ); + final Interval itvl = bbe.estimatePixelInterval( affine, data.getTargetSource( 0 ).getSpimSource().getSource( 0, 0 ) ); + + plateauTransformMask = PlateauSphericalMaskSource.build( new RealPoint( 3 ), itvl ); + transformMask = plateauTransformMask; + + + final RealARGBColorConverter< T > converter = (RealARGBColorConverter)RealARGBColorConverter.create( plateauTransformMask.getType(), 0, 1 ); converter.setColor( new ARGBType( 0xffffffff ) ); - final SourceAndConverter< FloatType > soc = new SourceAndConverter<>( gridSource, converter, null ); - data.converterSetups.add( BigDataViewer.createConverterSetup( soc, GRID_SOURCE_ID ) ); + final SourceAndConverter< T > soc = new SourceAndConverter( (Source)transformMask, converter, null ); + data.converterSetups.add( BigDataViewer.createConverterSetup( soc, TRANSFORM_MASK_SOURCE_ID ) ); data.sources.add( ( SourceAndConverter ) soc ); + + final SourceInfo sourceInfo = new SourceInfo( TRANSFORM_MASK_SOURCE_ID, false, transformMask.getName(), null, null ); + sourceInfo.setSourceAndConverter( soc ); + data.sourceInfos.put( TRANSFORM_MASK_SOURCE_ID, sourceInfo); + + // connect to UI + warpVisDialog.maskOptionsPanel.setMask( plateauTransformMask ); + addMaskMouseListener(); + bwTransform.setLambda( plateauTransformMask.getRandomAccessible() ); + bwTransform.setMaskIntensityBounds(0, 1); + + final ArrayList overlayList = new ArrayList<>(); + final BigWarpMaskSphereOverlay overlay = new BigWarpMaskSphereOverlay( viewerQ, ndims==3 ); + overlayList.add( overlay ); + + // first attach the overlay to the viewer + getViewerFrameQ().getViewerPanel().setMaskOverlay( overlay ); + plateauTransformMask.getRandomAccessible().setOverlays( overlayList ); + + + synchronizeSources(); + transformMaskSource = soc; return soc; } + public Source> getTansformMaskSource() + { + return transformMask; + } + + public PlateauSphericalMaskSource getTransformPlateauMaskSource() + { + return plateauTransformMask; + } + private static < T > SourceAndConverter< T > wrapSourceAsTransformed( final SourceAndConverter< T > src, final String name, final int ndims ) { if ( src.asVolatile() == null ) @@ -1842,25 +2624,30 @@ public void setupKeyListener() public void setWarpVisGridType( final GridSource.GRID_TYPE type ) { - ( ( GridSource< ? > ) gridSource.getSpimSource() ).setMethod( type ); + gridSource.setMethod( type ); viewerP.requestRepaint(); viewerQ.requestRepaint(); } public void setWarpGridWidth( final double width ) { - ( ( GridSource< ? > ) gridSource.getSpimSource() ).setGridWidth( width ); + gridSource.setGridWidth( width ); viewerP.requestRepaint(); viewerQ.requestRepaint(); } public void setWarpGridSpacing( final double spacing ) { - ( ( GridSource< ? > ) gridSource.getSpimSource() ).setGridSpacing( spacing ); + gridSource.setGridSpacing( spacing ); viewerP.requestRepaint(); viewerQ.requestRepaint(); } + public void setAutoSaver( final BigWarpAutoSaver autoSaver ) + { + this.autoSaver = autoSaver; + } + protected void setupWarpMagBaselineOptions( final CoordinateTransform[] xfm, final int ndim ) { if ( ndim == 2 ) @@ -1896,29 +2683,28 @@ protected void fitBaselineWarpMagModel() landmarkModel.copyLandmarks( p, q ); Arrays.fill( w, 1.0 ); - - try - { - final AbstractModel< ? > baseline = this.baseXfmList[ baselineModelIndex ]; - - baseline.fit( p, q, w ); // FITBASELINE - WrappedCoordinateTransform baselineTransform = new WrappedCoordinateTransform( - (InvertibleCoordinateTransform)baseline, ndims ); - - // the transform to compare is the inverse (because we use it for rendering) - // so need to give the inverse transform for baseline as well - ( ( WarpMagnitudeSource< ? > ) warpMagSource.getSpimSource() ).setBaseline( baselineTransform.inverse() ); - } - catch ( final NotEnoughDataPointsException e ) - { - e.printStackTrace(); - } - catch ( final IllDefinedDataPointsException e ) + + if( warpMagSource != null ) { - e.printStackTrace(); + try + { + final AbstractModel< ? > baseline = this.baseXfmList[ baselineModelIndex ]; + baseline.fit( p, q, w ); // FITBASELINE + final WrappedCoordinateTransform baselineTransform = new WrappedCoordinateTransform( + (InvertibleCoordinateTransform)baseline, ndims ); + + // the transform to compare is the inverse (because we use it for rendering) + // so need to give the inverse transform for baseline as well + warpMagSource.setBaseline( baselineTransform.inverse() ); + } + catch ( final IllDefinedDataPointsException | NotEnoughDataPointsException e ) + { + e.printStackTrace(); + } + + getViewerFrameP().getViewerPanel().requestRepaint(); + getViewerFrameQ().getViewerPanel().requestRepaint(); } - getViewerFrameP().getViewerPanel().requestRepaint(); - getViewerFrameQ().getViewerPanel().requestRepaint(); } public void setMovingSpimData( SpimData movingSpimData, File movingImageXml ) @@ -1927,10 +2713,39 @@ public void setMovingSpimData( SpimData movingSpimData, File movingImageXml ) this.movingImageXml = movingImageXml; } + public void setBookmarks( final Bookmarks bookmarks ) + { + this.bookmarks = bookmarks; + } + + public void setAutoEstimateMask( final boolean selected ) + { + warpVisDialog.maskOptionsPanel.getAutoEstimateMaskButton().setSelected( selected ); + } + + public void autoEstimateMask() + { + if( landmarkModel.numActive() < 4 ) + return; + + if( warpVisDialog.autoEstimateMask() && bwTransform.isMasked() && transformMask instanceof PlateauSphericalMaskSource ) + { + final Sphere sph = BoundingSphereRitter.boundingSphere(landmarkModel.getFixedPointsCopy()); + plateauTransformMask.getRandomAccessible().setCenter(sph.getCenter()); + plateauTransformMask.getRandomAccessible().setRadius(sph.getRadius()); + + final AbstractTransformSolver< ? > solver = getBwTransform().getSolver(); + if( solver instanceof MaskedSimRotTransformSolver ) + { + ((MaskedSimRotTransformSolver)solver).setCenter( sph.getCenter() ); + } + } + } + public enum WarpVisType { NONE, WARPMAG, JACDET, GRID - }; + } public void setWarpVisMode( final WarpVisType type, BigWarpViewerFrame viewerFrame, final boolean both ) { @@ -1954,19 +2769,9 @@ else if ( both ) { return; } - } -// -// int offImgIndex = 0; -// int onImgIndex = 1; -// -// if ( viewerFrame == viewerFrameP ) -// { -// offImgIndex = 1; -// onImgIndex = 0; -// } - if ( landmarkModel.getTransform() == null ) + if ( currentTransform == null ) { message.showMessage( "No warp - estimate warp first." ); return; @@ -1977,19 +2782,27 @@ else if ( both ) { case JACDET: { - // turn warp mag on - state.setSourceActive( warpMagSource, false ); - state.setSourceActive( jacDetSource, true ); - state.setSourceActive( gridSource, false ); + // turn jacobian determinant + if ( jacDetSource == null ) + { + jacDetSource = addJacobianDeterminantSource( ndims, data, "Jacobian determinant" ); + synchronizeSources(); + } + showSourceFused( viewerFrame, JACDET_SOURCE_ID ); + viewerFrame.getViewerPanel().showMessage( "Displaying Jacobian Determinant" ); + break; } case WARPMAG: { // turn warp mag on - state.setSourceActive( warpMagSource, true ); - state.setSourceActive( jacDetSource, false ); - state.setSourceActive( gridSource, false ); -// vg.setSourceActive( offImgIndex, false ); + if ( warpMagSource == null ) + { + + warpMagSource = addWarpMagnitudeSource( data, ndims == 2, "Warp magnitude" ); + synchronizeSources(); + } + showSourceFused( viewerFrame, WARPMAG_SOURCE_ID ); // estimate the max warp // final WarpMagnitudeSource< ? > wmSrc = ( ( WarpMagnitudeSource< ? > ) sources.get( warpMagSourceIndex ).getSpimSource() ); @@ -1998,30 +2811,32 @@ else if ( both ) // set the slider // ( ( RealARGBColorConverter< FloatType > ) ( sources.get( warpMagSourceIndex ).getConverter() ) ).setMax( maxval ); - state.setDisplayMode( DisplayMode.FUSED ); - message.showMessage( "Displaying Warp Magnitude" ); + + viewerFrame.getViewerPanel().showMessage( "Displaying Warp Magnitude" ); break; } case GRID: { // turn grid vis on + if ( gridSource == null ) + { + gridSource = addGridSource( ndims, data, "Transform grid" ); + synchronizeSources(); + data.getConverterSetup( GRID_SOURCE_ID ).setDisplayRange( 0, 512 ); + } + showSourceFused( viewerFrame, GRID_SOURCE_ID ); - state.setSourceActive( warpMagSource, false ); - state.setSourceActive( jacDetSource, false ); - state.setSourceActive( gridSource, true ); -// vg.setSourceActive( offImgIndex, false ); - - state.setDisplayMode( DisplayMode.FUSED ); - message.showMessage( "Displaying Warp Grid" ); + viewerFrame.getViewerPanel().showMessage( "Displaying Warp Grid" ); break; } default: { - state.setSourceActive( warpMagSource, false ); - state.setSourceActive( gridSource, false ); -// vg.setSourceActive( offImgIndex, true ); + if ( warpMagSource != null ) + state.setSourceActive( data.getSourceInfo( WARPMAG_SOURCE_ID ).getSourceAndConverter(), false ); + + if ( gridSource != null ) + state.setSourceActive( data.getSourceInfo( GRID_SOURCE_ID ).getSourceAndConverter(), false ); -// vg.setFusedEnabled( false ); message.showMessage( "Turning off warp vis" ); break; } @@ -2030,8 +2845,6 @@ else if ( both ) public void toggleWarpVisMode( BigWarpViewerFrame viewerFrame ) { -// int offImgIndex = 0; -// int onImgIndex = 1; if ( viewerFrame == null ) { if ( viewerFrameP.isActive() ) @@ -2046,13 +2859,7 @@ else if ( viewerFrameQ.isActive() ) return; } -// if ( viewerFrame == viewerFrameP ) -// { -// offImgIndex = 1; -// onImgIndex = 0; -// } - - if ( landmarkModel.getTransform() == null ) + if( getBwTransform().getTransformation() == null ) { message.showMessage( "No warp - estimate warp first." ); return; @@ -2060,23 +2867,20 @@ else if ( viewerFrameQ.isActive() ) final ViewerState state = viewerFrame.getViewerPanel().state(); - // TODO consider remembering whether fused was on before displaying - // warpmag + // TODO consider remembering whether fused was on before displaying warpmag // so that its still on or off after we turn it off - if ( state.isSourceActive( warpMagSource ) ) // warp mag is visible, - // turn it off - { - state.setSourceActive( warpMagSource, false ); -// vg.setSourceActive( offImgIndex, true ); + final SourceAndConverter< ? > wmSac = data.getSourceInfo( WARPMAG_SOURCE_ID ).getSourceAndConverter(); + if ( state.isSourceActive( wmSac ) ) // warp mag is visible, turn it off + { + state.setSourceActive( wmSac, false ); state.setDisplayMode( state.getDisplayMode().withFused( false ) ); message.showMessage( "Removing Warp Magnitude" ); } else // warp mag is invisible, turn it on { - state.setSourceActive( warpMagSource, true ); -// vg.setSourceActive( offImgIndex, false ); + state.setSourceActive( wmSac, true ); // estimate the max warp // final WarpMagnitudeSource< ? > wmSrc = ( ( WarpMagnitudeSource< ? > ) sources.get( warpMagSourceIndex ).getSpimSource() ); @@ -2092,40 +2896,88 @@ else if ( viewerFrameQ.isActive() ) viewerFrame.getViewerPanel().requestRepaint(); } - private void setTransformationMovingSourceOnly( final InvertibleRealTransform transform ) - { - this.currentTransform = transform; + private void showSourceFused(BigWarpViewerFrame viewerFrame, int sourceId) { - for ( int i = 0; i < movingSourceIndexList.length; i++ ) - { - int idx = movingSourceIndexList [ i ]; + if (viewerFrame == null) { + if (viewerFrameP.isActive()) { + viewerFrame = viewerFrameP; + } else if (viewerFrameQ.isActive()) { + viewerFrame = viewerFrameQ; + } else + return; + } + + if (getBwTransform().getTransformation() == null) { + message.showMessage("No warp - estimate warp first."); + return; + } + + final SourceAndConverter< ? > newSrc = data.getSourceInfo( sourceId ).getSourceAndConverter(); - // the xfm must always be 3d for bdv to be happy. - // when bigwarp has 2d images though, the z- component will be left unchanged - //InverseRealTransform xfm = new InverseRealTransform( new TpsTransformWrapper( 3, transform )); + final ViewerState state = viewerFrame.getViewerPanel().state(); + if (!state.getDisplayMode().equals(DisplayMode.FUSED)) { + final SourceAndConverter currentSource = state.getCurrentSource(); + for( final SourceAndConverter src : state.getSources()) + state.setSourceActive(src, src == currentSource || src == newSrc ); + } + else { + + final SourceInfo warpMagSrcInfo = data.getSourceInfo( WARPMAG_SOURCE_ID ); + final SourceInfo gridSrcInfo = data.getSourceInfo( GRID_SOURCE_ID ); + final SourceInfo jacDetSrcInfo = data.getSourceInfo( JACDET_SOURCE_ID ); + + // un-dispolay all the warp vis sources + if (warpMagSrcInfo != null) + state.setSourceActive(warpMagSrcInfo.getSourceAndConverter(), false); - // the updateTransform method creates a copy of the transform - ( ( WarpedSource< ? > ) ( sources.get( idx ).getSpimSource() ) ).updateTransform( transform ); - if ( sources.get( 0 ).asVolatile() != null ) - ( ( WarpedSource< ? > ) ( sources.get( idx ).asVolatile().getSpimSource() ) ).updateTransform( transform ); + if (gridSrcInfo != null) + state.setSourceActive(gridSrcInfo.getSourceAndConverter(), false); + + if (jacDetSrcInfo != null) + state.setSourceActive(jacDetSrcInfo.getSourceAndConverter(), false); + + // activate the requested one + state.setSourceActive(newSrc, true); } + + state.setDisplayMode(DisplayMode.FUSED); } - public void updateSourceBoundingBoxEstimators() + private void setTransformationMovingSourceOnly( final InvertibleRealTransform transform ) { - for ( int i = 0; i < movingSourceIndexList.length; i++ ) - { - int idx = movingSourceIndexList [ i ]; - - // the xfm must always be 3d for bdv to be happy. - // when bigwarp has 2d images though, the z- component will be left unchanged - //InverseRealTransform xfm = new InverseRealTransform( new TpsTransformWrapper( 3, transform )); + this.currentTransform = transform; + data.sourceInfos.values().forEach( sourceInfo -> { + if ( sourceInfo.isMoving() ) + { + // the xfm must always be 3d for bdv to be happy. + // when bigwarp has 2d images though, the z- component will be left unchanged + // InverseRealTransform xfm = new InverseRealTransform( new TpsTransformWrapper( 3, transform )); + + // the updateTransform method creates a copy of the transform + final SourceAndConverter< ? > sac = sourceInfo.getSourceAndConverter(); + final WarpedSource< ? > wsrc = ( WarpedSource< ? > ) sac.getSpimSource(); + wsrc.updateTransform( transform ); + if ( sac.asVolatile() != null ) + ( ( WarpedSource< ? > ) sourceInfo.getSourceAndConverter().asVolatile().getSpimSource() ).updateTransform( transform ); + } + } ); + } - // the updateTransform method creates a copy of the transform - ( ( WarpedSource< ? > ) ( sources.get( idx ).getSpimSource() ) ).setBoundingBoxEstimator(bboxOptions.copy()); - if ( sources.get( 0 ).asVolatile() != null ) - ( ( WarpedSource< ? > ) ( sources.get( idx ).asVolatile().getSpimSource() ) ).setBoundingBoxEstimator(bboxOptions.copy()); - } + public void updateSourceBoundingBoxEstimators() + { + data.sourceInfos.values().forEach( sourceInfo -> { + if ( sourceInfo.isMoving() ) + { + // the xfm must always be 3d for bdv to be happy. + // when bigwarp has 2d images though, the z- component will be left unchanged + //InverseRealTransform xfm = new InverseRealTransform( new TpsTransformWrapper( 3, transform )); + + // the updateTransform method creates a copy of the transform + ( ( WarpedSource< ? > ) sourceInfo.getSourceAndConverter().getSpimSource() ).setBoundingBoxEstimator( bboxOptions.copy() ); + if ( data.sources.get( 0 ).asVolatile() != null ) + ( ( WarpedSource< ? > ) sourceInfo.getSourceAndConverter().asVolatile().getSpimSource() ).setBoundingBoxEstimator( bboxOptions.copy() ); + } + } ); } private synchronized void notifyTransformListeners( ) @@ -2138,25 +2990,29 @@ private void setTransformationAll( final InvertibleRealTransform transform ) { setTransformationMovingSourceOnly( transform ); - final WarpMagnitudeSource< ? > wmSrc = ( ( WarpMagnitudeSource< ? > ) warpMagSource.getSpimSource() ); - final JacobianDeterminantSource< ? > jdSrc = ( ( JacobianDeterminantSource< ? > ) jacDetSource.getSpimSource() ); - final GridSource< ? > gSrc = ( ( GridSource< ? > ) gridSource.getSpimSource() ); - - wmSrc.setWarp( transform ); - fitBaselineWarpMagModel(); - - if( transform instanceof ThinplateSplineTransform ) + if( warpMagSource != null ) { - jdSrc.setTransform( (ThinplateSplineTransform)transform ); + warpMagSource.setWarp( transform ); + fitBaselineWarpMagModel(); } - else if ( transform instanceof WrappedIterativeInvertibleRealTransform ) + + if( jacDetSource != null ) { - jdSrc.setTransform( (ThinplateSplineTransform)((WrappedIterativeInvertibleRealTransform)transform).getTransform() ); + if( transform instanceof ThinplateSplineTransform ) + { + jacDetSource.setTransform( (ThinplateSplineTransform)transform ); + } + else if ( transform instanceof WrappedIterativeInvertibleRealTransform ) + { + final RealTransform xfm = ((WrappedIterativeInvertibleRealTransform)transform).getTransform(); + if( xfm instanceof ThinplateSplineTransform ) + jacDetSource.setTransform( (ThinplateSplineTransform) xfm ); + else + jacDetSource.setTransform( new RealTransformFiniteDerivatives( xfm )); + } + else + jacDetSource.setTransform( null ); } - else - jdSrc.setTransform( null ); - - gSrc.setWarp( transform ); } public boolean restimateTransformation() @@ -2194,15 +3050,17 @@ public boolean restimateTransformation() public synchronized void setIsMovingDisplayTransformed( final boolean isTransformed ) { - for( int i = 0 ; i < movingSourceIndexList.length; i ++ ) - { - int movingSourceIndex = movingSourceIndexList[ i ]; - ( ( WarpedSource< ? > ) ( sources.get( movingSourceIndex ).getSpimSource() ) ).setIsTransformed( isTransformed ); + data.sourceInfos.values().forEach( sourceInfo -> { + if ( sourceInfo.isMoving() ) + { + final SourceAndConverter< ? > sourceAndConverter = sourceInfo.getSourceAndConverter(); + ( ( WarpedSource< ? > ) sourceAndConverter.getSpimSource() ).setIsTransformed( isTransformed ); - if ( sources.get( movingSourceIndex ).asVolatile() != null ) - ( ( WarpedSource< ? > ) ( sources.get( movingSourceIndex ).asVolatile().getSpimSource() ) ).setIsTransformed( isTransformed ); - } + if ( sourceAndConverter.asVolatile() != null ) + ( ( WarpedSource< ? > ) sourceAndConverter.asVolatile().getSpimSource() ).setIsTransformed( isTransformed ); + } + } ); overlayP.setIsTransformed( isTransformed ); @@ -2220,14 +3078,21 @@ public synchronized void setIsMovingDisplayTransformed( final boolean isTransfor */ public boolean isRowIncomplete() { - LandmarkTableModel ltm = landmarkPanel.getTableModel(); + final LandmarkTableModel ltm = landmarkPanel.getTableModel(); return ltm.isPointUpdatePending() || ltm.isPointUpdatePendingMoving(); } public boolean isMovingDisplayTransformed() { // this implementation is okay, so long as all the moving images have the same state of 'isTransformed' - return ( ( WarpedSource< ? > ) ( sources.get( movingSourceIndexList[ 0 ] ).getSpimSource() ) ).isTransformed(); +// return ( ( WarpedSource< ? > ) ( data.sources.get( data.movingSourceIndexList.get( 0 ) ).getSpimSource() ) ).isTransformed(); + + // TODO better to explicitly keep track of this + + if( data.sources.size() < 1 ) + return true; + else + return ( ( WarpedSource< ? > ) ( data.getMovingSource( 0 ).getSpimSource() ) ).isTransformed(); } /** @@ -2236,7 +3101,7 @@ public boolean isMovingDisplayTransformed() */ protected int detectNumDims() { - return detectNumDims( sources ); + return detectNumDims( data.sources ); } /** @@ -2247,10 +3112,14 @@ protected int detectNumDims() */ public static int detectNumDims( List< SourceAndConverter< T > > sources ) { + // default to 3D if bigwarp is opened without any sources + if( sources.size() == 0 ) + return 3; + boolean isAnySource3d = false; - for ( SourceAndConverter< T > sac : sources ) + for ( final SourceAndConverter< T > sac : sources ) { - long[] dims = new long[ sac.getSpimSource().getSource( 0, 0 ).numDimensions() ]; + final long[] dims = new long[ sac.getSpimSource().getSource( 0, 0 ).numDimensions() ]; sac.getSpimSource().getSource( 0, 0 ).dimensions( dims ); if ( sac.getSpimSource().getSource( 0, 0 ).dimension( 2 ) > 1 ) @@ -2270,7 +3139,7 @@ public static int detectNumDims( List< SourceAndConverter< T > > sources ) public static void main( final String[] args ) { new ImageJ(); - + // TODO main String fnP = ""; String fnQ = ""; @@ -2282,11 +3151,6 @@ public static void main( final String[] args ) fnP = args[ i++ ]; fnQ = args[ i++ ]; } - else - { - System.err.println( "Must provide at least 2 inputs for moving and target image files" ); - System.exit( 1 ); - } if ( args.length > i ) fnLandmarks = args[ i++ ]; @@ -2311,35 +3175,41 @@ public static void main( final String[] args ) System.setProperty( "apple.laf.useScreenMenuBar", "false" ); - ProgressWriterIJ progress = new ProgressWriterIJ(); - BigWarp bw; - BigWarpData bwdata; + final ProgressWriterIJ progress = new ProgressWriterIJ(); + BigWarp bw; + BigWarpData bwdata; if ( fnP.endsWith( "xml" ) && fnQ.endsWith( "xml" ) ) { bwdata = BigWarpInit.createBigWarpDataFromXML( fnP, fnQ ); - bw = new BigWarp<>( bwdata, new File( fnP ).getName(), progress ); + bw = new BigWarp<>( bwdata, progress ); } else if ( fnP.endsWith( "xml" ) && !fnQ.endsWith( "xml" ) ) { final ImagePlus impQ = IJ.openImage( fnQ ); bwdata = BigWarpInit.createBigWarpDataFromXMLImagePlus( fnP, impQ ); - bw = new BigWarp<>( bwdata, new File( fnP ).getName(), progress ); + bw = new BigWarp<>( bwdata, progress ); } else if ( !fnP.endsWith( "xml" ) && fnQ.endsWith( "xml" ) ) { final ImagePlus impP = IJ.openImage( fnP ); bwdata = BigWarpInit.createBigWarpDataFromImagePlusXML( impP, fnQ ); - bw = new BigWarp<>( bwdata, new File( fnP ).getName(), progress ); + bw = new BigWarp<>( bwdata, progress ); } - else + else if (!fnP.isEmpty() && !fnQ.isEmpty()) { final ImagePlus impP = IJ.openImage( fnP ); final ImagePlus impQ = IJ.openImage( fnQ ); + // For testing display and color settings +// impP.setDisplayRange( 10, 200 ); +// impQ.setDisplayRange( 20, 180 ); +// impP.show(); +// impQ.show(); + if ( !( impP == null || impQ == null ) ) { bwdata = BigWarpInit.createBigWarpDataFromImages( impP, impQ ); - bw = new BigWarp<>( bwdata, new File( fnP ).getName(), progress ); + bw = new BigWarp<>( bwdata, progress ); } else { @@ -2347,10 +3217,13 @@ else if ( !fnP.endsWith( "xml" ) && fnQ.endsWith( "xml" ) ) return; } } + else + { + bw = new BigWarp<>( new BigWarpData<>(), progress ); + } - if ( !fnLandmarks.isEmpty() ) - bw.getLandmarkPanel().getTableModel().load( new File( fnLandmarks ) ); + bw.loadLandmarks( fnLandmarks ); if ( doInverse ) bw.invertPointCorrespondences(); @@ -2362,19 +3235,19 @@ else if ( !fnP.endsWith( "xml" ) && fnQ.endsWith( "xml" ) ) e.printStackTrace(); } } - + private void viewerXfmTest() { - AffineTransform3D srcTransform0 = new AffineTransform3D(); - sources.get( 0 ).getSpimSource().getSourceTransform(0, 0, srcTransform0 ); + final AffineTransform3D srcTransform0 = new AffineTransform3D(); + data.sources.get( 0 ).getSpimSource().getSourceTransform(0, 0, srcTransform0 ); - AffineTransform3D srcTransform1 = new AffineTransform3D(); - sources.get( 1 ).getSpimSource().getSourceTransform(0, 0, srcTransform1 ); + final AffineTransform3D srcTransform1 = new AffineTransform3D(); + data.sources.get( 1 ).getSpimSource().getSourceTransform(0, 0, srcTransform1 ); - AffineTransform3D viewerTransformM = new AffineTransform3D(); + final AffineTransform3D viewerTransformM = new AffineTransform3D(); viewerP.state().getViewerTransform( viewerTransformM ); - AffineTransform3D viewerTransformT = new AffineTransform3D(); + final AffineTransform3D viewerTransformT = new AffineTransform3D(); viewerQ.state().getViewerTransform( viewerTransformT ); System.out.println( " " ); @@ -2395,6 +3268,7 @@ public void checkBoxInputMaps() // Make it enter instead // This is super ugly ... why does it have to be this way. + final TableCellEditor celled = landmarkTable.getCellEditor( 0, 1 ); final Component c = celled.getTableCellEditorComponent( landmarkTable, Boolean.TRUE, true, 0, 1 ); @@ -2414,92 +3288,6 @@ public void checkBoxInputMaps() // SwingUtilities.replaceUIInputMap( getRootPane(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); } - public static class BigWarpData< T > - { - public final List< SourceAndConverter< T > > sources; - - public final List< ConverterSetup > converterSetups; - - public final CacheControl cache; - - public int[] movingSourceIndices; - - public int[] targetSourceIndices; - - public final ArrayList< Integer > movingSourceIndexList; - - public final ArrayList< Integer > targetSourceIndexList; - - public final HashMap< Integer, ColorSettings > setupSettings; - - public final HashMap< SourceAndConverter, ColorSettings > sourceColorSettings; - - public BigWarpData( final List< SourceAndConverter< T > > sources, final List< ConverterSetup > converterSetups, final CacheControl cache, int[] movingSourceIndices, int[] targetSourceIndices ) - { - this.sources = sources; - this.converterSetups = converterSetups; - this.movingSourceIndices = movingSourceIndices; - this.targetSourceIndices = targetSourceIndices; - - this.movingSourceIndexList = new ArrayList<>(); - this.targetSourceIndexList = new ArrayList<>(); - - - if ( cache == null ) - this.cache = new CacheControl.Dummy(); - else - this.cache = cache; - - setupSettings = new HashMap<>(); - sourceColorSettings = new HashMap<>(); - } - - public void wrapUp() - { - movingSourceIndices = movingSourceIndexList.stream().mapToInt( x -> x ).toArray(); - targetSourceIndices = targetSourceIndexList.stream().mapToInt( x -> x ).toArray(); - - Arrays.sort( movingSourceIndices ); - Arrays.sort( targetSourceIndices ); - } - - /** - * @deprecated - */ - public void transferChannelSettings( final SetupAssignments setupAssignments, final VisibilityAndGrouping visibility ) - { - for( Integer key : setupSettings.keySet() ) - setupSettings.get( key ).updateSetup( setupAssignments ); - } - - public void transferChannelSettings( final BigWarpViewerFrame viewer ) - { - SynchronizedViewerState state = viewer.getViewerPanel().state(); - ConverterSetups setups = viewer.getConverterSetups(); - synchronized ( state ) - { - for ( SourceAndConverter< ? > sac : state.getSources() ) - { - if ( sourceColorSettings.containsKey( sac ) ) - { - if ( sourceColorSettings.get( sac ) == null ) - continue; - - sourceColorSettings.get( sac ).updateSetup( setups.getConverterSetup( sac ) ); - } - else - { - final int timepoint = state.getCurrentTimepoint(); - final Bounds bounds = InitializeViewerState.estimateSourceRange( sac.getSpimSource(), timepoint, 0.001, 0.999 ); - ConverterSetup cs = setups.getConverterSetup(sac); - if( cs != null ) - cs.setDisplayRange( bounds.getMinBound(), bounds.getMaxBound() ); - } - } - } - } - } - protected class LandmarkModeListener implements KeyEventPostProcessor { @Override @@ -2602,7 +3390,7 @@ public void mousePressed( final MouseEvent e ) @Override public void mouseReleased( final MouseEvent e ) { - long clickLength = System.currentTimeMillis() - pressTime; + final long clickLength = System.currentTimeMillis() - pressTime; if( clickLength < keyClickMaxLength && selectedPointIndex != -1 ) return; @@ -2610,7 +3398,7 @@ public void mouseReleased( final MouseEvent e ) // shift when boolean isMovingLocal = isMoving; if ( e.isShiftDown() && e.isControlDown() ) - { + { isMovingLocal = !isMoving; } else if( e.isShiftDown()) @@ -2677,7 +3465,6 @@ public void mouseDragged( final MouseEvent e ) thisViewer.doUpdateOnDrag() && BigWarp.this.landmarkModel.isActive( selectedPointIndex ) ) { - logger.trace("Drag resolve"); solverThread.requestResolve( isMoving, selectedPointIndex, ptarrayLoc ); } else @@ -2686,7 +3473,6 @@ public void mouseDragged( final MouseEvent e ) // the undoable action is added on mouseRelease if( isMoving && isMovingDisplayTransformed() ) { - logger.trace("Drag moving transformed"); // The moving image: // Update the warped point during the drag even if there is a corresponding fixed image point // Do this so the point sticks on the mouse @@ -2698,7 +3484,6 @@ public void mouseDragged( final MouseEvent e ) } else { - logger.trace("Drag default"); // The fixed image BigWarp.this.landmarkModel.pointEdit( selectedPointIndex, ptarrayLoc, false, isMoving, false, false, currentTransform ); thisViewer.requestRepaint(); @@ -2711,7 +3496,7 @@ public void mouseDragged( final MouseEvent e ) public void mouseMoved( final MouseEvent e ) { thisViewer.getGlobalMouseCoordinates( hoveredPoint ); - int hoveredIndex = BigWarp.this.selectedLandmark( hoveredArray, isMoving, false ); + final int hoveredIndex = BigWarp.this.selectedLandmark( hoveredArray, isMoving, false ); thisViewer.setHoveredIndex( hoveredIndex ); } @@ -2747,8 +3532,8 @@ public class WarningTableCellRenderer extends DefaultTableCellRenderer @Override public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column ) { - LandmarkTableModel model = ( LandmarkTableModel ) table.getModel(); - Component c = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column ); + final LandmarkTableModel model = ( LandmarkTableModel ) table.getModel(); + final Component c = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column ); if ( model.rowNeedsWarning( row ) ) c.setBackground( LandmarkTableModel.WARNINGBGCOLOR ); else @@ -2764,11 +3549,12 @@ public void tableChanged( final TableModelEvent e ) { // re-estimate if a a point was set to or from active // note - this covers "resetting" points as well - if( e.getColumn() == LandmarkTableModel.ACTIVECOLUMN ) + if ( e.getColumn() == LandmarkTableModel.ACTIVECOLUMN ) { BigWarp.this.restimateTransformation(); BigWarp.this.landmarkPanel.repaint(); } + autoEstimateMask(); } } @@ -2895,11 +3681,43 @@ public void mouseReleased( final MouseEvent e ) public void setTransformType( final String type ) { - transformSelector.setTransformType( type ); - bwTransform.setTransformType( type ); + updateTransformTypePanel(type); + updateTransformTypeDialog(type); + bwTransform.setTransformType(type); this.restimateTransformation(); } + public void setTransformTypeUpdateUI( final String type ) + { + setTransformType( type ); + updateTransformTypeDialog( type ); + updateTransformTypePanel( type ); + } + + /** + * Update the transformation selection dialog to reflect the given transform type selection. + * + * @param type the transformation type + */ + public void updateTransformTypeDialog( final String type ) + { + transformSelector.deactivate(); + transformSelector.setTransformType( type ); + transformSelector.activate(); + } + + /** + * Update the transformation selection panel in the options dialog to reflect the given transform type selection. + * + * @param type the transformation type + */ + public void updateTransformTypePanel( final String type ) + { + warpVisDialog.transformTypePanel.deactivate(); + warpVisDialog.transformTypePanel.setType( type ); + warpVisDialog.transformTypePanel.activate(); + } + public String getTransformType() { return bwTransform.getTransformType(); @@ -2916,8 +3734,9 @@ public BoundingBoxEstimation getBoxEstimation() } /** - * Use getTps, getTpsBase, or getTransformation instead - * @return + * @deprecated Use getTps, getTpsBase, or getTransformation instead + * + * @return the {@link ThinPlateR2LogRSplineKernelTransform} iinnstance */ @Deprecated public ThinPlateR2LogRSplineKernelTransform getTransform() @@ -3012,7 +3831,7 @@ public void run() { try { - InvertibleRealTransform invXfm = bw.bwTransform.getTransformation( index ); + final InvertibleRealTransform invXfm = bw.bwTransform.getTransformation( index ); if ( invXfm == null ) return; @@ -3032,7 +3851,9 @@ public void run() else { // update the transform and warped point - bw.setTransformationMovingSourceOnly( invXfm ); +// bw.setTransformationMovingSourceOnly( invXfm ); +// bw.data.updateEditableTransformation( invXfm ); + bw.setTransformationAll( invXfm ); } // update fixed point - but don't allow undo/redo @@ -3044,7 +3865,7 @@ public void run() } /* - * repaint both panels so that: + * repaint both panels so that: * 1) new transform is displayed * 2) points are rendered */ @@ -3089,26 +3910,7 @@ public void requestResolve( final boolean isMoving, final int index, final doubl notify(); } } - - } - - /** - * Set the folder where the results of auto-saving will be stored. - * - * @param autoSaveFolder the destination folder - */ - public void setAutosaveFolder( final File autoSaveFolder ) - { - boolean exists = autoSaveFolder.exists(); - if( !exists ) - exists = autoSaveFolder.mkdir(); - if( exists && autoSaveFolder.isDirectory() ) - { - this.autoSaveDirectory = autoSaveFolder; - warpVisDialog.autoSaveFolderText.setText( autoSaveFolder.getAbsolutePath() ); - warpVisDialog.repaint(); - } } /** @@ -3117,12 +3919,12 @@ public void setAutosaveFolder( final File autoSaveFolder ) public void autoSaveLandmarks() { final File baseFolder; - if( autoSaveDirectory != null ) - baseFolder = autoSaveDirectory; + if ( autoSaver.autoSaveDirectory != null ) + baseFolder = autoSaver.autoSaveDirectory; else baseFolder = getBigwarpSettingsFolder(); - File proposedLandmarksFile = new File( baseFolder.getAbsolutePath() + + final File proposedLandmarksFile = new File( baseFolder.getAbsolutePath() + File.separator + "bigwarp_landmarks_" + new SimpleDateFormat( "yyyyMMdd-HHmmss" ).format( Calendar.getInstance().getTime() ) + ".csv" ); @@ -3131,13 +3933,13 @@ public void autoSaveLandmarks() { saveLandmarks( proposedLandmarksFile.getCanonicalPath() ); } - catch ( IOException e ) { e.printStackTrace(); } + catch ( final IOException e ) { e.printStackTrace(); } } /** * Saves landmarks to either the last File the user * saved landmarks to, or a unique location in the user's bigwarp folder. - * + * */ public void quickSaveLandmarks() { @@ -3147,7 +3949,7 @@ public void quickSaveLandmarks() { saveLandmarks( lastLandmarks.getCanonicalPath() ); } - catch ( IOException e ) { e.printStackTrace(); } + catch ( final IOException e ) { e.printStackTrace(); } } else { @@ -3158,7 +3960,7 @@ public void quickSaveLandmarks() /** * Returns the default location for bigwarp settings / auto saved files: ~/.bigwarp - * @return the folder + * @return the folder */ public File getBigwarpSettingsFolder() { @@ -3178,8 +3980,8 @@ public File getBigwarpSettingsFolder() /** * Returns the {@link BigWarpAutoSaver}. - * - * @return + * + * @return the {@link BigWarpAutoSaver} instance. */ public BigWarpAutoSaver getAutoSaver() { @@ -3190,7 +3992,7 @@ public void stopAutosave() { if( autoSaver != null ) { - autoSaver.stop();; + autoSaver.stop(); autoSaver = null; } } @@ -3222,7 +4024,10 @@ protected void saveLandmarks() protected void saveLandmarks( final String filename ) throws IOException { - landmarkModel.save(new File( filename )); + if( filename.endsWith("csv")) + landmarkModel.save(new File( filename )); + else if( filename.endsWith("json")) + TransformWriterJson.write(landmarkModel, bwTransform, new File( filename )); } protected void loadLandmarks() @@ -3247,18 +4052,26 @@ protected void loadLandmarks() public void loadLandmarks( final String filename ) { - File file = new File( filename ); + final File file = new File( filename ); setLastDirectory( file.getParentFile() ); - try + + if( filename.endsWith( "csv" )) { - landmarkModel.load( file ); + try + { + landmarkModel.load( file ); + } + catch ( final IOException e1 ) + { + e1.printStackTrace(); + } } - catch ( final IOException e1 ) + else if( filename.endsWith( "json" )) { - e1.printStackTrace(); + TransformWriterJson.read( file, this ); } - boolean didCompute = restimateTransformation(); + final boolean didCompute = restimateTransformation(); // didCompute = false means that there were not enough points // in the loaded points, so we should display the 'raw' moving @@ -3266,25 +4079,48 @@ public void loadLandmarks( final String filename ) if ( !didCompute ) setIsMovingDisplayTransformed( false ); + autoEstimateMask(); + viewerP.requestRepaint(); viewerQ.requestRepaint(); landmarkFrame.repaint(); } protected void saveSettings() + { + final File proposedSettingsFile = new File( "bigwarp.settings.xml" ); + saveSettingsOrProject( proposedSettingsFile ); + } + + protected void saveProject() + { + final File proposedSettingsFile = new File( "bigwarp-project.json" ); + saveSettingsOrProject( proposedSettingsFile ); + } + + protected void saveSettingsOrProject( final File proposedFile ) { final JFileChooser fileChooser = new JFileChooser( getLastDirectory() ); - File proposedSettingsFile = new File( "bigwarp.settings.xml" ); - fileChooser.setSelectedFile( proposedSettingsFile ); + File settingsFile; + fileChooser.setSelectedFile( proposedFile ); final int returnVal = fileChooser.showSaveDialog( null ); if ( returnVal == JFileChooser.APPROVE_OPTION ) { - proposedSettingsFile = fileChooser.getSelectedFile(); + settingsFile = fileChooser.getSelectedFile(); try { - saveSettings( proposedSettingsFile.getCanonicalPath() ); - } catch ( final IOException e ) + final String canonicalPath = settingsFile.getCanonicalPath(); + if ( canonicalPath.endsWith( ".xml" ) ) + { + saveSettings( canonicalPath ); + } + else + { + saveSettingsJson( canonicalPath ); + } + } + catch ( final IOException e ) { e.printStackTrace(); } @@ -3295,8 +4131,8 @@ protected void saveSettings( final String xmlFilename ) throws IOException { final Element root = new Element( "Settings" ); - Element viewerPNode = new Element( "viewerP" ); - Element viewerQNode = new Element( "viewerQ" ); + final Element viewerPNode = new Element( "viewerP" ); + final Element viewerQNode = new Element( "viewerQ" ); root.addContent( viewerPNode ); root.addContent( viewerQNode ); @@ -3309,8 +4145,8 @@ protected void saveSettings( final String xmlFilename ) throws IOException final Element autoSaveNode = new Element( "autosave" ); final Element autoSaveLocation = new Element( "location" ); - if( autoSaveDirectory != null ) - autoSaveLocation.setText( autoSaveDirectory.getAbsolutePath() ); + if ( autoSaver != null && autoSaver.autoSaveDirectory != null ) + autoSaveLocation.setText( autoSaver.autoSaveDirectory.getAbsolutePath() ); else autoSaveLocation.setText( getBigwarpSettingsFolder().getAbsolutePath() ); @@ -3320,53 +4156,183 @@ protected void saveSettings( final String xmlFilename ) throws IOException autoSaveNode.addContent( autoSaveLocation ); autoSaveNode.addContent( autoSavePeriod ); - root.addContent( autoSaveNode ); + root.addContent( autoSaveNode ); + if ( transformMask != null ) + { + // TODO check if is imported mask + root.addContent( plateauTransformMask.getRandomAccessible().toXml() ); + } final Document doc = new Document( root ); final XMLOutputter xout = new XMLOutputter( Format.getPrettyFormat() ); xout.output( doc, new FileWriter( xmlFilename ) ); } + protected void saveSettingsJson( final String jsonFilename ) throws IOException + { + final BigwarpSettings settings = getSettings(); + settings.serialize( jsonFilename ); + } + + public BigwarpSettings getSettings() + { + return new BigwarpSettings( + this, + viewerP, + viewerQ, + setupAssignments, + bookmarks, + autoSaver, + landmarkModel, + bwTransform, + data.sourceInfos + ); + } + protected void loadSettings() + { + loadSettingsOrProject( new File( "bigwarp.settings.xml" ) ); + } + + protected void loadProject() + { + loadSettingsOrProject( new File( "bigwarp-project.json" ) ); + } + + protected void loadSettingsOrProject( final File f ) { final JFileChooser fileChooser = new JFileChooser( getLastDirectory() ); - File proposedSettingsFile = new File( "bigwarp.settings.xml" ); - fileChooser.setSelectedFile( proposedSettingsFile ); + File settingsFile; + fileChooser.setSelectedFile( f ); final int returnVal = fileChooser.showOpenDialog( null ); if ( returnVal == JFileChooser.APPROVE_OPTION ) { - proposedSettingsFile = fileChooser.getSelectedFile(); - try - { - loadSettings( proposedSettingsFile.getCanonicalPath() ); - } catch ( final Exception e ) - { + settingsFile = fileChooser.getSelectedFile(); + + try { + loadSettings(settingsFile.getCanonicalPath(), true); + } catch (IOException | JDOMException e) { e.printStackTrace(); } + +// try { +// SwingUtilities.invokeAndWait( () -> { +// try { +// loadSettings(settingsFile.getCanonicalPath(), true); +// } catch (final IOException e) { +// e.printStackTrace(); +// } catch (final JDOMException e) { +// e.printStackTrace(); +// } +// }); +// } catch (final Exception e) { +// e.printStackTrace(); +// } + + // TODO I may need this +// Executors.newSingleThreadExecutor().execute(new Runnable() { +// @Override +// public void run() { +// try { +// loadSettings(settingsFile.getCanonicalPath(), true); +// } catch (final Exception e) { +// e.printStackTrace(); +// } +// } +// }); + } + } + + private void addInternalSource(int id) { + final boolean sourceWithIdPresent = data.sources.stream().map( it -> it.getConverter()).filter( it -> it instanceof ConverterSetup ).filter( it -> ( ( ConverterSetup ) it ).getSetupId() == id ).findAny().isPresent(); + if (sourceWithIdPresent) { + return; + } + switch ( id ) + { + case GRID_SOURCE_ID: + gridSource = addGridSource( ndims, data, "GridSource" ); + setGridType( GridSource.GRID_TYPE.LINE ); + break; + case WARPMAG_SOURCE_ID: + warpMagSource = addWarpMagnitudeSource( data, ndims == 2, "Warp magnitude" ); + break; + case JACDET_SOURCE_ID: + jacDetSource = addJacobianDeterminantSource( ndims, data, "Jacobian determinant" ); + break; + case TRANSFORM_MASK_SOURCE_ID: + updateTransformMask(); + break; + default: + break; } } - protected void loadSettings( final String xmlFilename ) throws IOException, + public void loadSettings( final String jsonOrXmlFilename ) throws IOException, + JDOMException + { + loadSettings( jsonOrXmlFilename, false ); + } + + public void loadSettings( final String jsonOrXmlFilename, boolean overwriteSources ) throws IOException, JDOMException { - final SAXBuilder sax = new SAXBuilder(); - final Document doc = sax.build( xmlFilename ); - final Element root = doc.getRootElement(); - viewerP.stateFromXml( root.getChild( "viewerP" ) ); - viewerQ.stateFromXml( root.getChild( "viewerQ" ) ); - setupAssignments.restoreFromXml( root ); - bookmarks.restoreFromXml( root ); - activeSourcesDialogP.update(); - activeSourcesDialogQ.update(); - - // auto-save settings - Element autoSaveElem = root.getChild( "autosave" ); - final String autoSavePath = autoSaveElem.getChild( "location" ).getText(); - final long autoSavePeriod = Integer.parseInt( autoSaveElem.getChild( "period" ).getText()); - setAutosaveFolder( new File( autoSavePath )); - BigWarpAutoSaver.setAutosaveOptions( this, autoSavePeriod, autoSavePath ); + if ( jsonOrXmlFilename.endsWith( ".xml" ) ) + { + final SAXBuilder sax = new SAXBuilder(); + final Document doc = sax.build( jsonOrXmlFilename ); + final Element root = doc.getRootElement(); + + /* add default sources if present */ + final List< Element > converterSetups = root.getChild( "SetupAssignments" ).getChild( "ConverterSetups" ).getChildren( "ConverterSetup" ); + for ( final Element converterSetup : converterSetups ) + { + final int id = Integer.parseInt( converterSetup.getChild( "id" ).getText() ); + addInternalSource( id ); + } + synchronizeSources(); + + viewerP.stateFromXml( root.getChild( "viewerP" ) ); + viewerQ.stateFromXml( root.getChild( "viewerQ" ) ); + setupAssignments.restoreFromXml( root ); + bookmarks.restoreFromXml( root ); + activeSourcesDialogP.update(); + activeSourcesDialogQ.update(); + + // auto-save settings + final Element autoSaveElem = root.getChild( "autosave" ); + final String autoSavePath = autoSaveElem.getChild( "location" ).getText(); + final long autoSavePeriod = Integer.parseInt( autoSaveElem.getChild( "period" ).getText() ); + BigWarpAutoSaver.setAutosaveOptions( this, autoSavePeriod, autoSavePath ); + + // TODO check if is imported mask + final Element maskSettings = root.getChild( "transform-mask" ); + if ( maskSettings != null ) + plateauTransformMask.getRandomAccessible().fromXml( maskSettings ); + } + else + { + final BigwarpSettings settings = getSettings(); + settings.setOverwriteSources( overwriteSources ); + settings.read( new JsonReader( new FileReader( jsonOrXmlFilename ) ) ); + + // TODO I may need this +// Executors.newSingleThreadExecutor().execute(new Runnable() { +// @Override +// public void run() { +// try { +// settings.read( new JsonReader( new FileReader( jsonOrXmlFilename ) ) ); +// } catch (final Exception e) { +// e.printStackTrace(); +// } +// } +// }); + + activeSourcesDialogP.update(); + activeSourcesDialogQ.update(); + } viewerFrameP.repaint(); viewerFrameQ.repaint(); diff --git a/src/main/java/bigwarp/BigWarp.java.orig b/src/main/java/bigwarp/BigWarp.java.orig new file mode 100755 index 00000000..d73e3fe6 --- /dev/null +++ b/src/main/java/bigwarp/BigWarp.java.orig @@ -0,0 +1,3632 @@ +/*- + * #%L + * BigWarp plugin for Fiji. + * %% + * Copyright (C) 2015 - 2021 Howard Hughes Medical Institute. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package bigwarp; + +<<<<<<< HEAD +import bdv.viewer.ConverterSetups; +import bdv.viewer.DisplayMode; +import bdv.viewer.TransformListener; +import bdv.viewer.ViewerState; +import java.awt.Color; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.FileDialog; +import java.awt.KeyEventPostProcessor; +import java.awt.KeyboardFocusManager; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Field; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; + +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JTable; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellEditor; + +import mpicbg.spim.data.SpimData; +import mpicbg.spim.data.XmlIoSpimData; +import mpicbg.spim.data.registration.ViewTransformAffine; + +import org.janelia.saalfeldlab.n5.Compression; +import org.janelia.saalfeldlab.n5.ij.N5Exporter; +import org.janelia.utility.geom.BoundingSphereRitter; +import org.janelia.utility.geom.Sphere; +import org.janelia.utility.ui.RepeatingReleasedEventsFixer; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +======= +>>>>>>> cmhulbert/jsonSettings +import bdv.BigDataViewer; +import bdv.cache.CacheControl; +import bdv.export.ProgressWriter; +import bdv.export.ProgressWriterConsole; +import bdv.gui.BigWarpLandmarkPanel; +import bdv.gui.BigWarpMessageAnimator; +import bdv.gui.BigWarpViewerFrame; +import bdv.gui.BigWarpViewerOptions; +import bdv.gui.BigwarpLandmarkSelectionPanel; +import bdv.gui.LandmarkKeyboardProcessor; +import bdv.gui.MaskedSourceEditorMouseListener; +import bdv.gui.TransformTypeSelectDialog; +import bdv.ij.ApplyBigwarpPlugin; +import bdv.ij.ApplyBigwarpPlugin.WriteDestinationOptions; +import bdv.ij.BigWarpToDeformationFieldPlugIn; +import bdv.ij.util.ProgressWriterIJ; +import bdv.img.WarpedSource; +import bdv.tools.InitializeViewerState; +import bdv.tools.VisibilityAndGroupingDialog; +import bdv.tools.bookmarks.Bookmarks; +import bdv.tools.bookmarks.BookmarksEditor; +import bdv.tools.brightness.BrightnessDialog; +import bdv.tools.brightness.ConverterSetup; +import bdv.tools.brightness.SetupAssignments; +import bdv.util.Bounds; +import bdv.viewer.BigWarpDragOverlay; +import bdv.viewer.BigWarpLandmarkFrame; +import bdv.viewer.BigWarpOverlay; +import bdv.viewer.BigWarpViewerPanel; +import bdv.viewer.BigWarpViewerSettings; +import bdv.viewer.ConverterSetups; +import bdv.viewer.DisplayMode; +import bdv.viewer.Interpolation; +import bdv.viewer.LandmarkPointMenu; +import bdv.viewer.MultiBoxOverlay2d; +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.SynchronizedViewerState; +import bdv.viewer.TransformListener; +import bdv.viewer.ViewerPanel; +import bdv.viewer.ViewerState; +import bdv.viewer.VisibilityAndGrouping; +import bdv.viewer.WarpNavigationActions; +import bdv.viewer.animate.SimilarityModel3D; +import bdv.viewer.animate.TranslationAnimator; +import bdv.viewer.overlay.BigWarpMaskSphereOverlay; +import bdv.viewer.overlay.BigWarpSourceOverlayRenderer; +import bdv.viewer.overlay.MultiBoxOverlayRenderer; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.loader.ImagePlusLoader.ColorSettings; +import bigwarp.source.GridSource; +import bigwarp.source.JacobianDeterminantSource; +import bigwarp.source.PlateauSphericalMaskSource; +import bigwarp.source.WarpMagnitudeSource; +import bigwarp.transforms.BigWarpTransform; +import bigwarp.transforms.WrappedCoordinateTransform; +import bigwarp.transforms.io.TransformWriterJson; +import bigwarp.util.BigWarpUtils; +import com.google.common.collect.Lists; +import com.google.gson.stream.JsonReader; +import fiji.util.gui.GenericDialogPlus; +import ij.IJ; +import ij.ImageJ; +import ij.ImagePlus; +import java.awt.Color; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.FileDialog; +import java.awt.KeyEventPostProcessor; +import java.awt.KeyboardFocusManager; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Field; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JTable; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellEditor; +import jitk.spline.ThinPlateR2LogRSplineKernelTransform; +import jitk.spline.XfmUtils; +import mpicbg.models.AbstractModel; +import mpicbg.models.AffineModel2D; +import mpicbg.models.AffineModel3D; +import mpicbg.models.CoordinateTransform; +import mpicbg.models.IllDefinedDataPointsException; +import mpicbg.models.InvertibleCoordinateTransform; +import mpicbg.models.NotEnoughDataPointsException; +import mpicbg.models.RigidModel2D; +import mpicbg.models.RigidModel3D; +import mpicbg.models.SimilarityModel2D; +import mpicbg.models.TranslationModel2D; +import mpicbg.models.TranslationModel3D; +import mpicbg.spim.data.SpimData; +import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.XmlIoSpimData; +import mpicbg.spim.data.registration.ViewTransformAffine; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealPoint; +import net.imglib2.display.RealARGBColorConverter; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.BoundingBoxEstimation; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.ThinplateSplineTransform; +import net.imglib2.realtransform.Wrapped2DTransformAs3D; +import net.imglib2.realtransform.inverse.RealTransformFiniteDerivatives; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.real.AbstractRealType; +import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.type.numeric.real.FloatType; +import org.janelia.saalfeldlab.n5.Compression; +import org.janelia.saalfeldlab.n5.ij.N5Exporter; +import org.janelia.utility.geom.BoundingSphereRitter; +import org.janelia.utility.geom.Sphere; +import org.janelia.utility.ui.RepeatingReleasedEventsFixer; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BigWarp< T > +{ + + protected static final int DEFAULT_WIDTH = 600; + + protected static final int DEFAULT_HEIGHT = 400; + + public static final int GRID_SOURCE_ID = 1696993146; + + public static final int WARPMAG_SOURCE_ID = 956736363; + + public static final int JACDET_SOURCE_ID = 1006827158; + + public static final int TRANSFORM_MASK_SOURCE_ID = 33872301; + + protected BigWarpViewerOptions options; + + protected BigWarpData< T > data; + + // descriptive names for indexing sources + protected int[] movingSourceIndexList; + + protected int[] targetSourceIndexList; + + protected List< SourceAndConverter< T > > sources; + + protected final SetupAssignments setupAssignments; + + protected final BrightnessDialog brightnessDialog; + + protected final WarpVisFrame warpVisDialog; + + protected final HelpDialog helpDialog; + + protected final SourceInfoDialog sourceInfoDialog; + + protected final VisibilityAndGroupingDialog activeSourcesDialogP; + + protected final VisibilityAndGroupingDialog activeSourcesDialogQ; + + final AffineTransform3D fixedViewXfm; + + private Bookmarks bookmarks; + + protected final BookmarksEditor bookmarkEditorP; + + protected final BookmarksEditor bookmarkEditorQ; + + private final BigWarpViewerFrame viewerFrameP; + + private final BigWarpViewerFrame viewerFrameQ; + + protected final BigWarpViewerPanel viewerP; + + protected final BigWarpViewerPanel viewerQ; + + protected final AffineTransform3D initialViewP; + + protected final AffineTransform3D initialViewQ; + + private JMenuItem toggleAlwaysWarpMenuP; + + private JMenuItem toggleAlwaysWarpMenuQ; + + protected BigWarpLandmarkPanel landmarkPanel; + + protected final LandmarkPointMenu landmarkPopupMenu; + + protected final BigWarpLandmarkFrame landmarkFrame; + + protected final BigWarpViewerSettings viewerSettings; + + protected final BigWarpOverlay overlayP; + + protected final BigWarpOverlay overlayQ; + + protected final BigWarpDragOverlay dragOverlayP; + + protected final BigWarpDragOverlay dragOverlayQ; + + protected RealPoint currentLandmark; + + protected LandmarkTableModel landmarkModel; + + protected InvertibleRealTransform currentTransform; + + protected JTable landmarkTable; + + protected LandmarkTableListener landmarkModellistener; + + protected MouseLandmarkListener landmarkClickListenerP; + + protected MouseLandmarkListener landmarkClickListenerQ; + + protected MouseLandmarkTableListener landmarkTableListener; + + protected MaskedSourceEditorMouseListener maskSourceMouseListenerP; + + protected MaskedSourceEditorMouseListener maskSourceMouseListenerQ; + + protected BigWarpMessageAnimator message; + + protected final Set< KeyEventPostProcessor > keyEventPostProcessorSet = new HashSet< KeyEventPostProcessor >(); + + private final RepeatingReleasedEventsFixer repeatedKeyEventsFixer; + + protected final SourceAndConverter< FloatType > gridSource; + + protected final SourceAndConverter< FloatType > warpMagSource; + + protected final SourceAndConverter< FloatType > jacDetSource; + + protected final SourceAndConverter< DoubleType > transformMaskSource; + + protected PlateauSphericalMaskSource transformMask; + + protected final AbstractModel< ? >[] baseXfmList; + + private final double[] ptBack; + + private SolveThread solverThread; + + private BigWarpTransform bwTransform; + + private BoundingBoxEstimation bboxOptions; + + private long keyClickMaxLength = 250; + + protected TransformTypeSelectDialog transformSelector; + + protected AffineTransform3D tmpTransform = new AffineTransform3D(); + + /* + * landmarks are placed on clicks only if we are inLandmarkMode during the + * click + */ + protected boolean inLandmarkMode; + + protected int baselineModelIndex; + + // file selection + final JFrame fileFrame; + + final FileDialog fileDialog; + + protected File lastDirectory; + + protected File lastLandmarks; + + protected BigWarpAutoSaver autoSaver; + + protected boolean updateWarpOnPtChange = false; + + protected boolean firstWarpEstimation = true; + + JMenu fileMenu; + + final ProgressWriter progressWriter; + + private static ImageJ ij; + + protected static Logger logger = LoggerFactory.getLogger( BigWarp.class ); + + private SpimData movingSpimData; + + private File movingImageXml; + + private CopyOnWriteArrayList< TransformListener< InvertibleRealTransform > > transformListeners = new CopyOnWriteArrayList<>( ); + + final int ndims; + + public BigWarp( final BigWarpData data, final String windowTitle, final ProgressWriter progressWriter ) throws SpimDataException + { + this( data, windowTitle, BigWarpViewerOptions.options( ( detectNumDims( data.sources ) == 2 ) ), progressWriter ); + } + + public BigWarp( final BigWarpData data, final String windowTitle, BigWarpViewerOptions options, final ProgressWriter progressWriter ) throws SpimDataException + { + repeatedKeyEventsFixer = RepeatingReleasedEventsFixer.installAnyTime(); + + ij = IJ.getInstance(); + + if( progressWriter == null ) + this.progressWriter = new ProgressWriterConsole(); + else + this.progressWriter = progressWriter; + + this.data = data; + this.options = options; + + ptBack = new double[ 3 ]; + if( options.is2d ) + ndims = 2; + else + ndims = 3; + + /* + * Set up LandmarkTableModel, holds the data and interfaces with the + * LandmarkPanel + */ + landmarkModel = new LandmarkTableModel( ndims ); + landmarkModellistener = new LandmarkTableListener(); + landmarkModel.addTableModelListener( landmarkModellistener ); + addTransformListener( landmarkModel ); + + /* Set up landmark panel */ + landmarkPanel = new BigWarpLandmarkPanel( landmarkModel ); + landmarkPanel.setOpaque( true ); + landmarkTable = landmarkPanel.getJTable(); + landmarkTable.setDefaultRenderer( Object.class, new WarningTableCellRenderer() ); + addDefaultTableMouseListener(); + + landmarkFrame = new BigWarpLandmarkFrame( "Landmarks", landmarkPanel, this ); + + baseXfmList = new AbstractModel< ? >[ 3 ]; + setupWarpMagBaselineOptions( baseXfmList, ndims ); + + fixedViewXfm = new AffineTransform3D(); + //sources.get( targetSourceIndexList[ 0 ] ).getSpimSource().getSourceTransform( 0, 0, fixedViewXfm ); + data.sources.get( data.targetSourceIndices[ 0 ] ).getSpimSource().getSourceTransform( 0, 0, fixedViewXfm ); + + baselineModelIndex = 0; + warpMagSource = addWarpMagnitudeSource( data, "WarpMagnitudeSource" ); + jacDetSource = addJacobianDeterminantSource( data, "JacobianDeterminantSource" ); + gridSource = addGridSource( data, "GridSource" ); + transformMaskSource = addTransformMaskSource( data, ndims, "Transform Mask Source" ); + + this.sources = this.data.sources; + final List< ConverterSetup > converterSetups = data.converterSetups; + this.movingSourceIndexList = data.movingSourceIndices; + this.targetSourceIndexList = data.targetSourceIndices; + Arrays.sort( movingSourceIndexList ); + Arrays.sort( targetSourceIndexList ); + + sources = wrapSourcesAsTransformed( data.sources, ndims, data ); + + setGridType( GridSource.GRID_TYPE.LINE ); + + viewerSettings = new BigWarpViewerSettings(); + + // key properties + final InputTriggerConfig keyProperties = BigDataViewer.getInputTriggerConfig( options ); + options = options.inputTriggerConfig( keyProperties ); + + // Viewer frame for the moving image + viewerFrameP = new BigWarpViewerFrame( this, DEFAULT_WIDTH, DEFAULT_HEIGHT, (List)sources, converterSetups, viewerSettings, + data.cache, options, "Bigwarp moving image", true, movingSourceIndexList, targetSourceIndexList ); + + viewerP = getViewerFrameP().getViewerPanel(); + + // Viewer frame for the fixed image + viewerFrameQ = new BigWarpViewerFrame( this, DEFAULT_WIDTH, DEFAULT_HEIGHT, (List)sources, converterSetups, viewerSettings, + data.cache, options, "Bigwarp fixed image", false, movingSourceIndexList, targetSourceIndexList ); + + viewerQ = getViewerFrameQ().getViewerPanel(); + + // setup messaging + message = options.getMessageAnimator(); + message.setViewers( viewerP, viewerQ ); + landmarkModel.setMessage( message ); + + // If the images are 2d, use a transform handler that limits + // transformations to rotations and scalings of the 2d plane ( z = 0 ) + if ( options.is2d ) + { + + final Class< ViewerPanel > c_vp = ViewerPanel.class; + try + { + final Field overlayRendererField = c_vp.getDeclaredField( "multiBoxOverlayRenderer" ); + overlayRendererField.setAccessible( true ); + + final MultiBoxOverlayRenderer overlayRenderP = new MultiBoxOverlayRenderer( DEFAULT_WIDTH, DEFAULT_HEIGHT ); + final MultiBoxOverlayRenderer overlayRenderQ = new MultiBoxOverlayRenderer( DEFAULT_WIDTH, DEFAULT_HEIGHT ); + + // TODO hopefully I won't' need reflection any more + final Field boxField = overlayRenderP.getClass().getDeclaredField( "box" ); + boxField.setAccessible( true ); + boxField.set( overlayRenderP, new MultiBoxOverlay2d() ); + boxField.set( overlayRenderQ, new MultiBoxOverlay2d() ); + boxField.setAccessible( false ); + + overlayRendererField.set( viewerP, overlayRenderP ); + overlayRendererField.set( viewerQ, overlayRenderQ ); + overlayRendererField.setAccessible( false ); + + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + } + + try + { + // TODO hopefully I won't' need reflection any more + final Class< ViewerPanel > c_vp = ViewerPanel.class; + final Field sourceInfoOverlayRendererField = c_vp.getDeclaredField( "sourceInfoOverlayRenderer" ); + sourceInfoOverlayRendererField.setAccessible( true ); + sourceInfoOverlayRendererField.set( viewerP, new BigWarpSourceOverlayRenderer() ); + sourceInfoOverlayRendererField.set( viewerQ, new BigWarpSourceOverlayRenderer() ); + sourceInfoOverlayRendererField.setAccessible( false ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + + viewerP.setNumDim( ndims ); + viewerQ.setNumDim( ndims ); + + activeSourcesDialogP = new VisibilityAndGroupingDialog( viewerFrameP, viewerP.getVisibilityAndGrouping() ); + activeSourcesDialogP.setTitle( "visibility and grouping ( moving )" ); + activeSourcesDialogQ = new VisibilityAndGroupingDialog( viewerFrameQ, viewerQ.getVisibilityAndGrouping() ); + activeSourcesDialogQ.setTitle( "visibility and grouping ( fixed )" ); + + // set sources to inactive at the start + for ( final SourceAndConverter< ? > source : Arrays.asList( warpMagSource, gridSource, jacDetSource, transformMaskSource ) ) + { + viewerP.state().setSourceActive( source, false ); + viewerQ.state().setSourceActive( source, false ); + } + + + overlayP = new BigWarpOverlay( viewerP, landmarkPanel ); + overlayQ = new BigWarpOverlay( viewerQ, landmarkPanel ); + viewerP.addOverlay( overlayP ); + viewerQ.addOverlay( overlayQ ); + + bwTransform = new BigWarpTransform( landmarkModel ); + bwTransform.initializeInverseParameters(data); + bwTransform.setLambda( transformMask.getRandomAccessible() ); + + solverThread = new SolveThread( this ); + solverThread.start(); + + bboxOptions = new BoundingBoxEstimation( BoundingBoxEstimation.Method.FACES, 5 ); + updateSourceBoundingBoxEstimators(); + + dragOverlayP = new BigWarpDragOverlay( this, viewerP, solverThread ); + dragOverlayQ = new BigWarpDragOverlay( this, viewerQ, solverThread ); + viewerP.addDragOverlay( dragOverlayP ); + viewerQ.addDragOverlay( dragOverlayQ ); + + landmarkPopupMenu = new LandmarkPointMenu( this ); + landmarkPopupMenu.setupListeners(); + + setupAssignments = new SetupAssignments( new ArrayList<>( converterSetups ), 0, 65535 ); + + brightnessDialog = new BrightnessDialog( landmarkFrame, setupAssignments ); + helpDialog = new HelpDialog( landmarkFrame ); + sourceInfoDialog = new SourceInfoDialog( landmarkFrame, data ); + + transformSelector = new TransformTypeSelectDialog( landmarkFrame, this ); + + // dialogs have to be constructed before action maps are made + warpVisDialog = new WarpVisFrame( viewerFrameQ, this ); + warpVisDialog.maskOptionsPanel.setMask( transformMask ); + + WarpNavigationActions.installActionBindings( getViewerFrameP().getKeybindings(), viewerFrameP, keyProperties, ( ndims == 2 ) ); + BigWarpActions.installActionBindings( getViewerFrameP().getKeybindings(), this, keyProperties ); + + WarpNavigationActions.installActionBindings( getViewerFrameQ().getKeybindings(), viewerFrameQ, keyProperties, ( ndims == 2 ) ); + BigWarpActions.installActionBindings( getViewerFrameQ().getKeybindings(), this, keyProperties ); + + BigWarpActions.installLandmarkPanelActionBindings( landmarkFrame.getKeybindings(), this, landmarkTable, keyProperties ); + + // this call has to come after the actions are set + warpVisDialog.setActions(); + warpVisDialog.toleranceSpinner.setValue( bwTransform.getInverseTolerance() ); + + setUpViewerMenu( viewerFrameP ); + setUpViewerMenu( viewerFrameQ ); + setUpLandmarkMenus(); + + /* Set the locations of frames */ + viewerFrameP.setLocation( 0, 0 ); + viewerFrameP.setSize( DEFAULT_WIDTH, DEFAULT_HEIGHT ); + viewerFrameQ.setLocation( DEFAULT_WIDTH, 0 ); + viewerFrameQ.setSize( DEFAULT_WIDTH, DEFAULT_HEIGHT ); + landmarkFrame.setLocation( 2 * DEFAULT_WIDTH, 0 ); + + landmarkClickListenerP = new MouseLandmarkListener( this.viewerP ); + landmarkClickListenerQ = new MouseLandmarkListener( this.viewerQ ); + addMaskMouseListener(); + + // have to be safe here and use 3dim point for both 3d and 2d + currentLandmark = new RealPoint( 3 ); + inLandmarkMode = false; + setupKeyListener(); + + initialViewP = new AffineTransform3D(); + initialViewQ = new AffineTransform3D(); + viewerP.state().getViewerTransform( initialViewP ); + viewerQ.state().getViewerTransform( initialViewQ ); + + // set colors and min/max ranges + viewerFrameP.setVisible( true ); + viewerFrameQ.setVisible( true ); + + landmarkFrame.pack(); + landmarkFrame.setVisible( true ); + + SwingUtilities.invokeLater( new Runnable() + { + @Override + public void run() + { + data.transferChannelSettings( viewerFrameP ); + data.transferChannelSettings( viewerFrameQ ); + } + } ); + + // set initial transforms so data are visible + InitializeViewerState.initTransform( viewerP ); + InitializeViewerState.initTransform( viewerQ ); + + checkBoxInputMaps(); + + // file selection + fileFrame = new JFrame( "Select File" ); + fileDialog = new FileDialog( fileFrame ); + + if ( ij == null || (IJ.getDirectory( "current" ) == null) ) + lastDirectory = new File( System.getProperty( "user.home" )); + else + lastDirectory = new File( IJ.getDirectory( "current" )); + + // default to linear interpolation + fileFrame.setVisible( false ); + + // add focus listener + //new BigwarpFocusListener( this ); + + bookmarks = new Bookmarks(); + bookmarkEditorP = new BookmarksEditor( viewerP, viewerFrameP.getKeybindings(), bookmarks ); + bookmarkEditorQ = new BookmarksEditor( viewerQ, viewerFrameQ.getKeybindings(), bookmarks ); + + // add landmark mode listener + //addKeyEventPostProcessor( new LandmarkModeListener() ); + } + + public int numDimensions() + { + return ndims; + } + + /** + * TODO Make a PR that updates this method in InitializeViewerState in bdv-core + * @deprecated Use {@link InitializeViewerState} method instead. + * + * @param cumulativeMinCutoff the min image intensity + * @param cumulativeMaxCutoff the max image intensity + * @param state the viewer state + * @param converterSetups the converter setups + */ + @Deprecated + public static void initBrightness( final double cumulativeMinCutoff, final double cumulativeMaxCutoff, final ViewerState state, final ConverterSetups converterSetups ) + { + final SourceAndConverter< ? > current = state.getCurrentSource(); + if ( current == null ) + return; + final Source< ? > source = current.getSpimSource(); + final int timepoint = state.getCurrentTimepoint(); + final Bounds bounds = InitializeViewerState.estimateSourceRange( source, timepoint, cumulativeMinCutoff, cumulativeMaxCutoff ); + final ConverterSetup setup = converterSetups.getConverterSetup( current ); + setup.setDisplayRange( bounds.getMinBound(), bounds.getMaxBound() ); + } + + public void addKeyEventPostProcessor( final KeyEventPostProcessor ke ) + { + keyEventPostProcessorSet.add( ke ); + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor( ke ); + } + + public void removeKeyEventPostProcessor( final KeyEventPostProcessor ke ) + { + keyEventPostProcessorSet.remove( ke ); + KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor( ke ); + } + + public void closeAll() + { + final ArrayList< KeyEventPostProcessor > ks = new ArrayList< KeyEventPostProcessor >( keyEventPostProcessorSet ); + for ( final KeyEventPostProcessor ke : ks ) + removeKeyEventPostProcessor( ke ); + + repeatedKeyEventsFixer.remove(); + + viewerFrameP.setVisible( false ); + viewerFrameQ.setVisible( false ); + landmarkFrame.setVisible( false ); + + viewerFrameP.getViewerPanel().stop(); + viewerFrameQ.getViewerPanel().stop(); + + viewerFrameP.dispose(); + viewerFrameQ.dispose(); + landmarkFrame.dispose(); + } + + public void setUpdateWarpOnChange( final boolean updateWarpOnPtChange ) + { + this.updateWarpOnPtChange = updateWarpOnPtChange; + } + + public void toggleUpdateWarpOnChange() + { + this.updateWarpOnPtChange = !this.updateWarpOnPtChange; + + if ( updateWarpOnPtChange ) + { + message.showMessage( "Always estimate transform on change" ); + + // toggleAlwaysWarpMenuP.setText( "Toggle always warp off" ); + // toggleAlwaysWarpMenuQ.setText( "Toggle always warp off" ); + } + else + { + message.showMessage( "Estimate transform on request only" ); + + // toggleAlwaysWarpMenuP.setText( "Warp on every point change" ); + // toggleAlwaysWarpMenuQ.setText( "Toggle always warp on" ); + } + } + + public boolean isUpdateWarpOnChange() + { + return updateWarpOnPtChange; + } + + public void invertPointCorrespondences() + { + landmarkModel = landmarkModel.invert(); + landmarkPanel.setTableModel( landmarkModel ); + } + + public File getLastDirectory() + { + return lastDirectory; + } + + public void setLastDirectory( final File dir ) + { + this.lastDirectory = dir; + } + + public void setSpotColor( final Color c ) + { + viewerSettings.setSpotColor( c ); + viewerP.requestRepaint(); + viewerQ.requestRepaint(); + } + + protected void setUpViewerMenu( final BigWarpViewerFrame vframe ) + { + // TODO setupviewermenu + + final ActionMap actionMap = vframe.getKeybindings().getConcatenatedActionMap(); + + final JMenuBar viewerMenuBar = new JMenuBar(); + + JMenu fileMenu = new JMenu( "File" ); + viewerMenuBar.add( fileMenu ); + + final JMenuItem openItem = new JMenuItem( actionMap.get( BigWarpActions.LOAD_LANDMARKS ) ); + openItem.setText( "Import landmarks" ); + fileMenu.add( openItem ); + + final JMenuItem saveItem = new JMenuItem( actionMap.get( BigWarpActions.SAVE_LANDMARKS )); + saveItem.setText( "Export landmarks" ); + fileMenu.add( saveItem ); + + fileMenu.addSeparator(); + final JMenuItem miLoadSettings = new JMenuItem( actionMap.get( BigWarpActions.LOAD_SETTINGS ) ); + miLoadSettings.setText( "Load settings" ); + fileMenu.add( miLoadSettings ); + + final JMenuItem miSaveSettings = new JMenuItem( actionMap.get( BigWarpActions.SAVE_SETTINGS ) ); + miSaveSettings.setText( "Save settings" ); + fileMenu.add( miSaveSettings ); + + if( ij != null ) + { + fileMenu.addSeparator(); + final JMenuItem exportToImagePlus = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_IP ) ); + exportToImagePlus.setText( "Export moving image" ); + fileMenu.add( exportToImagePlus ); + + final JMenuItem exportWarpField = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_WARP ) ); + exportWarpField.setText( "Export warp field" ); + fileMenu.add( exportWarpField ); + } + + final JMenu settingsMenu = new JMenu( "Settings" ); + viewerMenuBar.add( settingsMenu ); + + final JMenuItem toggleAlwaysWarpMenu; + if ( vframe.isMoving() ) + { + toggleAlwaysWarpMenuP = new JMenuItem( new BigWarpActions.ToggleAlwaysEstimateTransformAction( "", vframe ) ); + toggleAlwaysWarpMenu = toggleAlwaysWarpMenuP; + } + else + { + toggleAlwaysWarpMenuQ = new JMenuItem( new BigWarpActions.ToggleAlwaysEstimateTransformAction( "", vframe ) ); + toggleAlwaysWarpMenu = toggleAlwaysWarpMenuQ; + } + + toggleAlwaysWarpMenu.setText( "Toggle warp on drag" ); + settingsMenu.add( toggleAlwaysWarpMenu ); + + final JMenuItem miBrightness = new JMenuItem( actionMap.get( BigWarpActions.BRIGHTNESS_SETTINGS ) ); + miBrightness.setText( "Brightness & Color" ); + settingsMenu.add( miBrightness ); + + /* */ + final JMenuItem transformTypeMenu = new JMenuItem( actionMap.get( BigWarpActions.TRANSFORM_TYPE ) ); + transformTypeMenu.setText( "Transformation Options" ); + settingsMenu.add( transformTypeMenu ); + + /* Warp Visualization */ + final JMenuItem warpVisMenu = new JMenuItem( actionMap.get( BigWarpActions.SHOW_WARPTYPE_DIALOG ) ); + warpVisMenu.setText( "BigWarp Options" ); + settingsMenu.add( warpVisMenu ); + + vframe.setJMenuBar( viewerMenuBar ); + + final JMenu helpMenu = new JMenu( "Help" ); + viewerMenuBar.add( helpMenu ); + + final JMenuItem miHelp = new JMenuItem( actionMap.get( BigWarpActions.SHOW_HELP ) ); + miHelp.setText( "Show Help Menu" ); + helpMenu.add( miHelp ); + + final JMenuItem miSrcInfo = new JMenuItem( actionMap.get( BigWarpActions.SHOW_SOURCE_INFO ) ); + miSrcInfo.setText( "Show source information" ); + helpMenu.add( miSrcInfo ); + } + + protected void setupImageJExportOption() + { + final ActionMap actionMap = landmarkFrame.getKeybindings().getConcatenatedActionMap(); + + final JMenuItem exportToImagePlus = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_IP ) ); + exportToImagePlus.setText( "Export moving image" ); + fileMenu.add( exportToImagePlus ); + + final JMenuItem exportWarpField = new JMenuItem( actionMap.get( BigWarpActions.EXPORT_WARP ) ); + exportWarpField.setText( "Export warp field" ); + fileMenu.add( exportWarpField ); + } + + public void exportAsImagePlus( boolean virtual ) + { + exportAsImagePlus( virtual, "" ); + } + + @Deprecated + public void saveMovingImageToFile() + { + final JFileChooser fileChooser = new JFileChooser( getLastDirectory() ); + File proposedFile = new File( sources.get( movingSourceIndexList[ 0 ] ).getSpimSource().getName() ); + + fileChooser.setSelectedFile( proposedFile ); + final int returnVal = fileChooser.showSaveDialog( null ); + if ( returnVal == JFileChooser.APPROVE_OPTION ) + { + proposedFile = fileChooser.getSelectedFile(); + try + { + exportAsImagePlus( false, proposedFile.getCanonicalPath() ); + } catch ( final IOException e ) + { + e.printStackTrace(); + } + } + } + + public File saveMovingImageXml( ) + { + return saveMovingImageXml( null ); + } + + public File saveMovingImageXml( String proposedFilePath ) + { + + if ( movingSpimData == null ) + { + IJ.log("Cannot save warped moving image XML, because the input image was not a BDV/XML."); + return null; + } + + final AffineTransform3D bigWarpTransform = getMovingToFixedTransformAsAffineTransform3D(); + + System.out.println( "bigWarp transform as affine 3d: " + bigWarpTransform.toString() ); + + movingSpimData.getViewRegistrations().getViewRegistration( 0, 0 ).preconcatenateTransform( + new ViewTransformAffine( "Big Warp: " + bwTransform.getTransformType(), bigWarpTransform ) ); + + File proposedFile; + if ( proposedFilePath == null ) + { + final JFileChooser fileChooser = new JFileChooser( movingImageXml.getParent() ); + proposedFile = new File( movingImageXml.getName().replace( ".xml", "-bigWarp.xml" ) ); + + fileChooser.setSelectedFile( proposedFile ); + final int returnVal = fileChooser.showSaveDialog( null ); + if ( returnVal == JFileChooser.APPROVE_OPTION ) + proposedFile = fileChooser.getSelectedFile(); + else + return null; + } + else + { + proposedFile = new File( proposedFilePath ); + } + + try + { + new XmlIoSpimData().save( movingSpimData, proposedFile.getAbsolutePath() ); + } catch ( SpimDataException e ) + { + e.printStackTrace(); + } + + return proposedFile; + } + + public AffineTransform3D getMovingToFixedTransformAsAffineTransform3D() + { + double[][] affine3DMatrix = new double[ 3 ][ 4 ]; + double[][] affine2DMatrix = new double[ 2 ][ 3 ]; + + if ( currentTransform == null ) + { + return null; + } + + final InvertibleCoordinateTransform transform = + ( ( WrappedCoordinateTransform ) currentTransform ).getTransform().createInverse(); + + if ( transform instanceof AffineModel3D ) + { + ((AffineModel3D)transform).toMatrix( affine3DMatrix ); + } + else if ( transform instanceof SimilarityModel3D ) + { + ((SimilarityModel3D)transform).toMatrix( affine3DMatrix ); + } + else if ( transform instanceof RigidModel3D ) + { + ((RigidModel3D)transform).toMatrix( affine3DMatrix ); + } + else if ( transform instanceof TranslationModel3D ) + { + ((TranslationModel3D)transform).toMatrix( affine3DMatrix ); + } + else if ( transform instanceof AffineModel2D ) + { + ((AffineModel2D)transform).toMatrix( affine2DMatrix ); + affineMatrix2DtoAffineMatrix3D( affine2DMatrix, affine3DMatrix ); + } + else if ( transform instanceof SimilarityModel2D ) + { + ((SimilarityModel2D)transform).toMatrix( affine2DMatrix ); + affineMatrix2DtoAffineMatrix3D( affine2DMatrix, affine3DMatrix ); + } + else if ( transform instanceof RigidModel2D ) + { + ((RigidModel2D)transform).toMatrix( affine2DMatrix ); + affineMatrix2DtoAffineMatrix3D( affine2DMatrix, affine3DMatrix ); + } + else if ( transform instanceof TranslationModel2D ) + { + ((TranslationModel2D)transform).toMatrix( affine2DMatrix ); + affineMatrix2DtoAffineMatrix3D( affine2DMatrix, affine3DMatrix ); + } + else + { + IJ.error("Cannot convert transform of type " + transform.getClass().toString() + + "\nto an 3D affine tranform."); + return null; + } + + final AffineTransform3D bigWarpTransform = new AffineTransform3D(); + bigWarpTransform.set( affine3DMatrix ); + return bigWarpTransform; + } + + private void affineMatrix2DtoAffineMatrix3D( double[][] affine2DMatrix, double[][] affine3DMatrix ) + { + for ( int d = 0; d < 2; ++d ) + { + affine3DMatrix[ d ][ 0 ] = affine2DMatrix[ d ][ 0 ]; + affine3DMatrix[ d ][ 1 ] = affine2DMatrix[ d ][ 1 ]; + affine3DMatrix[ d ][ 3 ] = affine2DMatrix[ d ][ 2 ]; + } + } + + public void exportAsImagePlus( boolean virtual, String path ) + { + if( ij == null ) + return; + + final GenericDialogPlus gd = new GenericDialogPlus( "Apply Big Warp transform" ); + + gd.addMessage( "Field of view and resolution:" ); + gd.addChoice( "Resolution", + new String[]{ ApplyBigwarpPlugin.TARGET, ApplyBigwarpPlugin.MOVING, ApplyBigwarpPlugin.SPECIFIED }, + ApplyBigwarpPlugin.TARGET ); + + gd.addChoice( "Field of view", + new String[]{ ApplyBigwarpPlugin.TARGET, + ApplyBigwarpPlugin.MOVING_WARPED, + ApplyBigwarpPlugin.UNION_TARGET_MOVING, + ApplyBigwarpPlugin.LANDMARK_POINTS, + ApplyBigwarpPlugin.LANDMARK_POINT_CUBE_PIXEL, + ApplyBigwarpPlugin.LANDMARK_POINT_CUBE_PHYSICAL, + ApplyBigwarpPlugin.SPECIFIED_PIXEL, + ApplyBigwarpPlugin.SPECIFIED_PHYSICAL + }, + ApplyBigwarpPlugin.TARGET ); + + gd.addStringField( "point filter", "" ); + + gd.addMessage( "Resolution"); + gd.addNumericField( "x", 1.0, 4 ); + gd.addNumericField( "y", 1.0, 4 ); + gd.addNumericField( "z", 1.0, 4 ); + + gd.addMessage( "Offset"); + gd.addNumericField( "x", 0.0, 4 ); + gd.addNumericField( "y", 0.0, 4 ); + gd.addNumericField( "z", 0.0, 4 ); + + gd.addMessage( "Field of view"); + gd.addNumericField( "x", -1, 0 ); + gd.addNumericField( "y", -1, 0 ); + gd.addNumericField( "z", -1, 0 ); + + gd.addMessage( "Other Output options"); + gd.addChoice( "Interpolation", new String[]{ "Nearest Neighbor", "Linear" }, "Linear" ); + + gd.addMessage( "Virtual: fast to display,\n" + + "low memory requirements,\nbut slow to navigate" ); + gd.addCheckbox( "virtual?", false ); + int defaultCores = (int)Math.ceil( Runtime.getRuntime().availableProcessors()/4); + gd.addNumericField( "threads", defaultCores, 0 ); + + gd.addMessage( "Writing options (leave empty to opena new image window)" ); + gd.addDirectoryOrFileField( "File or n5 root", "" ); + gd.addStringField( "n5 dataset", "" ); + gd.addStringField( "n5 block size", "32" ); + gd.addChoice( "n5 compression", new String[] { + N5Exporter.GZIP_COMPRESSION, + N5Exporter.RAW_COMPRESSION, + N5Exporter.LZ4_COMPRESSION, + N5Exporter.XZ_COMPRESSION, + N5Exporter.BLOSC_COMPRESSION }, + N5Exporter.GZIP_COMPRESSION ); + + gd.showDialog(); + + if ( gd.wasCanceled() ) + return; + + final String resolutionOption = gd.getNextChoice(); + final String fieldOfViewOption = gd.getNextChoice(); + final String fieldOfViewPointFilter = gd.getNextString(); + + final double[] resolutionSpec = new double[ 3 ]; + resolutionSpec[ 0 ] = gd.getNextNumber(); + resolutionSpec[ 1 ] = gd.getNextNumber(); + resolutionSpec[ 2 ] = gd.getNextNumber(); + + final double[] offsetSpec = new double[ 3 ]; + offsetSpec[ 0 ] = gd.getNextNumber(); + offsetSpec[ 1 ] = gd.getNextNumber(); + offsetSpec[ 2 ] = gd.getNextNumber(); + + final double[] fovSpec = new double[ 3 ]; + fovSpec[ 0 ] = gd.getNextNumber(); + fovSpec[ 1 ] = gd.getNextNumber(); + fovSpec[ 2 ] = gd.getNextNumber(); + + + final String interpType = gd.getNextChoice(); + final boolean isVirtual = gd.getNextBoolean(); + final int nThreads = (int)gd.getNextNumber(); + + final String fileOrN5Root = gd.getNextString(); + final String n5Dataset = gd.getNextString(); + final String blockSizeString = gd.getNextString(); + final String compressionString = gd.getNextChoice(); + + final int[] blockSize = ApplyBigwarpPlugin.parseBlockSize( blockSizeString, this.ndims ); + final Compression compression = ApplyBigwarpPlugin.getCompression( compressionString ); + final WriteDestinationOptions writeOpts = new ApplyBigwarpPlugin.WriteDestinationOptions( fileOrN5Root, n5Dataset, + blockSize, compression ); + + final Interpolation interp; + if( interpType.equals( "Nearest Neighbor" )) + interp = Interpolation.NEARESTNEIGHBOR; + else + interp = Interpolation.NLINEAR; + + final double[] res = ApplyBigwarpPlugin.getResolution( this.data, resolutionOption, resolutionSpec ); + + final List outputIntervalList = ApplyBigwarpPlugin.getPixelInterval( this.data, + this.landmarkModel, this.currentTransform, + fieldOfViewOption, fieldOfViewPointFilter, bboxOptions, fovSpec, offsetSpec, res ); + + final List matchedPtNames = new ArrayList<>(); + if( outputIntervalList.size() > 1 ) + ApplyBigwarpPlugin.fillMatchedPointNames( matchedPtNames, getLandmarkPanel().getTableModel(), fieldOfViewPointFilter ); + + + // export has to be treated differently if we're doing fov's around + // landmark centers (because multiple images can be exported this way ) + if( matchedPtNames.size() > 0 ) + { + BigwarpLandmarkSelectionPanel selection = new BigwarpLandmarkSelectionPanel<>( + data, sources, fieldOfViewOption, + outputIntervalList, matchedPtNames, interp, + offsetSpec, res, isVirtual, nThreads, + progressWriter ); + } + else + { + if( writeOpts.n5Dataset != null && !writeOpts.n5Dataset.isEmpty()) + { + final String unit = ApplyBigwarpPlugin.getUnit( data, resolutionOption ); + // export async + new Thread() + { + @Override + public void run() + { + progressWriter.setProgress( 0.01 ); + ApplyBigwarpPlugin.runN5Export( data, sources, fieldOfViewOption, + outputIntervalList.get( 0 ), interp, + offsetSpec, res, unit, + progressWriter, writeOpts, + Executors.newFixedThreadPool( nThreads ) ); + + progressWriter.setProgress( 1.00 ); + } + }.start(); + } + else + { + // export + final boolean show = ( writeOpts.pathOrN5Root == null || writeOpts.pathOrN5Root.isEmpty() ); + ApplyBigwarpPlugin.runExport( data, sources, fieldOfViewOption, + outputIntervalList, matchedPtNames, interp, + offsetSpec, res, isVirtual, nThreads, + progressWriter, show, false, writeOpts ); + } + } + } + + public void exportWarpField() + { + BigWarpToDeformationFieldPlugIn dfieldExporter = new BigWarpToDeformationFieldPlugIn(); + dfieldExporter.runFromBigWarpInstance( landmarkModel, sources, targetSourceIndexList ); + } + + protected void setUpLandmarkMenus() + { + final ActionMap actionMap = landmarkFrame.getKeybindings().getConcatenatedActionMap(); + + final JMenuBar landmarkMenuBar = new JMenuBar(); + fileMenu = new JMenu( "File" ); + final JMenuItem openItem = new JMenuItem( actionMap.get( BigWarpActions.LOAD_LANDMARKS ) ); + openItem.setText( "Import landmarks" ); + fileMenu.add( openItem ); + + final JMenuItem saveItem = new JMenuItem( actionMap.get( BigWarpActions.SAVE_LANDMARKS )); + saveItem.setText( "Export landmarks" ); + fileMenu.add( saveItem ); + + fileMenu.addSeparator(); + final JMenuItem exportImageItem = new JMenuItem( "Export Moving Image" ); + + landmarkMenuBar.add( fileMenu ); + landmarkFrame.setJMenuBar( landmarkMenuBar ); + // exportMovingImage( file, state, progressWriter ); + + final JMenuItem saveExport = new JMenuItem( actionMap.get( BigWarpActions.SAVE_WARPED ) ); + saveExport.setText( "Save warped image" ); + fileMenu.add( saveExport ); + + final JMenu landmarkMenu = new JMenu( "Landmarks" ); + final JMenuItem landmarkGridItem = new JMenuItem( actionMap.get( BigWarpActions.LANDMARK_GRID_DIALOG ) ); + landmarkGridItem.setText( "Build landmark grid" ); + landmarkMenu.add( landmarkGridItem ); + + landmarkMenuBar.add( landmarkMenu ); + + + if( ij != null ) + setupImageJExportOption(); + } + + public Bookmarks getBookmarks() + { + return bookmarks; + } + + public BigWarpViewerFrame getViewerFrameP() + { + return viewerFrameP; + } + + public BigWarpViewerFrame getViewerFrameQ() + { + return viewerFrameQ; + } + + public BigWarpOverlay getOverlayP() + { + return overlayP; + } + + public BigWarpOverlay getOverlayQ() + { + return overlayQ; + } + + public SetupAssignments getSetupAssignments() + { + return setupAssignments; + } + + public BigWarpData getData() + { + return data; + } + + public List< SourceAndConverter< T > > getSources() + { + return sources; + } + + public BigWarpLandmarkFrame getLandmarkFrame() + { + return landmarkFrame; + } + + public BigWarpLandmarkPanel getLandmarkPanel() + { + return landmarkPanel; + } + + public boolean isInLandmarkMode() + { + return inLandmarkMode; + } + + public void toggleInLandmarkMode() + { + setInLandmarkMode( !inLandmarkMode ); + } + + public void setInLandmarkMode( final boolean inLmMode ) + { + if( inLandmarkMode == inLmMode ) + return; + + if ( inLmMode ) + { + disableTransformHandlers(); + message.showMessage( "Landmark mode on" ); + viewerFrameP.setCursor( Cursor.getPredefinedCursor( Cursor.CROSSHAIR_CURSOR ) ); + viewerFrameQ.setCursor( Cursor.getPredefinedCursor( Cursor.CROSSHAIR_CURSOR ) ); + } + else + { + enableTransformHandlers(); + message.showMessage( "Landmark mode off" ); + viewerFrameP.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) ); + viewerFrameQ.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) ); + } + inLandmarkMode = inLmMode; + } + + /** + * + * @param ptarray location of the clicked point + * @param isMoving is the viewer in moving space + * @param selectedPointIndex the index of the selected point + * @param viewer the BigWarpViewerPanel clicked on + */ + public void updatePointLocation( final double[] ptarray, final boolean isMoving, final int selectedPointIndex, final BigWarpViewerPanel viewer ) + { + final boolean isMovingViewerXfm = viewer.getOverlay().getIsTransformed(); + + // TODO avoid duplicate effort and comment this section + if ( landmarkModel.getTransform() == null || !isMovingViewerXfm ) + { + landmarkModel.setPoint( selectedPointIndex, isMoving, ptarray, false, currentTransform ); + + if ( !isMoving && !landmarkModel.isWarped( selectedPointIndex ) ) + landmarkModel.updateWarpedPoint( selectedPointIndex, ptarray ); + } + else + { + landmarkModel.getTransform().apply( ptarray, ptBack ); + landmarkModel.updateWarpedPoint( selectedPointIndex, ptarray ); + } + + if ( landmarkFrame.isVisible() ) + { + landmarkFrame.repaint(); + viewer.requestRepaint(); + } + } + + public void updatePointLocation( final double[] ptarray, final boolean isMoving, final int selectedPointIndex ) + { + if ( isMoving ) + updatePointLocation( ptarray, isMoving, selectedPointIndex, viewerP ); + else + updatePointLocation( ptarray, isMoving, selectedPointIndex, viewerQ ); + } + + public int updateWarpedPoint( final double[] ptarray, final boolean isMoving ) + { + final int selectedPointIndex = selectedLandmark( ptarray, isMoving ); + + // if a fixed point is changing its location, + // we need to update the warped position for the corresponding moving point + // so that it can be rendered correctly + if ( selectedPointIndex >= 0 && !isMoving && landmarkModel.getTransform() != null ) + { + landmarkModel.updateWarpedPoint( selectedPointIndex, ptarray ); + } + + return selectedPointIndex; + } + + public void updateRowSelection( boolean isMoving, int lastRowEdited ) + { + updateRowSelection( landmarkModel, landmarkTable, isMoving, lastRowEdited ); + landmarkPanel.repaint(); + } + + public static void updateRowSelection( + LandmarkTableModel landmarkModel, JTable table, + boolean isMoving, int lastRowEdited ) + { + logger.trace( "updateRowSelection " ); + + int i = landmarkModel.getNextRow( isMoving ); + if ( i < table.getRowCount() ) + { + logger.trace( " landmarkTable ( updateRowSelection ) selecting row " + i ); + table.setRowSelectionInterval( i, i ); + } else if( lastRowEdited >= 0 && lastRowEdited < table.getRowCount() ) + table.setRowSelectionInterval( lastRowEdited, lastRowEdited ); + } + + /** + * Returns the index of the selected row, if it is unpaired, -1 otherwise + * + * @param isMoving isMoving + * @return index of the selected row + */ + public int getSelectedUnpairedRow( boolean isMoving ) + { + int row = landmarkTable.getSelectedRow(); + if( row >= 0 && ( isMoving ? !landmarkModel.isMovingPoint( row ) : !landmarkModel.isFixedPoint( row ))) + return row; + + return -1; + } + + /** + * Updates the global variable ptBack + * + * @param ptarray the point location + * @param isMoving is the point location in moving image space + * @param viewer the viewer panel + * @return an error string if an error occurred, empty string otherwise + */ + public String addPoint( final double[] ptarray, final boolean isMoving, final BigWarpViewerPanel viewer ) + { + final boolean isViewerTransformed = viewer.getOverlay().getIsTransformed(); + + if ( !isMoving && viewer.getIsMoving() && !isViewerTransformed ) + return "Adding a fixed point in moving image space not supported"; + else + { + addPoint( ptarray, isMoving ); + return ""; + } + } + + /** + * Updates the global variable ptBack + * + * @param ptarray the point location + * @param isMoving is the point location in moving image space + * @return true if a new row was created + */ + public boolean addPoint( final double[] ptarray, final boolean isMoving ) + { + final boolean isWarped = ( isMoving && landmarkModel.getTransform() != null && BigWarp.this.isMovingDisplayTransformed() ); + + InvertibleRealTransform transform; + if( options.is2d && currentTransform != null ) + transform = ((Wrapped2DTransformAs3D)currentTransform).getTransform(); + else + transform = currentTransform; + + // TODO check this (current transform part) + final boolean didAdd = BigWarp.this.landmarkModel.pointEdit( -1, ptarray, false, isMoving, isWarped, true, transform ); + + if ( BigWarp.this.landmarkFrame.isVisible() ) + { + BigWarp.this.landmarkFrame.repaint(); + } + return didAdd; + } + + protected int selectedLandmark( final double[] pt, final boolean isMoving ) + { + return selectedLandmark( pt, isMoving, true ); + } + + /** + * Returns the index of the landmark closest to the input point, + * if it is within a certain distance threshold. + * + * Updates the global variable ptBack + * + * @param pt the point location + * @param isMoving is the point location in moving image space + * @param selectInTable also select the landmark in the table + * @return the index of the selected landmark + */ + protected int selectedLandmark( final double[] pt, final boolean isMoving, final boolean selectInTable ) + { + logger.trace( "clicked: " + XfmUtils.printArray( pt ) ); + + // TODO selectedLandmark + final int N = landmarkModel.getRowCount(); + + // a point will be selected if you click inside the spot ( with a 5 pixel buffer ) + double radsq = ( viewerSettings.getSpotSize() * viewerSettings.getSpotSize() ) + 5 ; + final AffineTransform3D viewerXfm = new AffineTransform3D(); + if ( isMoving ) //&& !isMovingDisplayTransformed() ) + { + viewerP.state().getViewerTransform( viewerXfm ); + radsq = viewerP.getSettings().getSpotSize(); + } + else + { + viewerQ.state().getViewerTransform( viewerXfm ); + radsq = viewerQ.getSettings().getSpotSize(); + } + radsq = ( radsq * radsq ); + final double scale = computeScaleAssumeRigid( viewerXfm ); + + double dist = 0.0; + int bestIdx = -1; + double smallestDist = Double.MAX_VALUE; + + logger.trace( " selectedLandmarkHelper dist scale: " + scale ); + logger.trace( " selectedLandmarkHelper radsq: " + radsq ); + + for ( int n = 0; n < N; n++ ) + { + final Double[] lmpt; + if( isMoving && landmarkModel.isWarped( n ) && isMovingDisplayTransformed() ) + { + lmpt = landmarkModel.getWarpedPoints().get( n ); + } + else if( isMoving && isMovingDisplayTransformed() ) + { + lmpt = landmarkModel.getPoints( false ).get( n ); + } + else + { + lmpt = landmarkModel.getPoints( isMoving ).get( n ); + } + + dist = 0.0; + for ( int i = 0; i < landmarkModel.getNumdims(); i++ ) + { + dist += ( pt[ i ] - lmpt[ i ] ) * ( pt[ i ] - lmpt[ i ] ); + } + + dist *= ( scale * scale ); + logger.trace( " dist squared of lm index : " + n + " is " + dist ); + if ( dist < radsq && dist < smallestDist ) + { + smallestDist = dist; + bestIdx = n; + } + } + + if ( selectInTable && landmarkFrame.isVisible() ) + { + if( landmarkTable.isEditing()) + { + landmarkTable.getCellEditor().stopCellEditing(); + } + + landmarkTable.setEditingRow( bestIdx ); + landmarkFrame.repaint(); + } + logger.trace( "selectedLandmark: " + bestIdx ); + return bestIdx; + } + + public static double computeScaleAssumeRigid( final AffineTransform3D xfm ) + { + return xfm.get( 0, 0 ) + xfm.get( 0, 1 ) + xfm.get( 0, 2 ); + } + + protected void disableTransformHandlers() + { + // disable navigation listeners + viewerFrameP.setTransformEnabled( false ); + viewerFrameQ.setTransformEnabled( false ); + } + + protected void enableTransformHandlers() + { + // enable navigation listeners + viewerFrameP.setTransformEnabled( true ); + viewerFrameQ.setTransformEnabled( true ); + } + + private void printSourceTransforms() + { + final AffineTransform3D xfm = new AffineTransform3D(); + for( SourceAndConverter sac : data.sources ) + { + sac.getSpimSource().getSourceTransform(0, 0, xfm ); + double det = BigWarpUtils.det( xfm ); + System.out.println( "source xfm ( " + sac.getSpimSource().getName() + " ) : " + xfm ); + System.out.println( " det = " + det ); + } + } + + private void printViewerTransforms() + { + final AffineTransform3D xfm = new AffineTransform3D(); + viewerP.state().getViewerTransform( xfm ); + System.out.println( "mvg viewer xfm: " + xfm ); + System.out.println( " det = " + BigWarpUtils.det( xfm )); + System.out.println( " dotxy = " + BigWarpUtils.dotXy( xfm )); + + viewerQ.state().getViewerTransform( xfm ); + System.out.println( "tgt viewer xfm: " + xfm ); + System.out.println( " det = " + BigWarpUtils.det( xfm )); + System.out.println( " dotxy = " + BigWarpUtils.dotXy( xfm )); + } + + /** + * Changes the view transformation of 'panelToChange' to match that of 'panelToMatch' + * @param panelToChange the viewer panel whose transform will change + * @param panelToMatch the viewer panel the transform will come from + */ + protected void matchWindowTransforms( final BigWarpViewerPanel panelToChange, final BigWarpViewerPanel panelToMatch ) + { + panelToChange.showMessage( "Aligning" ); + panelToMatch.showMessage( "Matching alignment" ); + + // get the transform from panelToMatch + final AffineTransform3D viewXfm = new AffineTransform3D(); + panelToMatch.state().getViewerTransform( viewXfm ); + + // change transform of panelToChange + panelToChange.animateTransformation( viewXfm ); + } + + public void matchOtherViewerPanelToActive() + { + if ( inLandmarkMode ) + { + message.showMessage( "Can't move viewer in landmark mode." ); + return; + } + + BigWarpViewerPanel panelToChange; + BigWarpViewerPanel panelToMatch; + + if ( viewerFrameP.isActive() ) + { + panelToChange = viewerQ; + panelToMatch = viewerP; + } + else if ( viewerFrameQ.isActive() ) + { + panelToChange = viewerP; + panelToMatch = viewerQ; + } + else + return; + + matchWindowTransforms( panelToChange, panelToMatch ); + } + + public void matchActiveViewerPanelToOther() + { + if ( inLandmarkMode ) + { + message.showMessage( "Can't move viewer in landmark mode." ); + return; + } + + BigWarpViewerPanel panelToChange; + BigWarpViewerPanel panelToMatch; + + if ( viewerFrameP.isActive() ) + { + panelToChange = viewerP; + panelToMatch = viewerQ; + } + else if ( viewerFrameQ.isActive() ) + { + panelToChange = viewerQ; + panelToMatch = viewerP; + } + else + return; + + matchWindowTransforms( panelToChange, panelToMatch ); + } + + public void warpToNearest( BigWarpViewerPanel viewer ) + { + if ( inLandmarkMode ) + { + message.showMessage( "Can't move viewer in landmark mode." ); + return; + } + + RealPoint mousePt = new RealPoint( 3 ); // need 3d point even for 2d images + viewer.getGlobalMouseCoordinates( mousePt ); + warpToLandmark( landmarkModel.getIndexNearestTo( mousePt, viewer.getIsMoving() ), viewer ); + } + + public void warpToLandmark( int row, BigWarpViewerPanel viewer ) + { + if( inLandmarkMode ) + { + message.showMessage( "Can't move viewer in landmark mode." ); + return; + } + + int offset = 0; + int ndims = landmarkModel.getNumdims(); + double[] pt = null; + if( viewer.getIsMoving() && viewer.getOverlay().getIsTransformed() ) + { + if ( BigWarp.this.landmarkModel.isWarped( row ) ) + { + pt = LandmarkTableModel.toPrimitive( BigWarp.this.landmarkModel.getWarpedPoints().get( row ) ); + } + else + { + offset = ndims; + } + }else if( !viewer.getIsMoving() ) + { + offset = ndims; + } + + if ( pt == null ) + { + if ( ndims == 3 ) + + pt = new double[] { + (Double) landmarkModel.getValueAt( row, offset + 2 ), + (Double) landmarkModel.getValueAt( row, offset + 3 ), + (Double) landmarkModel.getValueAt( row, offset + 4 ) }; + else + pt = new double[] { + (Double) landmarkModel.getValueAt( row, offset + 2 ), + (Double) landmarkModel.getValueAt( row, offset + 3 ), 0.0 }; + } + + // we have an unmatched point + if ( Double.isInfinite( pt[ 0 ] ) ) + return; + + final AffineTransform3D transform = viewer.state().getViewerTransform(); + final AffineTransform3D xfmCopy = transform.copy(); + xfmCopy.set( 0.0, 0, 3 ); + xfmCopy.set( 0.0, 1, 3 ); + xfmCopy.set( 0.0, 2, 3 ); + + final double[] center = new double[] { viewer.getWidth() / 2, viewer.getHeight() / 2, 0 }; + final double[] ptxfm = new double[ 3 ]; + xfmCopy.apply( pt, ptxfm ); + + // select appropriate row in the table + landmarkTable.setRowSelectionInterval( row, row ); + + // this should work fine in the 2d case + final TranslationAnimator animator = new TranslationAnimator( transform, new double[] { center[ 0 ] - ptxfm[ 0 ], center[ 1 ] - ptxfm[ 1 ], -ptxfm[ 2 ] }, 300 ); + viewer.setTransformAnimator( animator ); + } + + public void goToBookmark() + { + if ( viewerFrameP.isActive() ) + { + bookmarkEditorP.initGoToBookmark(); + } + else if ( viewerFrameQ.isActive() ) + { + bookmarkEditorQ.initGoToBookmark(); + } + } + + public void resetView() + { + final RandomAccessibleInterval< ? > interval = getSources().get( 1 ).getSpimSource().getSource( 0, 0 ); + + final AffineTransform3D viewXfm = new AffineTransform3D(); + viewXfm.identity(); + viewXfm.set( -interval.min( 2 ), 2, 3 ); + + if ( viewerFrameP.isActive() ) + { + if ( viewerP.getOverlay().getIsTransformed() ) + viewerP.animateTransformation( initialViewQ ); + else + viewerP.animateTransformation( initialViewP ); + } + else if ( viewerFrameQ.isActive() ) + viewerQ.animateTransformation( initialViewQ ); + } + + public void toggleNameVisibility() + { + viewerSettings.toggleNamesVisible(); + viewerP.requestRepaint(); + viewerQ.requestRepaint(); + } + + public void togglePointVisibility() + { + viewerSettings.togglePointsVisible(); + viewerP.requestRepaint(); + viewerQ.requestRepaint(); + } + + /** + * Toggles whether the moving image is displayed after warping (in the same + * space as the fixed image), or in its native space. + * + * @return true of the display mode changed + */ + public boolean toggleMovingImageDisplay() + { + // If this is the first time calling the toggle, there may not be enough + // points to estimate a reasonable transformation. + // return early if an re-estimation did not occur + boolean success = restimateTransformation(); + logger.trace( "toggleMovingImageDisplay, success: " + success ); + if ( !success ) + { + message.showMessage( "Require at least 4 points to estimate a transformation" ); + return false; + } + + final boolean newState = !getOverlayP().getIsTransformed(); + + if ( newState ) + message.showMessage( "Displaying warped" ); + else + message.showMessage( "Displaying raw" ); + + // Toggle whether moving image is displayed as transformed or not + setIsMovingDisplayTransformed( newState ); + return success; + } + + public void toggleMaskOverlayVisibility() + { + getViewerFrameQ().getViewerPanel().getMaskOverlay().toggleVisible(); + } + + public void setMaskOverlayVisibility( final boolean visible ) + { + getViewerFrameQ().getViewerPanel().getMaskOverlay().setVisible( visible ); + } + + protected void addDefaultTableMouseListener() + { + landmarkTableListener = new MouseLandmarkTableListener(); + landmarkPanel.getJTable().addMouseListener( landmarkTableListener ); + } + + protected void addMaskMouseListener() + { + final Color[] maskColors = new Color[]{ Color.ORANGE, Color.YELLOW }; + // only render mask overlay on target window + viewerQ.setMaskOverlay( new BigWarpMaskSphereOverlay( viewerQ, maskColors, numDimensions() == 3 )); + + maskSourceMouseListenerP = new MaskedSourceEditorMouseListener( getLandmarkPanel().getTableModel().getNumdims(), this, viewerP ); + maskSourceMouseListenerP.setActive( false ); + maskSourceMouseListenerP.setMask( transformMask.getRandomAccessible() ); + + maskSourceMouseListenerQ = new MaskedSourceEditorMouseListener( getLandmarkPanel().getTableModel().getNumdims(), this, viewerQ ); + maskSourceMouseListenerQ.setActive( false ); + maskSourceMouseListenerQ.setMask( transformMask.getRandomAccessible() ); + +<<<<<<< HEAD + tpsMask.getRandomAccessible().setOverlays( Arrays.asList( viewerQ.getMaskOverlay() )); +======= + transformMask.getRandomAccessible().setOverlays( Arrays.asList( viewerP.getMaskOverlay(), viewerQ.getMaskOverlay() )); +>>>>>>> cmhulbert/jsonSettings + } + + public void setGridType( final GridSource.GRID_TYPE method ) + { + ( ( GridSource< ? > ) gridSource.getSpimSource() ).setMethod( method ); + } + + public static List< SourceAndConverter > wrapSourcesAsTransformed( final List< SourceAndConverter > sources, + final int ndims, + final BigWarpData data ) + { + final List< SourceAndConverter> wrappedSource = new ArrayList<>(); + + int[] warpUsIndices = data.movingSourceIndices; + HashMap, ColorSettings> colorSettings = data.sourceColorSettings; + + int i = 0; + for ( final SourceAndConvertersac : sources ) + { + int idx = Arrays.binarySearch( warpUsIndices, i ); + if ( idx >= 0 ) + { + SourceAndConverter newSac = wrapSourceAsTransformed( sac, "xfm_" + i, ndims ); + wrappedSource.add( newSac ); + colorSettings.put( newSac, colorSettings.get( sac )); + } + else + { + wrappedSource.add( sac ); + } + + i++; + } + return wrappedSource; + } + + @SuppressWarnings( { "rawtypes", "unchecked" } ) + private static < T > SourceAndConverter< FloatType > addJacobianDeterminantSource( final BigWarpData< T > data, final String name ) + { + // TODO think about whether its worth it to pass a type parameter. + // or should we just stick with Doubles? + + final JacobianDeterminantSource< FloatType > jdSource = new JacobianDeterminantSource<>( name, data, new FloatType() ); + final RealARGBColorConverter< FloatType > converter = RealARGBColorConverter.create( new FloatType(), 0, 512 ); + converter.setColor( new ARGBType( 0xffffffff ) ); + final SourceAndConverter< FloatType > soc = new SourceAndConverter<>( jdSource, converter, null ); + data.converterSetups.add( BigDataViewer.createConverterSetup( soc, JACDET_SOURCE_ID ) ); + data.sources.add( ( SourceAndConverter ) soc ); + return soc; + } + + @SuppressWarnings( { "rawtypes", "unchecked" } ) + private static < T > SourceAndConverter< FloatType > addWarpMagnitudeSource( final BigWarpData< T > data, final String name ) + { + // TODO think about whether its worth it to pass a type parameter. + // or should we just stick with Doubles? + + final WarpMagnitudeSource< FloatType > magSource = new WarpMagnitudeSource<>( name, data, new FloatType() ); + final RealARGBColorConverter< FloatType > converter = RealARGBColorConverter.create( new FloatType(), 0, 512 ); + converter.setColor( new ARGBType( 0xffffffff ) ); + final SourceAndConverter< FloatType > soc = new SourceAndConverter<>( magSource, converter, null ); + data.converterSetups.add( BigDataViewer.createConverterSetup( soc, WARPMAG_SOURCE_ID ) ); + data.sources.add( ( SourceAndConverter ) soc ); + return soc; + } + + @SuppressWarnings( { "unchecked", "rawtypes" } ) + private static < T > SourceAndConverter< FloatType > addGridSource( final BigWarpData< T > data, final String name ) + { + // TODO think about whether its worth it to pass a type parameter. + // or should we just stick with Floats? + + final GridSource< FloatType > gridSource = new GridSource<>( name, data, new FloatType(), null ); + final RealARGBColorConverter< FloatType > converter = RealARGBColorConverter.create( new FloatType(), 0, 512 ); + converter.setColor( new ARGBType( 0xffffffff ) ); + final SourceAndConverter< FloatType > soc = new SourceAndConverter<>( gridSource, converter, null ); + data.converterSetups.add( BigDataViewer.createConverterSetup( soc, GRID_SOURCE_ID ) ); + data.sources.add( ( SourceAndConverter ) soc ); + return soc; + } + + @SuppressWarnings( { "unchecked", "rawtypes" } ) + private SourceAndConverter< DoubleType > addTransformMaskSource( final BigWarpData< T > data, final int ndims, final String name ) + { + // TODO think about whether its worth it to pass a type parameter. + // or should we just stick with Floats? + FinalInterval itvl = new FinalInterval( data.sources.get( data.targetSourceIndices[0] ).getSpimSource().getSource( 0, 0 )); + transformMask = PlateauSphericalMaskSource.build( new RealPoint( ndims ), itvl ); + + final RealARGBColorConverter< DoubleType > converter = RealARGBColorConverter.create( new DoubleType(), 0, 1 ); + converter.setColor( new ARGBType( 0xffffffff ) ); + final SourceAndConverter< DoubleType > soc = new SourceAndConverter( transformMask, converter, null ); + data.converterSetups.add( BigDataViewer.createConverterSetup( soc, TRANSFORM_MASK_SOURCE_ID ) ); + data.sources.add( ( SourceAndConverter ) soc ); + return soc; + } + + public PlateauSphericalMaskSource getTransformMaskSource() + { + return transformMask; + } + + private static < T > SourceAndConverter< T > wrapSourceAsTransformed( final SourceAndConverter< T > src, final String name, final int ndims ) + { + if ( src.asVolatile() == null ) + { + return new SourceAndConverter< T >( new WarpedSource< T >( src.getSpimSource(), name ), src.getConverter(), null ); + } + else + { + return new SourceAndConverter< T >( new WarpedSource< T >( src.getSpimSource(), name ), src.getConverter(), wrapSourceAsTransformed( src.asVolatile(), name + "_vol", ndims ) ); + } + } + + public void setupKeyListener() + { + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor( new LandmarkKeyboardProcessor( this ) ); + } + + public void setWarpVisGridType( final GridSource.GRID_TYPE type ) + { + ( ( GridSource< ? > ) gridSource.getSpimSource() ).setMethod( type ); + viewerP.requestRepaint(); + viewerQ.requestRepaint(); + } + + public void setWarpGridWidth( final double width ) + { + ( ( GridSource< ? > ) gridSource.getSpimSource() ).setGridWidth( width ); + viewerP.requestRepaint(); + viewerQ.requestRepaint(); + } + + public void setWarpGridSpacing( final double spacing ) + { + ( ( GridSource< ? > ) gridSource.getSpimSource() ).setGridSpacing( spacing ); + viewerP.requestRepaint(); + viewerQ.requestRepaint(); + } + + public void setAutoSaver( final BigWarpAutoSaver autoSaver ) + { + this.autoSaver = autoSaver; + } + + protected void setupWarpMagBaselineOptions( final CoordinateTransform[] xfm, final int ndim ) + { + if ( ndim == 2 ) + { + xfm[ 0 ] = new AffineModel2D(); + xfm[ 1 ] = new SimilarityModel2D(); + xfm[ 2 ] = new RigidModel2D(); + } + else + { + xfm[ 0 ] = new AffineModel3D(); + xfm[ 1 ] = new SimilarityModel3D(); + xfm[ 2 ] = new RigidModel3D(); + } + } + + public void setWarpMagBaselineIndex( int index ) + { + baselineModelIndex = index; + fitBaselineWarpMagModel(); + } + + protected void fitBaselineWarpMagModel() + { + final int numActive = landmarkModel.numActive(); + if( numActive < 4 ) + return; + + final int ndims = landmarkModel.getNumdims(); + final double[][] p = new double[ ndims ][ numActive ]; + final double[][] q = new double[ ndims ][ numActive ]; + final double[] w = new double[ numActive ]; + + int k = 0; + for ( int i = 0; i < landmarkModel.getTransform().getNumLandmarks(); i++ ) + { + if ( landmarkModel.isActive( i ) ) + { + w[ k ] = 1.0; + + for ( int d = 0; d < ndims; d++ ) + { + p[ d ][ k ] = landmarkModel.getMovingPoint( i )[ d ]; + q[ d ][ k ] = landmarkModel.getFixedPoint( i )[ d ]; + } + k++; + } + } + + try + { + final AbstractModel< ? > baseline = this.baseXfmList[ baselineModelIndex ]; + + baseline.fit( p, q, w ); // FITBASELINE + WrappedCoordinateTransform baselineTransform = new WrappedCoordinateTransform( + (InvertibleCoordinateTransform)baseline, ndims ); + + // the transform to compare is the inverse (because we use it for rendering) + // so need to give the inverse transform for baseline as well + ( ( WarpMagnitudeSource< ? > ) warpMagSource.getSpimSource() ).setBaseline( baselineTransform.inverse() ); + } + catch ( final NotEnoughDataPointsException e ) + { + e.printStackTrace(); + } + catch ( final IllDefinedDataPointsException e ) + { + e.printStackTrace(); + } + getViewerFrameP().getViewerPanel().requestRepaint(); + getViewerFrameQ().getViewerPanel().requestRepaint(); + } + + public void setMovingSpimData( SpimData movingSpimData, File movingImageXml ) + { + this.movingSpimData = movingSpimData; + this.movingImageXml = movingImageXml; + } + + public void setBookmarks( final Bookmarks bookmarks ) + { + this.bookmarks = bookmarks; + } + + public void setAutoEstimateMask( final boolean selected ) + { + warpVisDialog.maskOptionsPanel.getAutoEstimateMaskButton().setSelected( selected ); + } + + public void autoEstimateMask() + { + if( landmarkModel.numActive() < 4 ) + return; + + if( warpVisDialog.autoEstimateMask() && bwTransform.isMasked() ) + { + final Sphere sph = BoundingSphereRitter.boundingSphere(landmarkModel.getFixedPointsCopy()); + transformMask.getRandomAccessible().setCenter(sph.getCenter()); + transformMask.getRandomAccessible().setRadius(sph.getRadius()); + } + } + + public enum WarpVisType + { + NONE, WARPMAG, JACDET, GRID + } + + public void setWarpVisMode( final WarpVisType type, BigWarpViewerFrame viewerFrame, final boolean both ) + { + if ( viewerFrame == null ) + { + if ( viewerFrameP.isActive() ) + { + viewerFrame = viewerFrameP; + } + else if ( viewerFrameQ.isActive() ) + { + viewerFrame = viewerFrameQ; + } + else if ( both ) + { + setWarpVisMode( type, viewerFrameP, false ); + setWarpVisMode( type, viewerFrameQ, false ); + return; + } + else + { + return; + } + + } +// +// int offImgIndex = 0; +// int onImgIndex = 1; +// +// if ( viewerFrame == viewerFrameP ) +// { +// offImgIndex = 1; +// onImgIndex = 0; +// } + + if ( landmarkModel.getTransform() == null ) + { + message.showMessage( "No warp - estimate warp first." ); + return; + } + final ViewerState state = viewerFrame.getViewerPanel().state(); + + switch ( type ) + { + case JACDET: + { + // turn warp mag on + state.setSourceActive( warpMagSource, false ); + state.setSourceActive( jacDetSource, true ); + state.setSourceActive( gridSource, false ); + + } + case WARPMAG: + { + // turn warp mag on + state.setSourceActive( warpMagSource, true ); + state.setSourceActive( jacDetSource, false ); + state.setSourceActive( gridSource, false ); +// vg.setSourceActive( offImgIndex, false ); + + // estimate the max warp +// final WarpMagnitudeSource< ? > wmSrc = ( ( WarpMagnitudeSource< ? > ) sources.get( warpMagSourceIndex ).getSpimSource() ); +// final double maxval = wmSrc.getMax( landmarkModel ); + + // set the slider +// ( ( RealARGBColorConverter< FloatType > ) ( sources.get( warpMagSourceIndex ).getConverter() ) ).setMax( maxval ); + + state.setDisplayMode( DisplayMode.FUSED ); + message.showMessage( "Displaying Warp Magnitude" ); + break; + } + case GRID: + { + // turn grid vis on + + state.setSourceActive( warpMagSource, false ); + state.setSourceActive( jacDetSource, false ); + state.setSourceActive( gridSource, true ); +// vg.setSourceActive( offImgIndex, false ); + + state.setDisplayMode( DisplayMode.FUSED ); + message.showMessage( "Displaying Warp Grid" ); + break; + } + default: + { + state.setSourceActive( warpMagSource, false ); + state.setSourceActive( gridSource, false ); +// vg.setSourceActive( offImgIndex, true ); + +// vg.setFusedEnabled( false ); + message.showMessage( "Turning off warp vis" ); + break; + } + } + } + + public void toggleWarpVisMode( BigWarpViewerFrame viewerFrame ) + { +// int offImgIndex = 0; +// int onImgIndex = 1; + if ( viewerFrame == null ) + { + if ( viewerFrameP.isActive() ) + { + viewerFrame = viewerFrameP; + } + else if ( viewerFrameQ.isActive() ) + { + viewerFrame = viewerFrameQ; + } + else + return; + } + +// if ( viewerFrame == viewerFrameP ) +// { +// offImgIndex = 1; +// onImgIndex = 0; +// } + + if ( landmarkModel.getTransform() == null ) + { + message.showMessage( "No warp - estimate warp first." ); + return; + } + + final ViewerState state = viewerFrame.getViewerPanel().state(); + + // TODO consider remembering whether fused was on before displaying + // warpmag + // so that its still on or off after we turn it off + if ( state.isSourceActive( warpMagSource ) ) // warp mag is visible, + // turn it off + { + state.setSourceActive( warpMagSource, false ); + +// vg.setSourceActive( offImgIndex, true ); + + state.setDisplayMode( state.getDisplayMode().withFused( false ) ); + message.showMessage( "Removing Warp Magnitude" ); + } + else // warp mag is invisible, turn it on + { + state.setSourceActive( warpMagSource, true ); + +// vg.setSourceActive( offImgIndex, false ); + + // estimate the max warp +// final WarpMagnitudeSource< ? > wmSrc = ( ( WarpMagnitudeSource< ? > ) sources.get( warpMagSourceIndex ).getSpimSource() ); +// final double maxval = wmSrc.getMax( landmarkModel ); + + // set the slider +// ( ( RealARGBColorConverter< FloatType > ) ( sources.get( warpMagSourceIndex ).getConverter() ) ).setMax( maxval ); + + state.setDisplayMode( state.getDisplayMode().withFused( false ) ); + message.showMessage( "Displaying Warp Magnitude" ); + } + + viewerFrame.getViewerPanel().requestRepaint(); + } + + private void setTransformationMovingSourceOnly( final InvertibleRealTransform transform ) + { + this.currentTransform = transform; + + for ( int i = 0; i < movingSourceIndexList.length; i++ ) + { + int idx = movingSourceIndexList [ i ]; + + // the xfm must always be 3d for bdv to be happy. + // when bigwarp has 2d images though, the z- component will be left unchanged + //InverseRealTransform xfm = new InverseRealTransform( new TpsTransformWrapper( 3, transform )); + + // the updateTransform method creates a copy of the transform + ( ( WarpedSource< ? > ) ( sources.get( idx ).getSpimSource() ) ).updateTransform( transform ); + if ( sources.get( 0 ).asVolatile() != null ) + ( ( WarpedSource< ? > ) ( sources.get( idx ).asVolatile().getSpimSource() ) ).updateTransform( transform ); + } + } + + public void updateSourceBoundingBoxEstimators() + { + for ( int i = 0; i < movingSourceIndexList.length; i++ ) + { + int idx = movingSourceIndexList [ i ]; + + // the xfm must always be 3d for bdv to be happy. + // when bigwarp has 2d images though, the z- component will be left unchanged + //InverseRealTransform xfm = new InverseRealTransform( new TpsTransformWrapper( 3, transform )); + + // the updateTransform method creates a copy of the transform + ( ( WarpedSource< ? > ) ( sources.get( idx ).getSpimSource() ) ).setBoundingBoxEstimator(bboxOptions.copy()); + if ( sources.get( 0 ).asVolatile() != null ) + ( ( WarpedSource< ? > ) ( sources.get( idx ).asVolatile().getSpimSource() ) ).setBoundingBoxEstimator(bboxOptions.copy()); + } + } + + private synchronized void notifyTransformListeners( ) + { + for ( final TransformListener< InvertibleRealTransform > l : transformListeners ) + l.transformChanged( currentTransform ); + } + + private void setTransformationAll( final InvertibleRealTransform transform ) + { + setTransformationMovingSourceOnly( transform ); + + final WarpMagnitudeSource< ? > wmSrc = ( ( WarpMagnitudeSource< ? > ) warpMagSource.getSpimSource() ); + final JacobianDeterminantSource< ? > jdSrc = ( ( JacobianDeterminantSource< ? > ) jacDetSource.getSpimSource() ); + final GridSource< ? > gSrc = ( ( GridSource< ? > ) gridSource.getSpimSource() ); + + wmSrc.setWarp( transform ); + fitBaselineWarpMagModel(); + + if( transform instanceof ThinplateSplineTransform ) + { + jdSrc.setTransform( (ThinplateSplineTransform)transform ); + } + else if ( transform instanceof WrappedIterativeInvertibleRealTransform ) + { + RealTransform xfm = ((WrappedIterativeInvertibleRealTransform)transform).getTransform(); + if( xfm instanceof ThinplateSplineTransform ) + jdSrc.setTransform( (ThinplateSplineTransform) xfm ); + else + jdSrc.setTransform( new RealTransformFiniteDerivatives( xfm )); + } + else + jdSrc.setTransform( null ); + + gSrc.setWarp( transform ); + } + + public boolean restimateTransformation() + { + if ( landmarkModel.getActiveRowCount() < 4 ) + { + return false; + } + // TODO restimateTransformation + // This distinction is unnecessary right now, because + // transferUpdatesToModel just calls initTransformation.. but this may + // change +// if ( landmarkModel.getTransform() == null ) +// landmarkModel.initTransformation(); +// else +// landmarkModel.transferUpdatesToModel(); + + solverThread.requestResolve( true, -1, null ); + + // display the warped version automatically if this is the first + // time the transform was computed + if ( firstWarpEstimation ) + { + setUpdateWarpOnChange( true ); + firstWarpEstimation = false; + } + + viewerP.requestRepaint(); + viewerQ.requestRepaint(); + + notifyTransformListeners(); + + return true; + } + + public synchronized void setIsMovingDisplayTransformed( final boolean isTransformed ) + { + for( int i = 0 ; i < movingSourceIndexList.length; i ++ ) + { + int movingSourceIndex = movingSourceIndexList[ i ]; + + ( ( WarpedSource< ? > ) ( sources.get( movingSourceIndex ).getSpimSource() ) ).setIsTransformed( isTransformed ); + + if ( sources.get( movingSourceIndex ).asVolatile() != null ) + ( ( WarpedSource< ? > ) ( sources.get( movingSourceIndex ).asVolatile().getSpimSource() ) ).setIsTransformed( isTransformed ); + } + + overlayP.setIsTransformed( isTransformed ); + + viewerP.requestRepaint(); + + if ( viewerQ.state().getDisplayMode().hasFused() ) + { + viewerQ.requestRepaint(); + } + } + + /** + * Returns true if the currently selected row in the landmark table is missing on the the landmarks + * @return true if there is a missing value + */ + public boolean isRowIncomplete() + { + LandmarkTableModel ltm = landmarkPanel.getTableModel(); + return ltm.isPointUpdatePending() || ltm.isPointUpdatePendingMoving(); + } + + public boolean isMovingDisplayTransformed() + { + // this implementation is okay, so long as all the moving images have the same state of 'isTransformed' + return ( ( WarpedSource< ? > ) ( sources.get( movingSourceIndexList[ 0 ] ).getSpimSource() ) ).isTransformed(); + } + + /** + * The display will be in 3d if any of the input sources are 3d. + * @return dimension of the input sources + */ + protected int detectNumDims() + { + return detectNumDims( sources ); + } + + /** + * The display will be in 3d if any of the input sources are 3d. + * @param sources the sources + * @param the type + * @return dimension of the input sources + */ + public static int detectNumDims( List< SourceAndConverter< T > > sources ) + { + boolean isAnySource3d = false; + for ( SourceAndConverter< T > sac : sources ) + { + long[] dims = new long[ sac.getSpimSource().getSource( 0, 0 ).numDimensions() ]; + sac.getSpimSource().getSource( 0, 0 ).dimensions( dims ); + + if ( sac.getSpimSource().getSource( 0, 0 ).dimension( 2 ) > 1 ) + { + isAnySource3d = true; + break; + } + } + + int ndims = 2; + if ( isAnySource3d ) + ndims = 3; + + return ndims; + } + + public static void main( final String[] args ) + { + new ImageJ(); + + // TODO main + String fnP = ""; + String fnQ = ""; + String fnLandmarks = ""; + + int i = 0; + if ( args.length >= 2 ) + { + fnP = args[ i++ ]; + fnQ = args[ i++ ]; + } + else + { + System.err.println( "Must provide at least 2 inputs for moving and target image files" ); + System.exit( 1 ); + } + + if ( args.length > i ) + fnLandmarks = args[ i++ ]; + + boolean doInverse = false; + if ( args.length > i ) + { + try + { + doInverse = Boolean.parseBoolean( args[ i++ ] ); + System.out.println( "parsed inverse param: " + doInverse ); + } + catch ( final Exception e ) + { + // no inverse param + System.err.println( "Warning: Failed to parse inverse parameter." ); + } + } + + try + { + System.setProperty( "apple.laf.useScreenMenuBar", "false" ); + + + ProgressWriterIJ progress = new ProgressWriterIJ(); + BigWarp bw; + BigWarpData bwdata; + if ( fnP.endsWith( "xml" ) && fnQ.endsWith( "xml" ) ) + { + bwdata = BigWarpInit.createBigWarpDataFromXML( fnP, fnQ ); + bw = new BigWarp<>( bwdata, new File( fnP ).getName(), progress ); + } + else if ( fnP.endsWith( "xml" ) && !fnQ.endsWith( "xml" ) ) + { + final ImagePlus impQ = IJ.openImage( fnQ ); + bwdata = BigWarpInit.createBigWarpDataFromXMLImagePlus( fnP, impQ ); + bw = new BigWarp<>( bwdata, new File( fnP ).getName(), progress ); + } + else if ( !fnP.endsWith( "xml" ) && fnQ.endsWith( "xml" ) ) + { + final ImagePlus impP = IJ.openImage( fnP ); + bwdata = BigWarpInit.createBigWarpDataFromImagePlusXML( impP, fnQ ); + bw = new BigWarp<>( bwdata, new File( fnP ).getName(), progress ); + } + else + { + final ImagePlus impP = IJ.openImage( fnP ); + final ImagePlus impQ = IJ.openImage( fnQ ); + + if ( !( impP == null || impQ == null ) ) + { + bwdata = BigWarpInit.createBigWarpDataFromImages( impP, impQ ); + bw = new BigWarp<>( bwdata, new File( fnP ).getName(), progress ); + } + else + { + System.err.println( "Error reading images" ); + return; + } + } + + + if ( !fnLandmarks.isEmpty() ) + bw.loadLandmarks( fnLandmarks ); + + if ( doInverse ) + bw.invertPointCorrespondences(); + + } + catch ( final Exception e ) + { + + e.printStackTrace(); + } + } + + private void viewerXfmTest() + { + AffineTransform3D srcTransform0 = new AffineTransform3D(); + sources.get( 0 ).getSpimSource().getSourceTransform(0, 0, srcTransform0 ); + + AffineTransform3D srcTransform1 = new AffineTransform3D(); + sources.get( 1 ).getSpimSource().getSourceTransform(0, 0, srcTransform1 ); + + AffineTransform3D viewerTransformM = new AffineTransform3D(); + viewerP.state().getViewerTransform( viewerTransformM ); + + AffineTransform3D viewerTransformT = new AffineTransform3D(); + viewerQ.state().getViewerTransform( viewerTransformT ); + + System.out.println( " " ); + System.out.println( " " ); + System.out.println( "srcXfm 0 " + srcTransform0 ); + System.out.println( "srcXfm 1 " + srcTransform1 ); + + System.out.println( "viewerXfm M " + viewerTransformM ); + System.out.println( "viewerXfm T " + viewerTransformT ); + System.out.println( " " ); + System.out.println( " " ); + + } + + public void checkBoxInputMaps() + { + // Disable spacebar for toggling checkboxes + // Make it enter instead + // This is super ugly ... why does it have to be this way. + + final TableCellEditor celled = landmarkTable.getCellEditor( 0, 1 ); + final Component c = celled.getTableCellEditorComponent( landmarkTable, Boolean.TRUE, true, 0, 1 ); + + final InputMap parentInputMap = ( ( JCheckBox ) c ).getInputMap().getParent(); + parentInputMap.clear(); + final KeyStroke enterDownKS = KeyStroke.getKeyStroke( "pressed ENTER" ); + final KeyStroke enterUpKS = KeyStroke.getKeyStroke( "released ENTER" ); + + parentInputMap.put( enterDownKS, "pressed" ); + parentInputMap.put( enterUpKS, "released" ); + + /* + * Consider with replacing with something like the below Found in + * BigWarpViewerFrame + */ +// SwingUtilities.replaceUIActionMap( getRootPane(), keybindings.getConcatenatedActionMap() ); +// SwingUtilities.replaceUIInputMap( getRootPane(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); + } + + public static class BigWarpData< T > + { + public final List< SourceAndConverter< T > > sources; + + public final List< ConverterSetup > converterSetups; + + public final CacheControl cache; + + public int[] movingSourceIndices; + + public int[] targetSourceIndices; + + public final ArrayList< Integer > movingSourceIndexList; + + public final ArrayList< Integer > targetSourceIndexList; + + public final HashMap< Integer, ColorSettings > setupSettings; + + public final HashMap< SourceAndConverter, ColorSettings > sourceColorSettings; + public BigWarpData( final List< SourceAndConverter< T > > sources, final List< ConverterSetup > converterSetups, final CacheControl cache, int[] movingSourceIndices, int[] targetSourceIndices ) + { + this.sources = sources; + this.converterSetups = converterSetups; + this.movingSourceIndices = movingSourceIndices; + this.targetSourceIndices = targetSourceIndices; + + this.movingSourceIndexList = new ArrayList<>(); + this.targetSourceIndexList = new ArrayList<>(); + + + if ( cache == null ) + this.cache = new CacheControl.Dummy(); + else + this.cache = cache; + + setupSettings = new HashMap<>(); + sourceColorSettings = new HashMap<>(); + } + + public void wrapUp() + { + movingSourceIndices = movingSourceIndexList.stream().mapToInt( x -> x ).toArray(); + targetSourceIndices = targetSourceIndexList.stream().mapToInt( x -> x ).toArray(); + + Arrays.sort( movingSourceIndices ); + Arrays.sort( targetSourceIndices ); + } + + /** + * @deprecated + */ + public void transferChannelSettings( final SetupAssignments setupAssignments, final VisibilityAndGrouping visibility ) + { + for( Integer key : setupSettings.keySet() ) + setupSettings.get( key ).updateSetup( setupAssignments ); + } + + public void transferChannelSettings( final BigWarpViewerFrame viewer ) + { + SynchronizedViewerState state = viewer.getViewerPanel().state(); + ConverterSetups setups = viewer.getConverterSetups(); + synchronized ( state ) + { + for ( SourceAndConverter< ? > sac : state.getSources() ) + { + if ( sourceColorSettings.containsKey( sac ) ) + { + if ( sourceColorSettings.get( sac ) == null ) + continue; + + sourceColorSettings.get( sac ).updateSetup( setups.getConverterSetup( sac ) ); + } + else + { + final int timepoint = state.getCurrentTimepoint(); + final Bounds bounds = InitializeViewerState.estimateSourceRange( sac.getSpimSource(), timepoint, 0.001, 0.999 ); + ConverterSetup cs = setups.getConverterSetup(sac); + if( cs != null ) + cs.setDisplayRange( bounds.getMinBound(), bounds.getMaxBound() ); + } + } + } + } + } + + protected class LandmarkModeListener implements KeyEventPostProcessor + { + @Override + public boolean postProcessKeyEvent( final KeyEvent ke ) + { + if ( ke.isConsumed() ) + return false; + + if ( ke.getKeyCode() == KeyEvent.VK_SPACE ) + { + if ( ke.getID() == KeyEvent.KEY_PRESSED ) + { + BigWarp.this.setInLandmarkMode( true ); + return false; + } + else if ( ke.getID() == KeyEvent.KEY_RELEASED ) + { + BigWarp.this.setInLandmarkMode( false ); + return false; + } + } + return false; + } + } + + // TODO, + // consider this + // https://github.com/kwhat/jnativehook + // for arbitrary modifiers + protected class MouseLandmarkListener implements MouseListener, MouseMotionListener + { + + // -1 indicates that no point is selected + int selectedPointIndex = -1; + + double[] ptarrayLoc = new double[ 3 ]; + + double[] ptBackLoc = new double[ 3 ]; + + private BigWarpViewerPanel thisViewer; + + private boolean isMoving; + + private long pressTime; + + private RealPoint hoveredPoint; + + private double[] hoveredArray; + + protected MouseLandmarkListener( final BigWarpViewerPanel thisViewer ) + { + setViewer( thisViewer ); + thisViewer.getDisplay().addHandler( this ); + isMoving = ( thisViewer == BigWarp.this.viewerP ); + hoveredArray = new double[ 3 ]; + hoveredPoint = RealPoint.wrap( hoveredArray ); + } + + protected void setViewer( final BigWarpViewerPanel thisViewer ) + { + this.thisViewer = thisViewer; + } + + @Override + public void mouseClicked( final MouseEvent arg0 ) + {} + + @Override + public void mouseEntered( final MouseEvent arg0 ) + {} + + @Override + public void mouseExited( final MouseEvent arg0 ) + {} + + @Override + public void mousePressed( final MouseEvent e ) + { + pressTime = System.currentTimeMillis(); + + // shift down is reserved for drag overlay + if ( e.isShiftDown() ) { return; } + + if ( BigWarp.this.isInLandmarkMode() ) + { + thisViewer.getGlobalMouseCoordinates( BigWarp.this.currentLandmark ); + + BigWarp.this.currentLandmark.localize( ptarrayLoc ); + selectedPointIndex = BigWarp.this.selectedLandmark( ptarrayLoc, isMoving ); + + if ( selectedPointIndex >= 0 ) + { + landmarkTable.setRowSelectionInterval( selectedPointIndex, selectedPointIndex ); + landmarkFrame.repaint(); + BigWarp.this.landmarkModel.setLastPoint( selectedPointIndex, isMoving ); + } + } + } + + @Override + public void mouseReleased( final MouseEvent e ) + { + long clickLength = System.currentTimeMillis() - pressTime; + + if( clickLength < keyClickMaxLength && selectedPointIndex != -1 ) + return; + + // shift when + boolean isMovingLocal = isMoving; + if ( e.isShiftDown() && e.isControlDown() ) + { + isMovingLocal = !isMoving; + } + else if( e.isShiftDown()) + { + // shift is reserved for click-drag + return; + } + else if ( e.isControlDown() ) + { + if ( BigWarp.this.isInLandmarkMode() && selectedPointIndex < 0 ) + { + thisViewer.getGlobalMouseCoordinates( BigWarp.this.currentLandmark ); + addFixedPoint( BigWarp.this.currentLandmark, isMovingLocal ); + } + return; + } + + boolean wasNewRowAdded = false; + + // deselect any point that may be selected + if ( BigWarp.this.isInLandmarkMode() ) + { + thisViewer.getGlobalMouseCoordinates( BigWarp.this.currentLandmark ); + currentLandmark.localize( ptarrayLoc ); + + if ( selectedPointIndex == -1 ) + wasNewRowAdded = addPoint( ptarrayLoc, isMovingLocal ); + else + { + final boolean isWarped = isMovingLocal && ( landmarkModel.getTransform() != null ) && ( BigWarp.this.isMovingDisplayTransformed() ); + wasNewRowAdded = BigWarp.this.landmarkModel.pointEdit( selectedPointIndex, ptarrayLoc, false, isMovingLocal, isWarped, true, currentTransform ); + } + + if ( updateWarpOnPtChange && !wasNewRowAdded ) + { + // here, if a new row is added, then only one of the point + // pair was added. + // if we changed and existing row, then we have both points + // in the pair and should recompute + BigWarp.this.restimateTransformation(); + } + + if( wasNewRowAdded ) + updateRowSelection( isMovingLocal, landmarkModel.getRowCount() - 1 ); + else + updateRowSelection( isMovingLocal, selectedPointIndex ); + } + BigWarp.this.landmarkModel.resetLastPoint(); + selectedPointIndex = -1; + } + + @Override + public void mouseDragged( final MouseEvent e ) + { + // shift down is reserved for drag overlay + if ( e.isShiftDown() ) { return; } + + if ( BigWarp.this.isInLandmarkMode() && selectedPointIndex >= 0 ) + { + thisViewer.getGlobalMouseCoordinates( BigWarp.this.currentLandmark ); + currentLandmark.localize( ptarrayLoc ); + + if ( BigWarp.this.isMovingDisplayTransformed() && + thisViewer.doUpdateOnDrag() && + BigWarp.this.landmarkModel.isActive( selectedPointIndex ) ) + { + logger.trace("Drag resolve"); + solverThread.requestResolve( isMoving, selectedPointIndex, ptarrayLoc ); + } + else + { + // Make a non-undoable edit so that the point can be displayed correctly + // the undoable action is added on mouseRelease + if( isMoving && isMovingDisplayTransformed() ) + { + logger.trace("Drag moving transformed"); + // The moving image: + // Update the warped point during the drag even if there is a corresponding fixed image point + // Do this so the point sticks on the mouse + BigWarp.this.landmarkModel.pointEdit( + selectedPointIndex, + BigWarp.this.landmarkModel.getTransform().apply( ptarrayLoc ), + false, isMoving, ptarrayLoc, false ); + thisViewer.requestRepaint(); + } + else + { + logger.trace("Drag default"); + // The fixed image + BigWarp.this.landmarkModel.pointEdit( selectedPointIndex, ptarrayLoc, false, isMoving, false, false, currentTransform ); + thisViewer.requestRepaint(); + } + } + } + } + + @Override + public void mouseMoved( final MouseEvent e ) + { + thisViewer.getGlobalMouseCoordinates( hoveredPoint ); + int hoveredIndex = BigWarp.this.selectedLandmark( hoveredArray, isMoving, false ); + thisViewer.setHoveredIndex( hoveredIndex ); + } + + /** + * Adds a point in the moving and fixed images at the same point. + * @param pt the point + * @param isMovingImage is the point in moving image space + */ + public void addFixedPoint( final RealPoint pt, final boolean isMovingImage ) + { + if ( isMovingImage && viewerP.getTransformEnabled() ) + { + // Here we clicked in the space of the moving image + currentLandmark.localize( ptarrayLoc ); + addPoint( ptarrayLoc, true, viewerP ); + addPoint( ptarrayLoc, false, viewerQ ); + } + else + { + currentLandmark.localize( ptarrayLoc ); + addPoint( ptarrayLoc, true, viewerP ); + addPoint( ptarrayLoc, false, viewerQ ); + } + if ( updateWarpOnPtChange ) + BigWarp.this.restimateTransformation(); + } + } + + public class WarningTableCellRenderer extends DefaultTableCellRenderer + { + private static final long serialVersionUID = 7836269349663370123L; + + @Override + public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column ) + { + LandmarkTableModel model = ( LandmarkTableModel ) table.getModel(); + Component c = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column ); + if ( model.rowNeedsWarning( row ) ) + c.setBackground( LandmarkTableModel.WARNINGBGCOLOR ); + else + c.setBackground( LandmarkTableModel.DEFAULTBGCOLOR ); + return c; + } + } + + public class LandmarkTableListener implements TableModelListener + { + @Override + public void tableChanged( final TableModelEvent e ) + { + // re-estimate if a a point was set to or from active + // note - this covers "resetting" points as well + if ( e.getColumn() == LandmarkTableModel.ACTIVECOLUMN ) + { + BigWarp.this.restimateTransformation(); + BigWarp.this.landmarkPanel.repaint(); + } + autoEstimateMask(); + } + } + + public class MouseLandmarkTableListener implements MouseListener + { + boolean wasDoubleClick = false; + + Timer timer; + + public MouseLandmarkTableListener() + {} + + @Override + public void mouseClicked( final MouseEvent e ) + { + final int ndims = landmarkModel.getNumdims(); +// if ( BigWarp.this.isInLandmarkMode() ) +// { +// final JTable target = ( JTable ) e.getSource(); +// +// final int row = target.getSelectedRow(); +// final int column = target.getSelectedColumn(); +// +// final boolean isMoving = ( column > 1 && column < ( 2 + ndims ) ); +// +// BigWarp.this.landmarkModel.clearPt( row, isMoving ); +// +// } +// else if ( e.getClickCount() == 2 ) + if ( e.getClickCount() == 2 ) + { + final JTable target = ( JTable ) e.getSource(); + final int row = target.getSelectedRow(); + final int column = target.getSelectedColumn(); + + if( row < 0 ) + return; + + double[] pt = null; + int offset = 0; + + final BigWarpViewerPanel viewer; + if ( column >= ( 2 + ndims ) ) + { + // clicked on a fixed point + viewer = BigWarp.this.viewerQ; + offset = ndims; + } + else if ( column >= 2 && column < ( 2 + ndims ) ) + { + // clicked on a moving point + viewer = BigWarp.this.viewerP; + + if ( BigWarp.this.viewerP.getOverlay().getIsTransformed() ) + if ( BigWarp.this.landmarkModel.isWarped( row ) ) + pt = LandmarkTableModel.toPrimitive( BigWarp.this.landmarkModel.getWarpedPoints().get( row ) ); + else + offset = ndims; + } + else + { + // we're in a column that doesn't correspond to a point and + // should do nothing + return; + } + + // the pt variable might be set above by grabbing the warped point. + // if so, stick with it, else grab the appropriate value from the table + if ( pt == null ) + { + if ( ndims == 3 ) + pt = new double[] { ( Double ) target.getValueAt( row, offset + 2 ), ( Double ) target.getValueAt( row, offset + 3 ), ( Double ) target.getValueAt( row, offset + 4 ) }; + else + pt = new double[] { ( Double ) target.getValueAt( row, offset + 2 ), ( Double ) target.getValueAt( row, offset + 3 ), 0.0 }; + } + + // we have an unmatched point + if ( Double.isInfinite( pt[ 0 ] ) ) + return; + + final AffineTransform3D transform = viewer.state().getViewerTransform(); + final AffineTransform3D xfmCopy = transform.copy(); + xfmCopy.set( 0.0, 0, 3 ); + xfmCopy.set( 0.0, 1, 3 ); + xfmCopy.set( 0.0, 2, 3 ); + + final double[] center = new double[] { viewer.getWidth() / 2, viewer.getHeight() / 2, 0 + }; + final double[] ptxfm = new double[ 3 ]; + xfmCopy.apply( pt, ptxfm ); + + // this should work fine in the 2d case + final TranslationAnimator animator = new TranslationAnimator( transform, new double[] { center[ 0 ] - ptxfm[ 0 ], center[ 1 ] - ptxfm[ 1 ], -ptxfm[ 2 ] }, 300 ); + viewer.setTransformAnimator( animator ); + } + else + { + final JTable target = ( JTable ) e.getSource(); + final int row = target.rowAtPoint( e.getPoint() ); + + // if we click in the table but not on a row, deselect everything + if( row < 0 && target.getRowCount() > 0 ) + target.removeRowSelectionInterval( 0, target.getRowCount() - 1 ); + } + } + + @Override + public void mouseEntered( final MouseEvent e ) + {} + + @Override + public void mouseExited( final MouseEvent e ) + {} + + @Override + public void mousePressed( final MouseEvent e ) + {} + + @Override + public void mouseReleased( final MouseEvent e ) + {} + + } + + public void setTransformType( final String type ) + { + bwTransform.setTransformType( type ); + this.restimateTransformation(); + } + + public void setTransformTypeUpdateUI( final String type ) + { + setTransformType( type ); + updateTransformTypeDialog( type ); + updateTransformTypePanel( type ); + } + + /** + * Update the transformation selection dialog to reflect the given transform type selection. + * + * @param type the transformation type + */ + public void updateTransformTypeDialog( final String type ) + { + transformSelector.deactivate(); + transformSelector.setTransformType( type ); + transformSelector.activate(); + } + + /** + * Update the transformation selection panel in the options dialog to reflect the given transform type selection. + * + * @param type the transformation type + */ + public void updateTransformTypePanel( final String type ) + { + warpVisDialog.transformTypePanel.deactivate(); + warpVisDialog.transformTypePanel.setType( type ); + warpVisDialog.transformTypePanel.activate(); + } + + public String getTransformType() + { + return bwTransform.getTransformType(); + } + + public BigWarpTransform getBwTransform() + { + return bwTransform; + } + + public BoundingBoxEstimation getBoxEstimation() + { + return bboxOptions; + } + + /** + * Use getTps, getTpsBase, or getTransformation instead + * @return + */ + @Deprecated + public ThinPlateR2LogRSplineKernelTransform getTransform() + { + return landmarkPanel.getTableModel().getTransform(); + } + + public synchronized void addTransformListener( TransformListener< InvertibleRealTransform > listener ) + { + transformListeners.add( listener ); + } + + +// public InvertibleRealTransform getTransformation( final int index ) +// { +// int ndims = 3; +// InvertibleRealTransform invXfm = null; +// if( transformType.equals( TransformTypeSelectDialog.TPS )) +// { +// final ThinPlateR2LogRSplineKernelTransform xfm; +// if ( index >= 0 ) // a point position is modified +// { +// LandmarkTableModel tableModel = getLandmarkPanel().getTableModel(); +// if ( !tableModel.getIsActive( index ) ) +// return null; +// +// int numActive = tableModel.numActive(); +// ndims = tableModel.getNumdims(); +// +// double[][] mvgPts = new double[ ndims ][ numActive ]; +// double[][] tgtPts = new double[ ndims ][ numActive ]; +// +// tableModel.copyLandmarks( mvgPts, tgtPts ); +// +// // need to find the "inverse TPS" - the transform from target space to moving space. +// xfm = new ThinPlateR2LogRSplineKernelTransform( ndims, tgtPts, mvgPts ); +// } +// else // a point is added +// { +// landmarkModel.initTransformation(); +// ndims = landmarkModel.getNumdims(); +// xfm = getLandmarkPanel().getTableModel().getTransform(); +// } +// invXfm = new WrappedIterativeInvertibleRealTransform<>( new ThinplateSplineTransform( xfm )); +// } +// else +// { +// Model model = getModelType(); +// fitModel(model); +// int nd = landmarkModel.getNumdims(); +// invXfm = new WrappedCoordinateTransform( (InvertibleCoordinateTransform) model, nd ).inverse(); +// } +// +// if( options.is2d ) +// { +// invXfm = new Wrapped2DTransformAs3D( invXfm ); +// } +// +// return invXfm; +// } + + public static class SolveThread extends Thread + { + private boolean pleaseResolve; + + private BigWarp bw; + + private boolean isMoving; + + private int index; + + private double[] pt; + + public SolveThread( final BigWarp bw ) + { + this.bw = bw; + pleaseResolve = false; + } + + @Override + public void run() + { + while ( !isInterrupted() ) + { + final boolean b; + synchronized ( this ) + { + b = pleaseResolve; + pleaseResolve = false; + } + if ( b ) + { + try + { + InvertibleRealTransform invXfm = bw.bwTransform.getTransformation( index ); + + if ( invXfm == null ) + return; + + if ( index < 0 ) + { + // reset active warped points + bw.landmarkModel.resetWarpedPoints(); + + // re-compute all warped points for non-active points + bw.landmarkModel.updateAllWarpedPoints( bw.currentTransform ); + + // update sources with the new transformation + bw.setTransformationAll( invXfm ); + bw.fitBaselineWarpMagModel(); + } + else + { + // update the transform and warped point + bw.setTransformationMovingSourceOnly( invXfm ); + } + + // update fixed point - but don't allow undo/redo + // and update warped point + // both for rendering purposes + if ( !isMoving ) + { + bw.getLandmarkPanel().getTableModel().setPoint( index, isMoving, pt, false, bw.currentTransform ); + } + + /* + * repaint both panels so that: + * 1) new transform is displayed + * 2) points are rendered + */ + bw.getViewerFrameP().getViewerPanel().requestRepaint(); + bw.getViewerFrameQ().getViewerPanel().requestRepaint(); + +// bw.notifyTransformListeners(); + } + + catch ( final RejectedExecutionException e ) + { + // this happens when the rendering threadpool + // is killed before the painter thread. + } + } + + synchronized ( this ) + { + try + { + if ( !pleaseResolve ) + wait(); + } + catch ( final InterruptedException e ) + { + break; + } + } + } + } + + public void requestResolve( final boolean isMoving, final int index, final double[] newpt ) + { + synchronized ( this ) + { + pleaseResolve = true; + this.isMoving = isMoving; + this.index = index; + if ( newpt != null ) + this.pt = Arrays.copyOf( newpt, newpt.length ); + + notify(); + } + } + + } + + /** + * Saves landmarks to a new File in the user's bigwarp folder. + */ + public void autoSaveLandmarks() + { + final File baseFolder; + if ( autoSaver.autoSaveDirectory != null ) + baseFolder = autoSaver.autoSaveDirectory; + else + baseFolder = getBigwarpSettingsFolder(); + + File proposedLandmarksFile = new File( baseFolder.getAbsolutePath() + + File.separator + "bigwarp_landmarks_" + + new SimpleDateFormat( "yyyyMMdd-HHmmss" ).format( Calendar.getInstance().getTime() ) + + ".csv" ); + + try + { + saveLandmarks( proposedLandmarksFile.getCanonicalPath() ); + } + catch ( IOException e ) { e.printStackTrace(); } + } + + /** + * Saves landmarks to either the last File the user + * saved landmarks to, or a unique location in the user's bigwarp folder. + * + */ + public void quickSaveLandmarks() + { + if(lastLandmarks != null) + { + try + { + saveLandmarks( lastLandmarks.getCanonicalPath() ); + } + catch ( IOException e ) { e.printStackTrace(); } + } + else + { + autoSaveLandmarks(); + return; + } + } + + /** + * Returns the default location for bigwarp settings / auto saved files: ~/.bigwarp + * @return the folder + */ + public File getBigwarpSettingsFolder() + { + + final File hiddenFolder = new File( System.getProperty( "user.home" ) + File.separator + ".bigwarp"); + boolean exists = hiddenFolder.isDirectory(); + if( !exists ) + { + exists = hiddenFolder.mkdir(); + } + + if( exists ) + return hiddenFolder; + else + return null; + } + + /** + * Returns the {@link BigWarpAutoSaver}. + * + * @return + */ + public BigWarpAutoSaver getAutoSaver() + { + return autoSaver; + } + + public void stopAutosave() + { + if( autoSaver != null ) + { + autoSaver.stop(); + autoSaver = null; + } + } + + protected void saveLandmarks() + { + final JFileChooser fileChooser = new JFileChooser( getLastDirectory() ); + File proposedLandmarksFile; + if(lastLandmarks != null) + proposedLandmarksFile = lastLandmarks; + else + proposedLandmarksFile = new File( "landmarks.csv" ); + + fileChooser.setSelectedFile( proposedLandmarksFile ); + final int returnVal = fileChooser.showSaveDialog( null ); + if ( returnVal == JFileChooser.APPROVE_OPTION ) + { + proposedLandmarksFile = fileChooser.getSelectedFile(); + try + { + saveLandmarks( proposedLandmarksFile.getCanonicalPath() ); + lastLandmarks = proposedLandmarksFile; + } catch ( final IOException e ) + { + e.printStackTrace(); + } + } + } + + protected void saveLandmarks( final String filename ) throws IOException + { + if( filename.endsWith("csv")) + landmarkModel.save(new File( filename )); + else if( filename.endsWith("json")) + TransformWriterJson.write(landmarkModel, bwTransform, new File( filename )); + } + + protected void loadLandmarks() + { + final JFileChooser fileChooser = new JFileChooser( getLastDirectory() ); + File proposedLandmarksFile = new File( "landmarks.csv" ); + + fileChooser.setSelectedFile( proposedLandmarksFile ); + final int returnVal = fileChooser.showOpenDialog( null ); + if ( returnVal == JFileChooser.APPROVE_OPTION ) + { + proposedLandmarksFile = fileChooser.getSelectedFile(); + try + { + loadLandmarks( proposedLandmarksFile.getCanonicalPath() ); + } catch ( final IOException e ) + { + e.printStackTrace(); + } + } + } + + public void loadLandmarks( final String filename ) + { + File file = new File( filename ); + setLastDirectory( file.getParentFile() ); + + if( filename.endsWith( "csv" )) + { + try + { + landmarkModel.load( file ); + } + catch ( final IOException e1 ) + { + e1.printStackTrace(); + } + } + else if( filename.endsWith( "json" )) + { + TransformWriterJson.read( file, this ); + } + + boolean didCompute = restimateTransformation(); + + // didCompute = false means that there were not enough points + // in the loaded points, so we should display the 'raw' moving + // image + if ( !didCompute ) + setIsMovingDisplayTransformed( false ); + + autoEstimateMask(); + + viewerP.requestRepaint(); + viewerQ.requestRepaint(); + landmarkFrame.repaint(); + } + + protected void saveSettings() + { + final JFileChooser fileChooser = new JFileChooser( getLastDirectory() ); + File proposedSettingsFile = new File( "bigwarp.settings.xml" ); + + fileChooser.setSelectedFile( proposedSettingsFile ); + final int returnVal = fileChooser.showSaveDialog( null ); + if ( returnVal == JFileChooser.APPROVE_OPTION ) + { + proposedSettingsFile = fileChooser.getSelectedFile(); + try + { + final String canonicalPath = proposedSettingsFile.getCanonicalPath(); + if ( canonicalPath.endsWith( ".xml" ) ) + { + saveSettings( canonicalPath ); + } + else + { + saveSettingsJson( canonicalPath ); + } + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + } + } + + protected void saveSettings( final String xmlFilename ) throws IOException + { + final Element root = new Element( "Settings" ); + + Element viewerPNode = new Element( "viewerP" ); + Element viewerQNode = new Element( "viewerQ" ); + + root.addContent( viewerPNode ); + root.addContent( viewerQNode ); + + viewerPNode.addContent( viewerP.stateToXml() ); + viewerQNode.addContent( viewerQ.stateToXml() ); + + root.addContent( setupAssignments.toXml() ); + root.addContent( bookmarks.toXml() ); + + final Element autoSaveNode = new Element( "autosave" ); + final Element autoSaveLocation = new Element( "location" ); + if ( autoSaver != null && autoSaver.autoSaveDirectory != null ) + autoSaveLocation.setText( autoSaver.autoSaveDirectory.getAbsolutePath() ); + else + autoSaveLocation.setText( getBigwarpSettingsFolder().getAbsolutePath() ); + + final Element autoSavePeriod = new Element( "period" ); + final String periodString = autoSaver == null ? "-1" : Long.toString(autoSaver.getPeriod()); + autoSavePeriod.setText( periodString ); + + autoSaveNode.addContent( autoSaveLocation ); + autoSaveNode.addContent( autoSavePeriod ); + + root.addContent( autoSaveNode ); + root.addContent( transformMask.getRandomAccessible().toXml() ); + + final Document doc = new Document( root ); + final XMLOutputter xout = new XMLOutputter( Format.getPrettyFormat() ); + xout.output( doc, new FileWriter( xmlFilename ) ); + } + + protected void saveSettingsJson( final String jsonFilename ) throws IOException + { + BigwarpSettings settings = getSettings(); + settings.serialize( jsonFilename ); + } + + public BigwarpSettings getSettings() + { + return new BigwarpSettings( + this, + viewerP, + viewerQ, + setupAssignments, + bookmarks, + autoSaver, + transformMask.getRandomAccessible(), + landmarkModel, + bwTransform + ); + } + + protected void loadSettings() + { + final JFileChooser fileChooser = new JFileChooser( getLastDirectory() ); + File proposedSettingsFile = new File( "bigwarp.settings.xml" ); + + fileChooser.setSelectedFile( proposedSettingsFile ); + final int returnVal = fileChooser.showOpenDialog( null ); + if ( returnVal == JFileChooser.APPROVE_OPTION ) + { + proposedSettingsFile = fileChooser.getSelectedFile(); + try + { + loadSettings( proposedSettingsFile.getCanonicalPath() ); + } catch ( final Exception e ) + { + e.printStackTrace(); + } + } + } + + protected void loadSettings( final String jsonOrXmlFilename ) throws IOException, + JDOMException + { + if ( jsonOrXmlFilename.endsWith( ".xml" ) ) + { + final SAXBuilder sax = new SAXBuilder(); + final Document doc = sax.build( jsonOrXmlFilename ); + final Element root = doc.getRootElement(); + viewerP.stateFromXml( root.getChild( "viewerP" ) ); + viewerQ.stateFromXml( root.getChild( "viewerQ" ) ); + setupAssignments.restoreFromXml( root ); + bookmarks.restoreFromXml( root ); + activeSourcesDialogP.update(); + activeSourcesDialogQ.update(); + + // auto-save settings + Element autoSaveElem = root.getChild( "autosave" ); + final String autoSavePath = autoSaveElem.getChild( "location" ).getText(); + final long autoSavePeriod = Integer.parseInt( autoSaveElem.getChild( "period" ).getText() ); + BigWarpAutoSaver.setAutosaveOptions( this, autoSavePeriod, autoSavePath ); + + final Element maskSettings = root.getChild( "transform-mask" ); + if ( maskSettings != null ) + transformMask.getRandomAccessible().fromXml( maskSettings ); + } + else + { + getSettings().read( new JsonReader( new FileReader( jsonOrXmlFilename ) ) ); + activeSourcesDialogP.update(); + activeSourcesDialogQ.update(); + } + + viewerFrameP.repaint(); + viewerFrameQ.repaint(); + } +} diff --git a/src/main/java/bigwarp/BigWarpARGBExporter.java b/src/main/java/bigwarp/BigWarpARGBExporter.java index 168f0bcf..2de86dc0 100644 --- a/src/main/java/bigwarp/BigWarpARGBExporter.java +++ b/src/main/java/bigwarp/BigWarpARGBExporter.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -29,14 +29,11 @@ import java.util.ArrayList; import java.util.List; -import org.janelia.saalfeldlab.n5.ij.N5IJUtils; - import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; -import net.imglib2.RealRandomAccess; import net.imglib2.RealRandomAccessible; import net.imglib2.exception.ImgLibException; import net.imglib2.img.display.imagej.ImageJFunctions; @@ -44,16 +41,12 @@ import net.imglib2.img.imageplus.ImagePlusImgFactory; import net.imglib2.realtransform.AffineGet; import net.imglib2.realtransform.AffineRandomAccessible; -import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.realtransform.RealViews; -import net.imglib2.type.NativeType; import net.imglib2.type.numeric.ARGBType; -import net.imglib2.type.numeric.NumericType; import net.imglib2.view.MixedTransformView; import net.imglib2.view.Views; import bdv.export.ProgressWriter; import bdv.tools.brightness.ConverterSetup; -import bdv.viewer.ConverterSetups; import bdv.viewer.Interpolation; import bdv.viewer.SourceAndConverter; @@ -63,19 +56,17 @@ public class BigWarpARGBExporter extends BigWarpExporter private Interpolation interp; public BigWarpARGBExporter( - final List< SourceAndConverter< ARGBType >> sources, + BigWarpData bwData, final List< ConverterSetup > convSetups, - final int[] movingSourceIndexList, - final int[] targetSourceIndexList, final Interpolation interp, final ProgressWriter progress ) { - super( sources, convSetups, movingSourceIndexList, targetSourceIndexList, interp, progress ); + super( bwData, convSetups, interp, progress ); } /** * Returns true if moving image sources are all of the same type. - * + * * @param sources the sources * @param movingSourceIndexList list of indexes for moving sources * @return true if all moving sources are of the same type @@ -84,15 +75,15 @@ public static boolean isTypeListFullyConsistent( ArrayList< SourceAndConverter< { for ( int i = 0; i < movingSourceIndexList.length; i++ ) { - int idx = movingSourceIndexList[ i ]; - Object type = sources.get( idx ).getSpimSource().getType(); + final int idx = movingSourceIndexList[ i ]; + final Object type = sources.get( idx ).getSpimSource().getType(); if ( !type.getClass().equals( ARGBType.class ) ) return false; } return true; } - + @Override public boolean isRGB() { @@ -102,38 +93,38 @@ public boolean isRGB() @Override public RandomAccessibleInterval< ARGBType > exportRai() { - ArrayList< RandomAccessibleInterval< ARGBType > > raiList = new ArrayList< RandomAccessibleInterval< ARGBType > >(); + final ArrayList< RandomAccessibleInterval< ARGBType > > raiList = new ArrayList< RandomAccessibleInterval< ARGBType > >(); buildTotalRenderTransform(); - int numChannels = movingSourceIndexList.length; - VoxelDimensions voxdim = new FinalVoxelDimensions( unit, + final int numChannels = bwData.numMovingSources(); + final VoxelDimensions voxdim = new FinalVoxelDimensions( unit, resolutionTransform.get( 0, 0 ), resolutionTransform.get( 1, 1 ), resolutionTransform.get( 2, 2 )); for ( int i = 0; i < numChannels; i++ ) { - int movingSourceIndex = movingSourceIndexList[ i ]; - final RealRandomAccessible< ARGBType > raiRaw = ( RealRandomAccessible< ARGBType > )sources.get( movingSourceIndex ).getSpimSource().getInterpolatedSource( 0, 0, interp ); + final RealRandomAccessible< ARGBType > raiRaw = ( RealRandomAccessible< ARGBType > )bwData.getMovingSource( i ).getSpimSource().getInterpolatedSource( 0, 0, interp ); // apply the transformations - final AffineRandomAccessible< ARGBType, AffineGet > rai = RealViews.affine( + final AffineRandomAccessible< ARGBType, AffineGet > rai = RealViews.affine( raiRaw, pixelRenderToPhysical.inverse() ); raiList.add( Views.interval( Views.raster( rai ), outputInterval ) ); } - RandomAccessibleInterval< ARGBType > raiStack = Views.stack( raiList ); + final RandomAccessibleInterval< ARGBType > raiStack = Views.stack( raiList ); return raiStack; } + @Override public ImagePlus export() { buildTotalRenderTransform(); - int numChannels = movingSourceIndexList.length; - VoxelDimensions voxdim = new FinalVoxelDimensions( unit, + final int numChannels = bwData.numMovingSources(); + final VoxelDimensions voxdim = new FinalVoxelDimensions( unit, resolutionTransform.get( 0, 0 ), resolutionTransform.get( 1, 1 ), resolutionTransform.get( 2, 2 )); @@ -160,9 +151,9 @@ else if( nThreads == 1 ) dimensions[ 0 ] = outputInterval.dimension( 0 ); // x dimensions[ 1 ] = outputInterval.dimension( 1 ); // y dimensions[ 2 ] = numChannels; // c - dimensions[ 3 ] = outputInterval.dimension( 2 ); // z - FinalInterval destIntervalPerm = new FinalInterval( dimensions ); - RandomAccessibleInterval< ARGBType > img = copyToImageStack( + dimensions[ 3 ] = outputInterval.dimension( 2 ); // z + final FinalInterval destIntervalPerm = new FinalInterval( dimensions ); + final RandomAccessibleInterval< ARGBType > img = copyToImageStack( raiStack, destIntervalPerm, factory, nThreads ); ip = ((ImagePlusImg)img).getImagePlus(); @@ -173,9 +164,9 @@ else if ( outputInterval.numDimensions() == 2 ) dimensions[ 0 ] = outputInterval.dimension( 0 ); // x dimensions[ 1 ] = outputInterval.dimension( 1 ); // y dimensions[ 2 ] = numChannels; // c - dimensions[ 3 ] = 1; // z - FinalInterval destIntervalPerm = new FinalInterval( dimensions ); - RandomAccessibleInterval< ARGBType > img = copyToImageStack( + dimensions[ 3 ] = 1; // z + final FinalInterval destIntervalPerm = new FinalInterval( dimensions ); + final RandomAccessibleInterval< ARGBType > img = copyToImageStack( Views.addDimension( Views.extendMirrorDouble( raiStack )), destIntervalPerm, factory, nThreads ); ip = ((ImagePlusImg)img).getImagePlus(); @@ -186,15 +177,15 @@ else if ( outputInterval.numDimensions() == 2 ) ip.getCalibration().pixelHeight = voxdim.dimension( 1 ); ip.getCalibration().pixelDepth = voxdim.dimension( 2 ); ip.getCalibration().setUnit( voxdim.unit() ); - + if( offsetTransform != null ) { ip.getCalibration().xOrigin = offsetTransform.get( 0, 3 ); ip.getCalibration().yOrigin = offsetTransform.get( 1, 3 ); ip.getCalibration().zOrigin = offsetTransform.get( 2, 3 ); } - - ip.setTitle( sources.get( movingSourceIndexList[ 0 ]).getSpimSource().getName() + nameSuffix ); + + ip.setTitle( bwData.getMovingSource( 0 ).getSpimSource().getName() + nameSuffix ); return ip; } @@ -203,7 +194,7 @@ public static ImagePlus copyToImageStack( final RandomAccessible< ARGBType > rai { // A bit of hacking to make slices the 4th dimension and channels the 3rd // since that's how ImagePlusImgFactory does it - MixedTransformView< ARGBType > raip = Views.permute( rai, 2, 3 ); + final MixedTransformView< ARGBType > raip = Views.permute( rai, 2, 3 ); final long[] dimensions = new long[ itvl.numDimensions() ]; for( int d = 0; d < itvl.numDimensions(); d++ ) @@ -220,7 +211,7 @@ else if( d == 3 ) final ImagePlusImgFactory< ARGBType > factory = new ImagePlusImgFactory< ARGBType >( new ARGBType() ); final ImagePlusImg< ARGBType, ? > target = factory.create( dimensions ); - long[] dims = new long[ target.numDimensions() ]; + final long[] dims = new long[ target.numDimensions() ]; target.dimensions( dims ); double k = 0; diff --git a/src/main/java/bigwarp/BigWarpActions.java b/src/main/java/bigwarp/BigWarpActions.java index 372a25e6..54c559b2 100755 --- a/src/main/java/bigwarp/BigWarpActions.java +++ b/src/main/java/bigwarp/BigWarpActions.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -31,85 +31,540 @@ import javax.swing.KeyStroke; import javax.swing.table.TableCellEditor; +import org.scijava.plugin.Plugin; import org.scijava.ui.behaviour.KeyStrokeAdder; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; import org.scijava.ui.behaviour.util.AbstractNamedAction; +import org.scijava.ui.behaviour.util.Actions; import org.scijava.ui.behaviour.util.InputActionBindings; import bdv.gui.BigWarpViewerFrame; import bdv.tools.ToggleDialogAction; +import bdv.util.Prefs; +import bdv.viewer.LandmarkPointMenu; import bdv.viewer.SourceAndConverter; -import bigwarp.BigWarp.BigWarpData; +import bdv.viewer.AbstractViewerPanel.AlignPlane; import bigwarp.landmarks.LandmarkGridGenerator; import bigwarp.source.GridSource; import bigwarp.util.BigWarpUtils; import mpicbg.models.AbstractModel; import net.imglib2.realtransform.AffineTransform3D; -public class BigWarpActions +public class BigWarpActions extends Actions { + public static final CommandDescriptionProvider.Scope BIGWARP = new CommandDescriptionProvider.Scope( "bigwarp" ); + public static final String BIGWARP_CTXT = "bigwarp"; + public static final String NOT_MAPPED = "not mapped"; + public static final String LANDMARK_MODE_ON = "landmark mode on"; public static final String LANDMARK_MODE_OFF = "landmark mode off"; - public static final String TOGGLE_LANDMARK_MODE = "landmark mode toggle"; + public static final String TRANSFORM_TYPE = "transform type"; + + // General options + public static final String CLOSE_DIALOG = "close dialog window"; + public static final String[] CLOSE_DIALOG_KEYS = new String[] { NOT_MAPPED }; + + public static final String LOAD_PROJECT = "load project"; + public static final String[] LOAD_PROJECT_KEYS = new String[]{ "ctrl shift O" }; + + public static final String SAVE_PROJECT = "save project"; + public static final String[] SAVE_PROJECT_KEYS = new String[]{ "ctrl shift S" }; + + public static final String EXPAND_CARDS = "expand and focus cards panel"; + public static final String[] EXPAND_CARDS_KEYS = new String[] { "P" }; + + public static final String COLLAPSE_CARDS = "collapse cards panel"; + public static final String[] COLLAPSE_CARDS_KEYS = new String[] { "shift P", "shift ESCAPE" }; + + public static final String PREFERENCES_DIALOG = "Preferences"; + public static final String[] PREFERENCES_DIALOG_KEYS= new String[] { "meta COMMA", "ctrl COMMA" }; + + // Display options + public static final String TOGGLE_LANDMARK_MODE = "landmark mode toggle"; + public static final String[] TOGGLE_LANDMARK_MODE_KEYS = new String[]{ "SPACE" }; + + public static final String TOGGLE_POINTS_VISIBLE = "toggle points visible"; + public static final String[] TOGGLE_POINTS_VISIBLE_KEYS = new String[]{ "V" }; - public static final String TOGGLE_POINTS_VISIBLE = "toggle points visible"; public static final String TOGGLE_POINT_NAMES_VISIBLE = "toggle point names visible"; + public static final String[] TOGGLE_POINT_NAMES_VISIBLE_KEYS = new String[]{ "N" }; + public static final String TOGGLE_MOVING_IMAGE_DISPLAY = "toggle moving image display"; + public static final String[] TOGGLE_MOVING_IMAGE_DISPLAY_KEYS = new String[]{ "T" }; + public static final String TOGGLE_BOX_AND_TEXT_OVERLAY_VISIBLE = "toggle box and text overlay visible"; + public static final String[] TOGGLE_BOX_AND_TEXT_OVERLAY_VISIBLE_KEYS = new String[]{ "F8" }; + public static final String ESTIMATE_WARP = "estimate warp"; + public static final String[] ESTIMATE_WARP_KEYS = new String[] { "C" }; + public static final String PRINT_TRANSFORM = "print transform"; + public static final String[] PRINT_TRANSFORM_KEYS = new String[]{ "control shift T" }; + public static final String TOGGLE_ESTIMATE_WARP_ONDRAG = "toggle estimate warp on drag"; + public static final String[] TOGGLE_ESTIMATE_WARP_ONDRAG_KEYS = new String[]{ NOT_MAPPED }; + + public static final String SAVE_SETTINGS = "save settings"; + public static final String[] SAVE_SETTINGS_KEYS = new String[]{ NOT_MAPPED }; + + public static final String LOAD_SETTINGS = "load settings"; + public static final String[] LOAD_SETTINGS_KEYS = new String[]{ NOT_MAPPED }; + + public static final String BRIGHTNESS_SETTINGS = "brightness settings"; + public static final String[] BRIGHTNESS_SETTINGS_KEYS = new String[]{ "S" }; + + public static final String VISIBILITY_AND_GROUPING = "visibility and grouping %s"; + public static final String VISIBILITY_AND_GROUPING_MVG = String.format( VISIBILITY_AND_GROUPING, "moving" ); + public static final String[] VISIBILITY_AND_GROUPING_MVG_KEYS = new String[]{ "F3" }; + + public static final String VISIBILITY_AND_GROUPING_TGT = String.format( VISIBILITY_AND_GROUPING, "target" ); + public static final String[] VISIBILITY_AND_GROUPING_TGT_KEYS = new String[]{ "F4" }; + + public static final String SHOW_HELP = "help"; + public static final String[] SHOW_HELP_KEYS = new String[] { "F1" }; + + public static final String SHOW_SOURCE_INFO = "show source info"; + // Warp visualization options public static final String SHOW_WARPTYPE_DIALOG = "show warp vis dialog" ; + public static final String[] SHOW_WARPTYPE_DIALOG_KEYS = new String[]{ "U" }; + public static final String SET_WARPTYPE_VIS = "set warp vis type %s" ; + public static final String SET_WARPTYPE_VIS_P = "p " + SET_WARPTYPE_VIS; + public static final String SET_WARPTYPE_VIS_Q = "q " + SET_WARPTYPE_VIS; public static final String WARPMAG_BASE = "set warpmag base %s"; public static final String WARPVISGRID = "set warp vis grid %s"; public static final String WARPVISDIALOG = "warp vis dialog"; - public static final String RESET_VIEWER = "reset active viewer"; + // Navigation options + public static final String RESET_VIEWER = "reset active viewer"; + public static final String[] RESET_VIEWER_KEYS = new String[]{"R"}; + + public static final String ALIGN_VIEW_TRANSFORMS = "align view transforms %s"; - public static final String BRIGHTNESS_SETTINGS = "brightness settings"; - public static final String VISIBILITY_AND_GROUPING = "visibility and grouping %s"; - public static final String SHOW_HELP = "help"; - public static final String SHOW_SOURCE_INFO = "show source info"; + public static final String ALIGN_OTHER_TO_ACTIVE = String.format( ALIGN_VIEW_TRANSFORMS, AlignViewerPanelAction.TYPE.OTHER_TO_ACTIVE ); + public static final String[] ALIGN_OTHER_TO_ACTIVE_KEYS = new String[] { "Q" }; - public static final String CROP = "crop"; - public static final String SAVE_SETTINGS = "save settings"; - public static final String LOAD_SETTINGS = "load settings"; + public static final String ALIGN_ACTIVE_TO_OTHER = String.format( ALIGN_VIEW_TRANSFORMS, AlignViewerPanelAction.TYPE.ACTIVE_TO_OTHER ); + public static final String[] ALIGN_ACTIVE_TO_OTHER_KEYS = new String[] { "W" }; + + public static final String JUMP_TO_SELECTED_POINT = "center on selected landmark"; + public static final String[] JUMP_TO_SELECTED_POINT_KEYS = new String[]{ "D" }; + + public static final String JUMP_TO_NEXT_POINT = "center on next landmark"; + public static final String[] JUMP_TO_NEXT_POINT_KEYS = new String[]{ "ctrl D"}; + + public static final String JUMP_TO_PREV_POINT = "center on prev landmark"; + public static final String[] JUMP_TO_PREV_POINT_KEYS = new String[]{ "ctrl shift D"}; + + public static final String JUMP_TO_NEAREST_POINT = "center on nearest landmark"; + public static final String[] JUMP_TO_NEAREST_POINT_KEYS = new String[]{ "E" }; + + // landmark options public static final String LOAD_LANDMARKS = "load landmarks"; + public static final String[] LOAD_LANDMARKS_KEYS = new String[]{ "control O" }; + public static final String SAVE_LANDMARKS = "save landmarks"; + public static final String[] SAVE_LANDMARKS_KEYS = new String[]{ "control S" }; + public static final String QUICK_SAVE_LANDMARKS = "quick save landmarks"; + public static final String[] QUICK_SAVE_LANDMARKS_KEYS = new String[]{ "control Q" }; + + public static final String SET_BOOKMARK = "set bookmark"; + public static final String[] SET_BOOKMARK_KEYS = new String[]{ "shift B" }; + + public static final String GO_TO_BOOKMARK = "go to bookmark"; + public static final String[] GO_TO_BOOKMARK_KEYS = new String[]{ "B" }; + + public static final String GO_TO_BOOKMARK_ROTATION = "go to bookmark rotation"; + public static final String[] GO_TO_BOOKMARK_ROTATION_KEYS = new String[]{ "control shift B" }; + + public static final String UNDO = "undo"; + public static final String[] UNDO_KEYS = new String[]{ "control Z" }; + + public static final String REDO = "redo"; + public static final String[] REDO_KEYS = new String[]{ "control shift Z", "control Y" }; + + public static final String SELECT_TABLE_ROWS = "select table row %d"; + + public static final String LANDMARK_SELECT_ALL = "select all landmarks"; + public static final String[] LANDMARK_SELECT_ALL_KEYS = new String[]{ "ctrl A"}; + + public static final String LANDMARK_DESELECT_ALL = "deselect all landmarks"; + public static final String[] LANDMARK_DESELECT_ALL_KEYS = new String[]{ "ESCAPE", "ctrl shift A" }; + + public static final String LANDMARK_SELECT_ABOVE = "select landmark above"; + public static final String[] LANDMARK_SELECT_ABOVE_KEYS = new String[]{ "ctrl UP"}; + + public static final String LANDMARK_SELECT_ALL_ABOVE = "select all landmarks above"; + public static final String[] LANDMARK_SELECT_ALL_ABOVE_KEYS = new String[]{ "ctrl shift UP"}; + + public static final String LANDMARK_SELECT_BELOW = "select landmark below"; + public static final String[] LANDMARK_SELECT_BELOW_KEYS = new String[]{ "ctrl DOWN"}; + + public static final String LANDMARK_SELECT_ALL_BELOW = "select all landmarks below"; + public static final String[] LANDMARK_SELECT_ALL_BELOW_KEYS = new String[]{ "ctrl shift DOWN"}; + + public static final String LANDMARK_DELETE_SELECTED = "delete selected landmarks"; + public static final String[] LANDMARK_DELETE_SELECTED_KEYS = new String[]{ "DELETE" }; + + public static final String LANDMARK_DEACTIVATE_SELECTED = "deactivate selected landmarks"; + public static final String[] LANDMARK_DEACTIVATE_SELECTED_KEYS = new String[]{ "BACK_SPACE" }; + + public static final String[] LANDMARK_ACTIVATE_SELECTED_KEYS = new String[]{ "ctrl BACK_SPACE" }; + public static final String LANDMARK_ACTIVATE_SELECTED = "activate selected landmarks"; public static final String LANDMARK_GRID_DIALOG = "landmark grid dialog"; + public static final String MASK_IMPORT = "import mask"; + public static final String MASK_REMOVE = "remove mask"; + public static final String MASK_SIZE_EDIT = "mask edit"; + public static final String MASK_VIS_TOGGLE = "mask vis toggle"; + + // export options public static final String SAVE_WARPED = "save warped"; public static final String SAVE_WARPED_XML = "save warped xml"; + public static final String[] SAVE_WARPED_XML_KEYS = new String[] { "ctrl E" }; public static final String EXPORT_IP = "export imageplus"; - public static final String EXPORT_WARP = "export warp field"; - public static final String EXPORT_AFFINE = "export affine"; + public static final String[] EXPORT_IP_KEYS = new String[] { "ctrl shift W" }; - public static final String WARP_TO_SELECTED_POINT = "warp to selected landmark"; - public static final String WARP_TO_NEXT_POINT = "warp to next landmark %s"; - public static final String WARP_TO_NEAREST_POINT = "warp to nearest landmark"; + public static final String EXPORT_WARP = "export warp field"; + public static final String[] EXPORT_WARP_KEYS = new String[] { "ctrl W" }; - public static final String SET_BOOKMARK = "set bookmark"; - public static final String GO_TO_BOOKMARK = "go to bookmark"; - public static final String GO_TO_BOOKMARK_ROTATION = "go to bookmark rotation"; + public static final String EXPORT_AFFINE = "export affine"; + public static final String[] EXPORT_AFFINE_KEYS = new String[] { "ctrl A" }; - public static final String UNDO = "undo"; - public static final String REDO = "redo"; + public static final String CLEAR_MOVING = "table clear moving"; + public static final String[] CLEAR_MOVING_KEYS = new String[] { "BACK_SPACE" }; - public static final String SELECT_TABLE_ROWS = "select table row %d"; + public static final String CLEAR_FIXED = "table clear fixed"; + public static final String[] CLEAR_FIXED_KEYS = new String[] { "ctrl BACK_SPACE" }; + + public static final String CLEAR_SELECTED_MOVING = "table clear selected moving"; + public static final String[] CLEAR_SELECTED_MOVING_KEYS = new String[] { "ctrl BACK_SPACE" }; + + public static final String CLEAR_SELECTED_FIXED = "table clear selected fixed"; + public static final String[] CLEAR_SELECTED_FIXED_KEYS = new String[] { "ctrl BACK_SPACE" }; + + public static final String DELETE = "table delete"; + public static final String[] DELETE_KEYS = new String[] { NOT_MAPPED }; + + public static final String DELETE_SELECTED = "table delete selected "; + public static final String[] DELETE_SELECTED_KEYS = new String[] { "DELETE" }; + + public static final String ACTIVATE_SELECTED = "table activate selected"; + public static final String[] ACTIVATE_SELECTED_KEYS = new String[] { NOT_MAPPED }; + + public static final String DEACTIVATE_SELECTED = "table deactivate selected "; + public static final String[] DEACTIVATE_SELECTED_KEYS = new String[]{ NOT_MAPPED }; public static final String DEBUG = "debug"; public static final String GARBAGE_COLLECTION = "garbage collection"; + public static final String XYPLANE = "xyPlane"; + public static final String[] XYPLANE_KEYS = new String[] { "shift Z" }; + + public static final String YZPLANE = "yzPlane"; + public static final String[] YZPLANE_KEYS = new String[] { "shift X" }; + + public static final String XZPLANE = "xzPlane"; + public static final String[] XZPLANE_KEYS = new String[] { "shift Y", "shift A" }; + + public BigWarpActions( final KeyStrokeAdder.Factory keyConfig, String name ) + { + this( keyConfig, "bigwarp", name ); + } + + public BigWarpActions( final KeyStrokeAdder.Factory keyConfig, String context, String name ) + { + super( keyConfig, context, name ); + } + + /* + * Command descriptions for all provided commands + */ + @Plugin( type = CommandDescriptionProvider.class ) + public static class Descriptions extends CommandDescriptionProvider + { + public Descriptions() + { + super( BIGWARP, "bigwarp", "bw-table", "navigation" ); + } + + @Override + public void getCommandDescriptions( final CommandDescriptions descriptions ) + { + descriptions.add( CLOSE_DIALOG, CLOSE_DIALOG_KEYS, "Close bigwarp." ); + + descriptions.add( SAVE_PROJECT, SAVE_PROJECT_KEYS, "Save a bigwarp project." ); + descriptions.add( LOAD_PROJECT, LOAD_PROJECT_KEYS, "Load a bigwarp project." ); + + descriptions.add( TOGGLE_LANDMARK_MODE, TOGGLE_LANDMARK_MODE_KEYS, "Toggle landmark mode." ); + descriptions.add( TOGGLE_MOVING_IMAGE_DISPLAY, TOGGLE_MOVING_IMAGE_DISPLAY_KEYS, "Toggle landmark mode." ); + + descriptions.add( SHOW_HELP, SHOW_HELP_KEYS, "Show the Help dialog." ); + descriptions.add( SHOW_WARPTYPE_DIALOG, SHOW_WARPTYPE_DIALOG_KEYS, "Show the BigWarp options dialog." ); + descriptions.add( PREFERENCES_DIALOG, PREFERENCES_DIALOG_KEYS, "Show the appearance and keymap dialog." ); + + descriptions.add( BRIGHTNESS_SETTINGS, BRIGHTNESS_SETTINGS_KEYS, "Show the Brightness & Colors dialog." ); + descriptions.add( VISIBILITY_AND_GROUPING_MVG, VISIBILITY_AND_GROUPING_MVG_KEYS, "Show the Visibility&Grouping dialog for the moving frame." ); + descriptions.add( VISIBILITY_AND_GROUPING_TGT, VISIBILITY_AND_GROUPING_TGT_KEYS, "Show the Visibility&Grouping dialog for the fixed frame." ); + descriptions.add( SAVE_SETTINGS, SAVE_SETTINGS_KEYS, "Save the BigDataViewer settings to a settings.xml file." ); + descriptions.add( LOAD_SETTINGS, LOAD_SETTINGS_KEYS, "Load the BigDataViewer settings from a settings.xml file." ); + + descriptions.add( SET_BOOKMARK, SET_BOOKMARK_KEYS, "Set a labeled bookmark at the current location." ); + descriptions.add( GO_TO_BOOKMARK, GO_TO_BOOKMARK_KEYS, "Retrieve a labeled bookmark location." ); + descriptions.add( GO_TO_BOOKMARK_ROTATION, GO_TO_BOOKMARK_ROTATION_KEYS, "Retrieve a labeled bookmark, set only the orientation." ); + + descriptions.add( RESET_VIEWER, RESET_VIEWER_KEYS, "Resets the view to the view on startup." ); + descriptions.add( ALIGN_OTHER_TO_ACTIVE, ALIGN_OTHER_TO_ACTIVE_KEYS, "Sets the view of the non-active viewer to match the active viewer." ); + descriptions.add( ALIGN_ACTIVE_TO_OTHER, ALIGN_ACTIVE_TO_OTHER_KEYS, "Sets the view of the active viewer to match the non-active viewer." ); + descriptions.add( JUMP_TO_SELECTED_POINT, JUMP_TO_SELECTED_POINT_KEYS, "Center the viewer on the selected landmark." ); + descriptions.add( JUMP_TO_NEAREST_POINT, JUMP_TO_NEAREST_POINT_KEYS, "Center the viewer on the nearest landmark." ); + descriptions.add( JUMP_TO_NEXT_POINT, JUMP_TO_NEXT_POINT_KEYS, "Center the viewer on the next landmark." ); + descriptions.add( JUMP_TO_PREV_POINT, JUMP_TO_PREV_POINT_KEYS, "Center the viewer on the previous landmark." ); + + // cards + descriptions.add( EXPAND_CARDS, EXPAND_CARDS_KEYS, "Expand and focus the BigDataViewer card panel" ); + descriptions.add( COLLAPSE_CARDS, COLLAPSE_CARDS_KEYS, "Collapse the BigDataViewer card panel" ); + + // export + descriptions.add( EXPORT_IP, EXPORT_IP_KEYS, "Export moving image to ImageJ." ); + descriptions.add( SAVE_WARPED_XML, SAVE_WARPED_XML_KEYS, "Export moving image to BigDataViewer xml/h5." ); + descriptions.add( EXPORT_WARP, EXPORT_WARP_KEYS, "Show the dialog to export the displacement field." ); + descriptions.add( EXPORT_AFFINE, EXPORT_AFFINE_KEYS, "Print the affine transformation." ); + descriptions.add( PRINT_TRANSFORM,PRINT_TRANSFORM_KEYS, "Prints the current transformation." ); + + // landmarks + descriptions.add( LOAD_LANDMARKS, LOAD_LANDMARKS_KEYS, "Load landmark from a file." ); + descriptions.add( SAVE_LANDMARKS, SAVE_LANDMARKS_KEYS, "Save landmark from a file." ); + descriptions.add( QUICK_SAVE_LANDMARKS, QUICK_SAVE_LANDMARKS_KEYS, "Quick save landmarks."); + descriptions.add( UNDO, UNDO_KEYS, "Undo the last landmark change." ); + descriptions.add( REDO, REDO_KEYS, "Redo the last landmark change." ); + + descriptions.add( TOGGLE_POINTS_VISIBLE, TOGGLE_POINTS_VISIBLE_KEYS, "Toggle visibility of landmarks." ); + descriptions.add( TOGGLE_POINT_NAMES_VISIBLE, TOGGLE_POINT_NAMES_VISIBLE_KEYS , "Toggle visibility of landmark names." ); + + descriptions.add( TOGGLE_BOX_AND_TEXT_OVERLAY_VISIBLE, TOGGLE_BOX_AND_TEXT_OVERLAY_VISIBLE_KEYS, "Toggle visibility of bounding box and source information." ); + descriptions.add( TOGGLE_POINTS_VISIBLE, TOGGLE_POINTS_VISIBLE_KEYS, "Toggle visibility of landmark points." ); + descriptions.add( TOGGLE_POINT_NAMES_VISIBLE, TOGGLE_POINT_NAMES_VISIBLE_KEYS, "Toggle visibility of landmark point names." ); + + descriptions.add( LANDMARK_ACTIVATE_SELECTED, LANDMARK_ACTIVATE_SELECTED_KEYS, "Activate selected landmarks." ); + descriptions.add( LANDMARK_DEACTIVATE_SELECTED, LANDMARK_DEACTIVATE_SELECTED_KEYS, "Deactivate selected landmarks." ); + + // alignment + descriptions.add( XYPLANE, XYPLANE_KEYS, "xy plane" ); + descriptions.add( XZPLANE, XZPLANE_KEYS, "xz plane" ); + descriptions.add( YZPLANE, YZPLANE_KEYS, "yz plane" ); + } + } + + @Plugin( type = CommandDescriptionProvider.class ) + public static class TableDescriptions extends CommandDescriptionProvider + { + public TableDescriptions() + { + super( BIGWARP, "bw-table", "bigwarp", "navigation" ); + } + + @Override + public void getCommandDescriptions( final CommandDescriptions descriptions ) + { + descriptions.add( SAVE_PROJECT, SAVE_PROJECT_KEYS, "Save a bigwarp project." ); + descriptions.add( LOAD_PROJECT, LOAD_PROJECT_KEYS, "Load a bigwarp project." ); + + descriptions.add( CLEAR_MOVING, CLEAR_MOVING_KEYS, "Clears moving landmark under the mouse cursor." ); + descriptions.add( CLEAR_FIXED, CLEAR_FIXED_KEYS, "Clears fixed landmark under the mouse cursor." ); + descriptions.add( CLEAR_SELECTED_MOVING, CLEAR_SELECTED_MOVING_KEYS, "Clears moving landmark for currently selected row." ); + descriptions.add( CLEAR_SELECTED_FIXED, CLEAR_SELECTED_FIXED_KEYS, "Clears fixed landmark for currently selected row." ); + + descriptions.add( DELETE, DELETE_KEYS, "Delete table row under the mouse cursor" ); + descriptions.add( DELETE_SELECTED, DELETE_SELECTED_KEYS, "Delete all selected rows in the table" ); + + descriptions.add( ACTIVATE_SELECTED, ACTIVATE_SELECTED_KEYS, "Activate all selected rows in the table" ); + descriptions.add( DEACTIVATE_SELECTED, DEACTIVATE_SELECTED_KEYS, "Deactivate all selected rows in the table" ); + + descriptions.add( LOAD_LANDMARKS, LOAD_LANDMARKS_KEYS, "Load landmark from a file." ); + descriptions.add( SAVE_LANDMARKS, SAVE_LANDMARKS_KEYS, "Save landmark from a file." ); + descriptions.add( QUICK_SAVE_LANDMARKS, QUICK_SAVE_LANDMARKS_KEYS, "Quick save landmarks."); + + descriptions.add( LANDMARK_SELECT_ALL, LANDMARK_SELECT_ALL_KEYS, "Select all landmarks." ); + descriptions.add( LANDMARK_DESELECT_ALL, LANDMARK_DESELECT_ALL_KEYS, "Deselect all landmarks." ); + + descriptions.add( LANDMARK_DELETE_SELECTED, LANDMARK_DELETE_SELECTED_KEYS, "Delete selected landmarks." ); + descriptions.add( LANDMARK_ACTIVATE_SELECTED, LANDMARK_ACTIVATE_SELECTED_KEYS, "Activate selected landmarks." ); + descriptions.add( LANDMARK_DEACTIVATE_SELECTED, LANDMARK_DEACTIVATE_SELECTED_KEYS, "Deactivate selected landmarks." ); + + descriptions.add( LANDMARK_SELECT_ABOVE, LANDMARK_SELECT_ABOVE_KEYS, "Add the row above the curent selection to the selection" ); + descriptions.add( LANDMARK_SELECT_ALL_ABOVE, LANDMARK_SELECT_ALL_ABOVE_KEYS, "Add the all rows above the curent selection to the selection" ); + descriptions.add( LANDMARK_SELECT_BELOW, LANDMARK_SELECT_BELOW_KEYS, "Add the row below the curent selection to the selection" ); + descriptions.add( LANDMARK_SELECT_ALL_BELOW, LANDMARK_SELECT_ALL_BELOW_KEYS, "Add the all rows below the curent selection to the selection" ); + + descriptions.add( UNDO, UNDO_KEYS, "Undo the last landmark change." ); + descriptions.add( REDO, REDO_KEYS, "Redo the last landmark change." ); + + descriptions.add( TOGGLE_POINTS_VISIBLE, TOGGLE_POINTS_VISIBLE_KEYS, "Toggle visibility of landmark points." ); + descriptions.add( TOGGLE_POINT_NAMES_VISIBLE, TOGGLE_POINT_NAMES_VISIBLE_KEYS, "Toggle visibility of landmark point names." ); + } + } + + public static void installViewerActions( + Actions actions, + final BigWarpViewerFrame bwFrame, + final BigWarp< ? > bw ) + { + + final InputActionBindings inputActionBindings = bwFrame.getKeybindings(); + actions.install( inputActionBindings, "bw" ); + + actions.runnableAction( bw::saveProject, SAVE_PROJECT, SAVE_PROJECT_KEYS ); + actions.runnableAction( bw::loadProject, LOAD_PROJECT, LOAD_PROJECT_KEYS ); + + actions.runnableAction( () -> { bw.getBwTransform().transformToString(); }, PRINT_TRANSFORM, PRINT_TRANSFORM_KEYS); + actions.runnableAction( bw::toggleInLandmarkMode, TOGGLE_LANDMARK_MODE, TOGGLE_LANDMARK_MODE_KEYS); + actions.runnableAction( bw::toggleMovingImageDisplay, TOGGLE_MOVING_IMAGE_DISPLAY, TOGGLE_MOVING_IMAGE_DISPLAY_KEYS ); + + actions.namedAction( new TogglePointsVisibleAction( TOGGLE_POINTS_VISIBLE, bw ), TOGGLE_POINTS_VISIBLE_KEYS); + actions.namedAction( new TogglePointNameVisibleAction( TOGGLE_POINT_NAMES_VISIBLE, bw ), TOGGLE_POINT_NAMES_VISIBLE_KEYS); + + // navigation + actions.runnableAction( bw::resetView, RESET_VIEWER, RESET_VIEWER_KEYS); + actions.runnableAction( bw::matchOtherViewerPanelToActive, ALIGN_OTHER_TO_ACTIVE, ALIGN_OTHER_TO_ACTIVE_KEYS ); + actions.runnableAction( bw::matchActiveViewerPanelToOther, ALIGN_ACTIVE_TO_OTHER, ALIGN_ACTIVE_TO_OTHER_KEYS ); + actions.runnableAction( bw::jumpToSelectedLandmark, JUMP_TO_SELECTED_POINT, JUMP_TO_SELECTED_POINT_KEYS ); + actions.runnableAction( bw::jumpToNearestLandmark, JUMP_TO_NEAREST_POINT, JUMP_TO_NEAREST_POINT_KEYS ); + actions.runnableAction( bw::jumpToNextLandmark, JUMP_TO_NEXT_POINT, JUMP_TO_NEXT_POINT_KEYS ); + actions.runnableAction( bw::jumpToPrevLandmark, JUMP_TO_PREV_POINT, JUMP_TO_PREV_POINT_KEYS ); + + // bookmarks + actions.runnableAction( bw::goToBookmark, GO_TO_BOOKMARK, GO_TO_BOOKMARK_KEYS ); + actions.runnableAction( bw::goToBookmarkRotation, GO_TO_BOOKMARK_ROTATION, GO_TO_BOOKMARK_ROTATION_KEYS ); + actions.runnableAction( bw::setBookmark, SET_BOOKMARK, SET_BOOKMARK_KEYS ); + + // cards + actions.runnableAction( bwFrame::expandAndFocusCardPanel, EXPAND_CARDS, EXPAND_CARDS_KEYS ); + actions.runnableAction( bwFrame::collapseCardPanel, COLLAPSE_CARDS, COLLAPSE_CARDS_KEYS ); + + // export + actions.runnableAction( bw::exportWarpField, EXPORT_WARP, EXPORT_WARP_KEYS ); + actions.runnableAction( () -> { bw.getBwTransform().printAffine(); }, EXPORT_AFFINE, EXPORT_AFFINE_KEYS ); + + // dialogs + actions.namedAction( new ToggleDialogAction( SHOW_HELP, bw.helpDialog ), SHOW_HELP_KEYS ); + actions.namedAction( new ToggleDialogAction( VISIBILITY_AND_GROUPING_MVG, bw.activeSourcesDialogP ), VISIBILITY_AND_GROUPING_MVG_KEYS ); + actions.namedAction( new ToggleDialogAction( VISIBILITY_AND_GROUPING_TGT, bw.activeSourcesDialogQ ), VISIBILITY_AND_GROUPING_TGT_KEYS ); + actions.namedAction( new ToggleDialogAction( SHOW_WARPTYPE_DIALOG, bw.warpVisDialog ), SHOW_WARPTYPE_DIALOG_KEYS ); + actions.namedAction( new ToggleDialogAction( PREFERENCES_DIALOG, bw.preferencesDialog ), PREFERENCES_DIALOG_KEYS ); + + // landmarks unbound + actions.runnableAction( () -> { bw.getLandmarkPanel().getJTable().selectAll(); }, LANDMARK_SELECT_ALL, NOT_MAPPED ); + actions.runnableAction( () -> { bw.getLandmarkPanel().getJTable().clearSelection(); }, LANDMARK_DESELECT_ALL, NOT_MAPPED ); + + actions.namedAction( bw.landmarkPopupMenu.deleteSelectedHandler, LANDMARK_DELETE_SELECTED_KEYS ); + + actions.namedAction( bw.landmarkPopupMenu.activateSelectedHandler, LANDMARK_ACTIVATE_SELECTED_KEYS ); + actions.namedAction( bw.landmarkPopupMenu.deactivateSelectedHandler, LANDMARK_DEACTIVATE_SELECTED_KEYS ); + + actions.namedAction( bw.landmarkPopupMenu.addAboveHandler, LANDMARK_SELECT_ABOVE_KEYS ); + actions.namedAction( bw.landmarkPopupMenu.addAllAboveHandler, LANDMARK_SELECT_ALL_ABOVE_KEYS ); + actions.namedAction( bw.landmarkPopupMenu.addBelowHandler, LANDMARK_SELECT_BELOW_KEYS ); + actions.namedAction( bw.landmarkPopupMenu.addAllBelowHandler, LANDMARK_SELECT_ALL_BELOW_KEYS ); + + // landmarks bound + actions.runnableAction( bw::loadLandmarks, LOAD_LANDMARKS, LOAD_LANDMARKS_KEYS ); + actions.runnableAction( bw::saveLandmarks, SAVE_LANDMARKS, SAVE_LANDMARKS_KEYS ); + actions.runnableAction( bw::quickSaveLandmarks, QUICK_SAVE_LANDMARKS, QUICK_SAVE_LANDMARKS_KEYS ); + + actions.namedAction( new UndoRedoAction( UNDO, bw ), UNDO_KEYS ); + actions.namedAction( new UndoRedoAction( REDO, bw ), REDO_KEYS ); + + actions.namedAction( new ToggleBoxAndTexOverlayVisibility( TOGGLE_BOX_AND_TEXT_OVERLAY_VISIBLE, bw ), TOGGLE_BOX_AND_TEXT_OVERLAY_VISIBLE_KEYS); + actions.namedAction( new TogglePointsVisibleAction( TOGGLE_POINTS_VISIBLE, bw ), TOGGLE_POINTS_VISIBLE_KEYS ); + actions.namedAction( new TogglePointNameVisibleAction( TOGGLE_POINT_NAMES_VISIBLE, bw ), TOGGLE_POINT_NAMES_VISIBLE_KEYS); + + actions.runnableAction( () -> bw.alignActive( AlignPlane.XY ), XYPLANE, XYPLANE_KEYS ); + actions.runnableAction( () -> bw.alignActive( AlignPlane.XZ ), XZPLANE, XZPLANE_KEYS ); + actions.runnableAction( () -> bw.alignActive( AlignPlane.ZY ), YZPLANE, YZPLANE_KEYS ); + } + + public static void installTableActions( + Actions actions, + final InputActionBindings inputActionBindings, + final BigWarp< ? > bw ) + { + actions.install( inputActionBindings, "bw-table" ); + + actions.runnableAction( bw::saveProject, SAVE_PROJECT, SAVE_PROJECT_KEYS ); + actions.runnableAction( bw::loadProject, LOAD_PROJECT, LOAD_PROJECT_KEYS ); + + // unmapped + actions.runnableAction( () -> { bw.getBwTransform().transformToString(); }, PRINT_TRANSFORM, PRINT_TRANSFORM_KEYS); + actions.runnableAction( bw::toggleInLandmarkMode, TOGGLE_LANDMARK_MODE, TOGGLE_LANDMARK_MODE_KEYS); + actions.runnableAction( bw::toggleMovingImageDisplay, TOGGLE_MOVING_IMAGE_DISPLAY, TOGGLE_MOVING_IMAGE_DISPLAY_KEYS ); + + // navigation + actions.runnableAction( bw::resetView, RESET_VIEWER, NOT_MAPPED ); + actions.runnableAction( bw::matchOtherViewerPanelToActive, ALIGN_OTHER_TO_ACTIVE, NOT_MAPPED ); + actions.runnableAction( bw::matchActiveViewerPanelToOther, ALIGN_ACTIVE_TO_OTHER, NOT_MAPPED ); + actions.runnableAction( bw::jumpToSelectedLandmark, JUMP_TO_SELECTED_POINT, NOT_MAPPED ); + actions.runnableAction( bw::jumpToNearestLandmark, JUMP_TO_NEAREST_POINT, NOT_MAPPED ); + actions.runnableAction( bw::jumpToNextLandmark, JUMP_TO_NEXT_POINT, NOT_MAPPED ); + actions.runnableAction( bw::jumpToPrevLandmark, JUMP_TO_PREV_POINT, NOT_MAPPED ); + + // bookmarks + actions.runnableAction( bw::goToBookmark, GO_TO_BOOKMARK, NOT_MAPPED ); + actions.runnableAction( bw::goToBookmarkRotation, GO_TO_BOOKMARK_ROTATION, NOT_MAPPED ); + actions.runnableAction( bw::setBookmark, SET_BOOKMARK, NOT_MAPPED ); + + // cards + actions.runnableAction( ()->{}, EXPAND_CARDS, NOT_MAPPED ); + actions.runnableAction( ()->{}, COLLAPSE_CARDS, NOT_MAPPED ); + + // export + actions.runnableAction( bw::exportWarpField, EXPORT_WARP, EXPORT_WARP_KEYS ); + actions.runnableAction( () -> { bw.getBwTransform().printAffine(); }, EXPORT_AFFINE, EXPORT_AFFINE_KEYS ); + + // dialogs + actions.namedAction( new ToggleDialogAction( SHOW_HELP, bw.helpDialog ), SHOW_HELP_KEYS ); + actions.namedAction( new ToggleDialogAction( VISIBILITY_AND_GROUPING_MVG, bw.activeSourcesDialogP ), VISIBILITY_AND_GROUPING_MVG_KEYS ); + actions.namedAction( new ToggleDialogAction( VISIBILITY_AND_GROUPING_TGT, bw.activeSourcesDialogQ ), VISIBILITY_AND_GROUPING_TGT_KEYS ); + actions.namedAction( new ToggleDialogAction( SHOW_WARPTYPE_DIALOG, bw.warpVisDialog ), SHOW_WARPTYPE_DIALOG_KEYS ); + actions.namedAction( new ToggleDialogAction( PREFERENCES_DIALOG, bw.preferencesDialog ), PREFERENCES_DIALOG_KEYS ); + + // landmarks + actions.runnableAction( () -> { bw.getLandmarkPanel().getJTable().selectAll(); }, LANDMARK_SELECT_ALL, LANDMARK_SELECT_ALL_KEYS ); + actions.runnableAction( () -> { bw.getLandmarkPanel().getJTable().clearSelection(); }, LANDMARK_DESELECT_ALL, LANDMARK_DESELECT_ALL_KEYS ); + + actions.namedAction( bw.landmarkPopupMenu.deleteSelectedHandler, LANDMARK_DELETE_SELECTED_KEYS ); + + actions.namedAction( bw.landmarkPopupMenu.activateSelectedHandler, LANDMARK_ACTIVATE_SELECTED_KEYS ); + actions.namedAction( bw.landmarkPopupMenu.deactivateSelectedHandler, LANDMARK_DEACTIVATE_SELECTED_KEYS ); + + actions.namedAction( bw.landmarkPopupMenu.addAboveHandler, LANDMARK_SELECT_ABOVE_KEYS ); + actions.namedAction( bw.landmarkPopupMenu.addAllAboveHandler, LANDMARK_SELECT_ALL_ABOVE_KEYS ); + actions.namedAction( bw.landmarkPopupMenu.addBelowHandler, LANDMARK_SELECT_BELOW_KEYS ); + actions.namedAction( bw.landmarkPopupMenu.addAllBelowHandler, LANDMARK_SELECT_ALL_BELOW_KEYS ); + + actions.runnableAction( bw::loadLandmarks, LOAD_LANDMARKS, LOAD_LANDMARKS_KEYS ); + actions.runnableAction( bw::saveLandmarks, SAVE_LANDMARKS, SAVE_LANDMARKS_KEYS ); + actions.runnableAction( bw::quickSaveLandmarks, QUICK_SAVE_LANDMARKS, QUICK_SAVE_LANDMARKS_KEYS ); + + actions.namedAction( new UndoRedoAction( UNDO, bw ), UNDO_KEYS ); + actions.namedAction( new UndoRedoAction( REDO, bw ), REDO_KEYS ); + + actions.namedAction( new ToggleBoxAndTexOverlayVisibility( TOGGLE_BOX_AND_TEXT_OVERLAY_VISIBLE, bw ), NOT_MAPPED ); + actions.namedAction( new TogglePointsVisibleAction( TOGGLE_POINTS_VISIBLE, bw ), TOGGLE_POINTS_VISIBLE_KEYS ); + actions.namedAction( new TogglePointNameVisibleAction( TOGGLE_POINT_NAMES_VISIBLE, bw ), TOGGLE_POINT_NAMES_VISIBLE_KEYS); + } + /** * Create BigWarp actions and install them in the specified - * {@link InputActionBindings}. + * {@link InputActionBindings}. * * @param inputActionBindings * {@link InputMap} and {@link ActionMap} are installed here. @@ -126,10 +581,10 @@ public static void installActionBindings( inputActionBindings.addActionMap( "bw", createActionMap( bw ) ); inputActionBindings.addInputMap( "bw", createInputMap( keyProperties ) ); - inputActionBindings.addActionMap( "bwv", createActionMapViewer( bw ) ); + inputActionBindings.addActionMap( "bwV", createActionMapViewer( bw ) ); inputActionBindings.addInputMap( "bwv", createInputMapViewer( keyProperties ) ); } - + public static void installLandmarkPanelActionBindings( final InputActionBindings inputActionBindings, final BigWarp< ? > bw, @@ -138,14 +593,14 @@ public static void installLandmarkPanelActionBindings( { inputActionBindings.addActionMap( "bw", createActionMap( bw ) ); inputActionBindings.addInputMap( "bw", createInputMap( keyProperties ) ); - - TableCellEditor celled = landmarkTable.getCellEditor( 0, 1 ); - Component c = celled.getTableCellEditorComponent(landmarkTable, Boolean.TRUE, true, 0, 1 ); - InputMap parentInputMap = ((JCheckBox)c).getInputMap().getParent(); + final TableCellEditor celled = landmarkTable.getCellEditor( 0, 1 ); + final Component c = celled.getTableCellEditorComponent(landmarkTable, Boolean.TRUE, true, 0, 1 ); + + final InputMap parentInputMap = ((JCheckBox)c).getInputMap().getParent(); parentInputMap.clear(); - KeyStroke enterDownKS = KeyStroke.getKeyStroke("pressed ENTER" ); - KeyStroke enterUpKS = KeyStroke.getKeyStroke("released ENTER" ); + final KeyStroke enterDownKS = KeyStroke.getKeyStroke("pressed ENTER" ); + final KeyStroke enterUpKS = KeyStroke.getKeyStroke("released ENTER" ); parentInputMap.put( enterDownKS, "pressed" ); parentInputMap.put( enterUpKS, "released" ); @@ -157,20 +612,20 @@ public static InputMap createInputMapViewer( final KeyStrokeAdder.Factory keyPro final KeyStrokeAdder map = keyProperties.keyStrokeAdder( inputMap ); map.put(RESET_VIEWER, "R"); - + map.put( String.format( VISIBILITY_AND_GROUPING, "moving" ), "F3" ); map.put( String.format( VISIBILITY_AND_GROUPING, "target" ), "F4" ); - map.put( "transform type", "F2" ); - + map.put( TRANSFORM_TYPE, "F2" ); + map.put( String.format( ALIGN_VIEW_TRANSFORMS, AlignViewerPanelAction.TYPE.OTHER_TO_ACTIVE ), "Q" ); map.put( String.format( ALIGN_VIEW_TRANSFORMS, AlignViewerPanelAction.TYPE.ACTIVE_TO_OTHER ), "W" ); map.put( TOGGLE_MOVING_IMAGE_DISPLAY, "T" ); - map.put( WARP_TO_SELECTED_POINT, "D" ); - map.put( String.format( WARP_TO_NEXT_POINT, true), "ctrl D" ); - map.put( String.format( WARP_TO_NEXT_POINT, false), "ctrl shift D" ); - map.put( WARP_TO_NEAREST_POINT, "E" ); + map.put( JUMP_TO_SELECTED_POINT, "D" ); + map.put( String.format( JUMP_TO_NEXT_POINT, true), "ctrl D" ); + map.put( String.format( JUMP_TO_NEXT_POINT, false), "ctrl shift D" ); + map.put( JUMP_TO_NEAREST_POINT, "E" ); map.put( EXPORT_WARP, "ctrl W" ); map.put( EXPORT_AFFINE, "ctrl A" ); @@ -188,7 +643,7 @@ public static ActionMap createActionMapViewer( final BigWarp< ? > bw ) new ToggleDialogAction( String.format( VISIBILITY_AND_GROUPING, "moving" ), bw.activeSourcesDialogP ).put( actionMap ); new ToggleDialogAction( String.format( VISIBILITY_AND_GROUPING, "target" ), bw.activeSourcesDialogQ ).put( actionMap ); - new ToggleDialogAction( "transform type", bw.transformSelector ).put( actionMap ); + new ToggleDialogAction( TRANSFORM_TYPE, bw.transformSelector ).put( actionMap ); for( final BigWarp.WarpVisType t: BigWarp.WarpVisType.values()) { @@ -200,10 +655,10 @@ public static ActionMap createActionMapViewer( final BigWarp< ? > bw ) new ResetActiveViewerAction( bw ).put( actionMap ); new AlignViewerPanelAction( bw, AlignViewerPanelAction.TYPE.ACTIVE_TO_OTHER ).put( actionMap ); new AlignViewerPanelAction( bw, AlignViewerPanelAction.TYPE.OTHER_TO_ACTIVE ).put( actionMap ); - new WarpToSelectedAction( bw ).put( actionMap ); - new WarpToNextAction( bw, true ).put( actionMap ); - new WarpToNextAction( bw, false ).put( actionMap ); - new WarpToNearest( bw ).put( actionMap ); + new JumpToSelectedAction( bw ).put( actionMap ); + new JumpToNextAction( bw, true ).put( actionMap ); + new JumpToNextAction( bw, false ).put( actionMap ); + new JumpToNearest( bw ).put( actionMap ); for( final GridSource.GRID_TYPE t : GridSource.GRID_TYPE.values()) new SetWarpVisGridTypeAction( String.format( WARPVISGRID, t.name()), bw, t ).put( actionMap ); @@ -215,6 +670,9 @@ public static ActionMap createActionMapViewer( final BigWarp< ? > bw ) new SaveSettingsAction( bw ).put( actionMap ); new LoadSettingsAction( bw ).put( actionMap ); + new SaveProjectAction( bw ).put( actionMap ); + new LoadProjectAction( bw ).put( actionMap ); + return actionMap; } @@ -226,6 +684,7 @@ public static InputMap createInputMap( final KeyStrokeAdder.Factory keyPropertie map.put( SHOW_WARPTYPE_DIALOG, "U" ); map.put( TOGGLE_LANDMARK_MODE, "SPACE" ); + map.put( BRIGHTNESS_SETTINGS, "S" ); // map.put( LANDMARK_MODE_ON, "pressed SPACE" ); // // the few lines below are super ugly, but are necessary for robustness // map.put( LANDMARK_MODE_ON, "shift pressed SPACE" ); @@ -246,13 +705,15 @@ public static InputMap createInputMap( final KeyStrokeAdder.Factory keyPropertie // map.put( LANDMARK_MODE_OFF, "ctrl shift released SPACE", "released" ); // map.put( LANDMARK_MODE_OFF, "alt ctrl shift released SPACE", "released" ); - map.put( BRIGHTNESS_SETTINGS, "S" ); map.put( SHOW_HELP, "F1", "H" ); map.put( TOGGLE_POINTS_VISIBLE, "V" ); map.put( TOGGLE_POINT_NAMES_VISIBLE, "N" ); map.put( ESTIMATE_WARP, "C" ); + map.put( MASK_SIZE_EDIT, "M" ); + map.put( MASK_VIS_TOGGLE, "control M" ); + map.put( UNDO, "control Z" ); map.put( REDO, "control Y" ); map.put( REDO, "control shift Z" ); @@ -265,9 +726,9 @@ public static InputMap createInputMap( final KeyStrokeAdder.Factory keyPropertie // map.put( SAVE_WARPED, "control alt shift E" ); map.put( SAVE_WARPED_XML, "control shift E" ); -// map.put( LandmarkPointMenu.CLEAR_SELECTED_MOVING, "BACK_SPACE" ); -// map.put( LandmarkPointMenu.CLEAR_SELECTED_FIXED, "control BACK_SPACE" ); -// map.put( LandmarkPointMenu.DELETE_SELECTED, "DELETE" ); + map.put( CLEAR_SELECTED_MOVING, "BACK_SPACE" ); + map.put( CLEAR_SELECTED_FIXED, "control BACK_SPACE" ); + map.put( DELETE_SELECTED, "DELETE" ); map.put( String.format( SELECT_TABLE_ROWS, -1 ), "shift ESCAPE" ); @@ -304,7 +765,6 @@ public static ActionMap createActionMap( final BigWarp< ? > bw ) new ToggleDialogAction( SHOW_WARPTYPE_DIALOG, bw.warpVisDialog ).put( actionMap ); - new ToggleDialogAction( BRIGHTNESS_SETTINGS, bw.brightnessDialog ).put( actionMap ); new ToggleDialogAction( SHOW_HELP, bw.helpDialog ).put( actionMap ); new ToggleDialogAction( SHOW_SOURCE_INFO, bw.sourceInfoDialog ).put( actionMap ); @@ -326,6 +786,12 @@ public static ActionMap createActionMap( final BigWarp< ? > bw ) new ToggleMovingImageDisplayAction( TOGGLE_MOVING_IMAGE_DISPLAY, bw ).put( actionMap ); new EstimateWarpAction( ESTIMATE_WARP, bw ).put( actionMap ); + // MASK + new MaskSizeEdit( bw ).put(actionMap); + new MaskVisToggle( bw ).put(actionMap); + new MaskImport( bw ).put(actionMap); + new MaskRemove( bw ).put(actionMap); + for( int i = 0; i < bw.baseXfmList.length; i++ ){ final AbstractModel xfm = bw.baseXfmList[ i ]; new SetWarpMagBaseAction( String.format( WARPMAG_BASE, xfm.getClass().getName()), bw, i ).put( actionMap ); @@ -343,8 +809,6 @@ public static ActionMap createActionMap( final BigWarp< ? > bw ) return actionMap; } - private BigWarpActions(){} - public static class UndoRedoAction extends AbstractNamedAction { private static final long serialVersionUID = -5413579107763110117L; @@ -356,7 +820,7 @@ public UndoRedoAction( final String name, BigWarp< ? > bw ) { super( name ); this.bw = bw; - + isRedo = false; if ( name.equals( REDO ) ) @@ -367,16 +831,10 @@ public UndoRedoAction( final String name, BigWarp< ? > bw ) @Override public void actionPerformed( ActionEvent e ) { - if( bw.isInLandmarkMode() ) - { - bw.message.showMessage( "Undo/Redo not allowed in landmark mode" ); - return; - } - // I would love for this check to work instead of using a try-catch // bug it doesn't seem to be consistent // if( isRedo && manager.canRedo() ){ - try { + try { if( isRedo ) { @@ -401,7 +859,7 @@ public void actionPerformed( ActionEvent e ) // repaint this.bw.getLandmarkPanel().repaint(); } - catch( Exception ex ) + catch( final Exception ex ) { if( isRedo ) { @@ -411,8 +869,6 @@ public void actionPerformed( ActionEvent e ) { bw.message.showMessage("Can't undo"); } - //System.err.println( " Undo / redo error, or nothing to do " ); - //ex.printStackTrace(); } } } @@ -435,7 +891,6 @@ public LandmarkModeAction( final String name, final BigWarp< ? > bw, final boole @Override public void actionPerformed( ActionEvent e ) { -// System.out.println( "LM MODE : " + isOn ); bw.setInLandmarkMode( isOn ); } } @@ -455,12 +910,11 @@ public ToggleLandmarkModeAction( final String name, final BigWarp< ? > bw ) @Override public void actionPerformed( ActionEvent e ) { -// System.out.println( "TOGGLE LM MODE" ); bw.setInLandmarkMode( !bw.inLandmarkMode ); } } - public static class ToggleAlwaysEstimateTransformAction extends AbstractNamedAction + public static class ToggleAlwaysEstimateTransformAction extends AbstractNamedAction { private static final long serialVersionUID = 2909830484701853577L; @@ -479,7 +933,7 @@ public void actionPerformed( ActionEvent e ) } } - public static class GarbageCollectionAction extends AbstractNamedAction + public static class GarbageCollectionAction extends AbstractNamedAction { private static final long serialVersionUID = -4487441057212703143L; @@ -495,13 +949,13 @@ public void actionPerformed( ActionEvent e ) System.gc(); } } - + public static class PrintTransformAction extends AbstractNamedAction { private static final long serialVersionUID = 6065343788485350279L; private BigWarp< ? > bw; - + public PrintTransformAction( final String name, final BigWarp< ? > bw ) { super( name ); @@ -539,32 +993,31 @@ public void actionPerformed( ActionEvent e ) // System.out.println( ltm.getChangedSinceWarp() ); // System.out.println( ltm.getWarpedPoints() ); // ltm.printWarpedPoints(); - - AffineTransform3D xfm = new AffineTransform3D(); + + final AffineTransform3D xfm = new AffineTransform3D(); bw.viewerP.state().getViewerTransform( xfm ); System.out.println( "mvg xfm " + xfm + " DET = " + BigWarpUtils.det( xfm )); bw.viewerQ.state().getViewerTransform( xfm ); System.out.println( "tgt xfm " + xfm + " DET = " + BigWarpUtils.det( xfm )); - BigWarpData< ? > data = bw.getData(); - for( int mi : data.movingSourceIndices ) - { - ((SourceAndConverter)data.sources.get( mi )).getSpimSource().getSourceTransform( 0, 0, xfm ); - System.out.println( "mvg src xfm " + xfm ); - } - - for( int ti : data.targetSourceIndices ) - { - ((SourceAndConverter)data.sources.get( ti )).getSpimSource().getSourceTransform( 0, 0, xfm ); - System.out.println( "tgt src xfm " + xfm ); - } - - - System.out.println( " " ); +// BigWarpData< ? > data = bw.getData(); +// for( int mi : data.movingSourceIndices ) +// { +// ((SourceAndConverter)data.sources.get( mi )).getSpimSource().getSourceTransform( 0, 0, xfm ); +// System.out.println( "mvg src xfm " + xfm ); +// } +// +// for( int ti : data.targetSourceIndices ) +// { +// ((SourceAndConverter)data.sources.get( ti )).getSpimSource().getSourceTransform( 0, 0, xfm ); +// System.out.println( "tgt src xfm " + xfm ); +// } +// +// System.out.println( " " ); } } - + public static class EstimateWarpAction extends AbstractNamedAction { private static final long serialVersionUID = -210012348709096037L; @@ -583,13 +1036,13 @@ public void actionPerformed( ActionEvent e ) bw.restimateTransformation(); } } - + public static class ToggleMovingImageDisplayAction extends AbstractNamedAction { private static final long serialVersionUID = 6495981071796613953L; - + private BigWarp< ? > bw; - + public ToggleMovingImageDisplayAction( final String name, final BigWarp< ? > bw ) { super( name ); @@ -602,7 +1055,7 @@ public void actionPerformed( ActionEvent e ) bw.toggleMovingImageDisplay(); } } - + public static class TogglePointNameVisibleAction extends AbstractNamedAction { private static final long serialVersionUID = 2639535533224809586L; @@ -618,7 +1071,7 @@ public TogglePointNameVisibleAction( final String name, final BigWarp< ? > bw ) @Override public void actionPerformed( ActionEvent e ) { - bw.toggleNameVisibility(); + bw.toggleNameVisibility(); } } @@ -637,10 +1090,13 @@ public ToggleBoxAndTexOverlayVisibility( final String name, final BigWarp< ? > b @Override public void actionPerformed( ActionEvent e ) { - bw.getViewerFrameP().getViewerPanel().toggleBoxOverlayVisible(); - bw.getViewerFrameQ().getViewerPanel().toggleBoxOverlayVisible(); - bw.getViewerFrameP().getViewerPanel().toggleTextOverlayVisible(); - bw.getViewerFrameQ().getViewerPanel().toggleTextOverlayVisible(); +// bw.getViewerFrameP().getViewerPanel().toggleBoxOverlayVisible(); +// bw.getViewerFrameQ().getViewerPanel().toggleBoxOverlayVisible(); +// bw.getViewerFrameP().getViewerPanel().toggleTextOverlayVisible(); +// bw.getViewerFrameQ().getViewerPanel().toggleTextOverlayVisible(); + Prefs.showTextOverlay(!Prefs.showTextOverlay()); + Prefs.showMultibox(!Prefs.showMultibox()); + bw.getViewerFrameP().repaint(); bw.getViewerFrameQ().repaint(); } @@ -650,7 +1106,7 @@ public static class TogglePointsVisibleAction extends AbstractNamedAction { private static final long serialVersionUID = 8747830204501341125L; private BigWarp< ? > bw; - + public TogglePointsVisibleAction( final String name, final BigWarp< ? > bw ) { super( name ); @@ -660,44 +1116,46 @@ public TogglePointsVisibleAction( final String name, final BigWarp< ? > bw ) @Override public void actionPerformed( ActionEvent e ) { - bw.togglePointVisibility(); + bw.togglePointVisibility(); } } - + public static class ResetActiveViewerAction extends AbstractNamedAction { private static final long serialVersionUID = -130575800163574517L; - + private BigWarp< ? > bw; - + public ResetActiveViewerAction( final BigWarp< ? > bw ) { super( String.format( RESET_VIEWER ) ); this.bw = bw; } - + + @Override public void actionPerformed( ActionEvent e ) { bw.resetView(); } } - + public static class AlignViewerPanelAction extends AbstractNamedAction { private static final long serialVersionUID = -7023242695323421450L; - - public enum TYPE { ACTIVE_TO_OTHER, OTHER_TO_ACTIVE }; - + + public enum TYPE { ACTIVE_TO_OTHER, OTHER_TO_ACTIVE } + private BigWarp< ? >bw; private TYPE type; - + public AlignViewerPanelAction( final BigWarp< ? > bw, TYPE type ) { super( String.format( ALIGN_VIEW_TRANSFORMS, type ) ); this.bw = bw; this.type = type; } - + + @Override public void actionPerformed( ActionEvent e ) { if( type == TYPE.ACTIVE_TO_OTHER ) @@ -710,10 +1168,10 @@ public void actionPerformed( ActionEvent e ) public static class SetWarpMagBaseAction extends AbstractNamedAction { private static final long serialVersionUID = 7370813069619338918L; - + private BigWarp< ? > bw; private int i; - + public SetWarpMagBaseAction( final String name, final BigWarp< ? > bw, int i ) { super( name ); @@ -727,14 +1185,14 @@ public void actionPerformed( ActionEvent e ) bw.setWarpMagBaselineIndex( i ); } } - + public static class SetWarpVisGridTypeAction extends AbstractNamedAction { private static final long serialVersionUID = 7370813069619338918L; - + private final BigWarp< ? > bw; private final GridSource.GRID_TYPE type; - + public SetWarpVisGridTypeAction( final String name, final BigWarp< ? > bw, final GridSource.GRID_TYPE type ) { super( name ); @@ -748,20 +1206,20 @@ public void actionPerformed( ActionEvent e ) bw.setWarpVisGridType( type ); } } - + public static class SetWarpVisTypeAction extends AbstractNamedAction { private static final long serialVersionUID = 7370813069619338918L; - + private BigWarp< ? > bw; private BigWarpViewerFrame p; private BigWarp.WarpVisType type; - + public SetWarpVisTypeAction( final BigWarp.WarpVisType type, final BigWarp< ? > bw ) { this( type, bw, null ); } - + public SetWarpVisTypeAction( final BigWarp.WarpVisType type, final BigWarp< ? > bw, BigWarpViewerFrame p ) { super( getName( type, p )); @@ -778,7 +1236,7 @@ public void actionPerformed( ActionEvent e ) else bw.setWarpVisMode( type, p, false ); } - + public static String getName( final BigWarp.WarpVisType type, BigWarpViewerFrame p ) { if( p == null ) @@ -789,7 +1247,7 @@ else if( p.isMoving() ) return String.format( SET_WARPTYPE_VIS_Q, type.name() ); } } - + public static class TableSelectionAction extends AbstractNamedAction { private static final long serialVersionUID = -4647679094757721276L; @@ -893,6 +1351,23 @@ public void actionPerformed( final ActionEvent e ) private static final long serialVersionUID = 1L; } + public static class SaveProjectAction extends AbstractNamedAction + { + private static final long serialVersionUID = -965388576691467002L; + BigWarp< ? > bw; + public SaveProjectAction( final BigWarp< ? > bw ) + { + super( SAVE_PROJECT ); + this.bw = bw; + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + bw.saveProject(); + } + } + public static class LoadSettingsAction extends AbstractNamedAction { BigWarp< ? > bw; @@ -911,62 +1386,67 @@ public void actionPerformed( final ActionEvent e ) private static final long serialVersionUID = 1L; } - public static class WarpToSelectedAction extends AbstractNamedAction + public static class LoadProjectAction extends AbstractNamedAction + { + private static final long serialVersionUID = 1793182816804229398L; + BigWarp< ? > bw; + public LoadProjectAction( final BigWarp< ? > bw ) + { + super( LOAD_PROJECT ); + this.bw = bw; + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + bw.loadProject(); + } + } + + public static class JumpToSelectedAction extends AbstractNamedAction { final BigWarp< ? > bw; - public WarpToSelectedAction( final BigWarp< ? > bw ) + public JumpToSelectedAction( final BigWarp< ? > bw ) { - super( WARP_TO_SELECTED_POINT ); + super( JUMP_TO_SELECTED_POINT ); this.bw = bw; } @Override public void actionPerformed( ActionEvent e ) { - int[] selectedRows = bw.getLandmarkPanel().getJTable().getSelectedRows(); - - int row = 0; - if( selectedRows.length > 0 ) - row = selectedRows[ 0 ]; - - if( bw.getViewerFrameP().isActive() ) - bw.warpToLandmark( row, bw.getViewerFrameP().getViewerPanel() ); - else - bw.warpToLandmark( row, bw.getViewerFrameQ().getViewerPanel() ); + bw.jumpToSelectedLandmark(); } private static final long serialVersionUID = 5233843444920094805L; } - public static class WarpToNearest extends AbstractNamedAction + public static class JumpToNearest extends AbstractNamedAction { final BigWarp< ? > bw; - public WarpToNearest( final BigWarp< ? > bw ) + public JumpToNearest( final BigWarp< ? > bw ) { - super( WARP_TO_NEAREST_POINT ); + super( JUMP_TO_NEAREST_POINT ); this.bw = bw; } @Override public void actionPerformed( ActionEvent e ) { - if( bw.getViewerFrameP().isActive() ) - bw.warpToNearest( bw.getViewerFrameP().getViewerPanel() ); - else - bw.warpToNearest( bw.getViewerFrameQ().getViewerPanel() ); + bw.jumpToNearestLandmark(); } private static final long serialVersionUID = 3244181492305479433L; } - public static class WarpToNextAction extends AbstractNamedAction + public static class JumpToNextAction extends AbstractNamedAction { final BigWarp< ? > bw; final int inc; - public WarpToNextAction( final BigWarp< ? > bw, boolean fwd ) + public JumpToNextAction( final BigWarp< ? > bw, boolean fwd ) { - super( String.format( WARP_TO_NEXT_POINT, fwd) ); + super( fwd ? JUMP_TO_NEXT_POINT : JUMP_TO_PREV_POINT ); this.bw = bw; if( fwd ) inc = 1; @@ -977,31 +1457,7 @@ public WarpToNextAction( final BigWarp< ? > bw, boolean fwd ) @Override public void actionPerformed( ActionEvent e ) { - int[] selectedRows = bw.getLandmarkPanel().getJTable().getSelectedRows(); - - int row = 0; - if( selectedRows.length > 0 ) - row = selectedRows[ selectedRows.length - 1 ]; - - row = row + inc; // increment to get the *next* row - - // wrap to start if necessary - if( row >= bw.getLandmarkPanel().getTableModel().getRowCount() ) - row = 0; - else if( row < 0 ) - row = bw.getLandmarkPanel().getTableModel().getRowCount() - 1; - - // select new row - bw.getLandmarkPanel().getJTable().setRowSelectionInterval( row, row ); - - if( bw.getViewerFrameP().isActive() ) - { - bw.warpToLandmark( row, bw.getViewerFrameP().getViewerPanel() ); - } - else - { - bw.warpToLandmark( row, bw.getViewerFrameQ().getViewerPanel() ); - } + bw.jumpToLandmarkRelative(inc); } private static final long serialVersionUID = 8515568118251877405L; } @@ -1070,7 +1526,7 @@ public void actionPerformed(ActionEvent e) bw.exportAsImagePlus( false ); } } - + public static class ExportWarpAction extends AbstractNamedAction { private static final long serialVersionUID = 4626378501415886468L; @@ -1156,4 +1612,78 @@ public void actionPerformed(ActionEvent e) LandmarkGridGenerator.fillFromDialog( bw ); } } + + public static class MaskSizeEdit extends AbstractNamedAction + { + private static final long serialVersionUID = -7918625162439713732L; + private final BigWarp< ? > bw; + + public MaskSizeEdit( final BigWarp< ? > bw ) + { + super( MASK_SIZE_EDIT ); + this.bw = bw; + } + + @Override + public void actionPerformed(ActionEvent e) + { + if( bw.maskSourceMouseListenerQ != null ) + bw.maskSourceMouseListenerQ.toggleActive(); + } + } + + public static class MaskVisToggle extends AbstractNamedAction + { + private static final long serialVersionUID = 493457851797644046L; + private final BigWarp< ? > bw; + + public MaskVisToggle( final BigWarp< ? > bw ) + { + super( MASK_VIS_TOGGLE ); + this.bw = bw; + } + + @Override + public void actionPerformed(ActionEvent e) + { + bw.getViewerFrameQ().getViewerPanel().getMaskOverlay().toggleVisible(); + } + } + + public static class MaskImport extends AbstractNamedAction + { + private static final long serialVersionUID = 493457851797644046L; + private final BigWarp< ? > bw; + + public MaskImport( final BigWarp< ? > bw ) + { + super( MASK_IMPORT ); + this.bw = bw; + } + + @Override + public void actionPerformed(ActionEvent e) + { + bw.importTransformMaskSourceDialog(); + } + } + + public static class MaskRemove extends AbstractNamedAction + { + private static final long serialVersionUID = 4103338122650843631L; + private final BigWarp< ? > bw; + + public MaskRemove( final BigWarp< ? > bw ) + { + super( MASK_REMOVE ); + this.bw = bw; + } + + @Override + public void actionPerformed(ActionEvent e) + { + bw.removeMaskSource(); + } + } + } diff --git a/src/main/java/bigwarp/BigWarpAutoSaver.java b/src/main/java/bigwarp/BigWarpAutoSaver.java index 2cbfa9f2..91cd701f 100644 --- a/src/main/java/bigwarp/BigWarpAutoSaver.java +++ b/src/main/java/bigwarp/BigWarpAutoSaver.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -25,30 +25,37 @@ import java.util.Timer; import java.util.TimerTask; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; + import bigwarp.landmarks.LandmarkTableModel; /** - * Saves bigwarp landmarks to a file periodically, + * Saves bigwarp landmarks to a file periodically, * but only if modification has occured since the last save. - * + * * @author John Bogovic * */ -public class BigWarpAutoSaver +public class BigWarpAutoSaver { - private final BigWarp bw; + transient private final BigWarp bw; - final Timer timer; + transient final Timer timer; - final AutoSave saveTask; + transient final AutoSave saveTask; final long period; + @SerializedName("location") + @JsonAdapter( BigwarpSettings.FileAdapter.class ) + protected File autoSaveDirectory; + + public BigWarpAutoSaver( final BigWarp bw, final long period ) { this.bw = bw; this.period = period; - bw.autoSaver = this; timer = new Timer(); saveTask = new AutoSave(); timer.schedule( saveTask, period, period ); @@ -65,37 +72,57 @@ public void stop() timer.cancel(); } - private class AutoSave extends TimerTask + private class AutoSave extends TimerTask { @Override public void run() { - LandmarkTableModel ltm = bw.getLandmarkPanel().getTableModel(); + final LandmarkTableModel ltm = bw.getLandmarkPanel().getTableModel(); if( ltm.isModifiedSinceSave() ) { bw.autoSaveLandmarks(); } } } - + public static void setAutosaveOptions( final BigWarp bw, final long period, final String autoSavePath ) { - bw.setAutosaveFolder( new File( autoSavePath ) ); if( period > 0 ) { - bw.warpVisDialog.doAutoSaveBox.setSelected( true ); - int periodMinutes = ( int ) ( period / 60000 ); - bw.warpVisDialog.autoSavePeriodSpinner.setValue( periodMinutes ); + bw.warpVisDialog.getAutoSaveOptionsPanel().getDoAutoSaveBox().setSelected( true ); + final int periodMinutes = ( int ) ( period / 60000 ); + bw.warpVisDialog.getAutoSaveOptionsPanel().getAutoSavePeriodSpinner().setValue( periodMinutes ); if( bw.autoSaver != null ) bw.autoSaver.stop(); - new BigWarpAutoSaver( bw, period ); - bw.warpVisDialog.repaint(); + final BigWarpAutoSaver autoSaver = new BigWarpAutoSaver( bw, period ); + bw.setAutoSaver( autoSaver ); + autoSaver.setAutosaveFolder( new File( autoSavePath ) ); } else { - bw.warpVisDialog.doAutoSaveBox.setSelected( false ); + bw.warpVisDialog.getAutoSaveOptionsPanel().getDoAutoSaveBox().setSelected( false ); + bw.warpVisDialog.repaint(); + } + } + + /** + * Set the folder where the results of auto-saving will be stored. + * + * @param autoSaveFolder + * the destination folder + */ + public void setAutosaveFolder( final File autoSaveFolder ) + { + boolean exists = autoSaveFolder.exists(); + if ( !exists ) + exists = autoSaveFolder.mkdir(); + + if ( exists && autoSaveFolder.isDirectory() ) + { + autoSaveDirectory = autoSaveFolder; + bw.warpVisDialog.getAutoSaveOptionsPanel().getAutoSaveFolderText().setText( autoSaveFolder.getAbsolutePath() ); bw.warpVisDialog.repaint(); } } diff --git a/src/main/java/bigwarp/BigWarpBatchTransformFOV.java b/src/main/java/bigwarp/BigWarpBatchTransformFOV.java index b25db515..fe0a55b3 100644 --- a/src/main/java/bigwarp/BigWarpBatchTransformFOV.java +++ b/src/main/java/bigwarp/BigWarpBatchTransformFOV.java @@ -42,7 +42,6 @@ import bdv.spimdata.WrapBasicImgLoader; import bdv.viewer.Interpolation; import bdv.viewer.SourceAndConverter; -import bigwarp.BigWarp.BigWarpData; import bigwarp.BigWarpExporter.ExportThread; import bigwarp.landmarks.LandmarkTableModel; import bigwarp.loader.ImagePlusLoader; @@ -69,12 +68,6 @@ import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.realtransform.ThinplateSplineTransform; import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; -import net.imglib2.type.numeric.ARGBType; -import net.imglib2.type.numeric.integer.ByteType; -import net.imglib2.type.numeric.integer.IntType; -import net.imglib2.type.numeric.integer.UnsignedByteType; -import net.imglib2.type.numeric.integer.UnsignedShortType; -import net.imglib2.type.numeric.real.DoubleType; import net.imglib2.type.numeric.real.FloatType; import net.imglib2.util.ConstantUtils; @@ -339,11 +332,9 @@ public static < T > BigWarpExporter< T > applyBigWarpHelper( AbstractSpimData< ? movingSourceIndexList[ i ] = i; } - int[] targetSourceIndexList = data.targetSourceIndices; - @SuppressWarnings("unchecked") List< SourceAndConverter< T >> sourcesxfm = BigWarp.wrapSourcesAsTransformed( - data.sources, + data.sourceInfos, ltm.getNumdims(), data ); diff --git a/src/main/java/bigwarp/BigWarpData.java b/src/main/java/bigwarp/BigWarpData.java new file mode 100644 index 00000000..1d5a3465 --- /dev/null +++ b/src/main/java/bigwarp/BigWarpData.java @@ -0,0 +1,466 @@ +package bigwarp; + +import bigwarp.source.SourceInfo; +import bigwarp.util.BigWarpUtils; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; + +import bdv.cache.CacheControl; +import bdv.gui.BigWarpViewerFrame; +import bdv.img.WarpedSource; +import bdv.tools.InitializeViewerState; +import bdv.tools.brightness.ConverterSetup; +import bdv.tools.brightness.SetupAssignments; +import bdv.tools.transformation.TransformedSource; +import bdv.util.Bounds; +import bdv.viewer.ConverterSetups; +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.SynchronizedViewerState; +import bdv.viewer.VisibilityAndGrouping; +import net.imglib2.realtransform.AffineGet; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.InvertibleWrapped2DTransformAs3D; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.Wrapped2DTransformAs3D; + +public class BigWarpData< T > +{ + public List< SourceAndConverter< T > > sources; + + public final LinkedHashMap< Integer, SourceInfo > sourceInfos = new LinkedHashMap<>(); + public final List< ConverterSetup > converterSetups; + + public final CacheControl cache; + + public BigWarpData() + { + this( new ArrayList<>(), new ArrayList<>(), null ); + } + + public BigWarpData( final List< SourceAndConverter< T > > sources, final List< ConverterSetup > converterSetups, + final CacheControl cache, + final int[] movingIndexes, + final int[] targetIndexes ) + { + this( sources, converterSetups, cache, + listOf( movingIndexes ), + listOf( targetIndexes )); + } + + public BigWarpData( final List< SourceAndConverter< T > > sources, + final List< ConverterSetup > converterSetups, + final CacheControl cache ) + { + this( sources, null, converterSetups, cache ); + } + + public BigWarpData( final List< SourceAndConverter< T > > sources, + final List< RealTransform > transforms, + final List< ConverterSetup > converterSetups, + final CacheControl cache ) + { + this.sources = sources; + this.converterSetups = converterSetups; + + if ( cache == null ) + this.cache = new CacheControl.Dummy(); + else + this.cache = cache; + } + + public BigWarpData( final List< SourceAndConverter< T > > sources, final List< ConverterSetup > converterSetups, + final CacheControl cache, + final List movingIndexes, + final List targetIndexes ) + { + this.sources = sources; + this.converterSetups = converterSetups; + + for ( int i = 0; i < sources.size(); i++ ) + { + final SourceAndConverter< T > sourceAndConverter = sources.get( i ); + final SourceInfo sourceInfo = new SourceInfo( i, movingIndexes.contains( i ), sourceAndConverter.getSpimSource().getName() ); + sourceInfo.setSourceAndConverter( sourceAndConverter ); + sourceInfos.put( i, sourceInfo ); + } + + if ( cache == null ) + this.cache = new CacheControl.Dummy(); + else + this.cache = cache; + } + + private static ArrayList listOf( int[] x ) + { + final ArrayList< Integer > out = new ArrayList(); + for( final int i : x ) + out.add( i ); + + return out; + } + + public int numMovingSources() + { + final AtomicInteger movingCount = new AtomicInteger(); + sourceInfos.forEach( (id, info) -> { + if (info.isMoving()) movingCount.incrementAndGet(); + } ); + return movingCount.get(); + } + + @SuppressWarnings( "unchecked" ) + public SourceAndConverter< T > getMovingSource( int i ) + { + int curIdx = 0; + for ( final Map.Entry< Integer, SourceInfo > idToInfo : sourceInfos.entrySet() ) + { + final SourceInfo info = idToInfo.getValue(); + if (info.isMoving()) { + if (curIdx == i) return ( SourceAndConverter< T > ) info.getSourceAndConverter(); + curIdx++; + } + } + return null; + } + + public int numTargetSources() + { + return sourceInfos.size() - numMovingSources(); + } + + public List< Integer > getMovingSourceIndices() { + final ArrayList indices = new ArrayList<>(); + int idx = 0; + for ( final SourceInfo sourceInfo : sourceInfos.values() ) + { + if ( sourceInfo.isMoving()){ + indices.add( idx ); + } + idx++; + } + return indices; + } + + public SourceAndConverter< T > getTargetSource( int i ) + { + int curIdx = 0; + for ( final Map.Entry< Integer, SourceInfo > idToInfo : sourceInfos.entrySet() ) + { + final SourceInfo info = idToInfo.getValue(); + if (!info.isMoving()) { + if (curIdx == i) return ( SourceAndConverter< T > ) info.getSourceAndConverter(); + curIdx++; + } + } + return null; + } + + public List getMovingConverterSetups() + { + final ArrayList out = new ArrayList<>(); + + final SourceInfo[] infos = sourceInfos.values().toArray(new SourceInfo[]{} ); + for ( int i = 0; i < infos.length; i++ ) + { + final SourceInfo info = infos[ i ]; + if (info.isMoving()) { + out.add( converterSetups.get( i ) ); + } + } + return out; + } + + public List getTargetConverterSetups() + { + final ArrayList out = new ArrayList<>(); + + final SourceInfo[] infos = sourceInfos.values().toArray(new SourceInfo[]{} ); + for ( int i = 0; i < infos.length; i++ ) + { + final SourceInfo info = infos[ i ]; + if (!info.isMoving()) { + out.add( converterSetups.get( i ) ); + } + } + return out; + } + + public ConverterSetup getConverterSetup( final int id ) + { + final SourceInfo[] infos = sourceInfos.values().toArray(new SourceInfo[]{} ); + for ( int i = 0; i < infos.length; i++ ) + { + final SourceInfo info = infos[ i ]; + if (info.getId() == id ) + return converterSetups.get( i ); + } + return null; + } + + public SourceInfo getSourceInfo( int id ) + { + return sourceInfos.get( id ); + } + + public SourceInfo getSourceInfo( SourceAndConverter< ? > sac ) + { + for ( final SourceInfo info : sourceInfos.values() ) + { + if (info.getSourceAndConverter() == sac) { + return info; + } + } + return null; + } + + public boolean isMoving( SourceAndConverter sac ) { + return getSourceInfo( sac ).isMoving(); + } + + /** + * @deprecated only handled maintaing the moving lists, now a no-op + */ + @Deprecated + public void wrapUp() + { + } + + void addSource( Source src, boolean isMoving ) + { + addSource( src, isMoving, null ); + } + + void addSource( Source src, boolean isMoving, RealTransform transform ) + { + // find an unused id + int id = 0; + for( final ConverterSetup cs : converterSetups ) + { + if( id == cs.getSetupId() ) + id++; + } + addSource( id, src, isMoving, transform ); + } + + /** + * Adds a {@link Source} with the given id. Does not check that the id is unused. + * + * @param id the id + * @param src the source + * @param isMoving if the source is moving + * @param transform an optional transformation + */ + void addSource( final int id, Source src, boolean isMoving, RealTransform transform ) + { + BigWarpInit.add( this, src, id, 0, isMoving, transform ); + final SourceInfo sourceInfo = new SourceInfo( id, isMoving, src.getName(), null, transform ); + sourceInfo.setSourceAndConverter( sources.get( sources.size() -1 ) ); + sourceInfos.put( id, sourceInfo); + } + + int remove( SourceInfo sourceInfo) + { + final int idx = sources.indexOf( sourceInfo.getSourceAndConverter() ); + remove( idx ); + return idx; + } + + void remove( int i ) + { + final SourceAndConverter< T > sac = sources.get( i ); + sourceInfos.entrySet().stream().filter( it -> it.getValue().getSourceAndConverter() == sac ).map( Map.Entry::getKey ).findFirst().ifPresent( + sacId -> { sourceInfos.remove( sacId ); } ); + + sources.remove( i ); + converterSetups.remove( i ); + } + + public void applyTransformations() + { + int i = 0; + for ( final SourceAndConverter sac : sources ) + { + final SourceInfo info = getSourceInfo( sac ); + final RealTransform transform = info.getTransform(); + if ( transform != null ) + { + final SourceAndConverter newSac = inheritConverter( + applyFixedTransform( sac.getSpimSource(), transform), + sac ); + + info.setSourceAndConverter( newSac ); + sources.set( i, newSac ); + } + i++; + } + } + + public static < T > SourceAndConverter< T > inheritConverter( final Source src, final SourceAndConverter< T > sac ) + { + if ( sac.asVolatile() == null ) { + return new SourceAndConverter< T >( src, sac.getConverter(), null ); + } + else + { + System.err.println( "Inherit Converter can't handle volatile"); +// inheritConverter( src, sac ); + return null; +// return new SourceAndConverter< T >( src, sac.getConverter(), wrapSourceAsTransformed( src, name + "_vol", ndims ) ); + } + } + + /** + * Returns a source that applies the given {@link RealTransform} to the given {@link Source}. + *

+ * The returned source will be a new instance than unless the transform + * is a instance of {@link AffineGet} and source is an instance of {@link TransformedSource}. + * + * @param the type + * @param src the original source + * @param transform the transformation + * @return the transformed source + */ + public Source applyFixedTransform( final Source src, final RealTransform transform ) + { + RealTransform tform = transform; + if( transform.numSourceDimensions() < 3 ) + { + if( transform instanceof InvertibleRealTransform ) + tform = new InvertibleWrapped2DTransformAs3D( ( InvertibleRealTransform ) transform ); + else + tform = new Wrapped2DTransformAs3D( transform ); + } + + // TODO using a TransformedSource like the below wasn't working correctly + // investigate later + +// if( transform instanceof AffineGet ) +// { +// // if transform is a 2D affine, turn it into a 3D transform +// +// // can use TransformedSource +// final AffineTransform3D affine3d; +// if( transform instanceof AffineTransform3D ) +// affine3d = ( AffineTransform3D ) transform; +// else +// { +// affine3d = new AffineTransform3D(); +// final AffineGet transformTo3D = BigWarpUtils.toAffine3D((AffineGet)transform); +// System.out.println( transformTo3D ); +// affine3d.preConcatenate( transformTo3D ); +// System.out.println( affine3d ); +// } +// +// // could perhaps try to be clever if its a warped source (?), maybe later +// TransformedSource tsrc; +// if ( src instanceof TransformedSource ) +// { +// tsrc = ( TransformedSource ) ( src ); +// } +// else +// { +// tsrc = new TransformedSource( src ); +// } +// tsrc.setFixedTransform( affine3d ); +// return ( Source< T > ) tsrc; +// } +// else +// { +// // need to use WarpedSource +// final WarpedSource wsrc = new WarpedSource( src, src.getName() ); +// wsrc.updateTransform( tform ); +// wsrc.setIsTransformed( true ); +// return ( Source< T > ) wsrc; +// } + + + if( transform instanceof AffineGet ) + { + // if transform is a 2D affine, turn it into a 3D transform + + // can use TransformedSource + final AffineTransform3D affine3d; + if( transform instanceof AffineTransform3D ) + affine3d = ( AffineTransform3D ) transform; + else + { + affine3d = new AffineTransform3D(); + final AffineGet transformTo3D = BigWarpUtils.toAffine3D((AffineGet)transform); + affine3d.preConcatenate( transformTo3D ); + } + } + + // need to use WarpedSource + final WarpedSource wsrc = new WarpedSource( src, src.getName() ); + wsrc.updateTransform( tform ); + wsrc.setIsTransformed( true ); + return ( Source< T > ) wsrc; + } + + /** + * Updates the moving sources' transformation with the transform currently + * being edited by BigWarp. + * + * @param transform the transformation + */ + public void updateEditableTransformation( RealTransform transform ) + { + for ( final SourceInfo sourceInfo : sourceInfos.values() ) + { + // could be extra careful and ensure the source is a WarpedSource, but hopefully not necessary + if ( sourceInfo.isMoving() ) + { + final SourceAndConverter< ? > sac = sourceInfo.getSourceAndConverter(); + final WarpedSource< ? > wsrc = ( WarpedSource< ? > ) sac.getSpimSource(); + wsrc.updateTransform( transform ); + if ( sac.asVolatile() != null ) + ( ( WarpedSource< ? > ) sourceInfo.getSourceAndConverter().asVolatile().getSpimSource() ).updateTransform( transform ); + + wsrc.updateTransform( transform ); + + /* + * There was a time when I had a single WarpedSource manage a RealTransformSequence + * instead of a WarpedSource wrapping a different WarpedSource as I'm doing now. + * + * But I decided against having a single source because warped sources can toggle their transforms. + * That toggling makes sense for the editable transform, but the fixex should be "on" + * always, and therefore be handled by either a TransformedSource or a different + * WarpedSource instance. + */ + } + } + } + + public void transferChannelSettings( final BigWarpViewerFrame viewer ) + { + final SynchronizedViewerState state = viewer.getViewerPanel().state(); + final ConverterSetups setups = viewer.getConverterSetups(); + + for( final Entry< Integer, SourceInfo > infoEntry : sourceInfos.entrySet() ) + { + final int id = infoEntry.getKey(); + final SourceInfo info = infoEntry.getValue(); + final SourceAndConverter< ? > sac = info.getSourceAndConverter(); + final ConverterSetup cs = setups.getConverterSetup( sac ); + + if ( info.getColorSettings() == null ) + { + final int timepoint = state.getCurrentTimepoint(); + final Bounds bounds = InitializeViewerState.estimateSourceRange( sac.getSpimSource(), timepoint, 0.001, 0.999 ); + if( cs != null ) + cs.setDisplayRange( bounds.getMinBound(), bounds.getMaxBound() ); + } + else + { + info.getColorSettings().updateSetup( cs ); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/bigwarp/BigWarpExporter.java b/src/main/java/bigwarp/BigWarpExporter.java index 2bd86303..226f8e26 100644 --- a/src/main/java/bigwarp/BigWarpExporter.java +++ b/src/main/java/bigwarp/BigWarpExporter.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -40,7 +40,6 @@ import bdv.viewer.Interpolation; import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; -import bigwarp.BigWarp.BigWarpData; import ij.IJ; import ij.ImagePlus; import mpicbg.models.AffineModel2D; @@ -75,24 +74,20 @@ public abstract class BigWarpExporter { final protected List< SourceAndConverter< T >> sources; - private List< ConverterSetup > convSetups; - private List< ImagePlus > outputList; - final protected int[] movingSourceIndexList; + protected final BigWarpData bwData; - final protected int[] targetSourceIndexList; - protected AffineTransform3D pixelRenderToPhysical; - + protected AffineTransform3D resolutionTransform; - + protected AffineTransform3D offsetTransform; - + protected Interval outputInterval; protected Interpolation interp; - + protected boolean isVirtual = false; protected int nThreads = 1; @@ -107,7 +102,7 @@ public abstract class BigWarpExporter public enum ParallelizationPolicy { SLICE, ITER - }; + } public ParallelizationPolicy policy = ParallelizationPolicy.ITER; @@ -119,25 +114,75 @@ public enum ParallelizationPolicy { private String exportPath; +// public BigWarpExporter( +// final List< SourceAndConverter< T >> sourcesIn, +// final List< ConverterSetup > convSetups, +// final int[] movingSourceIndexList, +// final int[] targetSourceIndexList, +// final Interpolation interp, +// final ProgressWriter progress ) +// { +// this.sources = new ArrayList>(); +// this.convSetups = convSetups; +// for( SourceAndConverter sac : sourcesIn ) +// { +// Source srcCopy = null; +// Source src = sac.getSpimSource(); +// if( src instanceof WarpedSource ) +// { +// WarpedSource ws = (WarpedSource)( sac.getSpimSource() ); +// WarpedSource wsCopy = new WarpedSource<>( ws.getWrappedSource(), ws.getName() ) ; +// +// if( ws.getTransform() != null ) +// { +// wsCopy.updateTransform( ws.getTransform().copy() ); +// wsCopy.setIsTransformed( true ); +// } +// srcCopy = wsCopy; +// } +// else +// srcCopy = src; +// +// SourceAndConverter copy = new SourceAndConverter<>( srcCopy, sac.getConverter() ); +// sources.add( copy ); +// } +// +// this.movingSourceIndexList = movingSourceIndexList; +// this.targetSourceIndexList = targetSourceIndexList; +// +// if( progress == null ) +// this.progress = new ProgressWriterConsole(); +// else +// this.progress = progress; +// +// this.setInterp( interp ); +// +// pixelRenderToPhysical = new AffineTransform3D(); +// resolutionTransform = new AffineTransform3D(); +// offsetTransform = new AffineTransform3D(); +// +// try { +// unit = sources.get( targetSourceIndexList[ 0 ] ).getSpimSource().getVoxelDimensions().unit(); +// } catch( Exception e ) {} +// } + public BigWarpExporter( - final List< SourceAndConverter< T >> sourcesIn, + BigWarpData bwData, final List< ConverterSetup > convSetups, - final int[] movingSourceIndexList, - final int[] targetSourceIndexList, final Interpolation interp, final ProgressWriter progress ) { + this.bwData = bwData; this.sources = new ArrayList>(); - this.convSetups = convSetups; - for( SourceAndConverter sac : sourcesIn ) + for( final SourceAndConverter sac : bwData.sources ) { Source srcCopy = null; - Source src = sac.getSpimSource(); + final Source src = sac.getSpimSource(); if( src instanceof WarpedSource ) { - WarpedSource ws = (WarpedSource)( sac.getSpimSource() ); - WarpedSource wsCopy = new WarpedSource<>( ws.getWrappedSource(), ws.getName() ) ; - + final WarpedSource ws = (WarpedSource)( sac.getSpimSource() ); + final WarpedSource wsCopy = new WarpedSource<>( ws.getWrappedSource(), ws.getName() ) ; + if( ws.getTransform() != null ) { wsCopy.updateTransform( ws.getTransform().copy() ); @@ -147,28 +192,25 @@ public BigWarpExporter( } else srcCopy = src; - - SourceAndConverter copy = new SourceAndConverter<>( srcCopy, sac.getConverter() ); + + final SourceAndConverter copy = new SourceAndConverter<>( srcCopy, sac.getConverter() ); sources.add( copy ); } - this.movingSourceIndexList = movingSourceIndexList; - this.targetSourceIndexList = targetSourceIndexList; - if( progress == null ) this.progress = new ProgressWriterConsole(); else this.progress = progress; this.setInterp( interp ); - + pixelRenderToPhysical = new AffineTransform3D(); resolutionTransform = new AffineTransform3D(); offsetTransform = new AffineTransform3D(); try { - unit = sources.get( targetSourceIndexList[ 0 ] ).getSpimSource().getVoxelDimensions().unit(); - } catch( Exception e ) {} + unit = bwData.getTargetSource( 0 ).getSpimSource().getVoxelDimensions().unit(); + } catch( final Exception e ) {} } public abstract RandomAccessibleInterval exportRai(); @@ -201,7 +243,7 @@ public void setInterp( Interpolation interp ) { this.interp = interp; } - + public void setVirtual( final boolean isVirtual ) { this.isVirtual = isVirtual; @@ -227,10 +269,10 @@ public void setRenderResolution( double... res ) for( int i = 0; i < res.length; i++ ) resolutionTransform.set( res[ i ], i, i ); } - + /** * Set the offset of the output field of view in pixels. - * + * * @param offset the offset in pixel units. */ public void setOffset( double... offset ) @@ -241,8 +283,8 @@ public void setOffset( double... offset ) /** * Generate the transform from output pixel space to physical space. - * - * Call this after setRenderResolution and setOffset. + * + * Call this after setRenderResolution and setOffset. */ public void buildTotalRenderTransform() { @@ -261,13 +303,13 @@ public RandomAccessibleInterval exportSource( SourceAndConverter src ) final RealRandomAccessible< T > raiRaw = src.getSpimSource().getInterpolatedSource( 0, 0, interp ); // apply the transformations - final AffineRandomAccessible< T, AffineGet > rai = RealViews.affine( + final AffineRandomAccessible< T, AffineGet > rai = RealViews.affine( raiRaw, pixelRenderToPhysical.inverse() ); - return Views.interval( Views.raster( rai ), outputInterval ); + return Views.interval( Views.raster( rai ), outputInterval ); } - public static void updateBrightnessContrast( + public static void updateBrightnessContrast( final ImagePlus imp, final List convSetups, final int[] indexList ) @@ -276,9 +318,25 @@ public static void updateBrightnessContrast( for( int i = 0; i < indexList.length; i++ ) { - ConverterSetup setup = convSetups.get( indexList[ i ] ); - double rngmin = setup.getDisplayRangeMin(); - double rngmax = setup.getDisplayRangeMax(); + final ConverterSetup setup = convSetups.get( indexList[ i ] ); + final double rngmin = setup.getDisplayRangeMin(); + final double rngmax = setup.getDisplayRangeMax(); + + imp.setC( i + 1 ); // ImagePlus.setC is one-indexed + imp.setDisplayRange( rngmin, rngmax ); + imp.updateAndDraw(); + } + } + + public static void updateBrightnessContrast( + final ImagePlus imp, + final List convSetups) + { + for( int i = 0; i < convSetups.size(); i++ ) + { + final ConverterSetup setup = convSetups.get( i ); + final double rngmin = setup.getDisplayRangeMin(); + final double rngmax = setup.getDisplayRangeMax(); imp.setC( i + 1 ); // ImagePlus.setC is one-indexed imp.setDisplayRange( rngmin, rngmax ); @@ -286,7 +344,7 @@ public static void updateBrightnessContrast( } } - public static void updateBrightnessContrast( + public static void updateBrightnessContrast( final ImagePlus imp, final BigWarpData bwdata, final int[] indexList ) @@ -295,9 +353,9 @@ public static void updateBrightnessContrast( for( int i = 0; i < indexList.length; i++ ) { - ConverterSetup setup = bwdata.converterSetups.get( indexList[ i ] ); - double rngmin = setup.getDisplayRangeMin(); - double rngmax = setup.getDisplayRangeMax(); + final ConverterSetup setup = bwdata.converterSetups.get( indexList[ i ] ); + final double rngmin = setup.getDisplayRangeMin(); + final double rngmax = setup.getDisplayRangeMax(); imp.setC( i + 1 ); // ImagePlus.setC is one-indexed imp.setDisplayRange( rngmin, rngmax ); @@ -307,32 +365,32 @@ public static void updateBrightnessContrast( public FinalInterval destinationIntervalFromLandmarks( ArrayList pts, boolean isMoving ) { - int nd = pts.get( 0 ).length; - long[] min = new long[ nd ]; - long[] max = new long[ nd ]; + final int nd = pts.get( 0 ).length; + final long[] min = new long[ nd ]; + final long[] max = new long[ nd ]; Arrays.fill( min, Long.MAX_VALUE ); Arrays.fill( max, Long.MIN_VALUE ); - for( Double[] pt : pts ) + for( final Double[] pt : pts ) { for( int d = 0; d < nd; d++ ) { if( pt[ d ] > max [ d ] ) max[ d ] = (long)Math.ceil( pt[ d ]); - + if( pt[ d ] < min [ d ] ) min[ d ] = (long)Math.floor( pt[ d ]); } } return new FinalInterval( min, max ); } - + public static FinalInterval getSubInterval( Interval interval, int d, long start, long end ) { - int nd = interval.numDimensions(); - long[] min = new long[ nd ]; - long[] max = new long[ nd ]; + final int nd = interval.numDimensions(); + final long[] min = new long[ nd ]; + final long[] max = new long[ nd ]; for( int i = 0; i < nd; i++ ) { if( i == d ) @@ -349,20 +407,20 @@ public static FinalInterval getSubInterval( Interval interval, int d, long start return new FinalInterval( min, max ); } - public < T extends NumericType > RandomAccessibleInterval copyToImageStack( + public < T extends NumericType > RandomAccessibleInterval copyToImageStack( final RandomAccessible< T > raible, final Interval itvl, final ImgFactory factory, final int nThreads ) { - Img< T > target = factory.create( itvl ); + final Img< T > target = factory.create( itvl ); if( policy == ParallelizationPolicy.ITER ) return copyToImageStackIterOrder( raible, itvl, target, nThreads, progress ); else return copyToImageStackBySlice( raible, itvl, target, nThreads, progress ); } - public static < T extends NumericType > RandomAccessibleInterval copyToImageStackBySlice( + public static < T extends NumericType > RandomAccessibleInterval copyToImageStackBySlice( final RandomAccessible< T > raible, final Interval itvl, final ImgFactory factory, @@ -370,11 +428,11 @@ public static < T extends NumericType > RandomAccessibleInterval copyToIma final ProgressWriter progress ) { // create the image plus image - Img< T > target = factory.create( itvl ); + final Img< T > target = factory.create( itvl ); return copyToImageStackBySlice( raible, itvl, target, nThreads, progress ); } - public static < T extends NumericType > RandomAccessibleInterval copyToImageStackBySlice( + public static < T extends NumericType > RandomAccessibleInterval copyToImageStackBySlice( final RandomAccessible< T > ra, final Interval itvl, final RandomAccessibleInterval target, @@ -382,10 +440,10 @@ public static < T extends NumericType > RandomAccessibleInterval copyToIma final ProgressWriter progress ) { // TODO I wish I didn't have to do this inside this method - MixedTransformView< T > raible = Views.permute( ra, 2, 3 ); + final MixedTransformView< T > raible = Views.permute( ra, 2, 3 ); // what dimension should we split across? - int nd = raible.numDimensions(); + final int nd = raible.numDimensions(); int tmp = nd - 1; while( tmp >= 0 ) { @@ -397,8 +455,8 @@ public static < T extends NumericType > RandomAccessibleInterval copyToIma final int dim2split = tmp; final long[] splitPoints = new long[ nThreads + 1 ]; - long N = target.dimension( dim2split ); - long del = ( long )( N / nThreads ); + final long N = target.dimension( dim2split ); + final long del = ( long )( N / nThreads ); splitPoints[ 0 ] = 0; splitPoints[ nThreads ] = target.dimension( dim2split ); for( int i = 1; i < nThreads; i++ ) @@ -406,9 +464,9 @@ public static < T extends NumericType > RandomAccessibleInterval copyToIma splitPoints[ i ] = splitPoints[ i - 1 ] + del; } - ExecutorService threadPool = Executors.newFixedThreadPool( nThreads ); + final ExecutorService threadPool = Executors.newFixedThreadPool( nThreads ); - LinkedList> jobs = new LinkedList>(); + final LinkedList> jobs = new LinkedList>(); for( int i = 0; i < nThreads; i++ ) { final long start = splitPoints[ i ]; @@ -416,13 +474,14 @@ public static < T extends NumericType > RandomAccessibleInterval copyToIma jobs.add( new Callable() { + @Override public Boolean call() { try { final FinalInterval subItvl = getSubInterval( target, dim2split, start, end ); final IntervalView< T > subTgt = Views.interval( target, subItvl ); - long N = Intervals.numElements(subTgt); + final long N = Intervals.numElements(subTgt); final Cursor< T > c = subTgt.cursor(); final RandomAccess< T > ra = raible.randomAccess(); long j = 0; @@ -434,14 +493,14 @@ public Boolean call() if( start == 0 && j % 100000 == 0 ) { - double ratio = 1.0 * j / N; - progress.setProgress( ratio ); + final double ratio = 1.0 * j / N; + progress.setProgress( ratio ); } j++; } return true; } - catch( Exception e ) + catch( final Exception e ) { e.printStackTrace(); } @@ -455,7 +514,7 @@ public Boolean call() threadPool.shutdown(); // wait for all jobs to finish } - catch ( InterruptedException e1 ) + catch ( final InterruptedException e1 ) { e1.printStackTrace(); } @@ -464,7 +523,7 @@ public Boolean call() return target; } - public static < T extends NumericType > RandomAccessibleInterval copyToImageStackIterOrder( + public static < T extends NumericType > RandomAccessibleInterval copyToImageStackIterOrder( final RandomAccessible< T > raible, final Interval itvl, final ImgFactory factory, @@ -472,11 +531,11 @@ public static < T extends NumericType > RandomAccessibleInterval copyToIma final ProgressWriter progress ) { // create the image plus image - Img< T > target = factory.create( itvl ); + final Img< T > target = factory.create( itvl ); return copyToImageStackIterOrder( raible, itvl, target, nThreads, progress ); } - public static < T extends NumericType > RandomAccessibleInterval copyToImageStackIterOrder( + public static < T extends NumericType > RandomAccessibleInterval copyToImageStackIterOrder( final RandomAccessible< T > ra, final Interval itvl, final RandomAccessibleInterval target, @@ -486,25 +545,26 @@ public static < T extends NumericType > RandomAccessibleInterval copyToIma progress.setProgress(0.0); // TODO I wish I didn't have to do this inside this method.. // Maybe I don't have to, and should do it where I call this instead? - MixedTransformView< T > raible = Views.permute( ra, 2, 3 ); + final MixedTransformView< T > raible = Views.permute( ra, 2, 3 ); - ExecutorService threadPool = Executors.newFixedThreadPool( nThreads ); + final ExecutorService threadPool = Executors.newFixedThreadPool( nThreads ); - LinkedList> jobs = new LinkedList>(); + final LinkedList> jobs = new LinkedList>(); for( int i = 0; i < nThreads; i++ ) { final int offset = i; jobs.add( new Callable() { + @Override public Boolean call() { try { - IterableInterval it = Views.flatIterable( target ); + final IterableInterval it = Views.flatIterable( target ); final RandomAccess< T > access = raible.randomAccess(); - long N = it.size(); + final long N = it.size(); final Cursor< T > c = it.cursor(); c.jumpFwd( 1 + offset ); for( long j = offset; j < N; j += nThreads ) @@ -512,17 +572,17 @@ public Boolean call() access.setPosition( c ); c.get().set( access.get() ); c.jumpFwd( nThreads ); - + if( offset == 0 && j % (nThreads * 100000) == 0 ) { - double ratio = 1.0 * j / N; - progress.setProgress( ratio ); + final double ratio = 1.0 * j / N; + progress.setProgress( ratio ); } } return true; } - catch( Exception e ) + catch( final Exception e ) { e.printStackTrace(); } @@ -536,7 +596,7 @@ public Boolean call() threadPool.shutdown(); // wait for all jobs to finish } - catch ( InterruptedException e1 ) + catch ( final InterruptedException e1 ) { e1.printStackTrace(); } @@ -544,80 +604,80 @@ public Boolean call() progress.setProgress(1.0); return target; } - + public static FinalInterval transformRealInterval( RealTransform xfm, RealInterval interval ) { - int nd = interval.numDimensions(); - double[] pt = new double[ nd ]; - double[] ptxfm = new double[ nd ]; + final int nd = interval.numDimensions(); + final double[] pt = new double[ nd ]; + final double[] ptxfm = new double[ nd ]; - long[] min = new long[ nd ]; - long[] max = new long[ nd ]; + final long[] min = new long[ nd ]; + final long[] max = new long[ nd ]; - // transform min + // transform min for( int d = 0; d < nd; d++ ) pt[ d ] = interval.realMin( d ); - + xfm.apply( pt, ptxfm ); copyToLongFloor( ptxfm, min ); // transform max - + for( int d = 0; d < nd; d++ ) { pt[ d ] = interval.realMax( d ); } - + xfm.apply( pt, ptxfm ); copyToLongCeil( ptxfm, max ); - + return new FinalInterval( min, max ); } - + public static FinalInterval transformIntervalMinMax( RealTransform xfm, Interval interval ) { - int nd = interval.numDimensions(); - double[] pt = new double[ nd ]; - double[] ptxfm = new double[ nd ]; + final int nd = interval.numDimensions(); + final double[] pt = new double[ nd ]; + final double[] ptxfm = new double[ nd ]; - long[] min = new long[ nd ]; - long[] max = new long[ nd ]; + final long[] min = new long[ nd ]; + final long[] max = new long[ nd ]; - // transform min + // transform min for( int d = 0; d < nd; d++ ) pt[ d ] = interval.min( d ); - + xfm.apply( pt, ptxfm ); copyToLongFloor( ptxfm, min ); // transform max - + for( int d = 0; d < nd; d++ ) { pt[ d ] = interval.max( d ); } - + xfm.apply( pt, ptxfm ); copyToLongCeil( ptxfm, max ); - + return new FinalInterval( min, max ); } /** * Only works for 2D or 3D. - * + * * @param affine variable in which to store the result - * @param xfm the transform + * @param xfm the transform * @param interval the interval */ public static void estimateAffineFromCorners( AffineTransform3D affine, RealTransform xfm, Interval interval ) { - if( xfm == null ) + if( xfm == null ) return; - int nd = interval.numDimensions(); + final int nd = interval.numDimensions(); int N; Model model; if ( nd == 2 ) @@ -630,21 +690,21 @@ public static void estimateAffineFromCorners( AffineTransform3D affine, RealTran N = 8; model = new AffineModel3D(); } - - double[][] mvgPts = new double[ nd ][ N ]; - double[][] tgtPts = new double[ nd ][ N ]; - double[] w = new double[ N ]; + final double[][] mvgPts = new double[ nd ][ N ]; + final double[][] tgtPts = new double[ nd ][ N ]; + + final double[] w = new double[ N ]; Arrays.fill( w, 1.0 ); - double[] pt = new double[ nd ]; - double[] ptxfm = new double[ nd ]; + final double[] pt = new double[ nd ]; + final double[] ptxfm = new double[ nd ]; - long[] unitInterval = new long[ nd ]; + final long[] unitInterval = new long[ nd ]; Arrays.fill( unitInterval, 2 ); - + int i = 0; - IntervalIterator it = new IntervalIterator( unitInterval ); + final IntervalIterator it = new IntervalIterator( unitInterval ); while( it.hasNext() ) { it.fwd(); @@ -663,7 +723,7 @@ public static void estimateAffineFromCorners( AffineTransform3D affine, RealTran mvgPts[ d ][ i ] = pt[ d ]; tgtPts[ d ][ i ] = ptxfm[ d ]; } - + i++; } @@ -671,46 +731,46 @@ public static void estimateAffineFromCorners( AffineTransform3D affine, RealTran { model.fit( mvgPts, tgtPts, w ); } - catch ( Exception e ) + catch ( final Exception e ) { affine.identity(); return; } - + if ( nd == 2 ) { - double[] mat = new double[ 6 ]; + final double[] mat = new double[ 6 ]; ((AffineModel2D)model).toArray( mat ); affine.set( mat ); } else { - double[] mat = new double[ 12 ]; + final double[] mat = new double[ 12 ]; ((AffineModel3D)model).getMatrix( mat ); affine.set( mat ); } } - + public static FinalInterval estimateBounds( RealTransform xfm, Interval interval ) { if( xfm == null ) - return new FinalInterval( + return new FinalInterval( Intervals.minAsLongArray(interval), Intervals.maxAsLongArray(interval) ); - int nd = interval.numDimensions(); - double[] pt = new double[ nd ]; - double[] ptxfm = new double[ nd ]; + final int nd = interval.numDimensions(); + final double[] pt = new double[ nd ]; + final double[] ptxfm = new double[ nd ]; - long[] min = new long[ nd ]; - long[] max = new long[ nd ]; + final long[] min = new long[ nd ]; + final long[] max = new long[ nd ]; Arrays.fill( min, Long.MAX_VALUE ); Arrays.fill( max, Long.MIN_VALUE ); - long[] unitInterval = new long[ nd ]; + final long[] unitInterval = new long[ nd ]; Arrays.fill( unitInterval, 2 ); - - IntervalIterator it = new IntervalIterator( unitInterval ); + + final IntervalIterator it = new IntervalIterator( unitInterval ); while( it.hasNext() ) { it.fwd(); @@ -726,12 +786,12 @@ public static FinalInterval estimateBounds( RealTransform xfm, Interval interval for( int d = 0; d < nd; d++ ) { - long lo = (long)Math.floor( ptxfm[d] ); - long hi = (long)Math.ceil( ptxfm[d] ); - + final long lo = (long)Math.floor( ptxfm[d] ); + final long hi = (long)Math.ceil( ptxfm[d] ); + if( lo < min[ d ]) min[ d ] = lo; - + if( hi > max[ d ]) max[ d ] = hi; } @@ -765,7 +825,7 @@ public ImagePlus exportAsynch( final boolean wait, final boolean show ) { exportThread.join(); } - catch ( InterruptedException e ) + catch ( final InterruptedException e ) { e.printStackTrace(); } @@ -808,7 +868,7 @@ public void run() if (exporter.result != null && exporter.showResult && show ) { if( !exporter.isRGB() ) - BigWarpExporter.updateBrightnessContrast( exporter.result, exporter.convSetups, exporter.movingSourceIndexList ); + BigWarpExporter.updateBrightnessContrast( exporter.result, exporter.bwData.getMovingConverterSetups() ); } @@ -817,7 +877,7 @@ public void run() try{ IJ.save( exporter.result, exporter.exportPath ); } - catch( Exception e ) + catch( final Exception e ) { IJ.showMessage( "Failed to write : " + exporter.exportPath ); } @@ -839,17 +899,17 @@ public static BigWarpExporter getExporter( final Interpolation interp, final ProgressWriter progressWriter ) { - int[] movingSourceIndexList = bwData.movingSourceIndices; - int[] targetSourceIndexList = bwData.targetSourceIndices; + final List movingSourceIndexList = bwData.getMovingSourceIndices(); + //TODO Caleb: Consider a method that just takes a list of all moving sources if ( BigWarpRealExporter.isTypeListFullyConsistent( transformedSources, movingSourceIndexList ) ) { - Object baseType = transformedSources.get( movingSourceIndexList[ 0 ] ).getSpimSource().getType(); + final Object baseType = transformedSources.get( movingSourceIndexList.get( 0 ) ).getSpimSource().getType(); if( baseType instanceof RealType ) - return new BigWarpRealExporter( transformedSources, bwData.converterSetups, movingSourceIndexList, targetSourceIndexList, interp, (RealType)baseType, progressWriter); + return new BigWarpRealExporter( bwData, bwData.converterSetups, interp, (RealType)baseType, progressWriter); else if ( ARGBType.class.isInstance( baseType ) ) { - return new BigWarpARGBExporter( (List)transformedSources, bwData.converterSetups, movingSourceIndexList, targetSourceIndexList, interp, progressWriter ); + return new BigWarpARGBExporter( (BigWarpData)bwData, bwData.converterSetups, interp, progressWriter ); } else { diff --git a/src/main/java/bigwarp/BigWarpInit.java b/src/main/java/bigwarp/BigWarpInit.java index 47b0be1f..13144358 100644 --- a/src/main/java/bigwarp/BigWarpInit.java +++ b/src/main/java/bigwarp/BigWarpInit.java @@ -21,12 +21,24 @@ */ package bigwarp; +import java.io.File; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.N5URI; +import org.janelia.saalfeldlab.n5.hdf5.N5HDF5Reader; import org.janelia.saalfeldlab.n5.imglib2.N5Utils; +import org.janelia.saalfeldlab.n5.metadata.N5ViewerMultichannelMetadata; import org.janelia.saalfeldlab.n5.metadata.imagej.ImagePlusLegacyMetadataParser; import org.janelia.saalfeldlab.n5.metadata.imagej.N5ImagePlusMetadata; import org.janelia.saalfeldlab.n5.universe.N5DatasetDiscoverer; @@ -42,8 +54,10 @@ import org.janelia.saalfeldlab.n5.universe.metadata.N5ViewerMultiscaleMetadataParser; import org.janelia.saalfeldlab.n5.universe.metadata.SpatialMetadata; import org.janelia.saalfeldlab.n5.universe.metadata.canonical.CanonicalMetadataParser; +import org.janelia.saalfeldlab.n5.zarr.N5ZarrReader; import bdv.BigDataViewer; +import bdv.cache.SharedQueue; import bdv.img.BwRandomAccessibleIntervalSource; import bdv.img.RenamableSource; import bdv.spimdata.SpimDataMinimal; @@ -52,13 +66,18 @@ import bdv.tools.brightness.SetupAssignments; import bdv.tools.transformation.TransformedSource; import bdv.util.RandomAccessibleIntervalMipmapSource; +import bdv.util.RandomAccessibleIntervalSource; +import bdv.util.volatiles.VolatileViews; import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; -import bigwarp.BigWarp.BigWarpData; import bigwarp.loader.ImagePlusLoader; import bigwarp.loader.Loader; import bigwarp.loader.XMLLoader; +import bigwarp.source.SourceInfo; +import ij.IJ; import ij.ImagePlus; +import ij.io.FileInfo; +import ij.plugin.FolderOpener; import mpicbg.spim.data.SpimData; import mpicbg.spim.data.SpimDataException; import mpicbg.spim.data.XmlIoSpimData; @@ -68,23 +87,50 @@ import mpicbg.spim.data.sequence.Angle; import mpicbg.spim.data.sequence.Channel; import mpicbg.spim.data.sequence.FinalVoxelDimensions; +import net.imagej.Dataset; +import net.imagej.axis.Axes; +import net.imagej.axis.CalibratedAxis; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.cache.img.CachedCellImg; +import net.imglib2.cache.volatiles.CacheHints; +import net.imglib2.cache.volatiles.LoadingStrategy; import net.imglib2.converter.Converter; import net.imglib2.converter.Converters; import net.imglib2.display.RealARGBColorConverter; import net.imglib2.display.ScaledARGBConverter; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.type.NativeType; import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.UnsignedIntType; import net.imglib2.type.volatiles.VolatileARGBType; import net.imglib2.util.Util; +import net.imglib2.view.IntervalView; import net.imglib2.view.Views; public class BigWarpInit { + + public static final N5MetadataParser[] PARSERS = new N5MetadataParser[]{ + new N5CosemMetadataParser(), + new N5SingleScaleMetadataParser(), + new CanonicalMetadataParser(), + new ImagePlusLegacyMetadataParser(), + new N5GenericSingleScaleMetadataParser() + }; + + public static final N5MetadataParser[] GROUP_PARSERS = new N5MetadataParser[]{ +// new org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.OmeNgffMetadataParser(), +// new org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v03.OmeNgffMetadataParser(), // TODO test later + new N5CosemMultiScaleMetadata.CosemMultiScaleParser(), + new N5ViewerMultiscaleMetadataParser(), + new CanonicalMetadataParser(), + new N5ViewerMultichannelMetadata.N5ViewerMultichannelMetadataParser() + }; + private static String createSetupName( final BasicViewSetup setup ) { if ( setup.hasName() ) @@ -103,139 +149,6 @@ private static String createSetupName( final BasicViewSetup setup ) return name; } -// public static void initSetupsARGBTypeRandom( final AbstractSpimData< ? > spimData, final ARGBType type, final List< ConverterSetup > converterSetups, final List< SourceAndConverter< ? > > sources ) -// { -// if ( spimData.getSequenceDescription().getImgLoader() instanceof WrapBasicImgLoader ) -// { -// initSetupsARGBTypeNonVolatile( spimData, type, converterSetups, sources ); -// return; -// } -// -// final AbstractSequenceDescription< ?, ?, ? > seq = spimData.getSequenceDescription(); -// for ( final BasicViewSetup setup : seq.getViewSetupsOrdered() ) -// { -// -// final int setupId = setup.getId(); -// -// final String setupName = createSetupName( setup ); -// final VolatileSpimSource< ARGBType, VolatileARGBType > vs = new VolatileSpimSource< ARGBType, VolatileARGBType >( spimData, setupId, setupName ); -// final SpimSource< ARGBType > s = vs.nonVolatile(); -// -// // Decorate each source with an extra transformation, that can be -// // edited manually in this viewer. -// final TransformedSource< VolatileARGBType > tvs = new TransformedSource< VolatileARGBType >( vs ); -// final TransformedSource< ARGBType > ts = new TransformedSource< ARGBType >( s, tvs ); -// -// final SourceAndConverter< ARGBType > soc; -// final SourceAndConverter< VolatileARGBType > vsoc; -// final ConverterSetup converterSetup; -// -// final ARGBtoRandomARGBColorConverter.ToGray converter = new ARGBtoRandomARGBColorConverter.ToGray( 0, 255 ); -// final ARGBtoRandomARGBColorConverter.VolatileToGray vconverter = new ARGBtoRandomARGBColorConverter.VolatileToGray( 0, 255 ); -// -// converterSetup = new RealARGBColorConverterSetup( setupId, converter, vconverter ); -// vsoc = new SourceAndConverter< VolatileARGBType >( tvs, vconverter ); -// soc = new SourceAndConverter< ARGBType >( ts, converter, vsoc ); -// -// converterSetups.add( converterSetup ); -// -// sources.add( soc ); -// } -// } - -// public static void initSetupsARGBType( final AbstractSpimData< ? > spimData, final ARGBType type, final List< ConverterSetup > converterSetups, final List< SourceAndConverter< ? > > sources ) -// { -// initSetupsARGBType( spimData, type, converterSetups, sources, true ); -// } - -// public static void initSetupsARGBType( final AbstractSpimData< ? > spimData, final ARGBType type, final List< ConverterSetup > converterSetups, final List< SourceAndConverter< ? > > sources, final boolean grayConversion ) -// { -// if ( spimData.getSequenceDescription().getImgLoader() instanceof WrapBasicImgLoader ) -// { -// initSetupsARGBTypeNonVolatile( spimData, type, converterSetups, sources ); -// return; -// } -// -// final AbstractSequenceDescription< ?, ?, ? > seq = spimData.getSequenceDescription(); -// for ( final BasicViewSetup setup : seq.getViewSetupsOrdered() ) -// { -// -// final int setupId = setup.getId(); -// -// final String setupName = createSetupName( setup ); -// final VolatileSpimSource< ARGBType, VolatileARGBType > vs = new VolatileSpimSource< ARGBType, VolatileARGBType >( spimData, setupId, setupName ); -// final SpimSource< ARGBType > s = vs.nonVolatile(); -// -// // Decorate each source with an extra transformation, that can be -// // edited manually in this viewer. -// final TransformedSource< VolatileARGBType > tvs = new TransformedSource< VolatileARGBType >( vs ); -// final TransformedSource< ARGBType > ts = new TransformedSource< ARGBType >( s, tvs ); -// -// final SourceAndConverter< ARGBType > soc; -// final SourceAndConverter< VolatileARGBType > vsoc; -// final ConverterSetup converterSetup; -// if ( grayConversion ) -// { -// final ARGBARGBColorConverter.ToGray converter = new ARGBARGBColorConverter.ToGray( 0, 255 ); -// final ARGBARGBColorConverter.VolatileToGray vconverter = new ARGBARGBColorConverter.VolatileToGray( 0, 255 ); -//// converter = new ScaledARGBConverter.ARGB( 0, 255 ); -//// vconverter = new ScaledARGBConverter.VolatileARGB( 0, 255 ); -// -// converterSetup = new RealARGBColorConverterSetup( setupId, converter, vconverter ); -// vsoc = new SourceAndConverter< VolatileARGBType >( tvs, vconverter ); -// soc = new SourceAndConverter< ARGBType >( ts, converter, vsoc ); -// -// converterSetups.add( converterSetup ); -// } -// else -// { -// final Converter< VolatileARGBType, ARGBType > vconverter = new Converter< VolatileARGBType, ARGBType >() -// { -// @Override -// public void convert( final VolatileARGBType input, final ARGBType output ) -// { -// output.set( input.get() ); -// } -// }; -// final Converter< ARGBType, ARGBType > converter = new Converter< ARGBType, ARGBType >() -// { -// @Override -// public void convert( final ARGBType input, final ARGBType output ) -// { -// output.set( input.get() ); -// } -// }; -// -// vsoc = new SourceAndConverter< VolatileARGBType >( tvs, vconverter ); -// soc = new SourceAndConverter< ARGBType >( ts, converter, vsoc ); -// -// } -// -// sources.add( soc ); -// } -// } - -// private static void initSetupsARGBTypeNonVolatile( final AbstractSpimData< ? > spimData, final ARGBType type, final List< ConverterSetup > converterSetups, final List< SourceAndConverter< ? > > sources ) -// { -// final AbstractSequenceDescription< ?, ?, ? > seq = spimData.getSequenceDescription(); -// for ( final BasicViewSetup setup : seq.getViewSetupsOrdered() ) -// { -// final ScaledARGBConverter.ARGB converter = new ScaledARGBConverter.ARGB( 0, 255 ); -// -// final int setupId = setup.getId(); -// final String setupName = createSetupName( setup ); -// final SpimSource< ARGBType > s = new SpimSource< ARGBType >( spimData, setupId, setupName ); -// -// // Decorate each source with an extra transformation, that can be -// // edited manually in this viewer. -// final TransformedSource< ARGBType > ts = new TransformedSource< ARGBType >( s ); -// final SourceAndConverter< ARGBType > soc = new SourceAndConverter< ARGBType >( ts, converter ); -// -// sources.add( soc ); -// converterSetups.add( new RealARGBColorConverterSetup( setupId, converter ) ); -// } -// } - public static void initSetups( final AbstractSpimData< ? > spimData, final List< ConverterSetup > converterSetups, final List< SourceAndConverter< ? > > sources ) { BigDataViewer.initSetups( spimData, converterSetups, sources ); @@ -302,104 +215,453 @@ public static < T extends RealType< T > > void initSourceReal( final Source< T > public static BigWarpData< ? > createBigWarpData( final Source< ? >[] movingSourceList, final Source< ? >[] fixedSourceList, final String[] names ) { final BigWarpData data = initData(); - + int nameIdx = 0; int setupId = 0; // moving for ( final Source< ? > mvgSource : movingSourceList ) { - add( data, mvgSource, setupId++, 1, true ); + add( data, mvgSource, setupId, 1, true ); + final SourceAndConverter< ? > addedSource = ( ( BigWarpData< ? > ) data ).sources.get( data.sources.size() - 1 ); + final SourceInfo info = new SourceInfo( setupId, true, names[ nameIdx++ ] ); + info.setSourceAndConverter( addedSource ); + data.sourceInfos.put( setupId++, info ); } // target for ( final Source< ? > fxdSource : fixedSourceList ) { add( data, fxdSource, setupId, 1, false ); + final SourceAndConverter< ? > addedSource = ( ( BigWarpData< ? > ) data ).sources.get( data.sources.size() - 1 ); + final SourceInfo info = new SourceInfo( setupId, false, names[ nameIdx++ ] ); + info.setSourceAndConverter( addedSource ); + data.sourceInfos.put( setupId++, info ); } data.wrapUp(); - if ( names != null ) { return new BigWarpData( wrapSourcesAsRenamable( data.sources, names ), data.converterSetups, data.cache, data.movingSourceIndices, data.targetSourceIndices ); } + if ( names != null ) + { + final ArrayList wrappedSources = wrapSourcesAsRenamable( data.sources, names ); + final AtomicInteger sourceInfoIdx = new AtomicInteger(); + + final BigWarpData< ? > typedData = data; + typedData.sourceInfos.forEach( ( id, info ) -> { + info.setSourceAndConverter( typedData.sources.get( sourceInfoIdx.getAndIncrement() ) ); + } ); + + return new BigWarpData( wrappedSources, data.converterSetups, data.cache ); + + } return data; } - @SuppressWarnings( { "rawtypes" } ) - public static < T > int add( final BigWarpData bwdata, final ImagePlus ip, int setupId, final int numTimepoints, final boolean isMoving ) + /** + * Initialize BigWarp. + * + * @return the number of sources + * + * @deprecated Use {@code createSources(BigWarpData,ImagePlus,int,int,boolean)} instead, and pass output to + * {@code add(BigWarpData,LinkedHashMap)} + */ + @Deprecated + public static < T > int add( BigWarpData< T > bwdata, ImagePlus ip, int setupId, int numTimepoints, boolean isMoving ) + { + final LinkedHashMap< Source< T >, SourceInfo > sources = createSources( bwdata, ip, setupId, numTimepoints, isMoving ); + add( bwdata, sources ); + return sources.size(); + } + + public static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( BigWarpData< T > bwdata, ImagePlus ip, int setupId, int numTimepoints, boolean isMoving ) { final ImagePlusLoader loader = new ImagePlusLoader( ip ); final SpimDataMinimal[] dataList = loader.loadAll( setupId ); + + final LinkedHashMap< Source< T >, SourceInfo > sourceInfoMap = new LinkedHashMap<>(); for ( final SpimDataMinimal data : dataList ) { - add( bwdata, data, setupId, numTimepoints, isMoving ); - setupId++; + final LinkedHashMap< Source< T >, SourceInfo > map = createSources( bwdata, data, setupId, isMoving ); + sourceInfoMap.putAll( map ); + setupId += map.values().stream().map( SourceInfo::getId ).max( Integer::compare ).orElseGet( () -> 0 ); + } + + sourceInfoMap.forEach( ( sac, state ) -> { + loader.update( state ); + state.setUriSupplier( () -> { + final FileInfo originalFileInfo = ip.getOriginalFileInfo(); + if ( originalFileInfo != null ) + { + final String url = originalFileInfo.url; + if ( url != null && !url.isEmpty() ) + { + return url; + } + else + { + return originalFileInfo.getFilePath(); + } + } + return null; + } ); + } ); + + for ( final Map.Entry< Source< T >, SourceInfo > sourceSourceInfoEntry : sourceInfoMap.entrySet() ) + { + sourceSourceInfoEntry.getValue().setSerializable( true ); + /* Always break after the first */ + break; } - loader.update(bwdata); - return loader.numSources(); + return sourceInfoMap; } + /** + * Initialize BigWarp. + * + * @return a {@link BigWarpData} instance + * + * @deprecated Use the output from one of the + * {{@link #createSources(BigWarpData, String, int, boolean)}} + * to call {{@link #add(BigWarpData, LinkedHashMap)}} instead + */ + @Deprecated + public static < T > BigWarpData< T > add( BigWarpData< T > bwdata, Source< T > src, int setupId, int numTimepoints, boolean isMoving ) + { + return add( bwdata, src, setupId, numTimepoints, isMoving, null ); + } + + public static < T > BigWarpData< T > add( BigWarpData< T > bwdata, Source< T > source, SourceInfo sourceInfo ) + { + return add( bwdata, source, sourceInfo ); + } + + public static < T > BigWarpData< T > add( BigWarpData< T > bwdata, Source< T > source, SourceInfo sourceInfo, RealTransform transform, Supplier transformUriSupplier ) + { + final LinkedHashMap< Source< T >, SourceInfo > sourceToInfo = new LinkedHashMap<>(); + sourceToInfo.put( source, sourceInfo ); + return add( bwdata, sourceToInfo, transform, transformUriSupplier ); + } + + public static < T > BigWarpData< T > add( BigWarpData< T > bwdata, LinkedHashMap< Source< T >, SourceInfo > sources ) + { + add( bwdata, sources, null, null ); + return bwdata; + } + + /** + * Initialize BigWarp. + * + * @return a {@link BigWarpData} instance + * + * @deprecated Use the output from one of the + * {{@code createSources(BigWarpData, String, int, boolean)}} + * to call {{@code add(BigWarpData, LinkedHashMap, RealTransform)}} + * instead + */ @SuppressWarnings( { "unchecked", "rawtypes" } ) - public static < T > BigWarpData< ? > add( final BigWarpData bwdata, final Source< T > src, final int setupId, final int numTimepoints, final boolean isMoving ) + @Deprecated + public static < T > BigWarpData< T > add( BigWarpData bwdata, Source< T > src, int setupId, int numTimepoints, boolean isMoving, RealTransform transform ) { addSourceToListsGenericType( src, setupId, bwdata.converterSetups, bwdata.sources ); +// bwdata.transforms.add( transform ); + return bwdata; + } - final int N = bwdata.sources.size(); - if ( isMoving ) - bwdata.movingSourceIndexList.add( N - 1 ); - else - bwdata.targetSourceIndexList.add( N - 1 ); + public static < T > BigWarpData< T > add( BigWarpData< T > bwdata, LinkedHashMap< Source< T >, SourceInfo > sources, RealTransform transform, Supplier transformUriSupplier ) + { + sources.forEach( ( source, info ) -> { + addSourceToListsGenericType( source, info.getId(), bwdata.converterSetups, bwdata.sources ); + final SourceAndConverter< T > addedSource = bwdata.sources.get( bwdata.sources.size() - 1 ); + info.setSourceAndConverter( addedSource ); + if ( transform != null ) + { + info.setTransform( transform, transformUriSupplier ); + } + bwdata.sourceInfos.put( info.getId(), info ); + } ); return bwdata; } - @SuppressWarnings( { "unchecked", "rawtypes" } ) - public static < T > BigWarpData< ? > add( final BigWarpData bwdata, final AbstractSpimData< ? > data, final int baseId, final int numTimepoints, final boolean isMoving ) + @SuppressWarnings( { "rawtypes" } ) + public static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( BigWarpData bwdata, Dataset data, int baseId, final boolean isMoving ) { - final List> tmpSources = new ArrayList<>(); - final List tmpConverterSetups = new ArrayList<>(); + boolean first = true; + final LinkedHashMap< Source< T >, SourceInfo > sourceInfoMap = new LinkedHashMap<>(); + + final AffineTransform3D res = datasetResolution( data ); + final long nc = data.getChannels(); + boolean hasZ = false; + + final CalibratedAxis[] axes = new CalibratedAxis[ data.numDimensions() ]; + data.axes( axes ); + for (int i = 0; i < data.numDimensions(); i++) { + if (axes[i].type().equals(Axes.Z)) + { + hasZ = true; + break; + } + } + + if ( nc > 1 ) + { + int channelIdx = -1; + for ( int i = 0; i < data.numDimensions(); i++ ) + { + if ( axes[ i ].type().equals( Axes.CHANNEL ) ) + { + channelIdx = i; + break; + } + } + + for ( int c = 0; c < nc; c++ ) + { + final IntervalView> channelRaw = Views.hyperSlice( data, channelIdx, c ); + final IntervalView> channel = hasZ ? channelRaw : Views.addDimension( channelRaw, 0, 0 ); + + @SuppressWarnings( "unchecked" ) + final RandomAccessibleIntervalSource source = new RandomAccessibleIntervalSource( channel, Util.getTypeFromInterval( data ), res, data.getName() ); + + final SourceInfo info = new SourceInfo( baseId + c, isMoving, data.getName(), () -> data.getSource() ); + info.setSerializable( first ); + if ( first ) + first = false; + + sourceInfoMap.put( source, info ); + } + } + else + { + final RandomAccessibleInterval> img = hasZ ? data : Views.addDimension( data, 0, 0 ); + + @SuppressWarnings( "unchecked" ) + final RandomAccessibleIntervalSource source = new RandomAccessibleIntervalSource( img, Util.getTypeFromInterval( data ), res, data.getName() ); + + final SourceInfo info = new SourceInfo( baseId, isMoving, data.getName(), () -> data.getSource() ); + info.setSerializable( true ); + sourceInfoMap.put( source, info ); + } + + return sourceInfoMap; + } + + public static AffineTransform3D datasetResolution( Dataset data ) + { + final AffineTransform3D affine = new AffineTransform3D(); + final CalibratedAxis[] axes = new CalibratedAxis[ data.numDimensions() ]; + data.axes( axes ); + + for ( int d = 0; d < data.numDimensions(); d++ ) + { + if ( axes[ d ].type().equals( Axes.X ) ) + affine.set( axes[ d ].calibratedValue( 1 ), 0, 0 ); + else if ( axes[ d ].type().equals( Axes.Y ) ) + affine.set( axes[ d ].calibratedValue( 1 ), 1, 1 ); + else if ( axes[ d ].type().equals( Axes.Z ) ) + affine.set( axes[ d ].calibratedValue( 1 ), 2, 2 ); + } + return affine; + } + + @SuppressWarnings( { "rawtypes" } ) + public static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( BigWarpData bwdata, AbstractSpimData< ? > data, int baseId, final boolean isMoving ) + { + final List< SourceAndConverter< ? > > tmpSources = new ArrayList<>(); + final List< ConverterSetup > tmpConverterSetups = new ArrayList<>(); initSetups( data, tmpConverterSetups, tmpSources ); + final LinkedHashMap< Source< T >, SourceInfo > sourceInfoMap = new LinkedHashMap<>(); int setupId = baseId; - for( final SourceAndConverter sac : tmpSources ) - add( bwdata, sac.getSpimSource(), setupId++, numTimepoints, isMoving ); - -// int N = bwdata.sources.size(); -// final ArrayList idxList; -// if ( isMoving ) -// idxList = bwdata.movingSourceIndexList; -// else -// idxList = bwdata.targetSourceIndexList; -// -// for( int i = startSize; i < N; i++ ) -// idxList.add( i ); -// - return bwdata; + for ( final SourceAndConverter sac : tmpSources ) + { + final Source< T > source = sac.getSpimSource(); + sourceInfoMap.put( source, new SourceInfo( setupId++, isMoving, source.getName() ) ); + } + + return sourceInfoMap; + } + + public static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( BigWarpData bwdata, Source< T > src, int baseId, final boolean isMoving ) + { + final LinkedHashMap< Source< T >, SourceInfo > sourceInfoMap = new LinkedHashMap<>(); + sourceInfoMap.put( src, new SourceInfo( baseId, isMoving, src.getName() ) ); + return sourceInfoMap; + } + + private static String schemeSpecificPartWithoutQuery( URI uri ) + { + return uri.getSchemeSpecificPart().replaceAll( "\\?" + uri.getQuery(), "" ).replaceAll( "//", "" ); } - public static SpimData addToData( final BigWarpData bwdata, - final boolean isMoving, final int setupId, final String rootPath, final String dataset ) + public static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( final BigWarpData< T > bwData, String uri, int setupId, boolean isMoving ) throws URISyntaxException, IOException, SpimDataException { - if( rootPath.endsWith( "xml" )) + final SharedQueue sharedQueue = new SharedQueue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + final URI encodedUri = N5URI.encodeAsUri( uri ); + final LinkedHashMap< Source< T >, SourceInfo > sourceStateMap = new LinkedHashMap<>(); + if ( encodedUri.isOpaque() ) + { + final N5URI n5URL = new N5URI( encodedUri.getSchemeSpecificPart() ); + final String firstScheme = encodedUri.getScheme().toLowerCase(); + final N5Reader n5reader; + switch ( firstScheme.toLowerCase() ) + { + case "n5": + n5reader = new N5Factory().openReader( n5URL.getContainerPath() ); + break; + case "zarr": + n5reader = new N5ZarrReader( n5URL.getContainerPath() ); + break; + case "h5": + case "hdf5": + case "hdf": + n5reader = new N5HDF5Reader( n5URL.getContainerPath() ); + break; + default: + throw new URISyntaxException( firstScheme, "Unsupported Top Level Protocol" ); + } + + final Source< T > source = (Source)loadN5Source( n5reader, n5URL.getGroupPath(), sharedQueue ); + sourceStateMap.put( source, new SourceInfo( setupId, isMoving, n5URL.getGroupPath() ) ); + } + else + { + final N5URI n5URL = new N5URI( encodedUri ); + try + { + final String containerWithoutN5Scheme = n5URL.getContainerPath().replaceFirst( "^n5://", "" ); + final N5Reader n5reader = new N5Factory().openReader( containerWithoutN5Scheme ); + final String group = n5URL.getGroupPath(); + final Source< T > source = (Source)loadN5Source( n5reader, group, sharedQueue ); + + if( source != null ) + sourceStateMap.put( source, new SourceInfo( setupId, isMoving, group ) ); + } + catch ( final Exception ignored ) + {} + if ( sourceStateMap.isEmpty() ) + { + final String containerPath = n5URL.getContainerPath(); + if ( containerPath.trim().toLowerCase().endsWith( ".xml" ) ) + { + sourceStateMap.putAll( createSources( bwData, isMoving, setupId, containerPath, n5URL.getGroupPath() ) ); + } + else + { + final ImagePlus ijp; + if ( Objects.equals( encodedUri.getScheme(), "imagej" ) ) + { + final String title = n5URL.getContainerPath().replaceAll( "^imagej:(///|//)", "" ); + IJ.selectWindow( title ); + ijp = IJ.getImage(); + } + else if ( new File( uri ).isDirectory() ) + { + ijp = FolderOpener.open( uri ); + } + else + { + ijp = IJ.openImage( uri ); + } + sourceStateMap.putAll( createSources( bwData, ijp, setupId, 0, isMoving ) ); + } + } + + } + + /* + * override any already set urls with the uri we used to load this + * source. + */ + sourceStateMap.forEach( ( source, state ) -> { + state.setUriSupplier( () -> uri ); + } ); + for ( final Map.Entry< Source< T >, SourceInfo > sourceSourceInfoEntry : sourceStateMap.entrySet() ) + { + sourceSourceInfoEntry.getValue().setSerializable( true ); + /* Always break after the first */ + break; + } + return sourceStateMap; + } + + /** + * Initialize BigWarp. + * + * @return a {@link SpimData} instance + * + * @deprecated Use output from + * {@code createSources(BigWarpData, boolean, int, String, String)} and add with + * {@code add(BigWarpData, LinkedHashMap, RealTransform)} instead. + */ + @Deprecated + public static < T > SpimData addToData( final BigWarpData< T > bwdata, final boolean isMoving, final int setupId, final String rootPath, final String dataset ) + { + final AtomicReference< SpimData > returnMovingSpimData = new AtomicReference<>(); + final LinkedHashMap< Source< T >, SourceInfo > sources = createSources( bwdata, isMoving, setupId, rootPath, dataset, returnMovingSpimData ); + add( bwdata, sources ); + return returnMovingSpimData.get(); + } + + public static < T > Map< Source< T >, SourceInfo > createSources( final BigWarpData< T > bwdata, final boolean isMoving, final int setupId, final String rootPath, final String dataset ) + { + return createSources( bwdata, isMoving, setupId, rootPath, dataset, null ); + } + + private static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( final BigWarpData< T > bwdata, final boolean isMoving, final int setupId, final String rootPath, final String dataset, final AtomicReference< SpimData > returnMovingSpimData ) + { + final SharedQueue sharedQueue = new SharedQueue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + if ( rootPath.endsWith( "xml" ) ) { SpimData spimData; try { spimData = new XmlIoSpimData().load( rootPath ); - add( bwdata, spimData, setupId, 0, isMoving ); - - if( isMoving ) - return spimData; + if ( returnMovingSpimData != null && isMoving ) + { + returnMovingSpimData.set( spimData ); + } + final LinkedHashMap< Source< T >, SourceInfo > sources = createSources( bwdata, spimData, setupId, isMoving ); + + sources.forEach( ( source, state ) -> { + state.setUriSupplier( () -> { + try + { + return spimData.getBasePath().getCanonicalPath(); + } + catch ( final IOException e ) + { + return null; + } + } ); + } ); + + for ( final Map.Entry< Source< T >, SourceInfo > sourceSourceInfoEntry : sources.entrySet() ) + { + sourceSourceInfoEntry.getValue().setSerializable( true ); + /* Always break after the first */ + break; + } + return sources; + } + catch ( final SpimDataException e ) + { + e.printStackTrace(); } - catch ( final SpimDataException e ) { e.printStackTrace(); } return null; } else { - BigWarpInit.add( bwdata, loadN5Source( rootPath, dataset ), setupId, 0, isMoving ); - return null; + final LinkedHashMap< Source< T >, SourceInfo > map = new LinkedHashMap<>(); + final Source< T > source = (Source)loadN5Source( rootPath, dataset, sharedQueue ); + final SourceInfo info = new SourceInfo( setupId, isMoving, dataset, () -> rootPath + "$" + dataset ); + info.setSerializable( true ); + map.put( source, info ); + return map; } } - public static Source loadN5Source( final String n5Root, final String n5Dataset ) + + public static < T extends NativeType > Source< T > loadN5Source( final String n5Root, final String n5Dataset, final SharedQueue queue ) { final N5Reader n5; try @@ -410,27 +672,16 @@ public static Source loadN5Source( final String n5Root, final String n5Datase e.printStackTrace(); return null; } + return loadN5Source( n5, n5Dataset, queue ); + } - final N5MetadataParser[] PARSERS = new N5MetadataParser[]{ - new ImagePlusLegacyMetadataParser(), - new N5CosemMetadataParser(), - new N5SingleScaleMetadataParser(), - new CanonicalMetadataParser(), - new N5GenericSingleScaleMetadataParser() - }; - - final N5MetadataParser[] GROUP_PARSERS = new N5MetadataParser[]{ - new N5CosemMultiScaleMetadata.CosemMultiScaleParser(), - new N5ViewerMultiscaleMetadataParser(), - new CanonicalMetadataParser(), - }; + public static < T extends NativeType> Source< T > loadN5Source( final N5Reader n5, final String n5Dataset, final SharedQueue queue ) + { N5Metadata meta = null; try { - final N5DatasetDiscoverer discoverer = new N5DatasetDiscoverer( n5, - N5DatasetDiscoverer.fromParsers(PARSERS), - N5DatasetDiscoverer.fromParsers(GROUP_PARSERS) ); + final N5DatasetDiscoverer discoverer = new N5DatasetDiscoverer( n5, N5DatasetDiscoverer.fromParsers( PARSERS ), N5DatasetDiscoverer.fromParsers( GROUP_PARSERS ) ); final N5TreeNode node = discoverer.discoverAndParseRecursive( n5Dataset ); meta = node.getMetadata(); @@ -438,50 +689,51 @@ public static Source loadN5Source( final String n5Root, final String n5Datase catch ( final IOException e ) {} - if( meta instanceof MultiscaleMetadata ) + if ( meta instanceof MultiscaleMetadata ) { - return openAsSourceMulti( n5, (MultiscaleMetadata)meta, true ); + return openAsSourceMulti( n5, ( MultiscaleMetadata< ? > ) meta, queue, true ); } else { - return openAsSource( n5, meta, true ); + return openAsSource( n5, meta, queue, true ); } } @SuppressWarnings( { "unchecked", "rawtypes" } ) - public static Source openAsSource( final N5Reader n5, final T meta, final boolean isVolatile ) + public static < T extends NativeType, M extends N5Metadata > Source< T > openAsSource( final N5Reader n5, final M meta, final SharedQueue sharedQueue, final boolean isVolatile ) { final RandomAccessibleInterval imageRaw; final RandomAccessibleInterval image; + if( meta == null ) + return null; + try { - if( isVolatile ) - imageRaw = to3d( N5Utils.openVolatile( n5, meta.getPath() )); + if ( isVolatile ) + { + final CachedCellImg rai = N5Utils.openVolatile( n5, meta.getPath() ); + imageRaw = to3d( VolatileViews.wrapAsVolatile( rai, sharedQueue, new CacheHints(LoadingStrategy.VOLATILE, 0, true)) ); + } else - imageRaw = to3d( N5Utils.open( n5, meta.getPath() )); + imageRaw = to3d( N5Utils.open( n5, meta.getPath() ) ); - if( meta instanceof N5ImagePlusMetadata - && ((N5ImagePlusMetadata)meta).getType() == ImagePlus.COLOR_RGB - && Util.getTypeFromInterval( imageRaw ) instanceof UnsignedIntType ) + if ( meta instanceof N5ImagePlusMetadata && ( ( N5ImagePlusMetadata ) meta ).getType() == ImagePlus.COLOR_RGB && Util.getTypeFromInterval( imageRaw ) instanceof UnsignedIntType ) { image = toColor( imageRaw ); } else image = imageRaw; - if( meta instanceof SpatialMetadata ) + if ( meta instanceof SpatialMetadata ) { - final String unit = ((SpatialMetadata)meta).unit(); - final AffineTransform3D srcXfm = ((SpatialMetadata)meta).spatialTransform3d(); - final FinalVoxelDimensions voxelDims = new FinalVoxelDimensions( unit, - new double[]{ srcXfm.get( 0, 0 ), srcXfm.get( 1, 1 ), srcXfm.get( 2, 2 ) }); + final String unit = ( ( SpatialMetadata ) meta ).unit(); + final AffineTransform3D srcXfm = ( ( SpatialMetadata ) meta ).spatialTransform3d(); + final FinalVoxelDimensions voxelDims = new FinalVoxelDimensions( unit, new double[] { srcXfm.get( 0, 0 ), srcXfm.get( 1, 1 ), srcXfm.get( 2, 2 ) } ); - return new BwRandomAccessibleIntervalSource( image, (NumericType ) Util.getTypeFromInterval( image ), - srcXfm, meta.getPath(), voxelDims ); + return new BwRandomAccessibleIntervalSource( image, ( NumericType ) Util.getTypeFromInterval( image ), srcXfm, meta.getPath(), voxelDims ); } else - return new BwRandomAccessibleIntervalSource( image, ( NumericType ) Util.getTypeFromInterval( image ), - new AffineTransform3D(), meta.getPath() ); + return new BwRandomAccessibleIntervalSource( image, ( NumericType ) Util.getTypeFromInterval( image ), new AffineTransform3D(), meta.getPath() ); } catch ( final RuntimeException e ) { @@ -491,23 +743,27 @@ public static Source openAsSource( final N5Reader n5, return null; } - public static Source openAsSourceMulti( final N5Reader n5, final MultiscaleMetadata multiMeta, final boolean isVolatile ) + public static < T extends NativeType > Source< T > openAsSourceMulti( final N5Reader n5, final MultiscaleMetadata< ? > multiMeta, final SharedQueue sharedQueue, final boolean isVolatile ) { final String[] paths = multiMeta.getPaths(); final AffineTransform3D[] transforms = multiMeta.spatialTransforms3d(); - final String unit = multiMeta.units()[0]; + final String unit = multiMeta.units()[ 0 ]; @SuppressWarnings( "rawtypes" ) - final RandomAccessibleInterval[] images = new RandomAccessibleInterval[paths.length]; + final RandomAccessibleInterval[] images = new RandomAccessibleInterval[ paths.length ]; final double[][] mipmapScales = new double[ images.length ][ 3 ]; + final CacheHints cacheHints = new CacheHints(LoadingStrategy.VOLATILE, 0, true); for ( int s = 0; s < images.length; ++s ) { try { - if( isVolatile ) - images[ s ] = to3d( N5Utils.openVolatile( n5, paths[s] )); + if ( isVolatile ) + { + final CachedCellImg rai = N5Utils.openVolatile( n5, paths[ s ] ); + images[ s ] = to3d( VolatileViews.wrapAsVolatile( rai, sharedQueue, cacheHints) ); + } else - images[ s ] = to3d( N5Utils.open( n5, paths[s] )); + images[ s ] = to3d( N5Utils.open( n5, paths[ s ] ) ); } catch ( final RuntimeException e ) { @@ -520,46 +776,38 @@ public static Source openAsSourceMulti( final N5Reader n5, final MultiscaleMe } @SuppressWarnings( { "unchecked", "rawtypes" } ) - final RandomAccessibleIntervalMipmapSource source = new RandomAccessibleIntervalMipmapSource( - images, - (NumericType ) Util.getTypeFromInterval(images[0]), - mipmapScales, - new mpicbg.spim.data.sequence.FinalVoxelDimensions( unit, mipmapScales[0]), - new AffineTransform3D(), - multiMeta.getPaths()[0] + "_group" ); + final RandomAccessibleIntervalMipmapSource source = new RandomAccessibleIntervalMipmapSource( images, ( NumericType ) Util.getTypeFromInterval( images[ 0 ] ), mipmapScales, new mpicbg.spim.data.sequence.FinalVoxelDimensions( unit, mipmapScales[ 0 ] ), new AffineTransform3D(), multiMeta.getPaths()[ 0 ] + "_group" ); return source; } - private static RandomAccessibleInterval to3d( final RandomAccessibleInterval img ) + private static RandomAccessibleInterval< ? > to3d( RandomAccessibleInterval< ? > img ) { - if( img.numDimensions() == 2 ) + if ( img.numDimensions() == 2 ) return Views.addDimension( img, 0, 0 ); else return img; } - private static RandomAccessibleInterval toColor( final RandomAccessibleInterval img ) + private static RandomAccessibleInterval< ARGBType > toColor( RandomAccessibleInterval< UnsignedIntType > img ) { - return Converters.convertRAI( img, - new Converter() - { - @Override - public void convert( final UnsignedIntType input, final ARGBType output ) - { - output.set( input.getInt() ); - } - }, - new ARGBType() ); + return Converters.convertRAI( img, new Converter< UnsignedIntType, ARGBType >() + { + @Override + public void convert( UnsignedIntType input, ARGBType output ) + { + output.set( input.getInt() ); + } + }, new ARGBType() ); } @SuppressWarnings( { "rawtypes", "unchecked" } ) public static < T > BigWarpData< T > initData() { - final ArrayList< ConverterSetup > converterSetups = new ArrayList< ConverterSetup >(); - final ArrayList< SourceAndConverter< T > > sources = new ArrayList< SourceAndConverter< T > >(); - - return new BigWarpData( sources, converterSetups, null, null, null ); +// final ArrayList< ConverterSetup > converterSetups = new ArrayList< ConverterSetup >(); +// final ArrayList< SourceAndConverter< T > > sources = new ArrayList< SourceAndConverter< T > >(); +// return new BigWarpData( sources, converterSetups, null ); + return new BigWarpData(); } /** @@ -580,11 +828,7 @@ public static < T > BigWarpData< T > initData() * be added. */ @SuppressWarnings( { "rawtypes", "unchecked" } ) - private static < T > void addSourceToListsGenericType( - final Source< T > source, - final int setupId, - final List< ConverterSetup > converterSetups, - final List< SourceAndConverter< T > > sources ) + private static < T > void addSourceToListsGenericType( final Source< T > source, final int setupId, final List< ConverterSetup > converterSetups, final List< SourceAndConverter< T > > sources ) { final T type = source.getType(); if ( type instanceof RealType || type instanceof ARGBType || type instanceof VolatileARGBType ) @@ -610,15 +854,10 @@ private static < T > void addSourceToListsGenericType( * list of {@link SourceAndConverter}s to which the source should * be added. */ - private static < T extends NumericType< T > > void addSourceToListsNumericType( - final Source< T > source, - final int setupId, - final List< ConverterSetup > converterSetups, - final List< SourceAndConverter< T > > sources ) + private static < T extends NumericType< T > > void addSourceToListsNumericType( final Source< T > source, final int setupId, final List< ConverterSetup > converterSetups, final List< SourceAndConverter< T > > sources ) { final T type = source.getType(); - final SourceAndConverter< T > soc = BigDataViewer.wrapWithTransformedSource( - new SourceAndConverter<>( source, BigDataViewer.createConverterToARGB( type ) ) ); + final SourceAndConverter< T > soc = BigDataViewer.wrapWithTransformedSource( new SourceAndConverter<>( source, BigDataViewer.createConverterToARGB( type ) ) ); converterSetups.add( BigDataViewer.createConverterSetup( soc, setupId ) ); sources.add( soc ); } @@ -734,8 +973,7 @@ private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final Source return createBigWarpData( loaderP, loaderQ, null ); } - public static BigWarpData< ? > createBigWarpData( final ImagePlusLoader loaderP, final ImagePlusLoader loaderQ, - final String[] names ) + public static BigWarpData< ? > createBigWarpData( final ImagePlusLoader loaderP, final ImagePlusLoader loaderQ, final String[] names ) { /* Load the first source */ final AbstractSpimData< ? >[] spimDataP = loaderP.loadAll( 0 ); @@ -784,29 +1022,29 @@ private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final Source { /* Load the moving sources */ final AbstractSpimData< ? >[] spimDataP; - if( loaderP instanceof ImagePlusLoader ) + if ( loaderP instanceof ImagePlusLoader ) spimDataP = loaderP.load(); else spimDataP = loaderP.load(); /* Load the fixed sources */ final AbstractSpimData< ? >[] spimDataQ; - if( loaderQ instanceof ImagePlusLoader ) - spimDataQ = ((ImagePlusLoader)loaderQ).loadAll( spimDataP.length ); + if ( loaderQ instanceof ImagePlusLoader ) + spimDataQ = ( ( ImagePlusLoader ) loaderQ ).loadAll( spimDataP.length ); else spimDataQ = loaderQ.load(); final int N = loaderP.numSources() + loaderQ.numSources(); String[] names; - if( namesIn == null || namesIn.length != N ) + if ( namesIn == null || namesIn.length != N ) { names = new String[ N ]; int j = 0; - for( int i = 0; i < loaderP.numSources(); i++ ) + for ( int i = 0; i < loaderP.numSources(); i++ ) names[ j++ ] = loaderP.name( i ); - for( int i = 0; i < loaderQ.numSources(); i++ ) + for ( int i = 0; i < loaderQ.numSources(); i++ ) names[ j++ ] = loaderQ.name( i ); } else @@ -814,11 +1052,11 @@ private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final Source final BigWarpData< ? > data = createBigWarpData( spimDataP, spimDataQ, names ); - if( loaderP instanceof ImagePlusLoader ) - ((ImagePlusLoader)loaderP).update( data ); + if ( loaderP instanceof ImagePlusLoader ) + ( ( ImagePlusLoader ) loaderP ).update( data ); - if( loaderQ instanceof ImagePlusLoader ) - ((ImagePlusLoader)loaderQ).update( data ); + if ( loaderQ instanceof ImagePlusLoader ) + ( ( ImagePlusLoader ) loaderQ ).update( data ); return data; } @@ -832,9 +1070,33 @@ private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final Source * fixed source XML * @return BigWarpData */ - public static BigWarpData< ? > createBigWarpDataFromXML( final String xmlFilenameP, final String xmlFilenameQ ) + public static < T extends NativeType > BigWarpData< T > createBigWarpDataFromXML( final String xmlFilenameP, final String xmlFilenameQ ) { - return createBigWarpData( new XMLLoader( xmlFilenameP ), new XMLLoader( xmlFilenameQ ), null ); +// return createBigWarpData( new XMLLoader( xmlFilenameP ), new XMLLoader( xmlFilenameQ ), null ); + final BigWarpData< T > bwdata = BigWarpInit.initData(); + try + { + int id = 0; + LinkedHashMap< Source< T >, SourceInfo > mvgSrcs; + mvgSrcs = BigWarpInit.createSources( bwdata, xmlFilenameP, id, true ); + id += mvgSrcs.size(); + BigWarpInit.add( bwdata, mvgSrcs ); + BigWarpInit.add( bwdata, BigWarpInit.createSources( bwdata, xmlFilenameQ, id, false ) ); + } + catch ( final URISyntaxException e ) + { + e.printStackTrace(); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + catch ( final SpimDataException e ) + { + e.printStackTrace(); + } + + return bwdata; } /** @@ -846,9 +1108,16 @@ private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final Source * fixed source ImagePlus * @return BigWarpData */ - public static BigWarpData< ? > createBigWarpDataFromImages( final ImagePlus impP, final ImagePlus impQ ) + public static < T > BigWarpData< T > createBigWarpDataFromImages( final ImagePlus impP, final ImagePlus impQ ) { - return createBigWarpData( new ImagePlusLoader( impP ), new ImagePlusLoader( impQ ), null ); + int id = 0; + final BigWarpData< T > bwdata = BigWarpInit.initData(); + final LinkedHashMap< Source< T >, SourceInfo > mvgSrcs = BigWarpInit.createSources( bwdata, impP, id, 0, true ); + id += mvgSrcs.size(); + BigWarpInit.add( bwdata, mvgSrcs ); + BigWarpInit.add( bwdata, BigWarpInit.createSources( bwdata, impQ, id, 0, false ) ); + + return bwdata; } /** @@ -904,9 +1173,32 @@ private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final Source * fixed source ImagePlus * @return BigWarpData */ - public static BigWarpData< ? > createBigWarpDataFromXMLImagePlus( final String xmlFilenameP, final ImagePlus impQ ) + public static < T extends NativeType > BigWarpData< T > createBigWarpDataFromXMLImagePlus( final String xmlFilenameP, final ImagePlus impQ ) { - return createBigWarpData( new XMLLoader( xmlFilenameP ), new ImagePlusLoader( impQ ) ); + final BigWarpData< T > bwdata = BigWarpInit.initData(); + try + { + int id = 0; + LinkedHashMap< Source< T >, SourceInfo > mvgSrcs; + mvgSrcs = BigWarpInit.createSources( bwdata, xmlFilenameP, id, true ); + id += mvgSrcs.size(); + BigWarpInit.add( bwdata, mvgSrcs ); + BigWarpInit.add( bwdata, BigWarpInit.createSources( bwdata, impQ, id, 0, false ) ); + } + catch ( final URISyntaxException e ) + { + e.printStackTrace(); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + catch ( final SpimDataException e ) + { + e.printStackTrace(); + } + + return bwdata; } /** @@ -933,9 +1225,33 @@ private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final Source * fixed source XML * @return BigWarpData */ - public static BigWarpData< ? > createBigWarpDataFromImagePlusXML( final ImagePlus impP, final String xmlFilenameQ ) + public static < T extends NativeType > BigWarpData< T > createBigWarpDataFromImagePlusXML( final ImagePlus impP, final String xmlFilenameQ ) { - return createBigWarpData( new ImagePlusLoader( impP ), new XMLLoader( xmlFilenameQ ) ); +// return createBigWarpData( new ImagePlusLoader( impP ), new XMLLoader( xmlFilenameQ ) ); + final BigWarpData< T > bwdata = BigWarpInit.initData(); + try + { + int id = 0; + LinkedHashMap< Source< T >, SourceInfo > mvgSrcs; + mvgSrcs = BigWarpInit.createSources( bwdata, impP, id, 0, true ); + id += mvgSrcs.size(); + BigWarpInit.add( bwdata, mvgSrcs ); + BigWarpInit.add( bwdata, BigWarpInit.createSources( bwdata, xmlFilenameQ, id, false ) ); + } + catch ( final URISyntaxException e ) + { + e.printStackTrace(); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + catch ( final SpimDataException e ) + { + e.printStackTrace(); + } + + return bwdata; } /** @@ -956,7 +1272,7 @@ private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final Source /** * Create a {@link String} array of names from two {@link ImagePlus}es, * essentially concatenating the results from calling - * {@link namesFromImagePlus} with each. + * {@link #namesFromImagePlus(ImagePlus)} with each. * * @param impP * first image to generate names from diff --git a/src/main/java/bigwarp/BigWarpRealExporter.java b/src/main/java/bigwarp/BigWarpRealExporter.java index 84f83c4f..a264c3e1 100644 --- a/src/main/java/bigwarp/BigWarpRealExporter.java +++ b/src/main/java/bigwarp/BigWarpRealExporter.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -59,34 +59,30 @@ public class BigWarpRealExporter< T extends RealType< T > & NativeType< T > > e final private T baseType; public BigWarpRealExporter( - final List< SourceAndConverter< T >> sources, + final BigWarpData bwData, final List< ConverterSetup > convSetups, - final int[] movingSourceIndexList, - final int[] targetSourceIndexList, final Interpolation interp, final T baseType, final boolean needConversion, final ProgressWriter progress ) { - super( sources, convSetups, movingSourceIndexList, targetSourceIndexList, interp, progress ); + super( bwData, convSetups, interp, progress ); this.baseType = baseType; } public BigWarpRealExporter( - final List< SourceAndConverter< T >> sources, + final BigWarpData bwData, final List< ConverterSetup > convSetups, - final int[] movingSourceIndexList, - final int[] targetSourceIndexList, final Interpolation interp, final T baseType, final ProgressWriter progress ) { - this( sources, convSetups, movingSourceIndexList, targetSourceIndexList, interp, baseType, false, progress ); + this( bwData, convSetups, interp, baseType, false, progress ); } /** * Returns true if moving image sources are all of the same type. - * + * * @param sources the sources * @param the type * @param movingSourceIndexList list of indexes for moving sources @@ -94,12 +90,12 @@ public BigWarpRealExporter( */ public static boolean isTypeListFullyConsistent( List< SourceAndConverter< T >> sources, int[] movingSourceIndexList ) { - Object baseType = sources.get( movingSourceIndexList[ 0 ] ).getSpimSource().getType(); + final Object baseType = sources.get( movingSourceIndexList[ 0 ] ).getSpimSource().getType(); for ( int i = 1; i < movingSourceIndexList.length; i++ ) { - int idx = movingSourceIndexList[ i ]; - Object type = sources.get( idx ).getSpimSource().getType(); + final int idx = movingSourceIndexList[ i ]; + final Object type = sources.get( idx ).getSpimSource().getType(); if ( !baseType.getClass().equals( type.getClass() ) ) return false; @@ -107,45 +103,69 @@ public static boolean isTypeListFullyConsistent( List< SourceAndConverter< T return true; } - + + /** + * Returns true if moving image sources are all of the same type. + * + * @param sources the sources + * @param the type + * @param movingSourceIndexList list of indexes for moving sources + * @return true if all moving sources are of the same type + */ + public static boolean isTypeListFullyConsistent( List< SourceAndConverter< T >> sources, List movingSourceIndexList ) + { + final Object baseType = sources.get( movingSourceIndexList.get( 0 ) ).getSpimSource().getType(); + + for ( int i = 1; i < movingSourceIndexList.size(); i++ ) + { + final int idx = movingSourceIndexList.get( i ); + final Object type = sources.get( idx ).getSpimSource().getType(); + + if ( !baseType.getClass().equals( type.getClass() ) ) + return false; + } + + return true; + } + @Override public RandomAccessibleInterval< T > exportRai() { - ArrayList< RandomAccessibleInterval< T > > raiList = new ArrayList< RandomAccessibleInterval< T > >(); + final ArrayList< RandomAccessibleInterval< T > > raiList = new ArrayList< RandomAccessibleInterval< T > >(); buildTotalRenderTransform(); - final int numChannels = movingSourceIndexList.length; + final int numChannels = bwData.numMovingSources(); for ( int i = 0; i < numChannels; i++ ) { - final int movingSourceIndex = movingSourceIndexList[ i ]; - - final RealRandomAccessible< T > raiRaw = ( RealRandomAccessible< T > )sources.get( movingSourceIndex ).getSpimSource().getInterpolatedSource( 0, 0, interp ); + final SourceAndConverter< T > msrcTmp = bwData.getMovingSource( i ); + final RealRandomAccessible< T > raiRaw = ( RealRandomAccessible< T > ) bwData.getMovingSource( i ).getSpimSource().getInterpolatedSource( 0, 0, interp ); // apply the transformations - final AffineRandomAccessible< T, AffineGet > rai = RealViews.affine( + final AffineRandomAccessible< T, AffineGet > rai = RealViews.affine( raiRaw, pixelRenderToPhysical.inverse() ); - + raiList.add( Views.interval( Views.raster( rai ), outputInterval ) ); } - RandomAccessibleInterval< T > raiStack = Views.stack( raiList ); + final RandomAccessibleInterval< T > raiStack = Views.stack( raiList ); return raiStack; } - + @Override public boolean isRGB() { return false; } + @Override @SuppressWarnings("unchecked") public ImagePlus export() { - int numChannels = movingSourceIndexList.length; - RandomAccessibleInterval< T > raiStack = exportRai(); - - VoxelDimensions voxdim = new FinalVoxelDimensions( unit, + final int numChannels = bwData.numMovingSources(); + final RandomAccessibleInterval< T > raiStack = exportRai(); + + final VoxelDimensions voxdim = new FinalVoxelDimensions( unit, resolutionTransform.get( 0, 0 ), resolutionTransform.get( 1, 1 ), resolutionTransform.get( 2, 2 )); @@ -175,9 +195,9 @@ else if( nThreads == 1 ) dimensions[ 0 ] = outputInterval.dimension( 0 ); // x dimensions[ 1 ] = outputInterval.dimension( 1 ); // y dimensions[ 2 ] = numChannels; // c - dimensions[ 3 ] = outputInterval.dimension( 2 ); // z - FinalInterval destIntervalPerm = new FinalInterval( dimensions ); - RandomAccessibleInterval< T > img = copyToImageStack( + dimensions[ 3 ] = outputInterval.dimension( 2 ); // z + final FinalInterval destIntervalPerm = new FinalInterval( dimensions ); + final RandomAccessibleInterval< T > img = copyToImageStack( raiStack, destIntervalPerm, factory, nThreads ); ip = ((ImagePlusImg)img).getImagePlus(); @@ -188,9 +208,9 @@ else if ( outputInterval.numDimensions() == 2 ) dimensions[ 0 ] = outputInterval.dimension( 0 ); // x dimensions[ 1 ] = outputInterval.dimension( 1 ); // y dimensions[ 2 ] = numChannels; // c - dimensions[ 3 ] = 1; // z - FinalInterval destIntervalPerm = new FinalInterval( dimensions ); - RandomAccessibleInterval< T > img = copyToImageStack( + dimensions[ 3 ] = 1; // z + final FinalInterval destIntervalPerm = new FinalInterval( dimensions ); + final RandomAccessibleInterval< T > img = copyToImageStack( Views.addDimension( Views.extendMirrorDouble( raiStack )), destIntervalPerm, factory, nThreads ); ip = ((ImagePlusImg)img).getImagePlus(); @@ -201,15 +221,16 @@ else if ( outputInterval.numDimensions() == 2 ) ip.getCalibration().pixelHeight = voxdim.dimension( 1 ); ip.getCalibration().pixelDepth = voxdim.dimension( 2 ); ip.getCalibration().setUnit( voxdim.unit() ); - + if( offsetTransform != null ) { ip.getCalibration().xOrigin = offsetTransform.get( 0, 3 ); ip.getCalibration().yOrigin = offsetTransform.get( 1, 3 ); ip.getCalibration().zOrigin = offsetTransform.get( 2, 3 ); } - - ip.setTitle( sources.get( movingSourceIndexList[ 0 ]).getSpimSource().getName() + nameSuffix ); + +// ip.setTitle( sources.get( movingSourceIndexList[ 0 ]).getSpimSource().getName() + nameSuffix ); + ip.setTitle( bwData.getMovingSource( 0 ).getSpimSource().getName() + nameSuffix ); return ip; } @@ -241,10 +262,10 @@ else if ( d == 3 && itvl.numDimensions() > 3 ) final ImagePlusImgFactory< T > factory = new ImagePlusImgFactory< T >( t ); final ImagePlusImg< T, ? > target = factory.create( dimensions ); - long[] dims = new long[ target.numDimensions() ]; + final long[] dims = new long[ target.numDimensions() ]; target.dimensions( dims ); - long N = Intervals.numElements(itvl); + final long N = Intervals.numElements(itvl); final Cursor< T > c = target.cursor(); final RandomAccess< T > ra = raip.randomAccess(); double k = 0; diff --git a/src/main/java/bigwarp/BigwarpSettings.java b/src/main/java/bigwarp/BigwarpSettings.java new file mode 100644 index 00000000..2bca5232 --- /dev/null +++ b/src/main/java/bigwarp/BigwarpSettings.java @@ -0,0 +1,916 @@ +package bigwarp; + +import bdv.tools.bookmarks.Bookmarks; +import bdv.tools.brightness.ConverterSetup; +import bdv.tools.brightness.MinMaxGroup; +import bdv.tools.brightness.SetupAssignments; +import bdv.viewer.BigWarpViewerPanel; +import bdv.viewer.DisplayMode; +import bdv.viewer.Interpolation; +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.SynchronizedViewerState; +import bdv.viewer.ViewerPanel; +import bdv.viewer.state.SourceGroup; +import bdv.viewer.state.SourceState; +import bdv.viewer.state.ViewerState; +import bdv.viewer.state.XmlIoViewerState; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.source.SourceInfo; +import bigwarp.transforms.BigWarpTransform; +import bigwarp.transforms.NgffTransformations; +import bigwarp.transforms.io.TransformWriterJson; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import mpicbg.spim.data.SpimDataException; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.ARGBType; +import org.scijava.listeners.Listeners; + +import static bdv.viewer.Interpolation.NEARESTNEIGHBOR; + +public class BigwarpSettings extends TypeAdapter< BigwarpSettings > +{ + + public static Gson gson = new GsonBuilder() + .registerTypeAdapter( AffineTransform3D.class, new AffineTransform3dAdapter() ) + .setPrettyPrinting() + .create(); + + transient private final BigWarp< ? > bigWarp; + + private final LandmarkTableModel landmarks; + + private final BigWarpTransform transform; + + private final Map sourceInfos; + + BigWarpViewerPanel viewerP; + + BigWarpViewerPanel viewerQ; + + SetupAssignments setupAssignments; + + Bookmarks bookmarks; + + BigWarpAutoSaver autoSaver; + + boolean overwriteSources = false; + + public BigwarpSettings( + final BigWarp bigWarp, + final BigWarpViewerPanel viewerP, + final BigWarpViewerPanel viewerQ, + final SetupAssignments setupAssignments, + final Bookmarks bookmarks, + final BigWarpAutoSaver autoSaver, + final LandmarkTableModel landmarks, + final BigWarpTransform transform, + final Map< Integer, SourceInfo > sourceInfos + ) + { + + this.bigWarp = bigWarp; + this.viewerP = viewerP; + this.viewerQ = viewerQ; + this.setupAssignments = setupAssignments; + this.bookmarks = bookmarks; + this.autoSaver = autoSaver; + this.landmarks = landmarks; + this.transform = transform; + this.sourceInfos = sourceInfos; + } + + public void setOverwriteSources( final boolean overwriteSources ) + { + this.overwriteSources = overwriteSources; + } + + public void serialize( String jsonFilename ) throws IOException + { + try ( final FileWriter fileWriter = new FileWriter( jsonFilename ) ) + { + write( new JsonWriter( fileWriter ), this ); + } + } + + @Override + public void write( final JsonWriter out, final BigwarpSettings value ) throws IOException + { + + out.beginObject(); + out.name( "Sources" ); + new BigWarpSourcesAdapter( bigWarp, overwriteSources ).write( out, sourceInfos ); + out.name( "ViewerP" ); + new BigWarpViewerPanelAdapter( viewerP ).write( out, viewerP ); + out.name( "ViewerQ" ); + new BigWarpViewerPanelAdapter( viewerQ ).write( out, viewerQ ); + out.name( "SetupAssignments" ); + new SetupAssignmentsAdapter( setupAssignments ).write( out, setupAssignments ); + out.name( "Bookmarks" ); + gson.toJson( bookmarks, Bookmarks.class, out ); + out.name( "Autosave" ); + gson.toJson( autoSaver, BigWarpAutoSaver.class, out ); + if ( landmarks != null ) + { + out.name( "Transform" ); + out.jsonValue( TransformWriterJson.write( landmarks, transform ).toString() ); + } + out.endObject(); + } + + @Override + public BigwarpSettings read( final JsonReader in ) throws IOException + { + final JsonObject json = JsonParser.parseReader(in).getAsJsonObject(); + if( json.has("Sources")) + { + new BigWarpSourcesAdapter( bigWarp, overwriteSources ).fromJsonTree(json.get("Sources")); + final boolean is2D = BigWarp.detectNumDims(bigWarp.getSources()) == 2; + if (is2D != bigWarp.options.values.is2D()) { + bigWarp.changeDimensionality( is2D ); + } + } + + // need to parse transform first + if( json.has("Transform")) + TransformWriterJson.read( bigWarp, json.get("Transform").getAsJsonObject()); + + if( json.has("ViewerP")) + new BigWarpViewerPanelAdapter( viewerP ).fromJsonTree( json.get("ViewerP") ); + + if( json.has("ViewerQ")) + new BigWarpViewerPanelAdapter( viewerQ ).fromJsonTree( json.get("ViewerQ") ); + + if( json.has("SetupAssignments")) + new SetupAssignmentsAdapter(setupAssignments).fromJsonTree(json.get("SetupAssignments")); + + if( json.has("Bookmarks")) + bigWarp.setBookmarks(gson.fromJson(json.get("Bookmarks"), Bookmarks.class)); + + if( json.has("Autosave")) + bigWarp.setAutoSaver( gson.fromJson( json.get("Autosave"), BigWarpAutoSaver.class )); + + return this; + } + + public static class BigWarpSourcesAdapter< T extends NativeType > extends TypeAdapter< Map< Integer, SourceInfo > > + { + + private BigWarp< T > bigwarp; + + private boolean overwriteExisting; + + public BigWarpSourcesAdapter( final BigWarp< T > bigwarp, boolean overwriteSources ) + { + this.bigwarp = bigwarp; + this.overwriteExisting = overwriteSources; + } + + @Override + public void write( final JsonWriter out, final Map< Integer, SourceInfo > value ) throws IOException + { + out.beginObject(); + + /* We only want the lowest setupId with the same url*/ + + for ( final Map.Entry< Integer, SourceInfo > entry : value.entrySet() ) + { + if ( !entry.getValue().isSerializable() ) + continue; + + final SourceInfo sourceInfo = entry.getValue(); + final int id = sourceInfo.getId(); + final URI uriObj; + String uri = sourceInfo.getUri(); + final String name = sourceInfo.getName(); + out.name( "" + id ); + out.beginObject(); + if ( uri == null && name != null && !name.trim().isEmpty() ) + { + uri = "imagej:///" + name; + } + if ( uri != null ) + { + out.name( "uri" ).value( uri ); + } + if ( sourceInfo.getName() != null ) + { + out.name( "name" ).value( sourceInfo.getName() ); + } + out.name( "isMoving" ).value( sourceInfo.isMoving() ); + + final String transformUri = sourceInfo.getTransformUri(); + if( transformUri != null ) + out.name( "transform" ).value( transformUri ); + + out.endObject(); + } + out.endObject(); + } + + @Override + public Map< Integer, SourceInfo > read( final JsonReader in ) throws IOException + { + in.beginObject(); + while ( in.hasNext() ) + { + //TODO Caleb: What to do if `data` alrread has a source for this `id`? + final int id = Integer.parseInt( in.nextName() ); + in.beginObject(); + String uri = null; + String transformUri = null; + String name = null; + Boolean isMoving = null; + while ( in.hasNext() ) + { + final String key = in.nextName(); + switch ( key ) + { + case "uri": + uri = in.nextString(); + break; + case "name": + name = in.nextString(); + break; + case "isMoving": + isMoving = in.nextBoolean(); + break; + case "transform": + transformUri = in.nextString(); + break; + } + } + /* Only add if we either are told to override (in which case remove previous) or don't have. */ + SourceInfo existingInfo = bigwarp.data.sourceInfos.get( id ); + int targetIdx = -1; + if ( existingInfo != null && overwriteExisting ) + { + targetIdx = bigwarp.data.remove( existingInfo ); + existingInfo = null; + } + if ( existingInfo == null && uri != null ) + { + + final LinkedHashMap< Source< T >, SourceInfo > sources; + try + { + sources = BigWarpInit.createSources( bigwarp.data, uri, id, isMoving ); + } + catch ( URISyntaxException | SpimDataException e ) + { + throw new RuntimeException( e ); + } + + RealTransform transform = null; + final String tformUri = transformUri; + if( transformUri != null ) + transform = NgffTransformations.open(transformUri); + + BigWarpInit.add( bigwarp.data, sources, transform, () -> tformUri ); + + if ( targetIdx >= 0 ) + { + /* move the source and converterSetup to the correct idx */ + final SourceAndConverter< T > sacToMove = bigwarp.data.sources.remove( bigwarp.data.sources.size() - 1 ); + bigwarp.data.sources.add( targetIdx, sacToMove ); + + final ConverterSetup setupToMove = bigwarp.data.converterSetups.remove( bigwarp.data.converterSetups.size() - 1 ); + bigwarp.data.converterSetups.add( targetIdx, setupToMove ); + } + } + in.endObject(); + } + in.endObject(); + + bigwarp.data.applyTransformations(); + bigwarp.initialize(); + + return bigwarp.data.sourceInfos; + } + } + + public static class BigWarpViewerPanelAdapter extends TypeAdapter< BigWarpViewerPanel > + { + + private final BigWarpViewerPanel panel; + + private final ViewerState state; + + public BigWarpViewerPanelAdapter( final BigWarpViewerPanel viewerP ) + { + this.panel = viewerP; + + final Field deprecatedState; + try + { + deprecatedState = ViewerPanel.class.getDeclaredField( "deprecatedState" ); + deprecatedState.setAccessible( true ); + this.state = ( ViewerState ) deprecatedState.get( panel ); + } + catch ( NoSuchFieldException | IllegalAccessException e ) + { + throw new RuntimeException( e ); + } + } + + @Override + public void write( final JsonWriter out, final BigWarpViewerPanel value ) throws IOException + { + out.beginObject(); + + out.name( XmlIoViewerState.VIEWERSTATE_SOURCES_TAG ); + writeSources( out, value ); + out.name( XmlIoViewerState.VIEWERSTATE_GROUPS_TAG ); + writeGroups( out, value ); + out.name( XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_TAG ); + writeDisplayMode( out, value ); + out.name( XmlIoViewerState.VIEWERSTATE_INTERPOLATION_TAG ); + writeInterpolationMode( out, value ); + out.name( XmlIoViewerState.VIEWERSTATE_CURRENTSOURCE_TAG ).value( value.getState().getCurrentSource() ); + out.name( XmlIoViewerState.VIEWERSTATE_CURRENTGROUP_TAG ).value( value.getState().getCurrentGroup() ); + out.name( XmlIoViewerState.VIEWERSTATE_CURRENTTIMEPOINT_TAG ).value( value.getState().getCurrentTimepoint() ); + + out.endObject(); + } + + private void writeInterpolationMode( final JsonWriter out, final BigWarpViewerPanel value ) throws IOException + { + final Interpolation interpolation = value.getState().getInterpolation(); + switch ( interpolation ) + { + case NLINEAR: + out.value( XmlIoViewerState.VIEWERSTATE_INTERPOLATION_VALUE_NLINEAR ); + break; + case NEARESTNEIGHBOR: + default: + out.value( XmlIoViewerState.VIEWERSTATE_INTERPOLATION_VALUE_NEARESTNEIGHBOR ); + } + } + + private void readInterpolationMode( final JsonReader in ) throws IOException + { + switch ( in.nextString() ) + { + case XmlIoViewerState.VIEWERSTATE_INTERPOLATION_VALUE_NLINEAR: + state.setInterpolation( Interpolation.NLINEAR ); + break; + case XmlIoViewerState.VIEWERSTATE_INTERPOLATION_VALUE_NEARESTNEIGHBOR: + default: + state.setInterpolation( NEARESTNEIGHBOR ); + } + } + + private void writeDisplayMode( final JsonWriter out, final ViewerPanel value ) throws IOException + { + switch ( value.getState().getDisplayMode() ) + { + case GROUP: + out.value( XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_VALUE_GROUP ); + break; + case FUSED: + out.value( XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_VALUE_FUSED ); + break; + case FUSEDGROUP: + out.value( XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_VALUE_FUSEDGROUP ); + break; + case SINGLE: + default: + out.value( XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_VALUE_SINGLE ); + } + } + + private void readDisplayMode( final JsonReader in ) throws IOException + { + switch ( in.nextString() ) + { + case XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_VALUE_GROUP: + state.setDisplayMode( DisplayMode.GROUP ); + break; + case XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_VALUE_FUSED: + state.setDisplayMode( DisplayMode.FUSED ); + break; + case XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_VALUE_FUSEDGROUP: + state.setDisplayMode( DisplayMode.FUSEDGROUP ); + break; + case XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_VALUE_SINGLE: + default: + state.setDisplayMode( DisplayMode.SINGLE ); + } + } + + private void writeGroups( final JsonWriter out, final ViewerPanel value ) throws IOException + { + + out.beginArray(); + final List< SourceGroup > sourceGroups = value.getState().getSourceGroups(); + for ( final SourceGroup sourceGroup : sourceGroups ) + { + out.beginObject(); + out.name( XmlIoViewerState.VIEWERSTATE_GROUP_ACTIVE_TAG ).value( sourceGroup.isActive() ); + out.name( XmlIoViewerState.VIEWERSTATE_GROUP_NAME_TAG ).value( sourceGroup.getName() ); + out.name( XmlIoViewerState.VIEWERSTATE_GROUP_SOURCEID_TAG ); + out.beginArray(); + for ( final Integer sourceId : sourceGroup.getSourceIds() ) + { + out.value( sourceId ); + } + out.endArray(); + out.endObject(); + } + out.endArray(); + } + + private void readGroups( final JsonReader in ) throws IOException + { + in.beginArray(); + final SynchronizedViewerState state = panel.state(); + state.setGroupsActive( state.getActiveGroups(), false ); + state.removeGroups( state.getGroups() ); + while ( in.hasNext() ) + { + in.beginObject(); + final bdv.viewer.SourceGroup group = new bdv.viewer.SourceGroup(); + state.addGroup( group ); + while ( in.hasNext() ) + { + + switch ( in.nextName() ) + { + case XmlIoViewerState.VIEWERSTATE_GROUP_ACTIVE_TAG: + final boolean active = in.nextBoolean(); + state.setGroupActive( group, active ); + break; + case XmlIoViewerState.VIEWERSTATE_GROUP_NAME_TAG: + final String name = in.nextString(); + state.setGroupName( group, name ); + break; + case XmlIoViewerState.VIEWERSTATE_GROUP_SOURCEID_TAG: + state.removeSourcesFromGroup( new ArrayList<>( state.getSourcesInGroup( group ) ), group ); + in.beginArray(); + while ( in.hasNext() ) + { + state.addSourceToGroup( state.getSources().get( in.nextInt() ), group ); + } + in.endArray(); + break; + } + } + in.endObject(); + } + in.endArray(); + } + + private void writeSources( final JsonWriter out, final ViewerPanel value ) throws IOException + { + + out.beginArray(); + final List< SourceState< ? > > sources = value.getState().getSources(); + for ( final SourceState< ? > source : sources ) + { + out.value( source.isActive() ); + } + out.endArray(); + } + + private void readSources( final JsonReader in ) throws IOException + { + final List< SourceState< ? > > sources = state.getSources(); + in.beginArray(); + int i = 0; + while ( in.hasNext() ) + { + final boolean isActive = in.nextBoolean(); + final SourceState< ? > source = sources.get( i++ ); + source.setActive( isActive ); + } + in.endArray(); + } + + @Override + public BigWarpViewerPanel read( final JsonReader in ) throws IOException + { + + in.beginObject(); + while ( in.hasNext() ) + { + final String nextName = in.nextName(); + switch ( nextName ) + { + case XmlIoViewerState.VIEWERSTATE_SOURCES_TAG: + readSources( in ); + break; + case XmlIoViewerState.VIEWERSTATE_GROUPS_TAG: + readGroups( in ); + break; + case XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_TAG: + readDisplayMode( in ); + break; + case XmlIoViewerState.VIEWERSTATE_INTERPOLATION_TAG: + readInterpolationMode( in ); + break; + case XmlIoViewerState.VIEWERSTATE_CURRENTSOURCE_TAG: + state.setCurrentSource( in.nextInt() ); + break; + case XmlIoViewerState.VIEWERSTATE_CURRENTGROUP_TAG: + state.setCurrentGroup( in.nextInt() ); + break; + case XmlIoViewerState.VIEWERSTATE_CURRENTTIMEPOINT_TAG: + state.setCurrentTimepoint( in.nextInt() ); + break; + } + } + in.endObject(); + return panel; + } + } + + public static class SetupAssignmentsAdapter extends TypeAdapter< SetupAssignments > + { + + private final SetupAssignments setupAssignments; + + public SetupAssignmentsAdapter( final SetupAssignments setupAssignments ) + { + this.setupAssignments = setupAssignments; + } + + @Override + public void write( final JsonWriter out, final SetupAssignments value ) throws IOException + { + out.beginObject(); + out.name( "ConverterSetups" ); + out.beginObject(); + final List< ConverterSetup > converterSetups = value.getConverterSetups(); + final ConverterSetupAdapter converterSetupAdapter = new ConverterSetupAdapter( value ); + for ( final ConverterSetup converterSetup : converterSetups ) + { + out.name(Integer.toString( converterSetup.getSetupId() )); + out.beginObject(); + converterSetupAdapter.setSetupId( converterSetup.getSetupId() ); + converterSetupAdapter.write( out, converterSetup ); + out.endObject(); + } + out.endObject(); + final List< MinMaxGroup > minMaxGroups = value.getMinMaxGroups(); + out.name( "MinMaxGroups" ); + new MinMaxGroupsAdapter().write( out, minMaxGroups ); + out.endObject(); + } + + @Override + public SetupAssignments read( final JsonReader in ) throws IOException + { + final List< ConverterSetupDTO > converters = new ArrayList<>(); + final ArrayList< MinMaxGroup > minMaxGroups; + try + { + final Field minMaxGroupsField = setupAssignments.getClass().getDeclaredField( "minMaxGroups" ); + minMaxGroupsField.setAccessible( true ); + //noinspection unchecked + minMaxGroups = ( ArrayList< MinMaxGroup > ) minMaxGroupsField.get( setupAssignments ); + } + catch ( NoSuchFieldException | IllegalAccessException e ) + { + throw new RuntimeException( e ); + } + + minMaxGroups.clear(); + + in.beginObject(); + while ( in.hasNext() ) + { + final String name = in.nextName(); + switch ( name ) + { + case "ConverterSetups": + final ConverterSetupAdapter converterSetupAdapter = new ConverterSetupAdapter( setupAssignments ); + in.beginObject(); + while ( in.hasNext() ) + { + final int id = Integer.parseInt( in.nextName() ); + converterSetupAdapter.setSetupId( id ); + in.beginObject(); + final ConverterSetupDTO dto = ( ConverterSetupDTO ) converterSetupAdapter.read( in); + converters.add( dto ); + + in.endObject(); + } + in.endObject(); + + final List< ConverterSetup > converterSetups = setupAssignments.getConverterSetups(); + final List originalSetupIdOrder = converterSetups.stream().map( ConverterSetup::getSetupId ).collect( Collectors.toList()); + for ( int idx = 0; idx < converters.size(); idx++ ) + { + final ConverterSetupDTO converterSetupDTO = converters.get( idx ); + final int setupId = converterSetupDTO.getSetupId(); + + final int idxOfConverterSetup = originalSetupIdOrder.indexOf( setupId ); + if (idxOfConverterSetup >= 0 && idx != idxOfConverterSetup) { + converters.remove( idx ); + converters.add( idxOfConverterSetup, converterSetupDTO ); + } + } + break; + case "MinMaxGroups": + minMaxGroups.addAll( new MinMaxGroupsAdapter().read( in ) ); + break; + default: + throw new RuntimeException( "Unknown SetupAssignment Key: " + name ); + } + } + in.endObject(); + + for ( final ConverterSetupDTO setupDto : converters ) + { + final ConverterSetup setup = setupAssignments.getConverterSetups().stream().filter( it -> it.getSetupId() == setupDto.getSetupId() ).findFirst().get(); + setup.setDisplayRange( setupDto.getDisplayRangeMin(), setupDto.getDisplayRangeMax() ); + setup.setColor( setupDto.getColor() ); + final MinMaxGroup group = minMaxGroups.get( setupDto.getGroupId() ); + setupAssignments.moveSetupToGroup( setup, group ); + + } + return setupAssignments; + } + } + + public static class MinMaxGroupsAdapter extends TypeAdapter< List< MinMaxGroup > > + { + + @Override + public void write( final JsonWriter out, final List< MinMaxGroup > value ) throws IOException + { + out.beginObject(); + for ( int i = 0; i < value.size(); i++ ) + { + out.name( Integer.toString( i )); + out.beginObject(); + out.name( "fullRangeMin" ).value( value.get( i ).getFullRangeMin() ); + out.name( "fullRangeMax" ).value( value.get( i ).getFullRangeMax() ); + out.name( "rangeMin" ).value( value.get( i ).getRangeMin() ); + out.name( "rangeMax" ).value( value.get( i ).getRangeMax() ); + out.name( "currentMin" ).value( value.get( i ).getMinBoundedValue().getCurrentValue() ); + out.name( "currentMax" ).value( value.get( i ).getMaxBoundedValue().getCurrentValue() ); + out.endObject(); + } + out.endObject(); + } + + @Override + public List< MinMaxGroup > read( final JsonReader in ) throws IOException + { + final HashMap< Integer, MinMaxGroup > groupMap = new HashMap<>(); + final ArrayList< MinMaxGroup > groups = new ArrayList<>(); + in.beginObject(); + while ( in.hasNext() ) + { + final int id = Integer.parseInt( in.nextName() ); + double fullRangeMin = 0; + double fullRangeMax = 0; + double rangeMin = 0; + double rangeMax = 0; + double currentMin = 0; + double currentMax = 0; + in.beginObject(); + while ( in.hasNext() ) + { + switch ( in.nextName() ) + { + case "fullRangeMin": + fullRangeMin = in.nextDouble(); + break; + case "fullRangeMax": + fullRangeMax = in.nextDouble(); + break; + case "rangeMin": + rangeMin = in.nextDouble(); + break; + case "rangeMax": + rangeMax = in.nextDouble(); + break; + case "currentMin": + currentMin = in.nextDouble(); + break; + case "currentMax": + currentMax = in.nextDouble(); + break; + } + } + + /* Note: Currently, this MinMaxGroup constructor always passes in a private static final `0` for minIntervalSize */ + final double minIntervalSize = 0; + final MinMaxGroup group = new MinMaxGroup( fullRangeMin, + fullRangeMax, + rangeMin, + rangeMax, + currentMin, + currentMax, + minIntervalSize ); + groupMap.put( id, group ); + in.endObject(); + } + in.endObject(); + for ( int i = 0; i < groupMap.size(); i++ ) + { + /* We require that the `id` of the deserialized group matches the index of the returned list. */ + groups.add( groupMap.get( i ) ); + } + return groups; + } + } + + public static class ConverterSetupAdapter extends TypeAdapter< ConverterSetup > + { + private final SetupAssignments setupAssignments; + + private int setupId = -1; + + public ConverterSetupAdapter( final SetupAssignments setupAssignments ) + { + this.setupAssignments = setupAssignments; + } + + public void setSetupId( final int setupId ) + { + this.setupId = setupId; + } + + @Override + public void write( final JsonWriter out, final ConverterSetup value ) throws IOException + { + final List< MinMaxGroup > minMaxGroups = setupAssignments.getMinMaxGroups(); + out.name( "min" ).value( value.getDisplayRangeMin() ); + out.name( "max" ).value( value.getDisplayRangeMax() ); + out.name( "color" ).value( value.getColor().get() ); + out.name( "groupId" ).value( minMaxGroups.indexOf( setupAssignments.getMinMaxGroup( value ) ) ); + } + + @Override + public ConverterSetup read( final JsonReader in ) throws IOException + { + double tmpmin = 0; + double tmpmax = 0; + int tmpcolor = 0; + int tmpgroupId = 0; + while ( in.hasNext() ) + { + switch ( in.nextName() ) + { + case "min": + tmpmin = in.nextDouble(); + break; + case "max": + tmpmax = in.nextDouble(); + break; + case "color": + tmpcolor = in.nextInt(); + break; + case "groupId": + tmpgroupId = in.nextInt(); + break; + } + } + + final double min = tmpmin; + final double max = tmpmax; + final int color = tmpcolor; + final int groupId = tmpgroupId; + + final ConverterSetup converterSetupDTO = new ConverterSetupDTO() + { + + private final int id = setupId; + + @Override + public int getGroupId() + { + return groupId; + } + + @Override + public Listeners< SetupChangeListener > setupChangeListeners() + { + return null; + } + + @Override + public int getSetupId() + { + return id; + } + + @Override + public void setDisplayRange( final double min, final double max ) + { + } + + @Override + public void setColor( final ARGBType color ) + { + } + + @Override + public boolean supportsColor() + { + return false; + } + + @Override + public double getDisplayRangeMin() + { + return min; + } + + @Override + public double getDisplayRangeMax() + { + return max; + } + + @Override + public ARGBType getColor() + { + return new ARGBType( color ); + } + }; + return converterSetupDTO; + } + } + + public static class FileAdapter extends TypeAdapter< File > + { + @Override + public void write( final JsonWriter out, final File value ) throws IOException + { + out.value( value.getCanonicalPath() ); + } + + @Override + public File read( final JsonReader in ) throws IOException + { + return new File( in.nextString() ); + } + } + + public static class AffineTransform3dAdapter extends TypeAdapter< AffineTransform3D > + { + + @Override + public void write( final JsonWriter out, final AffineTransform3D value ) throws IOException + { + assert value.numDimensions() == 3; + + final double[] values = value.getRowPackedCopy(); + out.beginArray(); + for ( final double v : values ) + { + out.value( v ); + } + out.endArray(); + } + + @Override + public AffineTransform3D read( final JsonReader in ) throws IOException + { + final AffineTransform3D affine = new AffineTransform3D(); + + final double[] values = new double[ 12 ]; + in.beginArray(); + for ( int i = 0; i < values.length; i++ ) + { + values[ i ] = in.nextDouble(); + } + in.endArray(); + + affine.set( values ); + return affine; + } + } + + private interface ConverterSetupDTO extends ConverterSetup + { + + int getGroupId(); + + } +} + diff --git a/src/main/java/bigwarp/SourceInfoDialog.java b/src/main/java/bigwarp/SourceInfoDialog.java index 3b306281..c351482c 100644 --- a/src/main/java/bigwarp/SourceInfoDialog.java +++ b/src/main/java/bigwarp/SourceInfoDialog.java @@ -38,7 +38,6 @@ import bdv.util.RandomAccessibleIntervalMipmapSource; import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; -import bigwarp.BigWarp.BigWarpData; import net.imglib2.realtransform.AffineTransform3D; public class SourceInfoDialog extends JDialog @@ -58,9 +57,9 @@ public SourceInfoDialog( final Frame owner, final BigWarpData< ? > bwData ) final StringBuffer infoString = new StringBuffer(); infoString.append( "MOVING:\n" ); - for ( int i = 0; i < bwData.movingSourceIndices.length; i++ ) + for ( int i = 0; i < bwData.numMovingSources(); i++ ) { - SourceAndConverter< ? > src = bwData.sources.get( bwData.movingSourceIndices[ i ]); + SourceAndConverter< ? > src = bwData.getMovingSource( i); final String name = src.getSpimSource().getName(); if( name.equals( "WarpMagnitudeSource" ) || name.equals( "JacobianDeterminantSource" ) || @@ -73,9 +72,9 @@ public SourceInfoDialog( final Frame owner, final BigWarpData< ? > bwData ) } infoString.append( "\nTARGET:\n" ); - for ( int i = 0; i < bwData.targetSourceIndices.length; i++ ) + for ( int i = 0; i < bwData.numTargetSources(); i++ ) { - SourceAndConverter< ? > src = bwData.sources.get( bwData.targetSourceIndices[ i ]); + SourceAndConverter< ? > src = bwData.getTargetSource( i ); final String name = src.getSpimSource().getName(); if( name.equals( "WarpMagnitudeSource" ) || name.equals( "JacobianDeterminantSource" ) || diff --git a/src/main/java/bigwarp/WarpVisFrame.java b/src/main/java/bigwarp/WarpVisFrame.java index e63df4bd..31f22df6 100644 --- a/src/main/java/bigwarp/WarpVisFrame.java +++ b/src/main/java/bigwarp/WarpVisFrame.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -34,9 +34,6 @@ import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.io.File; import java.util.Enumeration; import javax.swing.AbstractButton; @@ -51,48 +48,51 @@ import javax.swing.JColorChooser; import javax.swing.JComboBox; import javax.swing.JDialog; -import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JSlider; import javax.swing.JSpinner; -import javax.swing.JTextField; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; import javax.swing.WindowConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import bdv.gui.AutosaveOptionsPanel; +import bdv.gui.MaskOptionsPanel; +import bdv.gui.TransformTypePanel; import bdv.viewer.BigWarpViewerSettings; import bigwarp.source.GridSource; import net.imglib2.realtransform.BoundingBoxEstimation; +import net.miginfocom.swing.MigLayout; -public class WarpVisFrame extends JDialog +public class WarpVisFrame extends JDialog { private static final long serialVersionUID = 7561228647761694686L; private final BigWarp bw; private final BigWarpViewerSettings settings; - + protected ButtonGroup visTypeGroup; protected JRadioButton setWarpVisOffButton; protected JRadioButton setWarpGridButton; protected JRadioButton setWarpMagButton; - + protected JRadioButton setJacobianDetButton; + protected JLabel noOptionsLabel; - + // landmark point options protected final JButton landmarkColorButton; private final JColorChooser colorChooser; protected final JSlider landmarkSizeSlider; - + // warp magnitude protected ButtonGroup warpMagButtons; protected JRadioButton warpMagAffineButton; protected JRadioButton warpMagSimilarityButton; protected JRadioButton warpMagRigidButton; - + // grid spacing protected ButtonGroup warpGridButtons; protected JRadioButton warpGridLineButton; @@ -112,41 +112,45 @@ public class WarpVisFrame extends JDialog final JComboBox bboxMethodDropdown; final JSpinner samplesPerDimSpinner; + // mask + final MaskOptionsPanel maskOptionsPanel; + + // transform type + final TransformTypePanel transformTypePanel; + // autosave - final SpinnerNumberModel savePeriodModel; - final JSpinner autoSavePeriodSpinner; - final JCheckBox doAutoSaveBox; - final JTextField autoSaveFolderText; + private final AutosaveOptionsPanel autoSaveOptionsPanel; + public static final int minGridSpacing = 5; public static final int maxGridSpacing = 400; public static final int defaultGridSpacing = 100; - + public static final int minGridWidth = 1; public static final int maxGridWidth = 50; public static final int defaultGridWidth = 5; - + public WarpVisFrame( final Frame owner, final BigWarp bw ) { - super( owner, "big warp options", false ); + super( owner, "Bigwarp options", false ); this.bw = bw; this.settings = bw.viewerSettings; - + final Container content = getContentPane(); - + setSize( 500, 400 ); - - JPanel landmarkPointOptionsPanel = new JPanel(); + + final JPanel landmarkPointOptionsPanel = new JPanel(); landmarkPointOptionsPanel.setLayout( new BoxLayout( landmarkPointOptionsPanel, BoxLayout.X_AXIS )); landmarkColorButton = new JButton( new ColorIcon( settings.getSpotColor() ) ); colorChooser = new JColorChooser(); - + landmarkSizeSlider = new JSlider(); landmarkSizeSlider.setValue( (int)settings.getSpotSize() ); landmarkSizeSlider.setMinimum( 1 ); landmarkSizeSlider.setMaximum( 96 ); - + landmarkPointOptionsPanel.add( landmarkColorButton ); landmarkPointOptionsPanel.add( landmarkSizeSlider ); landmarkPointOptionsPanel.setBorder( BorderFactory.createCompoundBorder( @@ -156,9 +160,9 @@ public WarpVisFrame( final Frame owner, final BigWarp bw ) BorderFactory.createEtchedBorder(), "landmark size & color" ), BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); - - // - JPanel visTypePanel = new JPanel(); + + // + final JPanel visTypePanel = new JPanel(); visTypePanel.setLayout( new BoxLayout( visTypePanel, BoxLayout.Y_AXIS) ); visTypePanel.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), @@ -168,8 +172,8 @@ public WarpVisFrame( final Frame owner, final BigWarp bw ) "warp display" ), BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); - - JPanel typeOptionPanel = new JPanel(); + + final JPanel typeOptionPanel = new JPanel(); typeOptionPanel.setLayout( new BoxLayout( typeOptionPanel, BoxLayout.Y_AXIS) ); typeOptionPanel.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), @@ -178,25 +182,28 @@ public WarpVisFrame( final Frame owner, final BigWarp bw ) BorderFactory.createEtchedBorder(), "options" ), BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); - + // label indicating that there are no options to be had noOptionsLabel = new JLabel( "None" ); - + // buttons choosing if and how the warp should be visualized visTypeGroup = new ButtonGroup(); setWarpVisOffButton = new JRadioButton( "Off" ); setWarpGridButton = new JRadioButton( "Grid" ); setWarpMagButton = new JRadioButton( "Magnitude" ); - + setJacobianDetButton = new JRadioButton( "Jacobian Determinant" ); + visTypeGroup.add( setWarpVisOffButton ); visTypeGroup.add( setWarpGridButton ); visTypeGroup.add( setWarpMagButton ); - + visTypeGroup.add( setJacobianDetButton ); + setWarpVisOffButton.setSelected( true ); + visTypePanel.add( setWarpVisOffButton ); visTypePanel.add( setWarpGridButton ); visTypePanel.add( setWarpMagButton ); - - + visTypePanel.add( setJacobianDetButton ); + // buttons for warp magnitude options warpMagAffineButton = new JRadioButton( "Affine baseline" ); warpMagSimilarityButton = new JRadioButton("Similarity baseline"); @@ -206,15 +213,16 @@ public WarpVisFrame( final Frame owner, final BigWarp bw ) warpMagButtons.add( warpMagAffineButton ); warpMagButtons.add( warpMagSimilarityButton ); warpMagButtons.add( warpMagRigidButton ); - - // buttons for warp grid options + + // buttons for warp grid options warpGridLineButton = new JRadioButton( "Line grid " ); warpGridModButton = new JRadioButton( "Modulo grid" ); - + warpGridButtons = new ButtonGroup(); warpGridButtons.add( warpGridLineButton ); warpGridButtons.add( warpGridModButton ); - + warpGridLineButton.setSelected( true ); + gridSpacingSlider = new JSlider( JSlider.HORIZONTAL, minGridSpacing, maxGridSpacing, defaultGridSpacing ); gridWidthSlider = new JSlider( JSlider.HORIZONTAL, minGridWidth, maxGridWidth, defaultGridWidth ); // label the sliders @@ -228,7 +236,7 @@ public WarpVisFrame( final Frame owner, final BigWarp bw ) typeOptionPanel.add( warpMagAffineButton ); typeOptionPanel.add( warpMagSimilarityButton ); typeOptionPanel.add( warpMagRigidButton ); - + typeOptionPanel.add( warpGridLineButton ); typeOptionPanel.add( warpGridModButton ); typeOptionPanel.add( bigSpace ); @@ -239,128 +247,6 @@ public WarpVisFrame( final Frame owner, final BigWarp bw ) typeOptionPanel.add( gridWidthSlider ); typeOptionPanel.add( noOptionsLabel ); - // autoSave Options panel - final JPanel autoSaveOptionsPanel = new JPanel(); - autoSaveOptionsPanel.setBorder( BorderFactory.createCompoundBorder( - BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), - BorderFactory.createCompoundBorder( - BorderFactory.createTitledBorder( - BorderFactory.createEtchedBorder(), - "Auto-save options" ), - BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); - autoSaveOptionsPanel.setLayout( new GridBagLayout() ); - - final JLabel autosaveLabel = new JLabel("Auto-save landmarks"); - doAutoSaveBox = new JCheckBox(); - - final JLabel autoSavePeriodLabel = new JLabel("save frequency (minutes)"); - autoSavePeriodSpinner = new JSpinner(); - savePeriodModel = new SpinnerNumberModel( 5, 1, 5000, 1 ); - autoSavePeriodSpinner.setModel( savePeriodModel ); - autoSavePeriodSpinner.addChangeListener( new ChangeListener() - { - @Override - public void stateChanged( ChangeEvent e ) - { - if( doAutoSaveBox.isSelected() ) - { - long periodMillis = ( ( Integer ) savePeriodModel.getValue() ).longValue() * 60000; - BigWarpAutoSaver autoSaver = bw.getAutoSaver(); - if ( autoSaver != null ) - autoSaver.stop(); - - new BigWarpAutoSaver( bw, periodMillis ); - } - } - } ); - - doAutoSaveBox.addItemListener( new ItemListener() - { - @Override - public void itemStateChanged( ItemEvent e ) - { - bw.stopAutosave(); - - if ( doAutoSaveBox.isSelected() ) - { - long periodMillis = ( ( Integer ) savePeriodModel.getValue() ).longValue() * 60000; - new BigWarpAutoSaver( bw, periodMillis ); - } - } - }); - - final JLabel destDirLabel = new JLabel("Directory"); - final File startingFolder = bw.getBigwarpSettingsFolder(); - autoSaveFolderText = new JTextField(); - autoSaveFolderText.setText( startingFolder.getAbsolutePath() ); - - final JButton browseBtn = new JButton( "Browse" ); - browseBtn.addActionListener( e -> { - - final JFileChooser fileChooser = new JFileChooser(); - fileChooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); - fileChooser.setCurrentDirectory( startingFolder ); - - final int ret = fileChooser.showOpenDialog(content); - if (ret == JFileChooser.APPROVE_OPTION) - { - final File folder = fileChooser.getSelectedFile(); - autoSaveFolderText.setText( folder.getAbsolutePath() ); - bw.setAutosaveFolder( folder ); - } - }); - - final GridBagConstraints gbcAutoSave = new GridBagConstraints(); - gbcAutoSave.gridx = 0; - gbcAutoSave.gridy = 0; - gbcAutoSave.gridwidth = 2; - gbcAutoSave.gridheight = 1; - gbcAutoSave.weightx = 1.0; - gbcAutoSave.weighty = 0.0; - gbcAutoSave.anchor = GridBagConstraints.EAST; - gbcAutoSave.fill = GridBagConstraints.HORIZONTAL; - gbcAutoSave.insets = new Insets( 5, 5, 5, 5 ); - - autoSaveOptionsPanel.add( autosaveLabel, gbcAutoSave ); - - gbcAutoSave.gridx = 2; - gbcAutoSave.gridwidth = 1; - gbcAutoSave.weightx = 0.0; - gbcAutoSave.anchor = GridBagConstraints.WEST; - autoSaveOptionsPanel.add( doAutoSaveBox, gbcAutoSave ); - - gbcAutoSave.weightx = 1.0; - gbcAutoSave.gridx = 0; - gbcAutoSave.gridwidth = 2; - gbcAutoSave.gridy = 1; - gbcAutoSave.anchor = GridBagConstraints.EAST; - autoSaveOptionsPanel.add( autoSavePeriodLabel, gbcAutoSave ); - - gbcAutoSave.weightx = 0.0; - gbcAutoSave.gridx = 2; - gbcAutoSave.anchor = GridBagConstraints.WEST; - autoSaveOptionsPanel.add( autoSavePeriodSpinner, gbcAutoSave ); - - gbcAutoSave.gridy = 2; - gbcAutoSave.gridx = 0; - gbcAutoSave.gridwidth = 1; - gbcAutoSave.weightx = 0.0; - gbcAutoSave.anchor = GridBagConstraints.EAST; - autoSaveOptionsPanel.add( destDirLabel, gbcAutoSave ); - - gbcAutoSave.gridy = 2; - gbcAutoSave.gridx = 1; - gbcAutoSave.weightx = 1.0; - gbcAutoSave.anchor = GridBagConstraints.EAST; - autoSaveOptionsPanel.add( autoSaveFolderText, gbcAutoSave ); - - gbcAutoSave.gridx = 2; - gbcAutoSave.weightx = 0.0; - gbcAutoSave.anchor = GridBagConstraints.WEST; - autoSaveOptionsPanel.add( browseBtn, gbcAutoSave ); - - - final JPanel inverseOptionsPanel = new JPanel(); inverseOptionsPanel.setLayout( new BorderLayout( 10, 10 )); @@ -403,7 +289,7 @@ public void stateChanged( ChangeEvent e ) } ); maxIterPanel.add( new JLabel( "Max iterations", SwingConstants.CENTER ), BorderLayout.WEST ); maxIterPanel.add( maxIterSpinner, BorderLayout.EAST ); - + inverseOptionsPanel.add( tolerancePanel, BorderLayout.NORTH ); inverseOptionsPanel.add( maxIterPanel, BorderLayout.SOUTH ); @@ -431,7 +317,7 @@ public void stateChanged( ChangeEvent e ) bboxMethodDropdown.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - String methodString = (String)(bboxMethodDropdown.getSelectedItem()); + final String methodString = (String)(bboxMethodDropdown.getSelectedItem()); bw.getBoxEstimation().setMethod( methodString ); bw.updateSourceBoundingBoxEstimators(); } @@ -459,6 +345,17 @@ public void stateChanged( ChangeEvent e ) bboxPanel.add( samplerPerDimPanel, BorderLayout.SOUTH ); + // mask options + maskOptionsPanel = new MaskOptionsPanel( bw ); + maskOptionsPanel.addActions(); + + // type options + transformTypePanel = new TransformTypePanel( bw ); + + // autosaver options + autoSaveOptionsPanel = new AutosaveOptionsPanel( bw, content ); + + // organize panels content.setLayout( new GridBagLayout() ); final GridBagConstraints gbcContent = new GridBagConstraints(); gbcContent.gridx = 0; @@ -469,35 +366,49 @@ public void stateChanged( ChangeEvent e ) gbcContent.weightx = 1.0; gbcContent.weighty = 1.0; gbcContent.insets = new Insets( 1, 1, 1, 1 ); - content.add( landmarkPointOptionsPanel, gbcContent ); + + content.add( transformTypePanel, gbcContent ); gbcContent.gridx = 0; gbcContent.gridy = 1; + content.add( landmarkPointOptionsPanel, gbcContent ); + + gbcContent.gridx = 0; + gbcContent.gridy = 2; gbcContent.gridwidth = 1; gbcContent.anchor = GridBagConstraints.WEST; content.add( visTypePanel, gbcContent ); - gbcContent.gridy = 1; gbcContent.gridx = 1; gbcContent.gridwidth = 2; gbcContent.anchor = GridBagConstraints.EAST; content.add( typeOptionPanel, gbcContent ); gbcContent.gridx = 0; - gbcContent.gridy = 2; + gbcContent.gridy = 3; gbcContent.gridwidth = 3; content.add( inverseOptionsPanel, gbcContent ); gbcContent.gridx = 0; - gbcContent.gridy = 3; + gbcContent.gridy = 4; gbcContent.gridwidth = 3; content.add( bboxPanel, gbcContent ); - gbcContent.gridy = 4; - content.add( autoSaveOptionsPanel , gbcContent ); - + gbcContent.gridy = 5; + content.add( maskOptionsPanel, gbcContent ); + + gbcContent.gridy = 6; + final JPanel toggle2DPanel = new JPanel(new MigLayout("", "[grow][][grow]")); + final JCheckBox toggle2D = new JCheckBox("Is 2D"); + toggle2DPanel.add(toggle2D, "cell 1 0"); + toggle2D.addActionListener(e -> bw.changeDimensionality(toggle2D.isSelected()) ); + content.add(toggle2DPanel, gbcContent); + + gbcContent.gridy = 7; + content.add( getAutoSaveOptionsPanel(), gbcContent ); + setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE ); - + addListeners(); updateOptions(); } @@ -528,87 +439,90 @@ public void actionPerformed( final ActionEvent arg0 ) d.setVisible( true ); } } ); - + landmarkSizeSlider.addChangeListener( new ChangeListener() { @Override public void stateChanged( ChangeEvent e ) { if( e.getSource() != landmarkSizeSlider ) return; - + settings.setSpotSize( landmarkSizeSlider.getValue() ); bw.viewerP.requestRepaint(); bw.viewerQ.requestRepaint(); } }); - - setWarpVisOffButton.setAction( + + setWarpVisOffButton.setAction( actionMap.get( String.format( BigWarpActions.SET_WARPTYPE_VIS, BigWarp.WarpVisType.NONE ))); - setWarpGridButton.setAction( + setWarpGridButton.setAction( actionMap.get( String.format( BigWarpActions.SET_WARPTYPE_VIS, BigWarp.WarpVisType.GRID ))); - setWarpMagButton.setAction( + setWarpMagButton.setAction( actionMap.get( String.format( BigWarpActions.SET_WARPTYPE_VIS, BigWarp.WarpVisType.WARPMAG ))); - + setJacobianDetButton.setAction( + actionMap.get( String.format( BigWarpActions.SET_WARPTYPE_VIS, BigWarp.WarpVisType.JACDET ))); + setWarpVisOffButton.setText("Off"); setWarpGridButton.setText("Grid"); setWarpMagButton.setText("Magnitude"); - - warpMagAffineButton.setAction( + setJacobianDetButton.setText("Jacobian Determinant"); + + warpMagAffineButton.setAction( actionMap.get( String.format( BigWarpActions.WARPMAG_BASE, bw.baseXfmList[ 0 ].getClass().getName() ))); - warpMagSimilarityButton .setAction( + warpMagSimilarityButton .setAction( actionMap.get( String.format( BigWarpActions.WARPMAG_BASE, bw.baseXfmList[ 1 ].getClass().getName() ) )); - warpMagRigidButton.setAction( + warpMagRigidButton.setAction( actionMap.get( String.format( BigWarpActions.WARPMAG_BASE, bw.baseXfmList[ 2 ].getClass().getName() ) )); - + warpMagAffineButton.setText("Affine"); warpMagSimilarityButton.setText("Similarity"); warpMagRigidButton.setText("Rigid"); - - warpGridLineButton.setAction( + + warpGridLineButton.setAction( actionMap.get( String.format( BigWarpActions.WARPVISGRID, GridSource.GRID_TYPE.LINE ))); - warpGridModButton.setAction( + warpGridModButton.setAction( actionMap.get( String.format( BigWarpActions.WARPVISGRID, GridSource.GRID_TYPE.MOD ))); - + warpGridLineButton.setText( "Line" ); warpGridModButton.setText( "Modulo" ); - + // turn on the default values - setWarpVisOffButton.doClick(); - warpMagAffineButton.doClick(); - warpGridLineButton.doClick(); +// setWarpVisOffButton.doClick(); +// warpMagAffineButton.doClick(); +// warpGridLineButton.doClick(); } - + public void addListeners() { - MyChangeListener mylistener = new MyChangeListener(); - setWarpVisOffButton.addChangeListener( mylistener ); - setWarpGridButton.addChangeListener( mylistener ); - setWarpMagButton.addChangeListener( mylistener ); - + final VisOptUiListener visOptListener = new VisOptUiListener(); + setWarpVisOffButton.addChangeListener( visOptListener ); + setWarpGridButton.addChangeListener( visOptListener ); + setWarpMagButton.addChangeListener( visOptListener ); + gridSpacingSlider.addChangeListener( new ChangeListener() { @Override public void stateChanged( ChangeEvent e ) { if( e.getSource() != gridSpacingSlider ) return; - + WarpVisFrame.this.bw.setWarpGridSpacing( gridSpacingSlider.getValue() ); } }); - + gridWidthSlider.addChangeListener( new ChangeListener() { @Override public void stateChanged( ChangeEvent e ) { if( e.getSource() != gridWidthSlider ) return; - + WarpVisFrame.this.bw.setWarpGridWidth( gridWidthSlider.getValue() ); } }); } - - public class MyChangeListener implements ChangeListener + + private class VisOptUiListener implements ChangeListener { @Override public void stateChanged( ChangeEvent e ) @@ -616,13 +530,13 @@ public void stateChanged( ChangeEvent e ) WarpVisFrame.this.updateOptions(); } } - + private void setGridOptionsVisibility( boolean isVisible ) { // disable all options - Enumeration< AbstractButton > elems = warpGridButtons.getElements(); + final Enumeration< AbstractButton > elems = warpGridButtons.getElements(); while( elems.hasMoreElements()) - elems.nextElement().setVisible( isVisible ); + elems.nextElement().setVisible( isVisible ); gridSpacingSlider.setVisible( isVisible ); gridWidthSlider.setVisible( isVisible ); @@ -630,15 +544,15 @@ private void setGridOptionsVisibility( boolean isVisible ) gridSpacingLabel.setVisible( isVisible ); gridWidthLabel.setVisible( isVisible ); } - + private void setMagOptionsVisibility( boolean isVisible ) { // disable all options - Enumeration< AbstractButton > elems = warpMagButtons.getElements(); + final Enumeration< AbstractButton > elems = warpMagButtons.getElements(); while( elems.hasMoreElements()) - elems.nextElement().setVisible( isVisible ); + elems.nextElement().setVisible( isVisible ); } - + public synchronized void updateOptions() { if( setWarpVisOffButton.isSelected() ) @@ -665,7 +579,7 @@ else if( setWarpMagButton.isSelected() ) } pack(); } - + private static class ColorIcon implements Icon { private final int size = 16; @@ -698,4 +612,14 @@ public int getIconHeight() return size; } } + + public boolean autoEstimateMask() + { + return maskOptionsPanel.getAutoEstimateMaskButton().isSelected(); + } + + public AutosaveOptionsPanel getAutoSaveOptionsPanel() + { + return autoSaveOptionsPanel; + } } diff --git a/src/main/java/bigwarp/landmarks/LandmarkGridGenerator.java b/src/main/java/bigwarp/landmarks/LandmarkGridGenerator.java index e6e46645..f008ea20 100644 --- a/src/main/java/bigwarp/landmarks/LandmarkGridGenerator.java +++ b/src/main/java/bigwarp/landmarks/LandmarkGridGenerator.java @@ -84,8 +84,6 @@ public int fill( LandmarkTableModel ltm ) it.fwd(); it.localize( p ); - //System.out.println( "Adding: " + Arrays.toString( p )); - // ltm makes a copy, so can re-use p ltm.add( p, true ); ltm.setPoint( ltm.getNextRow( false ), false, p, null ); @@ -164,7 +162,6 @@ public static boolean fillFromDialog( final BigWarp bw ) } double N = gen.approxNumberOfPoints(); - System.out.println("N : " + N ); if( N > 1 ) { final GenericDialog warningDialog = new GenericDialog( "Warning" ); diff --git a/src/main/java/bigwarp/landmarks/LandmarkTableModel.java b/src/main/java/bigwarp/landmarks/LandmarkTableModel.java index 78a947de..37540aa1 100644 --- a/src/main/java/bigwarp/landmarks/LandmarkTableModel.java +++ b/src/main/java/bigwarp/landmarks/LandmarkTableModel.java @@ -8,19 +8,19 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . * #L% */ /** - * + * */ package bigwarp.landmarks; @@ -30,6 +30,15 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -52,9 +61,16 @@ import net.imglib2.RealLocalizable; import net.imglib2.realtransform.InvertibleRealTransform; import net.imglib2.realtransform.RealTransform; -import net.imglib2.realtransform.Wrapped2DTransformAs3D; +import net.imglib2.realtransform.InvertibleWrapped2DTransformAs3D; import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.reflect.TypeToken; import com.opencsv.CSVReader; import com.opencsv.CSVWriter; import com.opencsv.exceptions.CsvException; @@ -68,7 +84,7 @@ public class LandmarkTableModel extends AbstractTableModel implements TransformListener< InvertibleRealTransform >{ private static final long serialVersionUID = -5865789085410559166L; - + private final double[] PENDING_PT; public static final int NAMECOLUMN = 0; @@ -76,14 +92,14 @@ public class LandmarkTableModel extends AbstractTableModel implements TransformL public static Color WARNINGBGCOLOR = new Color( 255, 204, 0 ); public static Color DEFAULTBGCOLOR = new Color( 255, 255, 255 ); - + private boolean DEBUG = false; - + protected int ndims = 3; - + protected int numCols = 8; protected int numRows = 0; - + protected int nextRowP = 0; protected int nextRowQ = 0; @@ -95,26 +111,26 @@ public class LandmarkTableModel extends AbstractTableModel implements TransformL protected ArrayList targetPts; // this list contains as many elemnts as the table, and - // contains a unique integer >= 0 if the row is active, or -1 otherwise + // contains a unique integer >= 0 if the row is active, or -1 otherwise protected ArrayList tableIndexToActiveIndex; protected boolean pointUpdatePending = false; // protected boolean pointUpdatePendingMoving = false; // protected Double[] pointToOverride; // hold a backup of a point for fallback - + // keeps track of whether points have been updated protected ArrayList doesPointHaveAndNeedWarp; protected ArrayList indicesOfChangedPoints; protected boolean elementDeleted = false; protected ArrayList needsInverse; - + // true for a row if, after the transform is updated, // the warped point has a higher error than the specified tolerance protected ArrayList movingDisplayPointUnreliable; - // the transformation + // the transformation protected ThinPlateR2LogRSplineKernelTransform estimatedXfm; - + // keeps track of warped points so we don't always have to do it on the fly protected ArrayList warpedPoints; @@ -128,13 +144,13 @@ public class LandmarkTableModel extends AbstractTableModel implements TransformL // keep track of the value of the last point that was edited but not-undoable // this lets us both render points correctly, and create desirable undo behavior // for point drags. - protected double[] lastPoint; + protected double[] lastPoint; protected double[] tmp; // keep track of edits for undo's and redo's protected LandmarkUndoManager undoRedoManager; - + protected BigWarpMessageAnimator message; protected boolean modifiedSinceLastSave; @@ -145,16 +161,16 @@ public class LandmarkTableModel extends AbstractTableModel implements TransformL "mvg-x","mvg-y","mvg-z", "fix-x","fix-y","fix-z" }; - + final static String[] columnNames2d = new String[] { "Name", "Active", "mvg-x","mvg-y", "fix-x","fix-y" }; - + final String[] columnNames; - + public final Logger logger = LoggerFactory.getLogger( LandmarkTableModel.class ); public LandmarkTableModel( int ndims ) @@ -164,32 +180,32 @@ public LandmarkTableModel( int ndims ) PENDING_PT = new double[ ndims ]; Arrays.fill( PENDING_PT, Double.POSITIVE_INFINITY ); lastPoint = PENDING_PT; - + names = new ArrayList<>(); activeList = new ArrayList<>(); tableIndexToActiveIndex = new ArrayList<>(); - + movingPts = new ArrayList(); targetPts = new ArrayList(); pointToOverride = new Double[ ndims ]; Arrays.fill( pointToOverride, Double.POSITIVE_INFINITY ); - + if( ndims == 2 ){ columnNames = columnNames2d; numCols = 6; }else{ columnNames = columnNames3d; } - + warpedPoints = new ArrayList(); doesPointHaveAndNeedWarp = new ArrayList(); movingDisplayPointUnreliable = new ArrayList(); indicesOfChangedPoints = new ArrayList(); needsInverse = new ArrayList(); - + setTableListener(); - + undoRedoManager = new LandmarkUndoManager(); estimatedXfm = new ThinPlateR2LogRSplineKernelTransform ( ndims ); @@ -206,18 +222,18 @@ public int getNumdims() { return ndims; } - + public double[] getPendingPoint() { return PENDING_PT; } - + @Deprecated public ThinPlateR2LogRSplineKernelTransform getTransform() { return estimatedXfm; } - + @Deprecated public void printDistances() { @@ -231,13 +247,14 @@ public void printDistances() } } } - + public void printState() { System.out.println("nextRowP: " + nextRowP ); System.out.println("nextRowQ: " + nextRowQ ); } + @Override public String toString() { String str = ""; @@ -263,10 +280,10 @@ public boolean validateTransformPoints() } } } - + return true; } - + public void setTableListener() { addTableModelListener( new TableModelListener() @@ -274,9 +291,9 @@ public void setTableListener() @Override public void tableChanged( TableModelEvent e ) { - if( estimatedXfm != null && e.getColumn() == 1 && e.getType() == TableModelEvent.UPDATE ) // if its active + if( estimatedXfm != null && e.getColumn() == 1 && e.getType() == TableModelEvent.UPDATE ) // if its active { - int row = e.getFirstRow(); + final int row = e.getFirstRow(); indicesOfChangedPoints.add( row ); } } @@ -290,13 +307,13 @@ public boolean isModifiedSinceSave() protected void importTransformation( File ffwd, File finv ) throws IOException { - byte[] data = FileUtils.readFileToByteArray( ffwd ); + final byte[] data = FileUtils.readFileToByteArray( ffwd ); estimatedXfm = (ThinPlateR2LogRSplineKernelTransform) SerializationUtils.deserialize( data ); } - + protected void exportTransformation( File ffwd, File finv ) throws IOException { - byte[] data = SerializationUtils.serialize( estimatedXfm ); + final byte[] data = SerializationUtils.serialize( estimatedXfm ); FileUtils.writeByteArrayToFile( ffwd, data ); } @@ -304,16 +321,16 @@ public boolean isPointUpdatePending() { return pointUpdatePending; } - + public boolean isPointUpdatePendingMoving() { return pointUpdatePendingMoving; } - + public void restorePendingUpdate( ) { ArrayList< Double[] > pts; - + int i = 0; if( pointUpdatePendingMoving ) { @@ -325,17 +342,17 @@ public void restorePendingUpdate( ) i = nextRowQ; pts = movingPts; } - + for( int d = 0; d < ndims; d++ ) pts.get( i )[ d ] = pointToOverride[ d ]; - + activeList.set( i, true ); buildTableToActiveIndex(); pointUpdatePending = false; - + fireTableRowsUpdated( i, i ); } - + @Override public int getColumnCount() { @@ -355,30 +372,30 @@ public int getActiveRowCount() // //TODO consider keeping track of this actively instead of recomputing // int N = 0; // for( Boolean b : activeList ) -// if( b ) +// if( b ) // N++; // // return N; } - @Override + @Override public String getColumnName( int col ){ return columnNames[col]; } - + public ArrayList getPoints( boolean moving ) { if( moving ) return movingPts; - else + else return targetPts; } - public ArrayList getNames() + public ArrayList getNames() { return names; } - + public void setColumnName( int row, String name ) { names.set( row, name ); @@ -436,6 +453,7 @@ public int getActiveIndex( int tableIndex ) return tableIndexToActiveIndex.get( tableIndex ); } + @Override public Class getColumnClass( int col ){ if( col == NAMECOLUMN ){ return String.class; @@ -445,7 +463,7 @@ public Class getColumnClass( int col ){ return Double.class; } } - + public boolean isActive( int i ){ if( i < 0 || i >= getRowCount() ){ return false; @@ -504,25 +522,25 @@ public void deleteRowHelper( int i ) } /** - * Returns true if the ith row is unpaired - i.e., + * Returns true if the ith row is unpaired - i.e., * if either of the moving or target points are unset. - * + * * @param i row index - * @return true of the + * @return true of the */ public boolean isRowUnpaired( final int i ) { for( int d = 0; d < ndims; d++ ) - if( Double.isInfinite( movingPts.get( i )[ d ] ) || + if( Double.isInfinite( movingPts.get( i )[ d ] ) || Double.isInfinite( targetPts.get( i )[ d ] )) return true; return false; } - + /** * Returns true if any row is unpaired. - * + * * @return is update pending */ public boolean isUpdatePending() @@ -530,7 +548,7 @@ public boolean isUpdatePending() for( int i = 0; i < movingPts.size(); i++ ) if( isRowUnpaired( i )) return true; - + return false; } @@ -586,10 +604,10 @@ public void updateNextRows( int lastAddedIndex ) // nextRowQ = ( nextRowQ == numRows ) ? -1 : numRows; } } - + /** * Returns the next row to be updated for the moving or target columns. - * + * * @param isMoving isMoving * @return index of the next row */ @@ -621,11 +639,11 @@ public void updateWarpedPoint( int i, double[] pt ) doesPointHaveAndNeedWarp.set( i, true ); } } - + public void printWarpedPoints() { String s = ""; - int N = doesPointHaveAndNeedWarp.size(); + final int N = doesPointHaveAndNeedWarp.size(); for( int i = 0; i < N; i++ ) { if( doesPointHaveAndNeedWarp.get( i )) @@ -634,7 +652,7 @@ public void printWarpedPoints() s += String.format("%04d : ", i); for ( int d = 0; d < ndims; d++ ) s += String.format("%f\t", warpedPoints.get( i )[ d ] ); - + s+="\n"; } } @@ -645,7 +663,7 @@ public ArrayList< Double[] > getWarpedPoints() { return warpedPoints; } - + public ArrayList getChangedSinceWarp() { return doesPointHaveAndNeedWarp; @@ -666,7 +684,7 @@ public void resetWarpedPoints() public void resetNeedsInverse(){ Collections.fill( needsInverse, false ); } - + public void setNeedsInverse( int i ) { needsInverse.set( i, true ); @@ -676,7 +694,7 @@ public boolean rowNeedsWarning( int row ) { return movingDisplayPointUnreliable.get( row ); } - + protected void firePointUpdated( int row, boolean isMoving ) { modifiedSinceLastSave = true; @@ -687,49 +705,55 @@ protected void firePointUpdated( int row, boolean isMoving ) private void addEmptyRow( int index ) { synchronized(this) { - Double[] movingPt = new Double[ ndims ]; - Double[] targetPt = new Double[ ndims ]; + final Double[] movingPt = new Double[ ndims ]; + final Double[] targetPt = new Double[ ndims ]; Arrays.fill( targetPt, Double.POSITIVE_INFINITY ); Arrays.fill( movingPt, Double.POSITIVE_INFINITY ); movingPts.add( index, movingPt ); targetPts.add( index, targetPt ); - + names.add( index, nextName( index )); activeList.add( index, false ); warpedPoints.add( index, new Double[ ndims ] ); doesPointHaveAndNeedWarp.add( index, false ); movingDisplayPointUnreliable.add( index, false ); tableIndexToActiveIndex.add( -1 ); - + numRows++; modifiedSinceLastSave = true; } fireTableRowsInserted( index, index ); } - + public void clearPt( int row, boolean isMoving ) { pointEdit( row, PENDING_PT, false, isMoving, null, true ); } - + public boolean add( double[] pt, boolean isMoving ) { return pointEdit( -1, pt, true, isMoving, false, true, null ); } + public boolean add( final double[] mvg, final double[] tgt ) { + add( mvg, true); + setPoint( numRows - 1, false, tgt, null ); + return true; + } + public boolean add( double[] pt, boolean isMoving, final RealTransform xfm ) { return pointEdit( -1, pt, true, isMoving, false, true, xfm ); } - + public void setPoint( int row, boolean isMoving, double[] pt, final RealTransform xfm ) { setPoint( row, isMoving, pt, true, xfm ); } - + public void setPoint( int row, boolean isMoving, double[] pt, boolean isUndoable, final RealTransform xfm ) { pointEdit( row, pt, false, isMoving, false, isUndoable, xfm ); @@ -750,7 +774,7 @@ public boolean pointEdit( final int index, final double[] pt, final boolean forc /** * Changes a point's position, or adds a new point. *

- * + * * @param index The index into this table that this edit will affect ( a value of -1 will add a new point ) * @param pt the point position * @param forceAdd force addition of a new point at the specified index @@ -764,7 +788,7 @@ public boolean pointEdit( int index, double[] pt, boolean forceAdd, boolean isMo boolean isAdd; synchronized ( this ) { - // this means we should add a new point. + // this means we should add a new point. // index of this point should be the next free row in the table if( index == -1 ) { @@ -794,7 +818,7 @@ public boolean pointEdit( int index, double[] pt, boolean forceAdd, boolean isMo oldpt = toPrimitive( targetPts.get( index ) ); } } - + ArrayList< Double[] > pts; /******************** @@ -807,7 +831,7 @@ public boolean pointEdit( int index, double[] pt, boolean forceAdd, boolean isMo pts = targetPts; // create a new point and add it - Double[] exPts = new Double[ ndims ]; + final Double[] exPts = new Double[ ndims ]; for( int i = 0; i < ndims; i++ ){ exPts[ i ] = pt[ i ]; } @@ -874,14 +898,14 @@ public void resetLastPoint() /** * Looks through the table for points where there is a point in moving space but not fixed space. * For any such landmarks that are found, compute the inverse transform and add the result to the fixed points line. - * + * * @param xfm the new transformation */ public void updateAllWarpedPoints( final InvertibleRealTransform xfm ) { final InvertibleRealTransform xfmToUse; - if (xfm instanceof Wrapped2DTransformAs3D && ndims == 2) - xfmToUse = ((Wrapped2DTransformAs3D) xfm).transform; + if (xfm instanceof InvertibleWrapped2DTransformAs3D && ndims == 2) + xfmToUse = ((InvertibleWrapped2DTransformAs3D) xfm).transform; else xfmToUse = xfm; @@ -903,7 +927,7 @@ public void updateAllWarpedPoints( final InvertibleRealTransform xfm ) * If these conditions are satisfied, the position of the moving point in * target space by iteratively estimating the inverse of the thin plate * spline transformation. - * + * * @param i the row in the table * @param xfm the invertible transformation */ @@ -915,15 +939,15 @@ public void computeWarpedPoint( int i, final InvertibleRealTransform xfm ) // TODO Perhaps move this into its own thread. and expose the parameters for solving the inverse. if ( !isFixedPoint( i ) && isMovingPoint( i ) && xfm != null ) { - double[] tgt = toPrimitive( movingPts.get( i ) ); + final double[] tgt = toPrimitive( movingPts.get( i ) ); - double[] warpedPt = new double[ ndims ]; + final double[] warpedPt = new double[ ndims ]; xfm.applyInverse( warpedPt, tgt ); if( xfm instanceof WrappedIterativeInvertibleRealTransform ) { - WrappedIterativeInvertibleRealTransform inv = (WrappedIterativeInvertibleRealTransform)xfm; - double error = inv.getOptimzer().getError(); + final WrappedIterativeInvertibleRealTransform inv = (WrappedIterativeInvertibleRealTransform)xfm; + final double error = inv.getOptimzer().getError(); if( error > inverseThreshold ) { @@ -936,8 +960,8 @@ public void computeWarpedPoint( int i, final InvertibleRealTransform xfm ) } // TODO should check for failure or non-convergence here - // can use the error returned by the inverse method to do this. - // BUT - it's not clear what to do upon failure + // can use the error returned by the inverse method to do this. + // BUT - it's not clear what to do upon failure updateWarpedPoint( i, warpedPt ); } } @@ -960,7 +984,7 @@ public int getIndexNearestTo( double[] pt, boolean isMoving ) for( int i = 0; i < numRows; i++ ) { p = getPoint( isMoving, i ); - double thisdist = squaredDistance( p, pt ); + final double thisdist = squaredDistance( p, pt ); if( thisdist < minDist ) { minDist = thisdist; @@ -988,7 +1012,7 @@ public int getIndexNearestTo( RealLocalizable pt, boolean isMoving ) for( int i = 0; i < numRows; i++ ) { p = getPoint( isMoving, i ); - double thisdist = squaredDistance( p, pt ); + final double thisdist = squaredDistance( p, pt ); if( thisdist < minDist ) { minDist = thisdist; @@ -1026,6 +1050,44 @@ public Double[] getFixedPoint( int index ) return targetPts.get( index ); } + public ArrayList< Double[] > getMovingPoints() + { + return movingPts; + } + + public ArrayList< Double[] > getFixedPoints() + { + return targetPts; + } + + public ArrayList getMovingPointsCopy() + { + final ArrayList< double[] > out = new ArrayList(); + for( final Double[] p : movingPts ) + { + final double[] q = new double[ ndims ]; + for( int d = 0; d < ndims; d++ ) + q[d] = p[d]; + + out.add( q ); + } + return out; + } + + public ArrayList getFixedPointsCopy() + { + final ArrayList< double[] > out = new ArrayList(); + for( final Double[] p : targetPts ) + { + final double[] q = new double[ ndims ]; + for( int d = 0; d < ndims; d++ ) + q[d] = p[d]; + + out.add( q ); + } + return out; + } + public boolean isMovingPoint( int index ) { return !Double.isInfinite( movingPts.get( index )[ 0 ] ); @@ -1061,7 +1123,7 @@ public void activateRow( int index ) { activate = false; break; - } + } } changed = activate != activeList.get( index ); @@ -1080,7 +1142,7 @@ public void activateRow( int index ) /** * Returns a name for a row to be inserted at the given index. - * + * * @param index index of the row * @return a name for the new row */ @@ -1099,18 +1161,18 @@ private String nextName( int index ) // Increment the index in the name of the previous row i = 1 + Integer.parseInt( names.get( index - 1 ).replaceAll( "Pt-", "" )); } - catch ( Exception e ){} + catch ( final Exception e ){} s = String.format( "Pt-%d", i ); } return s; } - + /** * Sets a flag that indicates the point at the input index has changed * since the last time a transform was estimated. - * - * Not currently in use, but may be in the future + * + * Not currently in use, but may be in the future * @param index the row index */ @SuppressWarnings("unused") @@ -1119,7 +1181,7 @@ private void markAsChanged( int index ) for( int i = 0; i < this.numRows; i++ ) { if( !indicesOfChangedPoints.contains( i )) - indicesOfChangedPoints.add( i ); + indicesOfChangedPoints.add( i ); } } @@ -1129,7 +1191,7 @@ public void resetUpdated() indicesOfChangedPoints.clear(); elementDeleted = false; } - + @Deprecated public void transferUpdatesToModel() { @@ -1141,12 +1203,12 @@ public void transferUpdatesToModel() resetUpdated(); // not strictly necessary } - + public void load( File f ) throws IOException { load( f, false ); } - + /** * Loads this table from a file * @param f the file @@ -1154,18 +1216,68 @@ public void load( File f ) throws IOException * @throws IOException an exception */ public void load( File f, boolean invert ) throws IOException + { + if( f.getCanonicalPath().endsWith("csv")) + loadCsv(f, invert); + else if( f.getCanonicalPath().endsWith("json")) + fromJson( f ); + } + + public static LandmarkTableModel loadFromCsv( File f, boolean invert ) throws IOException + { + final CSVReader reader = new CSVReader( new FileReader( f.getAbsolutePath() ) ); + List< String[] > rows = null; + try + { + rows = reader.readAll(); + reader.close(); + } + catch ( final CsvException e ) {} + + LandmarkTableModel ltm = null; + if ( rows.get( 0 ).length == 6 ) + ltm = new LandmarkTableModel( 2 ); + else if ( rows.get( 0 ).length == 8 ) + ltm = new LandmarkTableModel( 3 ); + + if( ltm != null ) + ltm.loadCsvHelper( invert, rows ); + + return ltm; + } + + /** + * Loads this table from a file + * @param f the file + * @param invert invert the moving and target point sets + * @throws IOException an exception + */ + public void loadCsv( File f, boolean invert ) throws IOException + { + final CSVReader reader = new CSVReader( new FileReader( f.getAbsolutePath() )); + List< String[] > rows = null; + try + { + rows = reader.readAll(); + reader.close(); + } + catch( final CsvException e ){} + + loadCsvHelper( invert, rows ); + } + + /** + * Loads this table from a file. + * + * @param invert if true, the moving and target point sets are inverted + * @param rows a list of strings containing rows of a csv file + * @throws IOException an exception + */ + protected void loadCsvHelper( boolean invert, final List rows ) throws IOException { synchronized(this) { clear(); - CSVReader reader = new CSVReader( new FileReader( f.getAbsolutePath() )); - List< String[] > rows = null; - try - { - rows = reader.readAll(); - reader.close(); - } - catch( CsvException e ){} if( rows == null || rows.size() < 1 ) { System.err.println("Error reading csv"); @@ -1176,7 +1288,7 @@ public void load( File f, boolean invert ) throws IOException int expectedRowLength = 8; int i = 0; - for( String[] row : rows ) + for( final String[] row : rows ) { // detect a file with 2d landmarks if( i == 0 && // only check for the first row @@ -1192,8 +1304,8 @@ public void load( File f, boolean invert ) throws IOException names.add( row[ 0 ] ); activeList.add( Boolean.parseBoolean( row[ 1 ]) ); - Double[] movingPt = new Double[ ndims ]; - Double[] targetPt = new Double[ ndims ]; + final Double[] movingPt = new Double[ ndims ]; + final Double[] targetPt = new Double[ ndims ]; int k = 2; for( int d = 0; d < ndims; d++ ) @@ -1238,7 +1350,7 @@ public int numActive() /** * Copies point values from this table into a destination array. - * Checks whether the provided index exists, returns false if no + * Checks whether the provided index exists, returns false if no * point exists for the requested index. * * @param point the destination array @@ -1263,7 +1375,7 @@ public boolean copyPointSafe(double[] point, int index, boolean moving) { /** * Copies moving point values from this table into a destination array. - * Checks whether the provided index exists, returns false if no + * Checks whether the provided index exists, returns false if no * point exists for the requested index. * * @param point the destination array @@ -1284,7 +1396,7 @@ public boolean copyMovingPointSafe(double[] point, int index) { /** * Copies warped moving point values from this table into a destination array. - * Checks whether the provided index exists, returns false if no + * Checks whether the provided index exists, returns false if no * point exists for the requested index. * * @param point the destination array @@ -1305,7 +1417,7 @@ public boolean copyWarpedPointSafe(double[] point, int index) { /** * Copies target point values from this table into a destination array. - * Checks whether the provided index exists, returns false if no + * Checks whether the provided index exists, returns false if no * point exists for the requested index. * * @param point the destination array @@ -1358,7 +1470,7 @@ public void copyLandmarks( int tableIndex, double[][] movingLandmarks, double[][ if ( activeList.get( tableIndex ) ) { - int activeIndex = getActiveIndex( tableIndex ); + final int activeIndex = getActiveIndex( tableIndex ); for ( int d = 0; d < ndims; d++ ) { movingLandmarks[ d ][ activeIndex ] = movingPts.get( tableIndex )[ d ]; @@ -1394,18 +1506,18 @@ public void copyLandmarks( double[][] movingLandmarks, double[][] targetLandmark @Deprecated public void initTransformation() { - int numActive = numActive(); + final int numActive = numActive(); // TODO: better to pass a factory here so the transformation can be any // CoordinateTransform ( not just a TPS ) - double[][] mvgPts = new double[ ndims ][ numActive ]; - double[][] tgtPts = new double[ ndims ][ numActive ]; + final double[][] mvgPts = new double[ ndims ][ numActive ]; + final double[][] tgtPts = new double[ ndims ][ numActive ]; copyLandmarks( mvgPts, tgtPts ); // need to find the "inverse TPS" so exchange moving and tgt estimatedXfm = new ThinPlateR2LogRSplineKernelTransform( ndims, tgtPts, mvgPts ); } - + /** * Saves the table to a file * @param f the file @@ -1413,16 +1525,16 @@ public void initTransformation() */ public void save( File f ) throws IOException { - CSVWriter csvWriter = new CSVWriter(new FileWriter( f.getAbsoluteFile() )); + final CSVWriter csvWriter = new CSVWriter(new FileWriter( f.getAbsoluteFile() )); + + synchronized(this) { + final int N = names.size(); + final List rows = new ArrayList( N ); - synchronized(this) { - int N = names.size(); - List rows = new ArrayList( N ); - - int rowLength = 2 * ndims + 2; + final int rowLength = 2 * ndims + 2; for( int i = 0; i < N; i++ ) { - String[] row = new String[ rowLength ]; + final String[] row = new String[ rowLength ]; row[ 0 ] = names.get( i ); row[ 1 ] = activeList.get( i ).toString(); @@ -1442,19 +1554,104 @@ public void save( File f ) throws IOException modifiedSinceLastSave = false; } } - + + public JsonElement toJson() + { + final Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create(); + final JsonObject out = new JsonObject(); + final JsonElement mvgPtsObj = gson.toJsonTree(getMovingPoints(), new TypeToken >() {}.getType()); + final JsonElement fixedPtsObj = gson.toJsonTree(getFixedPoints(), new TypeToken >() {}.getType()); + final JsonElement activeObj = gson.toJsonTree( activeList ); + final JsonElement namesObj = gson.toJsonTree( names ); + + out.add("type", new JsonPrimitive("BigWarpLandmarks")); + out.add("numDimensions", new JsonPrimitive( ndims )); + out.add("movingPoints", mvgPtsObj ); + out.add("fixedPoints", fixedPtsObj ); + out.add("active", activeObj ); + out.add("names", namesObj ); + + return out; + } + + public void fromJson( File f ) + { + final Gson gson = new Gson(); + final OpenOption[] options = new OpenOption[]{StandardOpenOption.READ}; + try { + final Reader reader = Channels.newReader(FileChannel.open(Paths.get( f.getCanonicalPath()), options), StandardCharsets.UTF_8.name()); + fromJson( gson.fromJson(reader, JsonObject.class )); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + public void fromJson( JsonElement json ) + { + if( !json.isJsonObject()) + return; + + final JsonObject obj = json.getAsJsonObject(); + final JsonObject landmarks = obj.get("landmarks").getAsJsonObject(); + + synchronized(this) { + clear(); + + final JsonArray namesArr = landmarks.get("names").getAsJsonArray(); + final JsonArray activeArr = landmarks.get("active").getAsJsonArray(); + final JsonArray mvgArr = landmarks.get("movingPoints").getAsJsonArray(); + final JsonArray fixedArr = landmarks.get("fixedPoints").getAsJsonArray(); + + numRows = namesArr.size(); + final int ndims = landmarks.get("numDimensions").getAsInt(); + + for( int i = 0; i < numRows; i++ ) + { + + names.add( namesArr.get(i).getAsString() ); + activeList.add( activeArr.get(i).getAsBoolean() ); + + final JsonElement mvg = mvgArr.get( i ); + final JsonElement fixed = fixedArr.get( i ); + final Double[] movingPt = new Double[ ndims ]; + final Double[] targetPt = new Double[ ndims ]; + + for( int d = 0; d < ndims; d++ ) + { + movingPt[ d ] = mvg.getAsJsonArray().get(d).getAsDouble(); + targetPt[ d ] = fixed.getAsJsonArray().get(d).getAsDouble(); + } + + movingPts.add( movingPt ); + targetPts.add( targetPt ); + + warpedPoints.add( new Double[ ndims ] ); + doesPointHaveAndNeedWarp.add( false ); + movingDisplayPointUnreliable.add( false ); + } + + this.ndims = ndims; + updateNextRows( 0 ); + buildTableToActiveIndex(); + } + + for( int i = 0; i < numRows; i++ ) + fireTableRowsInserted( i, i ); + } + public static String print( Double[] d ) { String out = ""; for( int i=0; i= numRows || col >= numCols ) @@ -1481,12 +1678,12 @@ else if( col == ACTIVECOLUMN ) } else if( col < 2 + ndims ) { - Double[] thesePts = movingPts.get(row); + final Double[] thesePts = movingPts.get(row); thesePts[ col - 2 ] = ((Double)value).doubleValue(); } else { - Double[] thesePts = targetPts.get(row); + final Double[] thesePts = targetPts.get(row); thesePts[ col - ndims - 2 ] = ((Double)value).doubleValue(); } @@ -1494,7 +1691,7 @@ else if( col < 2 + ndims ) fireTableCellUpdated(row, col); } - + @Override public Object getValueAt( int rowIndex, int columnIndex ) { @@ -1511,9 +1708,10 @@ else if( columnIndex < 2 + ndims ) } /** - * Only allow editing of the first ("Name") and + * Only allow editing of the first ("Name") and * second ("Active") column */ + @Override public boolean isCellEditable( int row, int col ) { return ( col <= ACTIVECOLUMN ); @@ -1526,15 +1724,15 @@ public boolean isCellEditable( int row, int col ) */ public LandmarkTableModel invert() { - LandmarkTableModel inv = new LandmarkTableModel( ndims ); + final LandmarkTableModel inv = new LandmarkTableModel( ndims ); - int N = this.getRowCount(); + final int N = this.getRowCount(); - double[] tmp = new double[ ndims ]; + final double[] tmp = new double[ ndims ]; for ( int i = 0; i < N; i++ ) { - Double[] thisMoving = movingPts.get( i ); - Double[] thisTarget = targetPts.get( i ); + final Double[] thisMoving = movingPts.get( i ); + final Double[] thisTarget = targetPts.get( i ); for ( int d = 0; d < ndims; d++ ) tmp[ d ] = thisMoving[ d ]; @@ -1557,16 +1755,16 @@ public LandmarkUndoManager getUndoManager() public static double[] copy( double[] in ) { - double[] out = new double[ in.length ]; + final double[] out = new double[ in.length ]; for ( int i = 0; i < in.length; i++ ) out[ i ] = in [ i ]; - + return out; } - + public static double[] toPrimitive( Double[] in ) { - double[] out = new double[ in.length ]; + final double[] out = new double[ in.length ]; for ( int i = 0; i < in.length; i++ ) out[ i ] = in[ i ]; diff --git a/src/main/java/bigwarp/loader/ImagePlusLoader.java b/src/main/java/bigwarp/loader/ImagePlusLoader.java index c1b68465..bd54fc73 100644 --- a/src/main/java/bigwarp/loader/ImagePlusLoader.java +++ b/src/main/java/bigwarp/loader/ImagePlusLoader.java @@ -37,6 +37,7 @@ */ package bigwarp.loader; +import bigwarp.source.SourceInfo; import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -50,7 +51,7 @@ import bdv.tools.brightness.ConverterSetup; import bdv.tools.brightness.SetupAssignments; import bdv.viewer.SourceAndConverter; -import bigwarp.BigWarp.BigWarpData; +import bigwarp.BigWarpData; import ij.ImagePlus; import ij.process.LUT; import mpicbg.spim.data.generic.sequence.BasicImgLoader; @@ -159,11 +160,15 @@ public void update( final BigWarpData< ? > data ) for( Integer key : settingsMap.keySet() ) { SourceAndConverter sac = data.sources.get( key.intValue() ); - data.setupSettings.put( key, settingsMap.get( key ) ); - data.sourceColorSettings.put( sac, settingsMap.get( key )); + data.getSourceInfo( key ).setColorSettings( settingsMap.get( key ) ); } } + public void update( final SourceInfo sourceInfo ) + { + sourceInfo.setColorSettings( settingsMap.get( sourceInfo.getId() ) ); + } + @SuppressWarnings( "unchecked" ) @Override public SpimDataMinimal[] load() @@ -266,7 +271,8 @@ public SpimDataMinimal load( final int setupIdOffset, ImagePlus imp ) for ( int s = 0; s < numSetups; ++s ) { final int id = setupIdOffset + s; - final BasicViewSetup setup = new BasicViewSetup( setupIdOffset + s, String.format( "%s channel %d", imp.getTitle(), id + 1 ), size, voxelSize ); + final String title = numSetups > 1 ? String.format( "%s channel %d", imp.getTitle(), id + 1 ) : imp.getTitle(); + final BasicViewSetup setup = new BasicViewSetup( setupIdOffset + s, title, size, voxelSize ); setup.setAttribute( new Channel( id + 1 ) ); setups.put( id, setup ); diff --git a/src/main/java/bigwarp/metadata/BwN5CosemMultiScaleMetadata.java b/src/main/java/bigwarp/metadata/BwN5CosemMultiScaleMetadata.java index 1d220e34..438cf6a1 100644 --- a/src/main/java/bigwarp/metadata/BwN5CosemMultiScaleMetadata.java +++ b/src/main/java/bigwarp/metadata/BwN5CosemMultiScaleMetadata.java @@ -118,7 +118,6 @@ public class BwN5CosemMultiScaleMetadata //extends MultiscaleMetadata paths = new ArrayList<>(); // // children.forEach( c -> { -//// System.out.println( c.getPath() ); // if( scaleLevelNodes.containsKey( c.getNodeName() )) // { // paths.add( c .getPath()); diff --git a/src/main/java/bigwarp/metadata/BwN5ViewerMultiscaleMetadataParser.java b/src/main/java/bigwarp/metadata/BwN5ViewerMultiscaleMetadataParser.java index bbebea9d..8651de50 100644 --- a/src/main/java/bigwarp/metadata/BwN5ViewerMultiscaleMetadataParser.java +++ b/src/main/java/bigwarp/metadata/BwN5ViewerMultiscaleMetadataParser.java @@ -88,7 +88,7 @@ public class BwN5ViewerMultiscaleMetadataParser //implements N5GroupParser< N5Mu // final List paths = new ArrayList<>(); // // children.forEach( c -> { -//// System.out.println( c.getPath() ); +//// System.out.println( c.getPath() ) // if( scaleLevelNodes.containsKey( c.getNodeName() )) // { // paths.add( c .getPath()); diff --git a/src/main/java/bigwarp/source/GridSource.java b/src/main/java/bigwarp/source/GridSource.java index c626610c..02e6f9a7 100644 --- a/src/main/java/bigwarp/source/GridSource.java +++ b/src/main/java/bigwarp/source/GridSource.java @@ -23,14 +23,14 @@ import bdv.viewer.Interpolation; import bdv.viewer.Source; -import bdv.viewer.SourceAndConverter; -import bigwarp.BigWarp.BigWarpData; +import bigwarp.BigWarpData; import mpicbg.spim.data.sequence.VoxelDimensions; import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealRandomAccessible; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.BoundingBoxEstimation; import net.imglib2.realtransform.RealTransform; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; @@ -78,7 +78,11 @@ private static AffineTransform3D getSourceTransform( BigWarpData data ) private static Interval getInterval( BigWarpData data ) { // return new FinalInterval( data.sources.get( data.targetSourceIndices[ 0 ] ).getSpimSource().getSource( 0, 0 )); - return new FinalInterval( data.sources.get( data.movingSourceIndices[ 0 ] ).getSpimSource().getSource( 0, 0 )); +// return new FinalInterval( data.sources.get( data.movingSourceIndices[ 0 ] ).getSpimSource().getSource( 0, 0 )); + BoundingBoxEstimation bbe = new BoundingBoxEstimation(); + AffineTransform3D affine = new AffineTransform3D(); + data.getTargetSource( 0 ).getSpimSource().getSourceTransform( 0, 0, affine ); + return bbe.estimatePixelInterval( affine, data.getTargetSource( 0 ).getSpimSource().getSource( 0, 0 ) ); } public void setGridSpacing( double spacing ) @@ -143,7 +147,8 @@ public String getName() @Override public VoxelDimensions getVoxelDimensions() { - return sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getVoxelDimensions(); +// return sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getVoxelDimensions(); + return sourceData.getTargetSource( 0 ).getSpimSource().getVoxelDimensions(); } @Override diff --git a/src/main/java/bigwarp/source/JacobianDeterminantSource.java b/src/main/java/bigwarp/source/JacobianDeterminantSource.java index 6a692458..e2b3fdaa 100644 --- a/src/main/java/bigwarp/source/JacobianDeterminantSource.java +++ b/src/main/java/bigwarp/source/JacobianDeterminantSource.java @@ -25,10 +25,8 @@ import bdv.viewer.Interpolation; import bdv.viewer.Source; -import bigwarp.BigWarp.BigWarpData; +import bigwarp.BigWarpData; import bigwarp.landmarks.LandmarkTableModel; -import jitk.spline.ThinPlateR2LogRSplineKernelTransform; -import mpicbg.models.AbstractModel; import mpicbg.spim.data.sequence.FinalVoxelDimensions; import mpicbg.spim.data.sequence.VoxelDimensions; import net.imglib2.Cursor; @@ -37,7 +35,7 @@ import net.imglib2.RealRandomAccess; import net.imglib2.RealRandomAccessible; import net.imglib2.realtransform.AffineTransform3D; -import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.BoundingBoxEstimation; import net.imglib2.realtransform.inverse.DifferentiableRealTransform; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; @@ -64,9 +62,17 @@ public JacobianDeterminantSource( String name, BigWarpData data, T t ) sourceData = data; //RandomAccessibleInterval fixedsrc = sourceData.sources.get( 1 ).getSpimSource().getSource( 0, 0 ); - interval = sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getSource( 0, 0 ); +// interval = sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getSource( 0, 0 ); +// interval = sourceData.getTargetSource( 0 ).getSpimSource().getSource( 0, 0 ); - VoxelDimensions srcVoxDims = sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getVoxelDimensions(); + final BoundingBoxEstimation bbe = new BoundingBoxEstimation(); + final AffineTransform3D affine = new AffineTransform3D(); + data.getTargetSource( 0 ).getSpimSource().getSourceTransform( 0, 0, affine ); + interval = bbe.estimatePixelInterval( affine, data.getTargetSource( 0 ).getSpimSource().getSource( 0, 0 ) ); + + +// VoxelDimensions srcVoxDims = sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getVoxelDimensions(); + VoxelDimensions srcVoxDims = sourceData.getTargetSource( 0 ).getSpimSource().getVoxelDimensions(); String unit = "pix"; if( srcVoxDims != null ) unit = srcVoxDims.unit(); diff --git a/src/main/java/bigwarp/source/PlateauSphericalMaskRealRandomAccessible.java b/src/main/java/bigwarp/source/PlateauSphericalMaskRealRandomAccessible.java new file mode 100644 index 00000000..7f2f2aed --- /dev/null +++ b/src/main/java/bigwarp/source/PlateauSphericalMaskRealRandomAccessible.java @@ -0,0 +1,476 @@ +package bigwarp.source; + +import java.util.List; +import java.util.ArrayList; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import bdv.viewer.overlay.BigWarpMaskSphereOverlay; +import org.jdom2.Element; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import mpicbg.spim.data.XmlHelpers; +import net.imglib2.*; +import net.imglib2.position.FunctionRealRandomAccessible; +import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.util.Intervals; +import org.jdom2.Element; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class PlateauSphericalMaskRealRandomAccessible implements RealRandomAccessible< DoubleType > +{ + transient private BiConsumer< RealLocalizable, DoubleType > pfun; + transient private FunctionRealRandomAccessible< DoubleType > rra; + transient private List overlays; + + private FalloffShape fallOffShape; + + transient private int nd; + + transient private double plateauR; + @SerializedName(SQUARED_RADIUS) + private double plateauR2; + + transient private double sigma; + + @SerializedName( SQUARED_SIGMA ) + private double sqrSigma; + transient private double invSqrSigma; + transient private double gaussInvSqrSigma; + + @JsonAdapter( RealPointToDoubleArray.class ) + private RealPoint center; + + private static final double EPS = 1e-6; + private static final double PIon2 = Math.PI / 2.0; + private static final double PI = Math.PI; + + public static enum FalloffShape + { + COSINE( it -> new CosineFalloff( it ) ), + GAUSSIAN( it -> new GaussianFalloff( it ) ), + LINEAR( it -> new LinearFalloff( it ) ); + + private final Function< PlateauSphericalMaskRealRandomAccessible, BiConsumer< RealLocalizable, DoubleType > > fallOffProvider; + + FalloffShape( Function< PlateauSphericalMaskRealRandomAccessible, BiConsumer< RealLocalizable, DoubleType > > fallOffProvider ) + { + this.fallOffProvider = fallOffProvider; + } + + public BiConsumer< RealLocalizable, DoubleType > createFalloffFunction( PlateauSphericalMaskRealRandomAccessible mask ) + { + return fallOffProvider.apply( mask ); + } + + } + + public PlateauSphericalMaskRealRandomAccessible( RealPoint center ) + { + this.nd = center.numDimensions(); + this.center = center; + fallOffShape = FalloffShape.COSINE; + pfun = fallOffShape.createFalloffFunction( this ); + update(); + + setRadius( 8.0 ); + setSigma ( 10.0 ); + } + + public void setOverlays( final List< BigWarpMaskSphereOverlay > overlays ) { + this.overlays = overlays; + if (overlays != null) { + overlays.stream().forEach(o -> { + o.setCenter(center); + o.setInnerRadius(plateauR); + o.setOuterRadiusDelta(sigma); + }); + } + } + + public static void main( String[] args ) + { + final long S = 50; + final double[] center = new double[] { S, S, S }; + final RealPoint pt = RealPoint.wrap( center ); + + final PlateauSphericalMaskRealRandomAccessible img = new PlateauSphericalMaskRealRandomAccessible( pt ); + img.setRadius( 10 ); + img.setSigma( 10 ); + final Interval interval = Intervals.createMinSize( 0, 0, 0, 2 * S, 2 * S, 2 * S ); +// +//// BdvOptions options = BdvOptions.options().screenScales( new double[] { 1 } ); +// BdvOptions options = BdvOptions.options(); +// BdvStackSource< DoubleType > bdv = BdvFunctions.show( img.rra, interval, "img", options ); +// bdv.getBdvHandle().getSetupAssignments().getMinMaxGroups().get( 0 ).setRange( 0, 1 ); +// +//// InputActionBindings kb = bdv.getBdvHandle().getKeybindings(); +//// System.out.println( kb ); +//// kb.removeActionMap( "navigation" ); +//// kb.removeInputMap( "navigation" ); +// +//// bdv.getBdvHandle().getTriggerbindings().removeInputTriggerMap( "block_transform" ); +// +// final MaskedSourceEditorMouseListener ml = new MaskedSourceEditorMouseListener( 3, null, bdv.getBdvHandle().getViewerPanel() ); +// ml.setMaskInterpolationType( img ); +//// bdv.getBdvHandle().getViewerPanel().getDisplay().addMouseListener( ml ); +// + double x = 50; + final RealRandomAccess< DoubleType > access = img.realRandomAccess(); + access.setPosition( center ); + while ( x < 100 ) + { + access.move( 1, 0 ); + System.out.println( x + "," + access.get().getRealDouble()); + + x = access.getDoublePosition( 0 ); + } + } + + private void update() + { + rra = new FunctionRealRandomAccessible<>( nd, getFalloffFunction(), DoubleType::new ); + } + + private BiConsumer< RealLocalizable, DoubleType > getFalloffFunction() + { + if ( pfun == null ) + { + pfun = fallOffShape.createFalloffFunction( this ); + } + return pfun; + } + + public FalloffShape getFallOffShape() + { + return fallOffShape; + } + + public void setFalloffShape( String type ) + { + setFalloffShape( FalloffShape.valueOf( type ) ); + } + + public void setFalloffShape( FalloffShape shape ) + { + + fallOffShape = shape; + pfun = shape.createFalloffFunction( this ); + update(); + } + + public double getSquaredRadius() + { + return plateauR2; + } + + public void setRadius( double r ) + { + plateauR = r; + plateauR2 = plateauR * plateauR ; + if ( overlays != null ) + overlays.stream().forEach( o -> o.setInnerRadius( plateauR ) ); + } + + public void setSquaredRadius( double r2 ) + { + plateauR2 = r2; + plateauR = Math.sqrt( plateauR2 ); + if ( overlays != null ) + overlays.stream().forEach( o -> o.setInnerRadius( plateauR ) ); + } + + public double getSquaredSigma() + { + return sqrSigma; + } + + public double getSigma() + { + return sigma; + } + + public void setSigma( double sigma ) + { + this.sigma = sigma < 0 ? -sigma : sigma; + sqrSigma = this.sigma * this.sigma; + + if( sqrSigma <= 0 ) + sqrSigma = EPS; + + invSqrSigma = 1.0 / sqrSigma; + updateGaussSigma(); + + if ( overlays != null ) + overlays.stream().forEach( o -> o.setOuterRadiusDelta( this.sigma )); + } + + public void setSquaredSigma( double squaredSigma ) + { + setSigma( Math.sqrt( sqrSigma )); + } + + public void incSquaredSigma( double increment ) + { + sqrSigma += increment; + sigma = Math.sqrt( sqrSigma ); + + if( sqrSigma <= 0 ) + sqrSigma = EPS; + + invSqrSigma = 1.0 / sqrSigma; + updateGaussSigma(); + if ( overlays != null ) + overlays.stream().forEach( o -> o.setOuterRadiusDelta( sigma )); + } + + private void updateGaussSigma() + { + final double gsig = gaussSigma( sigma ); + gaussInvSqrSigma = 1.0 / ( gsig * gsig ); + } + + public void setCenter( RealLocalizable p ) + { + p.localize( center ); + if ( overlays != null ) + overlays.stream().forEach( o -> o.setCenter( p ) ); + } + + public void setCenter( double[] p ) + { + center.setPosition( p ); + if ( overlays != null ) + overlays.stream().forEach( o -> o.setCenter( p ) ); + } + + public RealPoint getCenter() + { + return center; + } + + + final public static double squaredDistance( final RealLocalizable position1, final RealLocalizable position2 ) + { + double dist = 0; + + final int n = position1.numDimensions(); + for ( int d = 0; d < n; ++d ) + { + final double pos = position2.getDoublePosition( d ) - position1.getDoublePosition( d ); + + dist += pos * pos; + } + + return dist; + } + + @Override + public int numDimensions() + { + return rra.numDimensions(); + } + + @Override + public RealRandomAccess< DoubleType > realRandomAccess() + { + return rra.realRandomAccess(); + } + + @Override + public RealRandomAccess< DoubleType > realRandomAccess( RealInterval interval ) + { + return rra.realRandomAccess( interval ); + } + + public Element toXml() + { + final Element maskSettings = new Element( "transform-mask" ); + + final Element type = new Element( "type" ); + type.setText( "plateau-spherical" ); + maskSettings.addContent( type ); + + final Element c = XmlHelpers.doubleArrayElement( "center", center.positionAsDoubleArray() ); + maskSettings.addContent( c ); + + final Element p = new Element( "parameters" ); + p.addContent( XmlHelpers.doubleElement( "squaredRadius", plateauR2 ) ); + p.addContent( XmlHelpers.doubleElement( "squaredSigma", sqrSigma ) ); + + maskSettings.addContent( p ); + + return maskSettings; + } + + public void fromXml( Element elem ) + { + setCenter( XmlHelpers.getDoubleArray( elem, "center" ) ); + + final Element p = elem.getChild( "parameters" ); + setSquaredRadius( XmlHelpers.getDouble( p, "squaredRadius" )); + setSquaredSigma( XmlHelpers.getDouble( p, "squaredSigma" )); + } + + @Deprecated + public void fromJson( JsonObject json ) + { + final JsonArray c = json.get("center").getAsJsonArray(); + final double[] center = new double[ c.size() ]; + for( int i = 0; i < c.size(); i++ ) + center[i] = c.get( i ).getAsDouble(); + + setCenter( center ); + setSquaredRadius( json.get("squaredRadius").getAsDouble() ); + setSquaredSigma( json.get("squaredSigma").getAsDouble() ); + } + + /** + * Returns the sigma that makes a Gaussian shape most like a cosine with period T. + *

+ * see https://gist.github.com/bogovicj/d212b236868c76798edfd11150b2c9a0 + * + * @param T the cosine period + * @return the appropriate sigma + */ + public static double gaussSigma( double T ) + { + return ( 0.40535876907923957 * T + 0.03706937 ); + } + + public static class GaussianFalloff implements BiConsumer< RealLocalizable, DoubleType > + { + + private final PlateauSphericalMaskRealRandomAccessible mask; + + public GaussianFalloff( PlateauSphericalMaskRealRandomAccessible mask ) + { + this.mask = mask; + } + + @Override + public void accept( RealLocalizable x, DoubleType v ) + { + v.setZero(); + final double r2 = squaredDistance( x, mask.center ); + if ( r2 <= mask.plateauR2 ) + v.setOne(); + else + { + final double r = Math.sqrt( r2 ); +// final double t = (r2 - plateauR2); + final double t = ( r - mask.plateauR ); + // TODO sample exp function and interpolate to speed up + v.set( Math.exp( -0.5 * t * t * mask.gaussInvSqrSigma ) ); +// v.set( Math.cos( t * 0.5 + 0.5 )); +// v.set( 1 / t ); + } + } + } + + public static class CosineFalloff implements BiConsumer< RealLocalizable, DoubleType > + { + + private final PlateauSphericalMaskRealRandomAccessible mask; + + public CosineFalloff( PlateauSphericalMaskRealRandomAccessible mask ) + { + this.mask = mask; + } + + @Override + public void accept( RealLocalizable x, DoubleType v ) + { + final double r2 = squaredDistance( x, mask.center ); + final double r = Math.sqrt( r2 ); + if ( r2 <= mask.plateauR2 ) + v.setOne(); + else if ( r >= mask.plateauR + mask.sigma ) + v.setZero(); + else + { +// final double t = (r2 - plateauR2); +// final double r = Math.sqrt( r2 ); + final double t = ( r - mask.plateauR ); + final double val = 0.5 + 0.5 * Math.cos( t * PI / mask.sigma ); + v.set( val ); + } + } + } + + public static class LinearFalloff implements BiConsumer< RealLocalizable, DoubleType > + { + + private final PlateauSphericalMaskRealRandomAccessible mask; + + public LinearFalloff( PlateauSphericalMaskRealRandomAccessible mask ) + { + this.mask = mask; + } + + @Override + public void accept( RealLocalizable x, DoubleType v ) + { + v.setZero(); + final double r2 = squaredDistance( x, mask.center ); + final double d2 = mask.plateauR + mask.sigma; + if ( r2 <= mask.plateauR2 ) + v.setOne(); + else if ( r2 >= d2 * d2 ) + v.setZero(); + else + { + final double r = Math.sqrt( r2 ); + v.set( 1 - ( r - mask.plateauR ) / mask.sigma ); + } + } + } + + public static final String FALLOFF_SHAPE = "falloffShape"; + + public static final String CENTER = "center"; + + public static final String SQUARED_RADIUS = "squaredRadius"; + + public static final String SQUARED_SIGMA = "squaredSigma"; + + public static class RealPointToDoubleArray extends TypeAdapter< RealPoint > + { + + @Override + public void write( final JsonWriter out, final RealPoint value ) throws IOException + { + final JsonWriter jsonWriter = out.beginArray(); + for ( int i = 0; i < value.numDimensions(); i++ ) + { + jsonWriter.value( value.getDoublePosition( i ) ); + } + jsonWriter.endArray(); + } + + @Override + public RealPoint read( final JsonReader in ) throws IOException + { + in.beginArray(); + final ArrayList< Double > pos = new ArrayList<>(); + while ( in.hasNext() ) + { + pos.add( in.nextDouble() ); + } + in.endArray(); + return new RealPoint( pos.stream().mapToDouble( it -> it ).toArray() ); + } + } + +} diff --git a/src/main/java/bigwarp/source/PlateauSphericalMaskSource.java b/src/main/java/bigwarp/source/PlateauSphericalMaskSource.java new file mode 100644 index 00000000..a595f609 --- /dev/null +++ b/src/main/java/bigwarp/source/PlateauSphericalMaskSource.java @@ -0,0 +1,39 @@ +package bigwarp.source; + +import bdv.util.RealRandomAccessibleIntervalSource; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.RealPoint; +import net.imglib2.type.numeric.real.DoubleType; + +public class PlateauSphericalMaskSource extends RealRandomAccessibleIntervalSource< DoubleType > +{ + private PlateauSphericalMaskRealRandomAccessible plateauMask; + + private PlateauSphericalMaskSource( RealPoint pt, Interval interval ) + { + super( new PlateauSphericalMaskRealRandomAccessible( pt ), interval, new DoubleType(), "transform mask" ); + } + + private PlateauSphericalMaskSource( PlateauSphericalMaskRealRandomAccessible mask, Interval interval ) + { + super( mask, interval, new DoubleType(), "transform mask" ); + this.plateauMask = mask; + } + + public static PlateauSphericalMaskSource build( final PlateauSphericalMaskRealRandomAccessible mask, final FinalInterval interval ) + { + return new PlateauSphericalMaskSource( mask, interval ); + } + + public PlateauSphericalMaskRealRandomAccessible getRandomAccessible() + { + return plateauMask; + } + + public static PlateauSphericalMaskSource build( RealPoint pt, Interval interval ) + { + PlateauSphericalMaskRealRandomAccessible mask = new PlateauSphericalMaskRealRandomAccessible( pt ); + return new PlateauSphericalMaskSource( mask, interval ); + } +} diff --git a/src/main/java/bigwarp/source/SourceInfo.java b/src/main/java/bigwarp/source/SourceInfo.java new file mode 100644 index 00000000..ebc63408 --- /dev/null +++ b/src/main/java/bigwarp/source/SourceInfo.java @@ -0,0 +1,136 @@ +package bigwarp.source; + +import bdv.viewer.SourceAndConverter; +import bigwarp.loader.ImagePlusLoader.ColorSettings; +import java.util.function.Supplier; +import net.imglib2.realtransform.RealTransform; + +public class SourceInfo +{ + private SourceAndConverter sourceAndConverter = null; + private final int id; + + private String name; + + private Supplier uriSupplier; + + private ColorSettings colorSettings = null; + + private final boolean moving; + + private RealTransform transform; + + private Supplier transformUriSupplier; + + boolean serializable = false; + + public SourceInfo( final int id, final boolean moving ) + { + this( id, moving, null, null, null); + } + + public SourceInfo( final int id, final boolean moving, final String name ) + { + this(id, moving, name, () -> null, null); + } + + public SourceInfo( final int id, final boolean moving, final String name, Supplier uriSupplier ) + { + this(id, moving, name, uriSupplier, null); + } + + public SourceInfo( final int id, final boolean moving, final String name, final Supplier< String > uriSupplier, RealTransform transform ) + { + this.id = id; + this.moving = moving; + this.name = name; + this.uriSupplier = uriSupplier; + this.transform = transform; + } + + /** + * Some source origins (url, .xml file, ImagePlus) may lead to multiple Sources being created. + * We only want to setSerializable the initial one which caused the subsequent onces to be created. + * We do this by setting the {@link #serializable} flag to {@code true} for the first source in the sequence. + * + * @param serializable whether to setSerialize this source or not. + */ + public void setSerializable( final boolean serializable ) + { + this.serializable = serializable; + } + + public boolean isSerializable() + { + return serializable; + } + + public String getUri() + { + return uriSupplier.get(); + } + + public void setUriSupplier( final Supplier< String > getUri ) + { + this.uriSupplier = getUri; + } + + public int getId() + { + return id; + } + + public String getName() + { + return name; + } + + public void setName( final String name ) + { + this.name = name; + } + + public void setColorSettings( final ColorSettings colorSettings ) + { + this.colorSettings = colorSettings; + } + + public ColorSettings getColorSettings() + { + return colorSettings; + } + + public boolean isMoving() + { + return moving; + } + + public void setTransform( final RealTransform transform, Supplier transformUriSupplier ) + { + this.transform = transform; + this.transformUriSupplier = transformUriSupplier; + } + + public RealTransform getTransform() + { + return transform; + } + + public String getTransformUri() + { + if( transformUriSupplier != null ) + return transformUriSupplier.get(); + else + return null; + } + + public SourceAndConverter< ? > getSourceAndConverter() + { + return sourceAndConverter; + } + + public void setSourceAndConverter( final SourceAndConverter< ? > sourceAndConverter ) + { + this.sourceAndConverter = sourceAndConverter; + } +} diff --git a/src/main/java/bigwarp/source/WarpMagnitudeSource.java b/src/main/java/bigwarp/source/WarpMagnitudeSource.java index 67915613..f21fefd9 100644 --- a/src/main/java/bigwarp/source/WarpMagnitudeSource.java +++ b/src/main/java/bigwarp/source/WarpMagnitudeSource.java @@ -25,7 +25,7 @@ import bdv.viewer.Interpolation; import bdv.viewer.Source; -import bigwarp.BigWarp.BigWarpData; +import bigwarp.BigWarpData; import bigwarp.landmarks.LandmarkTableModel; import mpicbg.spim.data.sequence.FinalVoxelDimensions; import mpicbg.spim.data.sequence.VoxelDimensions; @@ -35,6 +35,7 @@ import net.imglib2.RealRandomAccess; import net.imglib2.RealRandomAccessible; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.BoundingBoxEstimation; import net.imglib2.realtransform.RealTransform; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; @@ -60,8 +61,17 @@ public WarpMagnitudeSource( String name, BigWarpData data, T t ) sourceData = data; - interval = sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getSource( 0, 0 ); - VoxelDimensions srcVoxDims = sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getVoxelDimensions(); +// interval = sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getSource( 0, 0 ); +// interval = sourceData.getTargetSource( 0 ).getSpimSource().getSource( 0, 0 ); + + final BoundingBoxEstimation bbe = new BoundingBoxEstimation(); + final AffineTransform3D affine = new AffineTransform3D(); + data.getTargetSource( 0 ).getSpimSource().getSourceTransform( 0, 0, affine ); + interval = bbe.estimatePixelInterval( affine, data.getTargetSource( 0 ).getSpimSource().getSource( 0, 0 ) ); + + +// VoxelDimensions srcVoxDims = sourceData.sources.get( sourceData.targetSourceIndices[ 0 ] ).getSpimSource().getVoxelDimensions(); + final VoxelDimensions srcVoxDims = sourceData.getTargetSource( 0 ).getSpimSource().getVoxelDimensions(); String unit = "pix"; if( srcVoxDims != null ) unit = srcVoxDims.unit(); diff --git a/src/main/java/bigwarp/transforms/AbstractTransformSolver.java b/src/main/java/bigwarp/transforms/AbstractTransformSolver.java new file mode 100644 index 00000000..8a37996b --- /dev/null +++ b/src/main/java/bigwarp/transforms/AbstractTransformSolver.java @@ -0,0 +1,65 @@ +/*- + * #%L + * BigWarp plugin for Fiji. + * %% + * Copyright (C) 2015 - 2021 Howard Hughes Medical Institute. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package bigwarp.transforms; + +import bigwarp.landmarks.LandmarkTableModel; +import net.imglib2.realtransform.InvertibleRealTransform; + +public abstract class AbstractTransformSolver implements TransformSolver +{ +// protected double[][] mvgPts; +// protected double[][] tgtPts; + + public T solve( final LandmarkTableModel landmarkTable ) + { + return solve( landmarkTable, -1 ); + } + + public T solve( final LandmarkTableModel landmarkTable, final int indexChanged ) + { + int numActive = landmarkTable.numActive(); + int ndims = landmarkTable.getNumdims(); + double[][] mvgPts = new double[ ndims ][ numActive ]; + double[][] tgtPts = new double[ ndims ][ numActive ]; + landmarkTable.copyLandmarks( mvgPts, tgtPts ); + + /* + * The optimization below causes problems because the landmark arrays are used directly by transformations, + * and they are modified below. + */ +// synchronized( landmarkTable ) +// { +// mvgPts; +// double[][] tgtPts; +// if( mvgPts == null || mvgPts[0].length != numActive ) +// { +// landmarkTable.copyLandmarks( mvgPts, tgtPts ); +// } +// else if( indexChanged >= 0 ) +// { +// landmarkTable.copyLandmarks( indexChanged, mvgPts, tgtPts ); +// } +// } + + return solve( mvgPts, tgtPts ); + } +} diff --git a/src/main/java/bigwarp/transforms/BigWarpTransform.java b/src/main/java/bigwarp/transforms/BigWarpTransform.java index 4b93da12..71c089b3 100644 --- a/src/main/java/bigwarp/transforms/BigWarpTransform.java +++ b/src/main/java/bigwarp/transforms/BigWarpTransform.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -21,16 +21,16 @@ */ package bigwarp.transforms; -import java.lang.reflect.Field; import java.util.Arrays; -import java.util.Optional; +import java.util.function.Supplier; import bdv.gui.TransformTypeSelectDialog; -import bdv.util.RandomAccessibleIntervalMipmapSource; +import bdv.util.BoundedRange; import bdv.viewer.SourceAndConverter; import bdv.viewer.animate.SimilarityModel3D; -import bigwarp.BigWarp.BigWarpData; +import bigwarp.BigWarpData; import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.source.PlateauSphericalMaskRealRandomAccessible; import ij.IJ; import jitk.spline.ThinPlateR2LogRSplineKernelTransform; import mpicbg.models.AbstractAffineModel2D; @@ -46,30 +46,64 @@ import mpicbg.models.SimilarityModel2D; import mpicbg.models.TranslationModel2D; import mpicbg.models.TranslationModel3D; +import net.imglib2.RealRandomAccessible; +import net.imglib2.converter.Converter; +import net.imglib2.converter.Converters; +import net.imglib2.realtransform.AffineGet; +import net.imglib2.realtransform.AffineTransform2D; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.realtransform.InverseRealTransform; import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.MaskedSimilarityTransform.Interpolators; import net.imglib2.realtransform.ThinplateSplineTransform; -import net.imglib2.realtransform.Wrapped2DTransformAs3D; +import net.imglib2.realtransform.Translation2D; +import net.imglib2.realtransform.InvertibleWrapped2DTransformAs3D; import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.DoubleType; public class BigWarpTransform { + public static final String TPS = "Thin Plate Spline"; + public static final String AFFINE = "Affine"; + public static final String SIMILARITY = "Similarity"; + public static final String ROTATION = "Rotation"; + public static final String TRANSLATION = "Translation"; + + public static final String NO_MASK_INTERP = "NONE"; + public static final String MASK_INTERP = "LINEAR"; + public static final String SIM_MASK_INTERP = "SIMILARITY"; + public static final String ROT_MASK_INTERP = "ROTATION"; + private final int ndims; private final LandmarkTableModel tableModel; private String transformType; - + + private String maskInterpolationType; + private InvertibleRealTransform currentTransform; private double inverseTolerance = 0.5; private int maxIterations = 200; + private RealRandomAccessible> lambdaRaw; + + private RealRandomAccessible> lambda; + + private Converter,? extends RealType> lambdaConverter; + + private double lambdaConverterMin; + + private double lambdaConverterMax; + + private AbstractTransformSolver solver; + public BigWarpTransform( final LandmarkTableModel tableModel ) { - this( tableModel, TransformTypeSelectDialog.TPS ); + this( tableModel, TPS ); } public BigWarpTransform( final LandmarkTableModel tableModel, final String transformType ) @@ -77,11 +111,97 @@ public BigWarpTransform( final LandmarkTableModel tableModel, final String trans this.tableModel = tableModel; this.ndims = tableModel.getNumdims(); this.transformType = transformType; + this.maskInterpolationType = "NONE"; + updateSolver(); } - public void setTransformType( final String transformType ) + public void setMaskIntensityBounds( final double min, final double max ) { + + lambdaConverterMin = min; + lambdaConverterMax = max; + lambdaConverter = new Converter,RealType>() { + @Override + public void convert(RealType input, RealType output) { + final double v = input.getRealDouble(); + if( v <= min ) + output.setZero(); + else if ( v >= max ) + output.setOne(); + else + output.setReal((v - min) / (max - min)); + } + }; + updateLambda(); + } + + public BoundedRange getMaskIntensityBounds() { + return new BoundedRange(lambdaConverterMin, lambdaConverterMax, lambdaConverterMin, lambdaConverterMax); + } + + @SuppressWarnings("unchecked") + protected void updateLambda() { + + if( lambdaConverter != null && !(lambdaRaw instanceof PlateauSphericalMaskRealRandomAccessible) ) + { + lambda = Converters.convert2(lambdaRaw, + (Converter, RealType>)lambdaConverter, + (Supplier>)DoubleType::new); + } + else + lambda = lambdaRaw; + + if( solver instanceof MaskedTransformSolver ) + ((MaskedTransformSolver)solver).setMask(lambda); + else if( solver instanceof MaskedSimRotTransformSolver ) + ((MaskedSimRotTransformSolver)solver).setMask(lambda); + } + + public void setMaskInterpolationType( String maskType ) { - this.transformType = transformType; + this.maskInterpolationType = maskType; + updateSolver(); + } + + public String getMaskInterpolationType() + { + return maskInterpolationType; + } + + public boolean isMasked() + { + return maskInterpolationType.equals( MASK_INTERP ) || maskInterpolationType.equals( ROT_MASK_INTERP ) || maskInterpolationType.equals( SIM_MASK_INTERP ); + } + + public AbstractTransformSolver< ? > getSolver() + { + return solver; + } + + public void updateSolver() + { + if ( transformType.equals( TPS ) ) + { + solver = new TpsTransformSolver(); + } + else + { + solver = new ModelTransformSolver( getModelType() ); + } + + if ( maskInterpolationType.equals( MASK_INTERP ) ) + { + solver = new MaskedTransformSolver( solver, lambda ); + } + else if ( maskInterpolationType.equals( ROT_MASK_INTERP ) || maskInterpolationType.equals( SIM_MASK_INTERP ) ) + { + final double[] center = new double[ 3 ]; + if ( lambda instanceof PlateauSphericalMaskRealRandomAccessible ) + { + ( ( PlateauSphericalMaskRealRandomAccessible ) lambda ).getCenter().localize( center ); + } + + solver = new MaskedSimRotTransformSolver( tableModel.getNumdims(), solver, lambda, center, Interpolators.valueOf( maskInterpolationType ) ); + } } public void setInverseTolerance( double inverseTolerance ) @@ -104,42 +224,66 @@ public int getInverseMaxIterations() return maxIterations; } + public void setTransformType( final String transformType ) + { + this.transformType = transformType; + updateSolver(); + } + public String getTransformType() { return transformType; } + public RealRandomAccessible> getLambda( ) + { + return lambda; + } + + public void setLambda( final RealRandomAccessible< ? extends RealType< ? > > lambda ) + { + this.lambdaRaw = lambda; + updateLambda(); + } + + public boolean isNonlinear() + { + return getTransformType().equals(BigWarpTransform.TPS); + } + public InvertibleRealTransform getTransformation() { - return getTransformation( -1 ); + return getTransformation( -1, true ); } - + public InvertibleRealTransform getTransformation( final int index ) + { + return getTransformation( index, true ); + } + + public InvertibleRealTransform getTransformation( final boolean force3D ) + { + return getTransformation( -1, force3D ); + } + + public InvertibleRealTransform getTransformation( final int index, final boolean force3D ) { InvertibleRealTransform invXfm = null; - if( transformType.equals( TransformTypeSelectDialog.TPS )) + if( transformType.equals( TPS )) { - WrappedIterativeInvertibleRealTransform tpsXfm = new TpsTransformSolver().solve( tableModel ); + final WrappedIterativeInvertibleRealTransform tpsXfm = (WrappedIterativeInvertibleRealTransform< ? >) solver.solve( tableModel, index ); tpsXfm.getOptimzer().setMaxIters(maxIterations); tpsXfm.getOptimzer().setTolerance(inverseTolerance); invXfm = tpsXfm; } else { - final double[][] mvgPts; - final double[][] tgtPts; - - final int numActive = tableModel.numActive(); - mvgPts = new double[ ndims ][ numActive ]; - tgtPts = new double[ ndims ][ numActive ]; - tableModel.copyLandmarks( mvgPts, tgtPts ); // synchronized - - invXfm = new ModelTransformSolver( getModelType() ).solve(mvgPts, tgtPts); + invXfm = solver.solve(tableModel, index); } - if( tableModel.getNumdims() == 2 ) + if( force3D && tableModel.getNumdims() == 2 ) { - invXfm = new Wrapped2DTransformAs3D( invXfm ); + invXfm = new InvertibleWrapped2DTransformAs3D( invXfm ); } currentTransform = invXfm; @@ -148,21 +292,21 @@ public InvertibleRealTransform getTransformation( final int index ) public void fitModel( final Model model ) { - int numActive = tableModel.numActive(); + final int numActive = tableModel.numActive(); - double[][] mvgPts = new double[ ndims ][ numActive ]; - double[][] tgtPts = new double[ ndims ][ numActive ]; + final double[][] mvgPts = new double[ ndims ][ numActive ]; + final double[][] tgtPts = new double[ ndims ][ numActive ]; tableModel.copyLandmarks( mvgPts, tgtPts ); - double[] w = new double[ numActive ]; + final double[] w = new double[ numActive ]; Arrays.fill( w, 1.0 ); try { model.fit( mvgPts, tgtPts, w ); - } catch (NotEnoughDataPointsException e) { + } catch (final NotEnoughDataPointsException e) { e.printStackTrace(); - } catch (IllDefinedDataPointsException e) { + } catch (final IllDefinedDataPointsException e) { e.printStackTrace(); } } @@ -178,13 +322,13 @@ public Model getModelType() public AbstractAffineModel3D getModel3D() { switch( transformType ){ - case TransformTypeSelectDialog.AFFINE: + case AFFINE: return new AffineModel3D(); - case TransformTypeSelectDialog.SIMILARITY: + case SIMILARITY: return new SimilarityModel3D(); - case TransformTypeSelectDialog.ROTATION: + case ROTATION: return new RigidModel3D(); - case TransformTypeSelectDialog.TRANSLATION: + case TRANSLATION: return new TranslationModel3D(); } return null; @@ -193,13 +337,13 @@ public AbstractAffineModel3D getModel3D() public AbstractAffineModel2D getModel2D() { switch( transformType ){ - case TransformTypeSelectDialog.AFFINE: + case AFFINE: return new AffineModel2D(); - case TransformTypeSelectDialog.SIMILARITY: + case SIMILARITY: return new SimilarityModel2D(); - case TransformTypeSelectDialog.ROTATION: + case ROTATION: return new RigidModel2D(); - case TransformTypeSelectDialog.TRANSLATION: + case TRANSLATION: return new TranslationModel2D(); } return null; @@ -207,9 +351,9 @@ public AbstractAffineModel2D getModel2D() public InvertibleCoordinateTransform getCoordinateTransform() { - if( !transformType.equals( TransformTypeSelectDialog.TPS )) + if( !transformType.equals( TPS )) { - WrappedCoordinateTransform wct = (WrappedCoordinateTransform)( unwrap2d( getTransformation() )); + final WrappedCoordinateTransform wct = (WrappedCoordinateTransform)( unwrap2d( getTransformation() )); return wct.getTransform(); } return null; @@ -217,8 +361,8 @@ public InvertibleCoordinateTransform getCoordinateTransform() public InvertibleRealTransform unwrap2d( InvertibleRealTransform ixfm ) { - if( ixfm instanceof Wrapped2DTransformAs3D ) - return ((Wrapped2DTransformAs3D)ixfm).getTransform(); + if( ixfm instanceof InvertibleWrapped2DTransformAs3D ) + return ((InvertibleWrapped2DTransformAs3D)ixfm).getTransform(); else return ixfm; } @@ -226,105 +370,30 @@ public InvertibleRealTransform unwrap2d( InvertibleRealTransform ixfm ) /** * Returns an AffineTransform3D that represents the the transform if the transform * is linear, or is the affine part of the transform if it is non-linear. - * + * * Returns a valid transform even if the estimated transformation is 2d. - * + * * @return the affine transform */ public AffineTransform3D affine3d() { - AffineTransform3D out = new AffineTransform3D(); - if( transformType.equals( TransformTypeSelectDialog.TPS )) + final AffineTransform3D out = new AffineTransform3D(); + if( transformType.equals( TPS )) { - double[][] tpsAffine = getTpsBase().getAffine(); - double[] translation = getTpsBase().getTranslation(); - - double[] affine = new double[ 12 ]; - if( ndims == 2 ) - { - affine[ 0 ] = 1 + tpsAffine[ 0 ][ 0 ]; - affine[ 1 ] = tpsAffine[ 0 ][ 1 ]; - // dont set affine 2 - affine[ 3 ] = translation[ 0 ]; - - affine[ 4 ] = tpsAffine[ 1 ][ 0 ]; - affine[ 5 ] = 1 + tpsAffine[ 1 ][ 1 ]; - // dont set 6 - affine[ 7 ] = translation[ 1 ]; - - // dont set 8,9,11 - affine[ 10 ] = 1.0; - } - else - { - affine[ 0 ] = 1 + tpsAffine[ 0 ][ 0 ]; - affine[ 1 ] = tpsAffine[ 0 ][ 1 ]; - affine[ 2 ] = tpsAffine[ 0 ][ 2 ]; - affine[ 3 ] = translation[ 0 ]; - - affine[ 4 ] = tpsAffine[ 1 ][ 0 ]; - affine[ 5 ] = 1 + tpsAffine[ 1 ][ 1 ]; - affine[ 6 ] = tpsAffine[ 1 ][ 2 ]; - affine[ 7 ] = translation[ 1 ]; - - affine[ 8 ] = tpsAffine[ 2 ][ 0 ]; - affine[ 9 ] = tpsAffine[ 2 ][ 1 ]; - affine[ 10 ] = 1 + tpsAffine[ 2 ][ 2 ]; - affine[ 11 ] = translation[ 2 ]; - } - - out.set( affine ); + return affine3d( getTpsBase(), out ); } else { if( ndims == 2 ) { - AbstractAffineModel2D model2d = (AbstractAffineModel2D)getCoordinateTransform(); - - double[][] mtx = new double[2][3]; - model2d.toMatrix( mtx ); - - double[] affine = new double[ 12 ]; - affine[ 0 ] = mtx[ 0 ][ 0 ]; - affine[ 1 ] = mtx[ 0 ][ 1 ]; - // dont set affine 2 - affine[ 3 ] = mtx[ 0 ][ 2 ]; - - affine[ 4 ] = mtx[ 1 ][ 0 ]; - affine[ 5 ] = mtx[ 1 ][ 1 ]; - // dont set affine 6 - affine[ 7 ] = mtx[ 1 ][ 2 ]; - - // dont set affines 8,9,11 - affine[ 10 ] = 1.0; - - out.set( affine ); + final AbstractAffineModel2D model2d = (AbstractAffineModel2D)getCoordinateTransform(); + return affine3d( model2d, out ); } else if( ndims == 3 ) { - AbstractAffineModel3D model3d = (AbstractAffineModel3D)getCoordinateTransform(); - - double[][] mtx = new double[3][4]; - model3d.toMatrix( mtx ); - - double[] affine = new double[ 12 ]; - affine[ 0 ] = mtx[ 0 ][ 0 ]; - affine[ 1 ] = mtx[ 0 ][ 1 ]; - affine[ 2 ] = mtx[ 0 ][ 2 ]; - affine[ 3 ] = mtx[ 0 ][ 3 ]; - - affine[ 4 ] = mtx[ 1 ][ 0 ]; - affine[ 5 ] = mtx[ 1 ][ 1 ]; - affine[ 6 ] = mtx[ 1 ][ 2 ]; - affine[ 7 ] = mtx[ 1 ][ 3 ]; - - affine[ 8 ] = mtx[ 2 ][ 0 ]; - affine[ 9 ] = mtx[ 2 ][ 1 ]; - affine[ 10 ] = mtx[ 2 ][ 2 ]; - affine[ 11 ] = mtx[ 2 ][ 3 ]; - - out.set( affine ); + final AbstractAffineModel3D model3d = (AbstractAffineModel3D)getCoordinateTransform(); + return affine3d( model3d, out ); } else { @@ -332,41 +401,24 @@ else if( ndims == 3 ) return null; } } - return out; } - + public ThinPlateR2LogRSplineKernelTransform getTpsBase() { - ThinplateSplineTransform tps = getTps(); + final ThinplateSplineTransform tps = getTps(); if( tps == null ) return null; else { - // TODO add get method in ThinplateSplineTransform to avoid reflection here - // this will be possible with imglib2-realtransform-4.0.0 - final Class< ThinplateSplineTransform > c_tps = ThinplateSplineTransform.class; - try - { - final Field tpsField = c_tps.getDeclaredField( "tps" ); - tpsField.setAccessible( true ); - ThinPlateR2LogRSplineKernelTransform tpsbase = (ThinPlateR2LogRSplineKernelTransform)tpsField.get( tps ); - tpsField.setAccessible( false ); - - return tpsbase; - } - catch(Exception e ) - { - e.printStackTrace(); - return null; - } + return tps.getKernelTransform(); } } public ThinplateSplineTransform getTps() { - if( transformType.equals( TransformTypeSelectDialog.TPS )) + if( transformType.equals( TPS )) { - WrappedIterativeInvertibleRealTransform wiirt = (WrappedIterativeInvertibleRealTransform)( unwrap2d( getTransformation()) ); + final WrappedIterativeInvertibleRealTransform wiirt = (WrappedIterativeInvertibleRealTransform)( unwrap2d( getTransformation()) ); return ((ThinplateSplineTransform)wiirt.getTransform()); } return null; @@ -402,9 +454,9 @@ else if( currentTransform instanceof WrappedCoordinateTransform ) { s = (( WrappedCoordinateTransform ) currentTransform).getTransform().toString(); } - else if( currentTransform instanceof Wrapped2DTransformAs3D ) + else if( currentTransform instanceof InvertibleWrapped2DTransformAs3D ) { - s = ( ( Wrapped2DTransformAs3D) currentTransform ).toString(); + s = ( ( InvertibleWrapped2DTransformAs3D) currentTransform ).toString(); } else { @@ -417,9 +469,9 @@ else if( currentTransform instanceof Wrapped2DTransformAs3D ) public String affineToString() { String s = ""; - if( getTransformType().equals( TransformTypeSelectDialog.TPS )) + if( getTransformType().equals( TPS )) { - double[][] affine = affinePartOfTpsHC(); + final double[][] affine = affinePartOfTpsHC(); for( int r = 0; r < affine.length; r++ ) { s += Arrays.toString(affine[r]).replaceAll("\\[|\\]||\\s", ""); @@ -434,11 +486,11 @@ else if( currentTransform instanceof WrappedCoordinateTransform ) } /** - * Returns the affine part of the thin plate spline model, + * Returns the affine part of the thin plate spline model, * as a matrix in homogeneous coordinates. - * + * * double[i][:] contains the i^th row of the matrix. - * + * * @return the matrix as a double array */ public double[][] affinePartOfTpsHC() @@ -456,10 +508,10 @@ public double[][] affinePartOfTpsHC() { mtx = new double[3][4]; } - - ThinPlateR2LogRSplineKernelTransform tps = getTpsBase(); - double[][] tpsAffine = tps.getAffine(); - double[] translation = tps.getTranslation(); + + final ThinPlateR2LogRSplineKernelTransform tps = getTpsBase(); + final double[][] tpsAffine = tps.getAffine(); + final double[] translation = tps.getTranslation(); for( int r = 0; r < nr; r++ ) for( int c = 0; c < nc; c++ ) { @@ -471,7 +523,7 @@ else if( r == c ) { /* the affine doesn't contain the identity "part" of the affine. * i.e., the tps builds the affine A such that - * y = x + Ax + * y = x + Ax * o * y = ( A + I )x */ @@ -485,16 +537,36 @@ else if( r == c ) return mtx; } + public AffineGet affinePartOfTps() + { + AffineGet affine = null; + final double[][] affineArray = affinePartOfTpsHC(); + if ( affineArray.length == 2 ) + { + final AffineTransform2D affine2d = new AffineTransform2D(); + affine2d.set( affineArray ); + affine = affine2d; + } + else + { + final AffineTransform3D affine3d = new AffineTransform3D(); + affine3d.set( affineArray ); + affine = affine3d; + } + return affine; + } + public void initializeInverseParameters( BigWarpData bwData ) { - int N = bwData.targetSourceIndices.length; + final int N = bwData.numTargetSources(); double val; double highestResDim = 0; for( int i = 0; i < N; i++ ) { - SourceAndConverter< ? > src = bwData.sources.get( bwData.targetSourceIndices[ i ]); +// SourceAndConverter< ? > src = bwData.sources.get( bwData.targetSourceIndices[ i ]); + final SourceAndConverter< ? > src = bwData.getTargetSource( i ); final String name = src.getSpimSource().getName(); if( name.equals( "WarpMagnitudeSource" ) || @@ -517,4 +589,142 @@ public void initializeInverseParameters( BigWarpData bwData ) setInverseTolerance( 0.5 * highestResDim); } + public static AffineTransform3D affine3d( AbstractAffineModel2D model2d, AffineTransform3D out ) + { + final double[][] mtx = new double[2][3]; + model2d.toMatrix( mtx ); + + final double[] affine = new double[ 12 ]; + affine[ 0 ] = mtx[ 0 ][ 0 ]; + affine[ 1 ] = mtx[ 0 ][ 1 ]; + // dont set affine 2 + affine[ 3 ] = mtx[ 0 ][ 2 ]; + + affine[ 4 ] = mtx[ 1 ][ 0 ]; + affine[ 5 ] = mtx[ 1 ][ 1 ]; + // dont set affine 6 + affine[ 7 ] = mtx[ 1 ][ 2 ]; + + // dont set affines 8,9,11 + affine[ 10 ] = 1.0; + + out.set( affine ); + return out; + } + + public static AffineTransform2D affine2d( AbstractAffineModel2D model2d, AffineTransform2D out ) + { + final double[][] mtx = new double[2][3]; + model2d.toMatrix( mtx ); + + final double[] affine = new double[ 6 ]; + affine[ 0 ] = mtx[ 0 ][ 0 ]; + affine[ 1 ] = mtx[ 0 ][ 1 ]; + affine[ 2 ] = mtx[ 0 ][ 2 ]; + + affine[ 3 ] = mtx[ 1 ][ 0 ]; + affine[ 4 ] = mtx[ 1 ][ 1 ]; + affine[ 5 ] = mtx[ 1 ][ 2 ]; + + out.set( affine ); + return out; + } + + public static AffineTransform3D affine3d( AbstractAffineModel3D model3d, AffineTransform3D out ) + { + final double[][] mtx = new double[3][4]; + model3d.toMatrix( mtx ); + + final double[] affine = new double[ 12 ]; + affine[ 0 ] = mtx[ 0 ][ 0 ]; + affine[ 1 ] = mtx[ 0 ][ 1 ]; + affine[ 2 ] = mtx[ 0 ][ 2 ]; + affine[ 3 ] = mtx[ 0 ][ 3 ]; + + affine[ 4 ] = mtx[ 1 ][ 0 ]; + affine[ 5 ] = mtx[ 1 ][ 1 ]; + affine[ 6 ] = mtx[ 1 ][ 2 ]; + affine[ 7 ] = mtx[ 1 ][ 3 ]; + + affine[ 8 ] = mtx[ 2 ][ 0 ]; + affine[ 9 ] = mtx[ 2 ][ 1 ]; + affine[ 10 ] = mtx[ 2 ][ 2 ]; + affine[ 11 ] = mtx[ 2 ][ 3 ]; + + out.set( affine ); + return out; + } + + public static AffineTransform3D affine3d( ThinPlateR2LogRSplineKernelTransform tps, AffineTransform3D out ) + { + final double[][] tpsAffine = tps.getAffine(); + final double[] translation = tps.getTranslation(); + final int ndims = tps.getNumDims(); + + final double[] affine = new double[ 12 ]; + if( ndims == 2 ) + { + affine[ 0 ] = 1 + tpsAffine[ 0 ][ 0 ]; + affine[ 1 ] = tpsAffine[ 0 ][ 1 ]; + // dont set affine 2 + affine[ 3 ] = translation[ 0 ]; + + affine[ 4 ] = tpsAffine[ 1 ][ 0 ]; + affine[ 5 ] = 1 + tpsAffine[ 1 ][ 1 ]; + // dont set 6 + affine[ 7 ] = translation[ 1 ]; + + // dont set 8,9,11 + affine[ 10 ] = 1.0; + } + else + { + affine[ 0 ] = 1 + tpsAffine[ 0 ][ 0 ]; + affine[ 1 ] = tpsAffine[ 0 ][ 1 ]; + affine[ 2 ] = tpsAffine[ 0 ][ 2 ]; + affine[ 3 ] = translation[ 0 ]; + + affine[ 4 ] = tpsAffine[ 1 ][ 0 ]; + affine[ 5 ] = 1 + tpsAffine[ 1 ][ 1 ]; + affine[ 6 ] = tpsAffine[ 1 ][ 2 ]; + affine[ 7 ] = translation[ 1 ]; + + affine[ 8 ] = tpsAffine[ 2 ][ 0 ]; + affine[ 9 ] = tpsAffine[ 2 ][ 1 ]; + affine[ 10 ] = 1 + tpsAffine[ 2 ][ 2 ]; + affine[ 11 ] = translation[ 2 ]; + } + + out.set( affine ); + return out; + } + + public AffineGet toImglib2( Model< ? > model ) + { + if ( tableModel.getNumdims() == 2 ) + return toAffine2D( ( AbstractAffineModel2D ) model ); + else + return toAffine3D( ( AbstractAffineModel3D ) model ); + } + + public static AffineGet toAffine2D( AbstractAffineModel2D model ) + { + if( model instanceof TranslationModel2D ) + { + final TranslationModel2D t = (TranslationModel2D)model; + return new Translation2D( t.getTranslation() ); + } + else + { + // affine, rigid, and similarity + // TODO split out rigid? + final AffineTransform2D out = new AffineTransform2D(); + return affine2d( model, out ); + } + } + + public static AffineTransform3D toAffine3D( AbstractAffineModel3D model ) + { + return affine3d( model, new AffineTransform3D() ); + } } diff --git a/src/main/java/bigwarp/transforms/MaskedSimRotTransformSolver.java b/src/main/java/bigwarp/transforms/MaskedSimRotTransformSolver.java new file mode 100644 index 00000000..5c0ca5e8 --- /dev/null +++ b/src/main/java/bigwarp/transforms/MaskedSimRotTransformSolver.java @@ -0,0 +1,170 @@ +/*- + * #%L + * BigWarp plugin for Fiji. + * %% + * Copyright (C) 2015 - 2021 Howard Hughes Medical Institute. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package bigwarp.transforms; + +import java.util.Arrays; + +import bdv.viewer.animate.SimilarityModel3D; +import bigwarp.landmarks.LandmarkTableModel; +import mpicbg.models.AbstractAffineModel2D; +import mpicbg.models.AbstractAffineModel3D; +import mpicbg.models.RigidModel2D; +import mpicbg.models.RigidModel3D; +import mpicbg.models.SimilarityModel2D; +import net.imglib2.RealLocalizable; +import net.imglib2.RealRandomAccessible; +import net.imglib2.realtransform.AffineTransform2D; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.MaskedSimilarityTransform; +import net.imglib2.realtransform.MaskedSimilarityTransform.Interpolators; +import net.imglib2.realtransform.MaskedSimilarityTransform2D; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.RealTransformSequence; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; +import net.imglib2.type.numeric.RealType; + +public class MaskedSimRotTransformSolver> extends AbstractTransformSolver< WrappedIterativeInvertibleRealTransform< ? >> +{ + private final AbstractTransformSolver baseSolver; + private final ModelTransformSolver interpSolver; + private RealRandomAccessible lambda; + private final double[] center; + private final Interpolators interp; + private final int ndims; + + public MaskedSimRotTransformSolver( AbstractTransformSolver solver, RealRandomAccessible lambda, double[] center, Interpolators interp ) + { + this( 3, solver, lambda, center, interp ); + } + + public MaskedSimRotTransformSolver( int nd, AbstractTransformSolver solver, RealRandomAccessible lambda, double[] center, Interpolators interp ) + { + this.ndims = nd; + this.lambda = lambda; + this.center = center; + this.interp = interp; + baseSolver = solver; + + if( interp == Interpolators.SIMILARITY ) + if( nd == 2 ) + interpSolver = new ModelTransformSolver( new SimilarityModel2D() ); + else + interpSolver = new ModelTransformSolver( new SimilarityModel3D() ); + else + if( nd == 2 ) + interpSolver = new ModelTransformSolver( new RigidModel2D() ); + else + interpSolver = new ModelTransformSolver( new RigidModel3D() ); + + System.out.println( this ); + } + + public void setMask( final RealRandomAccessible lambda ) + { + this.lambda = lambda; + } + + @Override + public String toString() + { + return String.format( "MaskedSolver. center %s; interp: %s ", Arrays.toString( center ), this.interp.toString() ); + } + + public void setCenter( double[] c ) + { + // assume center is always longer than c + System.arraycopy( c, 0, center, 0, c.length ); + } + + public void setCenter( RealLocalizable c ) + { + c.localize( center ); + } + + @Override + @SuppressWarnings("rawtypes") + public WrappedIterativeInvertibleRealTransform solve( final double[][] mvgPts, final double[][] tgtPts ) + { +// WrappedCoordinateTransform simXfm = interpSolver.solve( mvgPts, tgtPts ); + final WrappedCoordinateTransform simXfm = interpSolver.solve( tgtPts, mvgPts ); + + RealTransform msim; + if ( ndims == 2 ) + { + final AffineTransform2D sim = new AffineTransform2D(); + BigWarpTransform.affine2d( ( AbstractAffineModel2D ) interpSolver.getModel(), sim ); + msim = new MaskedSimilarityTransform2D( sim, lambda, center, interp ); + } + else + { + final AffineTransform3D sim = BigWarpTransform.toAffine3D( ( AbstractAffineModel3D ) interpSolver.getModel() ); + msim = new MaskedSimilarityTransform( sim, lambda, center, interp ); + } + +// final double[][] xfmMvg = transformPoints( msim, mvgPts ); +// final InvertibleRealTransform baseTransform = baseSolver.solve( xfmMvg, tgtPts ); + + final double[][] xfmTgt = transformPoints( msim, tgtPts ); + final InvertibleRealTransform baseTransform = baseSolver.solve( mvgPts, xfmTgt ); + + final RealTransformSequence seq = new RealTransformSequence(); + seq.add( msim ); + seq.add( baseTransform ); + + return new WrappedIterativeInvertibleRealTransform<>( MaskedTransformSolver.wrap( seq, lambda ) ); + } + + @Override + public WrappedIterativeInvertibleRealTransform solve( + final LandmarkTableModel landmarkTable ) + { + final int numActive = landmarkTable.numActive(); + final int nd = landmarkTable.getNumdims(); + final double[][] mvgPts = new double[ nd ][ numActive ]; + final double[][] tgtPts = new double[ nd ][ numActive ]; + landmarkTable.copyLandmarks( mvgPts, tgtPts ); // synchronized + return solve( mvgPts, tgtPts ); + } + + private static double[][] transformPoints( RealTransform xfm, double[][] pts ) + { + final int nd = pts.length; + final int np = pts[0].length; + + final double[] tmp = new double[nd]; + final double[][] out = new double[ nd ][ np ]; + + for( int i = 0; i < np; i++ ) + { + for( int d = 0; d < nd; d++ ) + tmp[d] = pts[d][i]; + + xfm.apply(tmp,tmp); + + for( int d = 0; d < nd; d++ ) + out[d][i] = tmp[d]; + } + + return out; + } +} diff --git a/src/main/java/bigwarp/transforms/MaskedTransformSolver.java b/src/main/java/bigwarp/transforms/MaskedTransformSolver.java new file mode 100644 index 00000000..7382bd59 --- /dev/null +++ b/src/main/java/bigwarp/transforms/MaskedTransformSolver.java @@ -0,0 +1,81 @@ +/*- + * #%L + * BigWarp plugin for Fiji. + * %% + * Copyright (C) 2015 - 2021 Howard Hughes Medical Institute. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package bigwarp.transforms; + +import bigwarp.landmarks.LandmarkTableModel; +import jitk.spline.ThinPlateR2LogRSplineKernelTransform; +import net.imglib2.RandomAccessible; +import net.imglib2.RealRandomAccessible; +import net.imglib2.realtransform.SpatiallyInterpolatedRealTransform; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.RealTransformSequence; +import net.imglib2.realtransform.ThinplateSplineTransform; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; +import net.imglib2.type.numeric.RealType; + +public class MaskedTransformSolver, S extends AbstractTransformSolver > extends AbstractTransformSolver< WrappedIterativeInvertibleRealTransform< ? >> +{ + private final S solver; + + private RealRandomAccessible lambda; + + public MaskedTransformSolver( final S solver, final RealRandomAccessible lambda ) + { + this.lambda = lambda; + this.solver = solver; + } + + public S getSolver() + { + return solver; + } + + public void setMask( final RealRandomAccessible lambda ) + { + this.lambda = lambda; + } + + @Override + public WrappedIterativeInvertibleRealTransform solve( final double[][] mvgPts, final double[][] tgtPts ) + { + return wrap( solver.solve( mvgPts, tgtPts ), lambda ); + } + + @Override + public WrappedIterativeInvertibleRealTransform< ? > solve( final LandmarkTableModel landmarkTable ) + { + return wrap( solver.solve( landmarkTable ), lambda ); + } + + @Override + public WrappedIterativeInvertibleRealTransform< ? > solve( final LandmarkTableModel landmarkTable, final int indexChanged ) + { + return wrap( solver.solve( landmarkTable, indexChanged ), lambda ); + } + + public static > WrappedIterativeInvertibleRealTransform< ? > wrap( RealTransform base, RealRandomAccessible< T > lambda ) + { + final RealTransformSequence identity = new RealTransformSequence(); + return new WrappedIterativeInvertibleRealTransform<>( + new SpatiallyInterpolatedRealTransform< T >( base, identity, lambda ) ); + } +} diff --git a/src/main/java/bigwarp/transforms/ModelTransformSolver.java b/src/main/java/bigwarp/transforms/ModelTransformSolver.java index 32238965..e98c0f7d 100644 --- a/src/main/java/bigwarp/transforms/ModelTransformSolver.java +++ b/src/main/java/bigwarp/transforms/ModelTransformSolver.java @@ -28,18 +28,23 @@ import mpicbg.models.Model; import mpicbg.models.NotEnoughDataPointsException; -public class ModelTransformSolver implements TransformSolver< WrappedCoordinateTransform > +public class ModelTransformSolver extends AbstractTransformSolver< WrappedCoordinateTransform > { - private Model< ? > model; + private final Model< ? > model; public ModelTransformSolver( Model< ? > model ) { this.model = model; } - + + public Model< ? > getModel() + { + return model; + } + public WrappedCoordinateTransform solve( final double[][] mvgPts, final double[][] tgtPts ) { - double[] w = new double[ mvgPts[ 0 ].length ]; + final double[] w = new double[ mvgPts[ 0 ].length ]; Arrays.fill( w, 1.0 ); try { diff --git a/src/main/java/bigwarp/transforms/NgffTransformations.java b/src/main/java/bigwarp/transforms/NgffTransformations.java new file mode 100644 index 00000000..2e386567 --- /dev/null +++ b/src/main/java/bigwarp/transforms/NgffTransformations.java @@ -0,0 +1,512 @@ +package bigwarp.transforms; + +import java.io.FileWriter; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.janelia.saalfeldlab.n5.Compression; +import org.janelia.saalfeldlab.n5.N5Exception; +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.N5URI; +import org.janelia.saalfeldlab.n5.N5Writer; +import org.janelia.saalfeldlab.n5.imglib2.N5Utils; +import org.janelia.saalfeldlab.n5.universe.N5Factory; +import org.janelia.saalfeldlab.n5.universe.metadata.axes.Axis; +import org.janelia.saalfeldlab.n5.universe.metadata.axes.CoordinateSystem; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.Common; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.graph.TransformGraph; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.AffineCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.CoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.CoordinateTransformAdapter; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.DisplacementFieldCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.IdentityCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.InvertibleCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.ReferencedCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.ScaleCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.SequenceCoordinateTransform; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.TranslationCoordinateTransform; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; + +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.realtransform.AffineGet; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.ScaleGet; +import net.imglib2.realtransform.TranslationGet; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.util.Pair; +import net.imglib2.util.ValuePair; + + +public class NgffTransformations +{ + + @SuppressWarnings("unchecked") + public static < T extends RealTransform> T open( final N5Reader n5, final String dataset, final String input, final String output ) + { + // TODO error handling + final TransformGraph g = Common.openGraph( n5, dataset ); + return (T)g.path( input, output ).get().totalTransform( n5, g ); + } + + public static RealTransform open( final String url ) + { + final Pair< CoordinateTransform< ? >, N5Reader > pair = openTransformN5( url ); + return pair.getA().getTransform( pair.getB() ); + } + + public static InvertibleRealTransform openInvertible(final String url) { + + final Pair, N5Reader> pair = openTransformN5(url); + final CoordinateTransform ct = pair.getA(); + if (ct instanceof InvertibleCoordinateTransform) + return ((InvertibleCoordinateTransform)ct).getInvertibleTransform(pair.getB()); + else + return null; + } + + public static RealTransform findFieldTransformFirst(final N5Reader n5, final String group) { + + final String normGrp = N5URI.normalizeGroupPath(group); + System.out.println( "nnrmGrp: " + normGrp ); + + final CoordinateTransform[] transforms = n5.getAttribute(group, CoordinateTransform.KEY, CoordinateTransform[].class); + if (transforms == null) + return null; + + if( transforms.length == 1 ) + { + + } + + boolean found = false; + for (final CoordinateTransform ct : transforms) { + System.out.println(ct); + final String nrmInput = N5URI.normalizeGroupPath(ct.getInput()); + System.out.println( "nrmInput: " + nrmInput ); + if (nrmInput.equals(normGrp)) { + found = true; + System.out.println( "found: " + ct ); + } + + } + + return null; + } + + public static RealTransform findFieldTransformStrict(final N5Reader n5, final String group, final String output ) { + + final String normGrp = N5URI.normalizeGroupPath(group); + System.out.println( "nnrmGrp: " + normGrp ); + + final CoordinateTransform[] transforms = n5.getAttribute(group, CoordinateTransform.KEY, CoordinateTransform[].class); + if (transforms == null) + return null; + + final boolean found = false; + for (final CoordinateTransform ct : transforms) { + System.out.println(ct); + final String nrmInput = N5URI.normalizeGroupPath(ct.getInput()); + System.out.println( "nrmInput: " + nrmInput ); + if (nrmInput.equals(normGrp) && ct.getOutput().equals(output) ) { + System.out.println( "found: " + ct ); + return ct.getTransform(n5); + } + } + return null; + } + + /** + * Finds a candidate transformation in the n5 attributes at the given url and returns the + * complete URI for that transformation if found, otherwise null. + * + * @param url the base url + * @return the complete n5uri for a transformation, or null + */ + public static String detectTransforms( final String url ) + { + // detect transformations + final N5URI uri; + try { + uri = new N5URI(url); + } catch (final URISyntaxException e) { + return null; + } + +// final String grp = ( uri.getGroupPath() != null ) ? uri.getGroupPath() : ""; +// final String attr = ( uri.getAttributePath() != null && !uri.getAttributePath().equals("/")) ? uri.getAttributePath() : "coordinateTransformations[0]"; + + if( isValidTransformUri( url )) + return url; + + if (!uri.getAttributePath().equals("coordinateTransformations[0]")) { + + String defaultUri; + try { + defaultUri = N5URI.from(uri.getContainerPath(), uri.getGroupPath(), "coordinateTransformations[0]").toString(); + if( isValidTransformUri( defaultUri )) + return defaultUri; + } catch (final URISyntaxException e) { } + } + +// final N5Reader n5 = new N5Factory().gsonBuilder(gsonBuilder()).openReader(uri.getContainerPath()); +// final CoordinateTransform[] cts = n5.getAttribute(grp, attr, CoordinateTransform[].class); +// +// if (cts != null && cts.length > 0) +// try { +// return N5URI.from(uri.getContainerPath(), grp, "coordinateTransformations[0]").toString(); +// } catch (final URISyntaxException e) {} + + + return null; + } + + private static boolean isValidTransformUri(final String uri) { + + final Pair, N5Reader> out = openTransformN5(uri); + if (out != null && out.getA() != null) + return true; + + return false; + } + + public static Pair,N5Reader> openTransformN5( final String url ) + { + try + { + final N5URI n5url = new N5URI( url ); + final String loc = n5url.getContainerPath(); + if( loc.endsWith( ".json" )) + { + return new ValuePair<>( openJson( url ), null); + } + else + { + final N5Reader n5 = new N5Factory().gsonBuilder( gsonBuilder() ).openReader( loc ); + final String dataset = n5url.getGroupPath() != null ? n5url.getGroupPath() : "/"; + final String attribute = n5url.getAttributePath(); +// final String attribute = n5url.getAttributePath() != null ? n5url.getAttributePath() : "coordinateTransformations[0]"; + + try { + final CoordinateTransform ct = n5.getAttribute(dataset, attribute, CoordinateTransform.class); + return new ValuePair<>( ct, n5 ); + } catch( N5Exception | ClassCastException e ) {} + + try { + return openReference( url, n5, dataset, attribute ); // try to open a reference + } catch( N5Exception | ClassCastException e ) {} + +// final CoordinateTransform nct = CoordinateTransform.create(ct); +// return new ValuePair<>( nct, n5 ); + } + } + catch ( final URISyntaxException e ) { } + + return null; + } + + public static Pair,N5Reader> openReference( final String url, final N5Reader n5, final String dataset, final String attribute) { + + final ReferencedCoordinateTransform ref = n5.getAttribute(dataset, attribute, ReferencedCoordinateTransform.class); + if( ref == null ) + return null; + else if( url != null && url.equals( ref.getUrl() )) + return null; // avoid self-reference + else + return openTransformN5( ref.getUrl()); + } + + public static CoordinateTransform openJson( final String url ) + { + final Path path = Paths.get( url ); + String string; + try + { + string = new String(Files.readAllBytes(path)); + } + catch ( final IOException e ) + { + return null; + } + + final Gson gson = gsonBuilder().create(); + final JsonElement elem = gson.fromJson( string, JsonElement.class ); + +// final CoordinateTransformation ct = gson.fromJson( elem.getAsJsonArray().get( 0 ), CoordinateTransformation.class ); + final CoordinateTransform ct = gson.fromJson( elem, CoordinateTransform.class ); + if( ct != null ) + { + final CoordinateTransform< ? > nct = CoordinateTransform.create( ct ); + return nct; + } else if( elem.isJsonObject()) { + // TODO figure out what should be returned here + final String refUrl = elem.getAsJsonObject().get("url").getAsString(); + if( url.equals( refUrl )) + return null; //avoid self-reference + else + return openTransformN5( refUrl ).getA(); + } + return null; + } + + public static void save( final String jsonFile, final CoordinateTransform transform ) + { + final GsonBuilder gb = new GsonBuilder(); + gb.registerTypeAdapter(CoordinateTransform.class, new CoordinateTransformAdapter() ); + final Gson gson = gb.create(); + try( FileWriter writer = new FileWriter( jsonFile )) + { + gson.toJson( transform, writer ); + writer.close(); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + } + + public static < T extends NativeType< T > & RealType< T > > DisplacementFieldCoordinateTransform save( + final N5Writer n5, + final String dataset, + final RandomAccessibleInterval< T > dfield, + final String inName, + final String outName, + final double[] spacing, + final double[] offset, + final String unit, + final int[] blockSize, + final Compression compression, + final int nThreads ) + { + final String[] axisNames = ( spacing.length == 2 ) ? new String[] { "x", "y" } : new String[] { "x", "y", "z"}; + final CoordinateSystem inputCoordinates = new CoordinateSystem( inName, Axis.space( unit, axisNames ) ); + final CoordinateSystem outputCoordinates = new CoordinateSystem( outName, Axis.space( unit, axisNames ) ); + + final ThreadPoolExecutor threadPool = new ThreadPoolExecutor( nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue() ); + final DisplacementFieldCoordinateTransform ngffDfield = saveDisplacementFieldNgff( n5, dataset, "/", inputCoordinates, outputCoordinates, + dfield, spacing, offset, blockSize, compression, threadPool ); + + return ngffDfield; + +// final DisplacementFieldCoordinateTransform ngffDfield = new DisplacementFieldCoordinateTransform( "", dataset, "linear" ); +// return ngffDfield; +// N5DisplacementField.addCoordinateTransformations( n5, "/", ngffDfield ); + } + + public static void addCoordinateTransformations( final N5Writer n5, final String groupPath, final CoordinateTransform transform ) { + + final CoordinateTransform[] cts = n5.getAttribute(groupPath, CoordinateTransform.KEY, CoordinateTransform[].class); + final CoordinateTransform[] ctsOut; + if (cts == null) + ctsOut = new CoordinateTransform[] { transform }; + else + { + ctsOut = new CoordinateTransform[cts.length + 1]; + System.arraycopy(cts, 0, ctsOut, 0, cts.length); + ctsOut[ ctsOut.length - 1 ] = transform; + } + + if( !n5.exists(groupPath)) + n5.createGroup(groupPath); + + n5.setAttribute(groupPath, CoordinateTransform.KEY, ctsOut); + } + + public static final & RealType> DisplacementFieldCoordinateTransform saveDisplacementFieldNgff( + final N5Writer n5Writer, + final String dataset, + final String metadataDataset, + final CoordinateSystem inputCoordinates, + final CoordinateSystem outputCoordinates, + final RandomAccessibleInterval dfield, + final double[] spacing, + final double[] offset, + final int[] blockSize, + final Compression compression, + ExecutorService exec ) { + + int[] vecBlkSz; + if( blockSize.length >= dfield.numDimensions() ) + vecBlkSz = blockSize; + else { + vecBlkSz = new int[ blockSize.length + 1 ]; + vecBlkSz[ 0 ] = (int)dfield.dimension( 0 ); + for( int i = 1; i < vecBlkSz.length; i++ ) + { + vecBlkSz[ i ] = blockSize[ i - 1 ]; + } + } + + try + { + N5Utils.save(dfield, n5Writer, dataset, vecBlkSz, compression, exec); + } + catch ( final InterruptedException | ExecutionException e ) + { + e.printStackTrace(); + } + + final String vecFieldCsName = inputCoordinates.getName(); + final CoordinateSystem[] cs = new CoordinateSystem[] { + createVectorFieldCoordinateSystem( vecFieldCsName, inputCoordinates ) }; + n5Writer.setAttribute(dataset, CoordinateSystem.KEY, cs); + + final CoordinateTransform[] ct = new CoordinateTransform[] { + createTransformation( "", spacing, offset, dataset, cs[0] ) }; + n5Writer.setAttribute(dataset, CoordinateTransform.KEY, ct ); + + return new DisplacementFieldCoordinateTransform( "", dataset, "linear", + inputCoordinates.getName(), outputCoordinates.getName() ); + } + + + public static CoordinateTransform createTransformation(final String name, + final double[] scale, final double[] offset, + final String dataset, final CoordinateSystem output) { + + CoordinateTransform ct; + if ((scale != null || allOnes(scale)) && (offset != null || allZeros(offset))) { + ct = new SequenceCoordinateTransform(name, dataset, output.getName(), + new CoordinateTransform[]{ + new ScaleCoordinateTransform(prepend(1, scale)), + new TranslationCoordinateTransform(prepend(0, offset))}); + } else if (offset != null || !allZeros(offset)) + ct = new TranslationCoordinateTransform(name, dataset, output.getName(), prepend(0, offset)); + else if (scale != null || !allOnes(scale)) + ct = new ScaleCoordinateTransform(name, dataset, output.getName(), prepend(1, scale)); + else + ct = new IdentityCoordinateTransform(name, dataset, output.getName()); + + return ct; + } + + private static boolean allZeros(final double[] x) { + + for (int i = 0; i < x.length; i++) + if (x[i] != 0.0) + return false; + + return true; + } + + private static boolean allOnes(final double[] x) { + + for (int i = 0; i < x.length; i++) + if (x[i] != 1.0) + return false; + + return true; + } + + private static double[] prepend(double val, double[] array) { + + final double[] out = new double[array.length + 1]; + out[0] = val; + for (int i = 1; i < out.length; i++) { + out[i] = array[i - 1]; + } + return out; + } + + public static CoordinateSystem createVectorFieldCoordinateSystem(final String name, final CoordinateSystem input) { + + final Axis[] vecAxes = new Axis[input.getAxes().length + 1]; + vecAxes[0] = new Axis("displacement", "d", null, true); + for (int i = 1; i < vecAxes.length; i++) + vecAxes[i] = input.getAxes()[i - 1]; + + return new CoordinateSystem(name, vecAxes); + } + + + /** + * returns null if no permutation needed + * + * @param cs + * @return a permutation if needed + * @throws Exception + */ + public static final int[] vectorAxisLastNgff( final CoordinateSystem cs ) throws Exception { + + final Axis[] axes = cs.getAxes(); + final int n = axes.length; + + if ( axes[ n - 1 ].getType().equals( Axis.DISPLACEMENT)) + return null; + else + { + int vecDim = -1; +// for( int i = 0; i < n; i++ ) +// { +// vecDim = i; +// break; +// } +// +// if( vecDim < 0 ) +// return null; + + final int[] permutation = new int[ n ]; + + int k = 0; + for( int i = 0; i < n; i++ ) + { + if ( axes[ i ].getType().equals( Axis.DISPLACEMENT)) + vecDim = i; + else + permutation[i] = k++; + } + + // did not find a matching axis + if( vecDim < 0 ) + return null; + + permutation[vecDim] = n-1; + return permutation; + } + } + + /** + * @throws Exception the exception + */ + public static final int[] vectorAxisLastNgff( + final N5Reader n5, final String dataset ) throws Exception { + + // TODO move to somewhere more central + final TransformGraph g = Common.openGraph( n5, dataset ); + + // TODO need to be smarter about which coordinate system to get + final CoordinateSystem cs = g.getCoordinateSystems().getCollection().iterator().next(); + return vectorAxisLastNgff( cs ); + } + + public static GsonBuilder gsonBuilder() { + + final GsonBuilder gb = new GsonBuilder(); + gb.registerTypeAdapter(CoordinateTransform.class, new CoordinateTransformAdapter()); + return gb; + } + + public static CoordinateTransform createAffine(AffineGet transform) { + + if (transform instanceof TranslationGet) { + return new TranslationCoordinateTransform(((TranslationGet)transform).getTranslationCopy()); + } else if (transform instanceof ScaleGet) { + return new ScaleCoordinateTransform(((ScaleGet)transform).getScaleCopy()); + } else { + return new AffineCoordinateTransform(transform.getRowPackedCopy()); + } + } + +} diff --git a/src/main/java/bigwarp/transforms/SlicerTransformations.java b/src/main/java/bigwarp/transforms/SlicerTransformations.java new file mode 100644 index 00000000..37881a24 --- /dev/null +++ b/src/main/java/bigwarp/transforms/SlicerTransformations.java @@ -0,0 +1,152 @@ +package bigwarp.transforms; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +import org.janelia.saalfeldlab.n5.Compression; +import org.janelia.saalfeldlab.n5.N5Exception; +import org.janelia.saalfeldlab.n5.N5Writer; +import org.janelia.saalfeldlab.n5.hdf5.N5HDF5Writer; +import org.janelia.saalfeldlab.n5.imglib2.N5DisplacementField; +import org.janelia.saalfeldlab.n5.imglib2.N5Utils; +import org.janelia.saalfeldlab.n5.universe.N5Factory; + +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.realtransform.AffineGet; +import net.imglib2.realtransform.AffineTransform2D; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.view.IntervalView; +import net.imglib2.view.Views; + +public class SlicerTransformations +{ + + public static final String AFFINE_ATTR = "Transformation_Matrix"; + + /* + * Saves an arbitrary {@link RealTransform} as a deformation field + * into a specified n5 dataset using slicer's convention. + * + * @param the type parameter + * @param n5Writer the n5 writer + * @param dataset the dataset path + * @param dfield the displacement field + * @param blockSize the block size + * @param compression the compression type + * @param exec the executor service + * @throws IOException the exception + */ + public static final & RealType> void saveDisplacementField( + final N5Writer n5Writer, + final String dataset, + final RandomAccessibleInterval dfield, + final int[] blockSize, + final Compression compression, + final ExecutorService exec ) { + + int[] vecBlkSz; + if( blockSize.length >= dfield.numDimensions() ) + vecBlkSz = blockSize; + else { + vecBlkSz = new int[ blockSize.length + 1 ]; + vecBlkSz[ 0 ] = (int)dfield.dimension( 0 ); + for( int i = 1; i < vecBlkSz.length; i++ ) + { + vecBlkSz[ i ] = blockSize[ i - 1 ]; + } + } + + /* Converts [3,X,Y,Z] to [X,Y,Z,3] displacement field */ + final RandomAccessibleInterval< T > dfieldPerm = Views.moveAxis( dfield, 0, 3 ); + try + { + N5Utils.save( dfieldPerm, n5Writer, dataset, vecBlkSz, compression, exec ); + } + catch ( final N5Exception e ) + { + e.printStackTrace(); + } + catch ( final InterruptedException e ) + { + e.printStackTrace(); + } + catch ( final ExecutionException e ) + { + e.printStackTrace(); + } + } + + public static final int[] permXYZ = new int[] { 2, 1, 0 }; + public static final int[] perm = new int[] { 1, 2, 3, 0 }; + + /** + * Converts a [3,X,Y,Z] displacement field to a [3,Z,Y,X] displacement field + * + * @param the type + * @param df the displacement field + * @return a permuted displacement field + */ + public static final & RealType> RandomAccessibleInterval dxyz2dzyx( final RandomAccessibleInterval df ) + { + final IntervalView< T > dx = Views.hyperSlice( df, 0, 0 ); + final IntervalView< T > dy = Views.hyperSlice( df, 0, 1 ); + final IntervalView< T > dz = Views.hyperSlice( df, 0, 2 ); + + // besides permuting the axes, we also need to re-order the components of the displacement vectors + final IntervalView< T > dxp = N5DisplacementField.permute( dx, permXYZ ); + final IntervalView< T > dyp = N5DisplacementField.permute( dy, permXYZ ); + final IntervalView< T > dzp = N5DisplacementField.permute( dz, permXYZ ); + final RandomAccessibleInterval< T > dfp = Views.stack( dxp, dyp, dzp ); + + return N5DisplacementField.permute( dfp, perm ); + } + + public static final & RealType> void saveAffine( final N5Writer n5Writer, final String dataset, final AffineGet affine) + { + try + { + final double[][] mtx; + if ( affine instanceof AffineTransform3D ) + { + final AffineTransform3D a3d = ( AffineTransform3D ) affine; + mtx = new double[ 4 ][ 4 ]; + a3d.toMatrix( mtx ); + } + else if ( affine instanceof AffineTransform2D ) + { + final AffineTransform2D a2d = ( AffineTransform2D ) affine; + mtx = new double[ 3 ][ 3 ]; + a2d.toMatrix( mtx ); + } + else { + // src and tgt dims always the same for AffineGets + final int nd = affine.numTargetDimensions(); + mtx = new double[nd][nd]; + for( int i = 0; i < nd; i++ ) + for( int j = 0; j < nd; j++ ) + mtx[i][j] = affine.get( i, j ); + } + n5Writer.setAttribute( dataset, AFFINE_ATTR, mtx ); + } + catch ( final N5Exception e ) {} + } + + public static void main( final String[] args ) throws IOException + { + final double[][] mtx = new double[4][4]; + mtx[0][0] = 2; + mtx[1][1] = 3; + mtx[2][2] = 4; + mtx[3][3] = 1; + + final N5HDF5Writer h5 = new N5Factory().openHDF5Writer( "/home/john/tmp/mri-stack-landmarks-df-slicer.h5" ); + h5.setAttribute( "dfield", AFFINE_ATTR, mtx ); + h5.close(); + + System.out.println( "done" ); + } + +} diff --git a/src/main/java/bigwarp/transforms/TpsTransformSolver.java b/src/main/java/bigwarp/transforms/TpsTransformSolver.java index 256c7a86..ffdf0e73 100644 --- a/src/main/java/bigwarp/transforms/TpsTransformSolver.java +++ b/src/main/java/bigwarp/transforms/TpsTransformSolver.java @@ -21,16 +21,12 @@ */ package bigwarp.transforms; -import bigwarp.landmarks.LandmarkTableModel; import jitk.spline.ThinPlateR2LogRSplineKernelTransform; import net.imglib2.realtransform.ThinplateSplineTransform; import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; -public class TpsTransformSolver implements TransformSolver< WrappedIterativeInvertibleRealTransform< ? >> +public class TpsTransformSolver extends AbstractTransformSolver< WrappedIterativeInvertibleRealTransform< ? >> { - private double[][] mvgPts; - private double[][] tgtPts; - public WrappedIterativeInvertibleRealTransform solve( final double[][] mvgPts, final double[][] tgtPts ) { return new WrappedIterativeInvertibleRealTransform( @@ -38,32 +34,4 @@ public WrappedIterativeInvertibleRealTransform solve( final double[][] mvgPts new ThinPlateR2LogRSplineKernelTransform( tgtPts.length, tgtPts, mvgPts ))); } - public WrappedIterativeInvertibleRealTransform solve( - final LandmarkTableModel landmarkTable ) - { - return solve( landmarkTable, -1 ); - } - - public WrappedIterativeInvertibleRealTransform solve( - final LandmarkTableModel landmarkTable, final int indexChanged ) - { - synchronized( landmarkTable ) - { - int numActive = landmarkTable.numActive(); - int ndims = landmarkTable.getNumdims(); - - if( mvgPts == null || mvgPts[0].length != numActive ) - { - mvgPts = new double[ ndims ][ numActive ]; - tgtPts = new double[ ndims ][ numActive ]; - landmarkTable.copyLandmarks( mvgPts, tgtPts ); - } - else if( indexChanged >= 0 ) - { - landmarkTable.copyLandmarks( indexChanged, mvgPts, tgtPts ); - } - } - - return solve( mvgPts, tgtPts ); - } } diff --git a/src/main/java/bigwarp/transforms/io/LandmarkWriterJson.java b/src/main/java/bigwarp/transforms/io/LandmarkWriterJson.java new file mode 100644 index 00000000..b1f7e53b --- /dev/null +++ b/src/main/java/bigwarp/transforms/io/LandmarkWriterJson.java @@ -0,0 +1,21 @@ +package bigwarp.transforms.io; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; + +import bigwarp.landmarks.LandmarkTableModel; + +public class LandmarkWriterJson { + + private final Gson gson; + + public LandmarkWriterJson() { + gson = new Gson(); + } + + public JsonElement toJson(final LandmarkTableModel ltm) { + + return ltm.toJson(); + } + +} diff --git a/src/main/java/bigwarp/transforms/io/TransformWriterJson.java b/src/main/java/bigwarp/transforms/io/TransformWriterJson.java new file mode 100644 index 00000000..302ab48c --- /dev/null +++ b/src/main/java/bigwarp/transforms/io/TransformWriterJson.java @@ -0,0 +1,121 @@ +package bigwarp.transforms.io; + + +import bigwarp.BigWarp; +import bigwarp.BigwarpSettings; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.source.PlateauSphericalMaskRealRandomAccessible; +import bigwarp.transforms.BigWarpTransform; +import com.google.gson.JsonObject; + +import bdv.util.BoundedRange; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class TransformWriterJson { + + public static void write(LandmarkTableModel ltm, BigWarpTransform bwTransform, File f ) { + + final JsonObject transformObj = write( ltm, bwTransform ); + + try { + final Path path = Paths.get(f.getCanonicalPath()); + final OpenOption[] options = new OpenOption[]{StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE}; + final Writer writer = Channels.newWriter(FileChannel.open(path, options), StandardCharsets.UTF_8.name()); + BigwarpSettings.gson.toJson(transformObj, writer); + writer.flush(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + public static void read( final File f, final BigWarp bw ) + { + try + { + final Path path = Paths.get(f.getCanonicalPath()); + final OpenOption[] options = new OpenOption[]{StandardOpenOption.READ}; + final Reader reader = Channels.newReader(FileChannel.open(path, options), StandardCharsets.UTF_8.name()); + final JsonObject json = BigwarpSettings.gson.fromJson( reader, JsonObject.class ); + + read( bw, json ); + + } catch ( final IOException e ) { } + } + + public static JsonObject write(LandmarkTableModel ltm, BigWarpTransform bwTransform) { + + final JsonObject transformObj = new JsonObject(); + transformObj.addProperty("type", bwTransform.getTransformType() ); + transformObj.add("landmarks", ltm.toJson()); + + if( bwTransform.isMasked() ) + { + final JsonObject maskObj = new JsonObject(); + if (bwTransform.getLambda() instanceof PlateauSphericalMaskRealRandomAccessible) { + final PlateauSphericalMaskRealRandomAccessible mask = (PlateauSphericalMaskRealRandomAccessible)bwTransform.getLambda(); + maskObj.add("parameters", BigwarpSettings.gson.toJsonTree(mask)); + } + maskObj.add("range", BigwarpSettings.gson.toJsonTree(bwTransform.getMaskIntensityBounds())); + maskObj.addProperty("interpolationType", bwTransform.getMaskInterpolationType() ); + transformObj.add("mask", maskObj); + } + + return transformObj; + } + + public static void read( final BigWarp< ? > bw, final JsonObject json ) + { + + if( json.has( "landmarks" )) + { + final int nd = json.get("landmarks").getAsJsonObject().get("numDimensions").getAsInt(); + final boolean nonEmpty = json.get("landmarks").getAsJsonObject().get("active").getAsJsonArray().size() > 0; + if( bw.numDimensions() != nd && nonEmpty ) + bw.changeDimensionality(nd == 2); + + bw.getLandmarkPanel().getTableModel().fromJson( json ); + } + + if( json.has( "mask" )) + { + final JsonObject maskParams = json.get("mask").getAsJsonObject(); + + if( maskParams.has("parameters")) + { + bw.addTransformMaskSource(); + final JsonObject paramsObj = maskParams.get("parameters").getAsJsonObject(); + final PlateauSphericalMaskRealRandomAccessible maskFromJson = BigwarpSettings.gson.fromJson( paramsObj, PlateauSphericalMaskRealRandomAccessible.class ); + bw.setTransformMaskProperties( + maskFromJson.getFallOffShape(), + maskFromJson.getSquaredRadius(), + maskFromJson.getCenter().positionAsDoubleArray()); + }else + bw.connectMaskSource(); + + bw.setTransformMaskType(maskParams.get("interpolationType").getAsString()); + + if( maskParams.has("range")) + { + final BoundedRange maskRange = BigwarpSettings.gson.fromJson(maskParams.get("range"), BoundedRange.class); + bw.getBwTransform().setMaskIntensityBounds(maskRange.getMin(), maskRange.getMax()); + bw.setTransformMaskRange(maskRange.getMin(), maskRange.getMax()); + } + } + + if( json.has( "type" )) + bw.setTransformType(json.get("type").getAsString()); + + } + +} diff --git a/src/main/java/bigwarp/transforms/metadata/N5TransformMetadata.java b/src/main/java/bigwarp/transforms/metadata/N5TransformMetadata.java new file mode 100644 index 00000000..65f1fc51 --- /dev/null +++ b/src/main/java/bigwarp/transforms/metadata/N5TransformMetadata.java @@ -0,0 +1,20 @@ +package bigwarp.transforms.metadata; + +import org.janelia.saalfeldlab.n5.universe.metadata.AbstractN5Metadata; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.CoordinateTransform; + +public class N5TransformMetadata extends AbstractN5Metadata { + + private CoordinateTransform[] transforms; + + public N5TransformMetadata(String path, CoordinateTransform[] transforms) { + super(path); + this.transforms = transforms; + } + + public CoordinateTransform[] getTransforms() + { + return transforms; + } + +} diff --git a/src/main/java/bigwarp/transforms/metadata/N5TransformMetadataParser.java b/src/main/java/bigwarp/transforms/metadata/N5TransformMetadataParser.java new file mode 100644 index 00000000..1d40ba5d --- /dev/null +++ b/src/main/java/bigwarp/transforms/metadata/N5TransformMetadataParser.java @@ -0,0 +1,27 @@ +package bigwarp.transforms.metadata; + +import java.util.Optional; + +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.universe.N5Factory; +import org.janelia.saalfeldlab.n5.universe.N5TreeNode; +import org.janelia.saalfeldlab.n5.universe.metadata.N5MetadataParser; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.CoordinateTransform; + +import bigwarp.transforms.NgffTransformations; + +public class N5TransformMetadataParser implements N5MetadataParser { + + @Override + public Optional parseMetadata(N5Reader n5, N5TreeNode node) { + + final N5Reader n5Tform = new N5Factory().gsonBuilder( NgffTransformations.gsonBuilder() ).openReader( n5.getURI().toString() ); + final CoordinateTransform[] cts = n5Tform.getAttribute(node.getPath(), "coordinateTransformations", CoordinateTransform[].class); + + if( cts != null ) + return Optional.of( new N5TransformMetadata(node.getPath(), cts)); + else + return Optional.empty(); + } + +} diff --git a/src/main/java/bigwarp/transforms/metadata/N5TransformTreeCellRenderer.java b/src/main/java/bigwarp/transforms/metadata/N5TransformTreeCellRenderer.java new file mode 100644 index 00000000..cf4b7fd5 --- /dev/null +++ b/src/main/java/bigwarp/transforms/metadata/N5TransformTreeCellRenderer.java @@ -0,0 +1,76 @@ +package bigwarp.transforms.metadata; + +import java.awt.Component; + +import javax.swing.JTree; + +import org.janelia.saalfeldlab.n5.ui.N5DatasetTreeCellRenderer; +import org.janelia.saalfeldlab.n5.ui.N5SwingTreeNode; +import org.janelia.saalfeldlab.n5.universe.N5TreeNode; +import org.janelia.saalfeldlab.n5.universe.metadata.N5Metadata; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.transformations.CoordinateTransform; + +public class N5TransformTreeCellRenderer extends N5DatasetTreeCellRenderer { + + private static final long serialVersionUID = 9198333318002035941L; + + public N5TransformTreeCellRenderer(boolean showConversionWarning) { + + super(showConversionWarning); + } + + @Override + public String getParameterString(final N5TreeNode node) { + + final N5Metadata meta = node.getMetadata(); + if (meta == null || !(meta instanceof N5TransformMetadata)) + return ""; + + final CoordinateTransform[] tforms = ((N5TransformMetadata)node.getMetadata()).getTransforms(); + + if (tforms != null) { + final String suffix = tforms.length > 1 ? " ..." : ""; + final String first = tforms.length > 0 ? String.format("\"%s\" (%s)", tforms[0].getName(), tforms[0].getType()) : ""; + + return String.format(" (%d) [%s%s]", tforms.length, first, suffix); + } else + return ""; + + } + + @Override + public Component getTreeCellRendererComponent( final JTree tree, final Object value, + final boolean sel, final boolean exp, final boolean leaf, final int row, final boolean hasFocus ) + { + + super.getTreeCellRendererComponent( tree, value, sel, exp, leaf, row, hasFocus ); + + N5SwingTreeNode node; + if ( value instanceof N5SwingTreeNode ) + { + node = ( ( N5SwingTreeNode ) value ); + if ( node.getMetadata() != null ) + { + + final String memStr = memString( node ); + final String memSizeString = memStr.isEmpty() ? "" : " (" + memStr + ")"; + final String name = node.getParent() == null ? rootName : node.getNodeName(); + + setText( String.join( "", new String[]{ + "", + String.format(nameFormat, name), + getParameterString( node ), + memSizeString, + "" + })); + } + else + { + setText(node.getParent() == null ? rootName : node.getNodeName()); + } + } + return this; + } + +} + diff --git a/src/main/java/bigwarp/ui/keymap/DumpInputConfig.java b/src/main/java/bigwarp/ui/keymap/DumpInputConfig.java new file mode 100644 index 00000000..b11207ba --- /dev/null +++ b/src/main/java/bigwarp/ui/keymap/DumpInputConfig.java @@ -0,0 +1,94 @@ +/*- + * #%L + * BigDataViewer core classes with minimal dependencies. + * %% + * Copyright (C) 2012 - 2022 BigDataViewer developers. + * %% + * 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 bigwarp.ui.keymap; + +import bdv.TransformEventHandler2D; +import bdv.TransformEventHandler3D; +import bdv.tools.CloseWindowActions; +import bdv.viewer.NavigationActions; +import bigwarp.BigWarpActions; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import org.scijava.Context; +import org.scijava.plugin.PluginService; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.io.InputTriggerDescription; +import org.scijava.ui.behaviour.io.InputTriggerDescriptionsBuilder; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionsBuilder; +import org.scijava.ui.behaviour.io.yaml.YamlConfigIO; + +class DumpInputConfig +{ + public static void writeToYaml( final String fileName, final InputTriggerConfig config ) throws IOException + { + mkdirs( fileName ); + final List< InputTriggerDescription > descriptions = new InputTriggerDescriptionsBuilder( config ).getDescriptions(); + YamlConfigIO.write( descriptions, fileName ); + } + + public static void writeDefaultConfigToYaml( final String fileName, final Context context ) throws IOException + { + mkdirs( fileName ); + final List< InputTriggerDescription > descriptions = new InputTriggerDescriptionsBuilder( buildCommandDescriptions( context ).createDefaultKeyconfig() ).getDescriptions(); + YamlConfigIO.write( descriptions, fileName ); + } + + private static boolean mkdirs( final String fileName ) + { + final File dir = new File( fileName ).getParentFile(); + return dir != null && dir.mkdirs(); + } + + static CommandDescriptions buildCommandDescriptions( final Context context ) + { + final CommandDescriptionsBuilder builder = new CommandDescriptionsBuilder(); + context.inject( builder ); + + builder.addManually( new BigWarpActions.Descriptions(), BigWarpActions.BIGWARP_CTXT ); + builder.addManually( new NavigationActions.Descriptions(), BigWarpActions.BIGWARP_CTXT ); + builder.addManually( new CloseWindowActions.Descriptions(), BigWarpActions.BIGWARP_CTXT ); + builder.addManually( new TransformEventHandler3D.Descriptions(), BigWarpActions.BIGWARP_CTXT ); + builder.addManually( new TransformEventHandler2D.Descriptions(), BigWarpActions.BIGWARP_CTXT ); + + builder.verifyManuallyAdded(); // TODO: It should be possible to filter by Scope here + + return builder.build(); + } + + public static void main( String[] args ) throws IOException + { + final String target = KeymapManager.class.getResource( "default.yaml" ).getFile(); + final File resource = new File( target.replaceAll( "target/classes", "src/main/resources" ) ); + System.out.println( "resource = " + resource ); + writeDefaultConfigToYaml( resource.getAbsolutePath(), new Context( PluginService.class ) ); + } +} diff --git a/src/main/java/bigwarp/ui/keymap/KeymapManager.java b/src/main/java/bigwarp/ui/keymap/KeymapManager.java new file mode 100644 index 00000000..1a2cb2b0 --- /dev/null +++ b/src/main/java/bigwarp/ui/keymap/KeymapManager.java @@ -0,0 +1,156 @@ +package bigwarp.ui.keymap; + +import bdv.KeyConfigScopes; +import bdv.ui.keymap.AbstractKeymapManager; +import bdv.ui.keymap.Keymap; +import bigwarp.BigWarpActions; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import org.scijava.Context; +import org.scijava.plugin.PluginService; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionsBuilder; +import org.scijava.ui.behaviour.io.yaml.YamlConfigIO; + +/** + * Manages a collection of {@link Keymap}. + *

+ * Provides de/serialization of user-defined keymaps. + * + * @author Tobias Pietzsch + * @author John Bogovic + */ +public class KeymapManager extends AbstractKeymapManager< KeymapManager > +{ + private static final String CONFIG_FILE_NAME = "keymap/"; + + private final String configFile; + + private CommandDescriptions descriptions; + + public KeymapManager( final String configDir ) + { + this( true, configDir ); + discoverCommandDescriptions(); + } + + public KeymapManager() + { + this( false, null ); + } + + private KeymapManager( final boolean loadStyles, final String configDir ) + { + configFile = configDir == null ? null : configDir + "/" + CONFIG_FILE_NAME; + if ( loadStyles ) + loadStyles(); + } + + public synchronized void setCommandDescriptions( final CommandDescriptions descriptions ) + { + this.descriptions = descriptions; + final Consumer< Keymap > augmentInputTriggerConfig = k -> descriptions.augmentInputTriggerConfig( k.getConfig() ); + builtinStyles.forEach( augmentInputTriggerConfig ); + userStyles.forEach( augmentInputTriggerConfig ); + } + + public CommandDescriptions getCommandDescriptions() + { + return descriptions; + } + + /** + * Discover all {@code CommandDescriptionProvider}s with scope {@link KeyConfigScopes#BIGDATAVIEWER}, + * build {@code CommandDescriptions}, and set it. + */ + public synchronized void discoverCommandDescriptions() + { + final CommandDescriptionsBuilder builder = new CommandDescriptionsBuilder(); + final Context context = new Context( PluginService.class ); + context.inject( builder ); + builder.discoverProviders( BigWarpActions.BIGWARP ); + context.dispose(); + setCommandDescriptions( builder.build() ); + } + + @Override + protected List< Keymap > loadBuiltinStyles() + { + synchronized ( KeymapManager.class ) + { + // TODO figures this out + if ( loadedBuiltinStyles == null ) + try + { + loadedBuiltinStyles = Arrays.asList( + loadBuiltinStyle( "Default", "default.yaml" ) ); + } +// catch ( final IOException e ) + catch ( final Exception e ) + { + e.printStackTrace(); + loadedBuiltinStyles = Arrays.asList( createDefaultStyle( "Default" ) ); + } + +// if ( loadedBuiltinStyles == null ) +// loadedBuiltinStyles = Arrays.asList( createDefaultStyle( "Default" ) ); + } + + return loadedBuiltinStyles; + } + + private static List< Keymap > loadedBuiltinStyles; + + private static Keymap loadBuiltinStyle( final String name, final String filename ) throws IOException + { + System.out.println( "loadBuiltinStyle" ); + System.out.println( filename ); + System.out.println( KeymapManager.class.getResourceAsStream( filename ) ); + System.out.println( "" ); + + try ( Reader reader = new InputStreamReader( KeymapManager.class.getResourceAsStream( filename ) ) ) + { + return new Keymap( name, new InputTriggerConfig( YamlConfigIO.read( reader ) ) ); + } + } + + private static Keymap createDefaultStyle( final String name ) + { + final Context context = new Context( PluginService.class ); + final InputTriggerConfig defaultKeyconfig = DumpInputConfig.buildCommandDescriptions( context ).createDefaultKeyconfig(); + return new Keymap( name, defaultKeyconfig ); + } + + public void loadStyles() + { + try + { + loadStyles( new File( configFile ) ); + } + catch ( IOException e ) + { + e.printStackTrace(); + } + } + + @Override + public void saveStyles() + { + try + { + saveStyles( new File( configFile ) ); + } + catch ( IOException e ) + { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/bigwarp/ui/keymap/NavigationKeys.java b/src/main/java/bigwarp/ui/keymap/NavigationKeys.java new file mode 100644 index 00000000..a022d32e --- /dev/null +++ b/src/main/java/bigwarp/ui/keymap/NavigationKeys.java @@ -0,0 +1,132 @@ +package bigwarp.ui.keymap; + +import java.util.stream.IntStream; + +import org.scijava.plugin.Plugin; +import org.scijava.ui.behaviour.KeyStrokeAdder.Factory; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; +import org.scijava.ui.behaviour.util.Actions; + +import bdv.TransformEventHandler3D; +import bdv.viewer.AbstractViewerPanel; +import bdv.viewer.NavigationActions; +import bigwarp.BigWarpActions; + +public class NavigationKeys extends NavigationActions +{ + + public NavigationKeys( Factory keyConfig ) + { + super( keyConfig ); + } + + /* + * Command descriptions for all provided commands + */ + @Plugin( type = CommandDescriptionProvider.class ) + public static class Descriptions extends CommandDescriptionProvider + { + public Descriptions() + { + super( BigWarpActions.BIGWARP, BigWarpActions.BIGWARP_CTXT, "navigation", "bw-table" ); + } + + @Override + public void getCommandDescriptions( final CommandDescriptions descriptions ) + { + descriptions.add( TOGGLE_INTERPOLATION, TOGGLE_INTERPOLATION_KEYS, "Switch between nearest-neighbor and n-linear interpolation mode in BigDataViewer." ); + descriptions.add( TOGGLE_FUSED_MODE, TOGGLE_FUSED_MODE_KEYS, "TODO" ); + descriptions.add( TOGGLE_GROUPING, TOGGLE_GROUPING_KEYS, "TODO" ); + + final String[] numkeys = new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }; + IntStream.range( 0, numkeys.length ).forEach( i -> { + descriptions.add( String.format( SET_CURRENT_SOURCE, i ), new String[] { String.format( SET_CURRENT_SOURCE_KEYS_FORMAT, numkeys[ i ] ) }, "TODO" ); + descriptions.add( String.format( TOGGLE_SOURCE_VISIBILITY, i ), new String[] { String.format( TOGGLE_SOURCE_VISIBILITY_KEYS_FORMAT, numkeys[ i ] ) }, "TODO" ); + } ); + + descriptions.add( NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS, "TODO" ); + descriptions.add( PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS, "TODO" ); + descriptions.add( ALIGN_XY_PLANE, ALIGN_XY_PLANE_KEYS, "TODO" ); + descriptions.add( ALIGN_ZY_PLANE, ALIGN_ZY_PLANE_KEYS, "TODO" ); + descriptions.add( ALIGN_XZ_PLANE, ALIGN_XZ_PLANE_KEYS, "TODO" ); + + // from TransformEventHandler3D + descriptions.add( TransformEventHandler3D.DRAG_TRANSLATE, TransformEventHandler3D.DRAG_TRANSLATE_KEYS, "Pan the view by mouse-dragging." ); + descriptions.add( TransformEventHandler3D.ZOOM_NORMAL, TransformEventHandler3D.ZOOM_NORMAL_KEYS, "Zoom in by scrolling." ); + + descriptions.add( TransformEventHandler3D.SELECT_AXIS_X, TransformEventHandler3D.SELECT_AXIS_X_KEYS, "Select X as the rotation axis for keyboard rotation." ); + descriptions.add( TransformEventHandler3D.SELECT_AXIS_Y, TransformEventHandler3D.SELECT_AXIS_Y_KEYS, "Select Y as the rotation axis for keyboard rotation." ); + descriptions.add( TransformEventHandler3D.SELECT_AXIS_Z, TransformEventHandler3D.SELECT_AXIS_Z_KEYS, "Select Z as the rotation axis for keyboard rotation." ); + + descriptions.add( TransformEventHandler3D.DRAG_ROTATE, TransformEventHandler3D.DRAG_ROTATE_KEYS, "Rotate the view by mouse-dragging." ); + descriptions.add( TransformEventHandler3D.SCROLL_Z, TransformEventHandler3D.SCROLL_Z_KEYS, "Translate in Z by scrolling." ); + descriptions.add( TransformEventHandler3D.ROTATE_LEFT, TransformEventHandler3D.ROTATE_LEFT_KEYS, "Rotate left (counter-clockwise) by 1 degree." ); + descriptions.add( TransformEventHandler3D.ROTATE_RIGHT, TransformEventHandler3D.ROTATE_RIGHT_KEYS, "Rotate right (clockwise) by 1 degree." ); + descriptions.add( TransformEventHandler3D.KEY_ZOOM_IN, TransformEventHandler3D.KEY_ZOOM_IN_KEYS, "Zoom in." ); + descriptions.add( TransformEventHandler3D.KEY_ZOOM_OUT, TransformEventHandler3D.KEY_ZOOM_OUT_KEYS, "Zoom out." ); + descriptions.add( TransformEventHandler3D.KEY_FORWARD_Z, TransformEventHandler3D.KEY_FORWARD_Z_KEYS, "Translate forward in Z." ); + descriptions.add( TransformEventHandler3D.KEY_BACKWARD_Z, TransformEventHandler3D.KEY_BACKWARD_Z_KEYS, "Translate backward in Z." ); + + descriptions.add( TransformEventHandler3D.DRAG_ROTATE_FAST, TransformEventHandler3D.DRAG_ROTATE_FAST_KEYS, "Rotate the view by mouse-dragging (fast)." ); + descriptions.add( TransformEventHandler3D.SCROLL_Z_FAST, TransformEventHandler3D.SCROLL_Z_FAST_KEYS, "Translate in Z by scrolling (fast)." ); + descriptions.add( TransformEventHandler3D.ROTATE_LEFT_FAST, TransformEventHandler3D.ROTATE_LEFT_FAST_KEYS, "Rotate left (counter-clockwise) by 10 degrees." ); + descriptions.add( TransformEventHandler3D.ROTATE_RIGHT_FAST, TransformEventHandler3D.ROTATE_RIGHT_FAST_KEYS, "Rotate right (clockwise) by 10 degrees." ); + descriptions.add( TransformEventHandler3D.KEY_ZOOM_IN_FAST, TransformEventHandler3D.KEY_ZOOM_IN_FAST_KEYS, "Zoom in (fast)." ); + descriptions.add( TransformEventHandler3D.KEY_ZOOM_OUT_FAST, TransformEventHandler3D.KEY_ZOOM_OUT_FAST_KEYS, "Zoom out (fast)." ); + descriptions.add( TransformEventHandler3D.KEY_FORWARD_Z_FAST, TransformEventHandler3D.KEY_FORWARD_Z_FAST_KEYS, "Translate forward in Z (fast)." ); + descriptions.add( TransformEventHandler3D.KEY_BACKWARD_Z_FAST, TransformEventHandler3D.KEY_BACKWARD_Z_FAST_KEYS, "Translate backward in Z (fast)." ); + + descriptions.add( TransformEventHandler3D.DRAG_ROTATE_SLOW, TransformEventHandler3D.DRAG_ROTATE_SLOW_KEYS, "Rotate the view by mouse-dragging (slow)." ); + descriptions.add( TransformEventHandler3D.SCROLL_Z_SLOW, TransformEventHandler3D.SCROLL_Z_SLOW_KEYS, "Translate in Z by scrolling (slow)." ); + descriptions.add( TransformEventHandler3D.ROTATE_LEFT_SLOW, TransformEventHandler3D.ROTATE_LEFT_SLOW_KEYS, "Rotate left (counter-clockwise) by 0.1 degree." ); + descriptions.add( TransformEventHandler3D.ROTATE_RIGHT_SLOW, TransformEventHandler3D.ROTATE_RIGHT_SLOW_KEYS, "Rotate right (clockwise) by 0.1 degree." ); + descriptions.add( TransformEventHandler3D.KEY_ZOOM_IN_SLOW, TransformEventHandler3D.KEY_ZOOM_IN_SLOW_KEYS, "Zoom in (slow)." ); + descriptions.add( TransformEventHandler3D.KEY_ZOOM_OUT_SLOW, TransformEventHandler3D.KEY_ZOOM_OUT_SLOW_KEYS, "Zoom out (slow)." ); + descriptions.add( TransformEventHandler3D.KEY_FORWARD_Z_SLOW, TransformEventHandler3D.KEY_FORWARD_Z_SLOW_KEYS, "Translate forward in Z (slow)." ); + descriptions.add( TransformEventHandler3D.KEY_BACKWARD_Z_SLOW, TransformEventHandler3D.KEY_BACKWARD_Z_SLOW_KEYS, "Translate backward in Z (slow)." ); + + + // from TransformEventHandler2D +// descriptions.add( TransformEventHandler2D.DRAG_TRANSLATE, TransformEventHandler2D.DRAG_TRANSLATE_KEYS, "Pan the view by mouse-dragging. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.DRAG_ROTATE, TransformEventHandler2D.DRAG_ROTATE_KEYS, "Rotate the view by mouse-dragging. Active in 2D mode." ); +// +// descriptions.add( TransformEventHandler2D.ZOOM_NORMAL, TransformEventHandler2D.ZOOM_NORMAL_KEYS, "Zoom in by scrolling. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.ZOOM_FAST, TransformEventHandler2D.ZOOM_FAST_KEYS, "Zoom in by scrolling (fast). Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.ZOOM_SLOW, TransformEventHandler2D.ZOOM_SLOW_KEYS, "Zoom in by scrolling (slow). Active in 2D mode." ); +// +// descriptions.add( TransformEventHandler2D.SCROLL_TRANSLATE, TransformEventHandler2D.SCROLL_TRANSLATE_KEYS, "Translate by scrolling. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.SCROLL_TRANSLATE_FAST, TransformEventHandler2D.SCROLL_TRANSLATE_FAST_KEYS, "Translate by scrolling (fast). Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.SCROLL_TRANSLATE_SLOW, TransformEventHandler2D.SCROLL_TRANSLATE_SLOW_KEYS, "Translate by scrolling (slow). Active in 2D mode." ); +// +// descriptions.add( TransformEventHandler2D.ROTATE_LEFT, TransformEventHandler2D.ROTATE_LEFT_KEYS, "Rotate left (counter-clockwise) by 1 degree. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.ROTATE_RIGHT, TransformEventHandler2D.ROTATE_RIGHT_KEYS, "Rotate right (clockwise) by 1 degree. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.KEY_ZOOM_IN, TransformEventHandler2D.KEY_ZOOM_IN_KEYS, "Zoom in. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.KEY_ZOOM_OUT, TransformEventHandler2D.KEY_ZOOM_OUT_KEYS, "Zoom out. Active in 2D mode." ); +// +// descriptions.add( TransformEventHandler2D.ROTATE_LEFT_FAST, TransformEventHandler2D.ROTATE_LEFT_FAST_KEYS, "Rotate left (counter-clockwise) by 10 degrees. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.ROTATE_RIGHT_FAST, TransformEventHandler2D.ROTATE_RIGHT_FAST_KEYS, "Rotate right (clockwise) by 10 degrees. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.KEY_ZOOM_IN_FAST, TransformEventHandler2D.KEY_ZOOM_IN_FAST_KEYS, "Zoom in (fast). Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.KEY_ZOOM_OUT_FAST, TransformEventHandler2D.KEY_ZOOM_OUT_FAST_KEYS, "Zoom out (fast). Active in 2D mode." ); +// +// descriptions.add( TransformEventHandler2D.ROTATE_LEFT_SLOW, TransformEventHandler2D.ROTATE_LEFT_SLOW_KEYS, "Rotate left (counter-clockwise) by 0.1 degree. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.ROTATE_RIGHT_SLOW, TransformEventHandler2D.ROTATE_RIGHT_SLOW_KEYS, "Rotate right (clockwise) by 0.1 degree. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.KEY_ZOOM_IN_SLOW, TransformEventHandler2D.KEY_ZOOM_IN_SLOW_KEYS, "Zoom in (slow). Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.KEY_ZOOM_OUT_SLOW, TransformEventHandler2D.KEY_ZOOM_OUT_SLOW_KEYS, "Zoom out (slow). Active in 2D mode." ); +// +// descriptions.add( TransformEventHandler2D.SCROLL_ROTATE, TransformEventHandler2D.SCROLL_ROTATE_KEYS, "Rotate by scrolling. Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.SCROLL_ROTATE_FAST, TransformEventHandler2D.SCROLL_ROTATE_FAST_KEYS, "Rotate by scrolling (fast). Active in 2D mode." ); +// descriptions.add( TransformEventHandler2D.SCROLL_ROTATE_SLOW, TransformEventHandler2D.SCROLL_ROTATE_SLOW_KEYS, "Rotate by scrolling (slow). Active in 2D mode." ); + } + } + + public static void install( final Actions actions, final AbstractViewerPanel viewer, final boolean is2D ) + { + installModeActions( actions, viewer.state() ); + installSourceActions( actions, viewer.state() ); + installTimeActions( actions, viewer.state() ); + // align plane actions need to be moved to bigwarp actions due to tricky two-viewer issue + } + + +} diff --git a/src/main/java/bigwarp/ui/keymap/UnmappedNavigationActions.java b/src/main/java/bigwarp/ui/keymap/UnmappedNavigationActions.java new file mode 100644 index 00000000..3df4d08f --- /dev/null +++ b/src/main/java/bigwarp/ui/keymap/UnmappedNavigationActions.java @@ -0,0 +1,64 @@ +package bigwarp.ui.keymap; + +import java.util.stream.IntStream; + +import org.scijava.ui.behaviour.KeyStrokeAdder.Factory; +import org.scijava.ui.behaviour.util.Actions; + +import bdv.viewer.AbstractViewerPanel; +import bdv.viewer.AbstractViewerPanel.AlignPlane; +import bdv.viewer.NavigationActions; +import bdv.viewer.ViewerState; +import bigwarp.BigWarpActions; + +public class UnmappedNavigationActions extends NavigationActions { + + public UnmappedNavigationActions(Factory keyConfig) { + super(keyConfig); + } + + public static void install( final Actions actions, boolean is2D ) + { + installModeActions( actions ); + installSourceActions( actions ); + installTimeActions( actions ); + installAlignPlaneActions( actions, is2D ); + } + + public static void installModeActions( final Actions actions ) + { + actions.runnableAction( () -> {}, TOGGLE_INTERPOLATION, BigWarpActions.NOT_MAPPED ); + actions.runnableAction( () -> {}, TOGGLE_FUSED_MODE, BigWarpActions.NOT_MAPPED ); + actions.runnableAction( () -> {}, TOGGLE_GROUPING, BigWarpActions.NOT_MAPPED ); + } + + public static void installTimeActions( final Actions actions ) + { + actions.runnableAction( () -> {}, NEXT_TIMEPOINT, BigWarpActions.NOT_MAPPED ); + actions.runnableAction( () -> {}, PREVIOUS_TIMEPOINT, BigWarpActions.NOT_MAPPED ); + } + + public static void installSourceActions( final Actions actions ) + { + final String[] numkeys = new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }; + IntStream.range( 0, numkeys.length ).forEach( i -> { + actions.runnableAction( () -> {}, + String.format( SET_CURRENT_SOURCE, i ), + String.format( SET_CURRENT_SOURCE_KEYS_FORMAT, numkeys[ i ] ) ); + actions.runnableAction( () -> {}, + String.format( TOGGLE_SOURCE_VISIBILITY, i ), + String.format( TOGGLE_SOURCE_VISIBILITY_KEYS_FORMAT, numkeys[ i ] ) ); + } ); + } + + public static void installAlignPlaneActions( final Actions actions, boolean is2D ) + { + actions.runnableAction( () -> {}, ALIGN_XY_PLANE, BigWarpActions.NOT_MAPPED ); + if ( !is2D ) + { + actions.runnableAction( () -> {}, ALIGN_ZY_PLANE, BigWarpActions.NOT_MAPPED ); + actions.runnableAction( () -> {}, ALIGN_XZ_PLANE, BigWarpActions.NOT_MAPPED ); + } + } + +} diff --git a/src/main/java/bigwarp/util/BigWarpUtils.java b/src/main/java/bigwarp/util/BigWarpUtils.java index 0a98a964..3cffd7d1 100644 --- a/src/main/java/bigwarp/util/BigWarpUtils.java +++ b/src/main/java/bigwarp/util/BigWarpUtils.java @@ -8,12 +8,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -22,12 +22,14 @@ package bigwarp.util; import java.awt.Dimension; +import java.util.HashMap; import bdv.util.Affine3DHelpers; import bdv.viewer.Source; import bdv.viewer.ViewerPanel; import bdv.viewer.ViewerState; import net.imglib2.Interval; +import net.imglib2.realtransform.AffineGet; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.util.LinAlgHelpers; @@ -70,12 +72,12 @@ public static void ensurePositiveDeterminant( final AffineTransform3D xfm ) if( det( xfm ) < 0 ) flipX( xfm ); } - + public static double det( final AffineTransform3D xfm ) { return LinAlgHelpers.det3x3( - xfm.get(0, 0), xfm.get(0, 1), xfm.get(0, 2), - xfm.get(1, 0), xfm.get(1, 1), xfm.get(1, 2), + xfm.get(0, 0), xfm.get(0, 1), xfm.get(0, 2), + xfm.get(1, 0), xfm.get(1, 1), xfm.get(1, 2), xfm.get(2, 0), xfm.get(2, 1), xfm.get(2, 2) ); } @@ -188,7 +190,7 @@ public static AffineTransform3D initTransform( final int viewerWidth, final int /** * Computes the angle of rotation between the two input quaternions, * returning the result in radians. Assumes the inputs are unit quaternions. - * + * * @param q1 first quaternion * @param q2 second quaternion * @return the angle in radians @@ -201,7 +203,7 @@ public static double quaternionAngle( double[] q1, double[] q2 ) return Math.acos( 2 * dot * dot - 1); } - + // public static double angleBetween( final AffineTransform3D xfm1, final AffineTransform3D xfm2 ) // { // double[][] tmpMat = new double[ 3 ][ 4 ]; @@ -210,7 +212,7 @@ public static double quaternionAngle( double[] q1, double[] q2 ) // // xfm1.toMatrix( tmpMat ); // LinAlgHelpers.qu -// +// // normalize( q1 ); // normalize( q2 ); // @@ -226,17 +228,82 @@ public static void normalize( double[] x ) x[ i ] /= magSqr; } + public static HashMap parseMacroArguments( String input ) + { + return parseMacroArguments( input, "=", "[", "]" ); + } + + public static HashMap parseMacroArguments( String input, String keyDelim, String startDelim, String endDelim ) + { + final HashMap output = new HashMap<>(); + + String arguments = input.trim(); + boolean done = false; + while( !done ) + { + int i = arguments.indexOf( keyDelim ); + final String key = arguments.substring( 0, i ); + output.put( key, parse( arguments, startDelim, endDelim )); + + i = arguments.indexOf( endDelim ); + if( i < 0 || i +1 > arguments.length() ) + done = true; + + arguments = arguments.substring( i + 1 ).trim(); + if( arguments.length() == 0 ) + done = true; + else + arguments = arguments.substring( 1 ).trim(); // remove comma + } + + return output; + } + + public static String parse( String arg, String start, String end ) + { + final int startIdx = arg.indexOf( start ) + 1; + final int endIdx = arg.indexOf( end ); + if ( startIdx < 0 || endIdx < 0 ) + return null; + + return arg.substring( startIdx, endIdx ); + } + + /** + * Return a 3D {@link AffineGet} from a lower-dimensional (1D or 2D) AffineGet. + * The input instance is returned if it is 3D. + * + * @param affine the input affine. + * @return the 3D affine + */ + public static AffineGet toAffine3D( final AffineGet affine ) + { + if( affine.numSourceDimensions() == 3) + return affine; + + final AffineTransform3D out = new AffineTransform3D(); + final int N = affine.numSourceDimensions(); + for (int i = 0; i < N; i++) + for (int j = 0; j < N; j++) + out.set(affine.get(i, j), i, j); + + // translation part + for (int i = 0; i < N; i++) + out.set(affine.get(i, N+1), i, 3); + + return out; + } // /** // * Computes the angle of rotation between the two input quaternions, // * returning the result in degrees. Assumes the inputs are unit quaternions. -// * +// * // * @param q1 first quaternion // * @param q2 second quaternion // * @return the angle in degrees // */ // public static double quaternionAngleD( double[] q1, double q2 ) // { -// +// // } } diff --git a/src/main/java/net/imglib2/realtransform/AffineInterpolator.java b/src/main/java/net/imglib2/realtransform/AffineInterpolator.java new file mode 100644 index 00000000..f07c8f0b --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/AffineInterpolator.java @@ -0,0 +1,7 @@ +package net.imglib2.realtransform; + +public interface AffineInterpolator +{ + + public AffineGet get( final double t ); +} diff --git a/src/main/java/net/imglib2/realtransform/InvertibleWrapped2DIntermediate3D.java b/src/main/java/net/imglib2/realtransform/InvertibleWrapped2DIntermediate3D.java new file mode 100644 index 00000000..04a7cb0c --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/InvertibleWrapped2DIntermediate3D.java @@ -0,0 +1,108 @@ +/*- + * #%L + * BigWarp plugin for Fiji. + * %% + * Copyright (C) 2015 - 2022 Howard Hughes Medical Institute. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.imglib2.realtransform; + +import net.imglib2.RealLocalizable; +import net.imglib2.RealPositionable; + +public class InvertibleWrapped2DIntermediate3D implements InvertibleRealTransform +{ + public InvertibleRealTransform transform; + + public double[] tmp; + + public InvertibleWrapped2DIntermediate3D( final InvertibleRealTransform transform ) + { + this.transform = transform; + tmp = new double[ 3 ]; + } + + @Override + public int numSourceDimensions() + { + return 2; + } + + @Override + public int numTargetDimensions() + { + return 2; + } + + public InvertibleRealTransform getTransform() + { + return transform; + } + + @Override + public void apply(double[] source, double[] target) + { + tmp[ 0 ] = target[ 0 ]; + tmp[ 1 ] = target[ 1 ]; + transform.apply( tmp, tmp ); + source[ 0 ] = tmp[ 0 ]; + source[ 1 ] = tmp[ 1 ]; + } + + @Override + public void apply(RealLocalizable source, RealPositionable target) + { + tmp[ 0 ] = source.getDoublePosition( 0 ); + tmp[ 1 ] = source.getDoublePosition( 1 ); + transform.apply( tmp, tmp ); + target.setPosition( tmp[ 0 ], 0 ); + target.setPosition( tmp[ 1 ], 1 ); + } + + @Override + public void applyInverse( double[] source, double[] target ) + { + tmp[ 0 ] = target[ 0 ]; + tmp[ 1 ] = target[ 1 ]; + transform.applyInverse( tmp, tmp ); + source[ 0 ] = tmp[ 0 ]; + source[ 1 ] = tmp[ 1 ]; + } + + @Override + public void applyInverse( RealPositionable source, RealLocalizable target ) + { + tmp[ 0 ] = target.getDoublePosition( 0 ); + tmp[ 1 ] = target.getDoublePosition( 1 ); + transform.applyInverse( tmp, tmp ); + source.setPosition( tmp[ 0 ], 0 ); + source.setPosition( tmp[ 1 ], 1 ); + } + + @Override + public InvertibleWrapped2DIntermediate3D copy() + { + return new InvertibleWrapped2DIntermediate3D( transform.copy() ); + } + + @Override + public InvertibleRealTransform inverse() + { + return new InvertibleWrapped2DIntermediate3D( transform.inverse() ); + } + +} diff --git a/src/main/java/net/imglib2/realtransform/InvertibleWrapped2DTransformAs3D.java b/src/main/java/net/imglib2/realtransform/InvertibleWrapped2DTransformAs3D.java new file mode 100644 index 00000000..12833be7 --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/InvertibleWrapped2DTransformAs3D.java @@ -0,0 +1,80 @@ +/*- + * #%L + * BigWarp plugin for Fiji. + * %% + * Copyright (C) 2015 - 2022 Howard Hughes Medical Institute. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.imglib2.realtransform; + +import net.imglib2.RealLocalizable; +import net.imglib2.RealPositionable; + +public class InvertibleWrapped2DTransformAs3D extends Wrapped2DTransformAs3D implements InvertibleRealTransform +{ + public InvertibleRealTransform transform; + + public double[] tmp; + + public InvertibleWrapped2DTransformAs3D( final InvertibleRealTransform transform ) + { + super( transform ); + this.transform = transform; + tmp = new double[ 2 ]; + } + + public InvertibleRealTransform getTransform() + { + return transform; + } + + @Override + public void applyInverse( double[] source, double[] target ) + { + tmp[ 0 ] = target[ 0 ]; + tmp[ 1 ] = target[ 1 ]; + transform.applyInverse( tmp, tmp ); + source[ 0 ] = tmp[ 0 ]; + source[ 1 ] = tmp[ 1 ]; +// transform.applyInverse( source, target ); + source[ 2 ] = target[ 2 ]; + } + + @Override + public void applyInverse( RealPositionable source, RealLocalizable target ) + { + tmp[ 0 ] = target.getDoublePosition( 0 ); + tmp[ 1 ] = target.getDoublePosition( 1 ); + transform.applyInverse( tmp, tmp ); + source.setPosition( tmp[ 0 ], 0 ); + source.setPosition( tmp[ 1 ], 1 ); +// transform.applyInverse( source, target ); + source.setPosition( target.getDoublePosition( 2 ), 2 ); + } + + public InvertibleWrapped2DTransformAs3D copy() + { + return new InvertibleWrapped2DTransformAs3D( transform.copy() ); + } + + @Override + public InvertibleRealTransform inverse() + { + return new InvertibleWrapped2DTransformAs3D( transform.inverse() ); + } + +} diff --git a/src/main/java/net/imglib2/realtransform/MaskedSimilarityTransform.java b/src/main/java/net/imglib2/realtransform/MaskedSimilarityTransform.java new file mode 100644 index 00000000..7ad34922 --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/MaskedSimilarityTransform.java @@ -0,0 +1,98 @@ +package net.imglib2.realtransform; + +import bdv.viewer.animate.AbstractTransformAnimator; +import net.imglib2.RealLocalizable; +import net.imglib2.RealPositionable; +import net.imglib2.RealRandomAccess; +import net.imglib2.RealRandomAccessible; +import net.imglib2.type.numeric.RealType; + +/** + * Spatially-varying mask for a {@link RealTransform}. + *

+ * Given a {@link RealRandomAccessible} "lambda", and a transformation "a", implements the transformation + * lambda * a(x) + (1-lambda) * x for a point x. + * + * @author John Bogovic + * + * @param lambda's type + */ +public class MaskedSimilarityTransform> implements RealTransform { + + public static enum Interpolators { SIMILARITY, ROTATION }; + + private final RealRandomAccessible lambda; + + private RealRandomAccess lambdaAccess; + + private final AffineTransform3D transform; + + private final AbstractTransformAnimator interpolator; + + private final double[] c; + +// private final boolean flip; + + public MaskedSimilarityTransform(final AffineTransform3D transform, final RealRandomAccessible lambda ) { + this( transform, lambda, new double[3], Interpolators.SIMILARITY ); + } + + public MaskedSimilarityTransform(final AffineTransform3D transform, final RealRandomAccessible lambda, boolean flip ) { + this( transform, lambda, new double[3], Interpolators.SIMILARITY ); + } + + public MaskedSimilarityTransform(final AffineTransform3D transform, final RealRandomAccessible lambda, double[] c ) { + this( transform, lambda, c, Interpolators.SIMILARITY ); + } + + public MaskedSimilarityTransform(final AffineTransform3D transform, final RealRandomAccessible lambda, Interpolators interp ) { + this( transform, lambda, new double[3], interp ); + } + + public MaskedSimilarityTransform(final AffineTransform3D transform, final RealRandomAccessible lambda, double[] c, Interpolators interp ) { + + assert ( transform.numSourceDimensions() == lambda.numDimensions() ); + this.transform = transform; + this.c = c; + this.lambda = lambda; + lambdaAccess = lambda.realRandomAccess(); + + if( interp == Interpolators.SIMILARITY ) + interpolator = new SimilarityTransformInterpolator( transform, c ); + else + interpolator = new RotationTransformInterpolator( transform, c ); + } + + @Override + public int numSourceDimensions() { + + return transform.numSourceDimensions(); + } + + @Override + public int numTargetDimensions() { + + return transform.numTargetDimensions(); + } + + @Override + public void apply(double[] source, double[] target) { + lambdaAccess.setPosition(source); + final double lam = lambdaAccess.get().getRealDouble(); + interpolator.get( lam ).apply( source, target ); + } + + @Override + public void apply(RealLocalizable source, RealPositionable target) { + lambdaAccess.setPosition(source); + final double lam = lambdaAccess.get().getRealDouble(); + interpolator.get( lam ).apply( source, target ); + } + + @Override + public RealTransform copy() { + + return new MaskedSimilarityTransform(transform.copy(), lambda, c ); + } + +} diff --git a/src/main/java/net/imglib2/realtransform/MaskedSimilarityTransform2D.java b/src/main/java/net/imglib2/realtransform/MaskedSimilarityTransform2D.java new file mode 100644 index 00000000..c77e7cdb --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/MaskedSimilarityTransform2D.java @@ -0,0 +1,100 @@ +package net.imglib2.realtransform; + +import bdv.viewer.animate.AbstractTransformAnimator; +import net.imglib2.RealLocalizable; +import net.imglib2.RealPoint; +import net.imglib2.RealPositionable; +import net.imglib2.RealRandomAccess; +import net.imglib2.RealRandomAccessible; +import net.imglib2.realtransform.MaskedSimilarityTransform.Interpolators; +import net.imglib2.type.numeric.RealType; + +/** + * Spatially-varying mask for a {@link RealTransform}. + * + * @author John Bogovic + * + */ +public class MaskedSimilarityTransform2D> implements RealTransform { + + private final RealRandomAccessible lambda; + + private RealRandomAccess lambdaAccess; + + private final AffineTransform2D transform; + + private final AffineInterpolator interpolator; + + private final double[] c; + + private final Interpolators interp; + +// private final boolean flip; + + public MaskedSimilarityTransform2D(final AffineTransform2D transform, final RealRandomAccessible lambda ) { + this( transform, lambda, new double[3], Interpolators.SIMILARITY ); + } + + public MaskedSimilarityTransform2D(final AffineTransform2D transform, final RealRandomAccessible lambda, boolean flip ) { + this( transform, lambda, new double[3], Interpolators.SIMILARITY ); + } + + public MaskedSimilarityTransform2D(final AffineTransform2D transform, final RealRandomAccessible lambda, double[] c ) { + this( transform, lambda, c, Interpolators.SIMILARITY ); + } + + public MaskedSimilarityTransform2D(final AffineTransform2D transform, final RealRandomAccessible lambda, Interpolators interp ) { + this( transform, lambda, new double[3], interp ); + } + + public MaskedSimilarityTransform2D(final AffineTransform2D transform, final RealRandomAccessible lambda, double[] c, Interpolators interp ) { + + assert ( transform.numSourceDimensions() == lambda.numDimensions() ); + this.transform = transform; + this.c = c; + this.lambda = lambda; + this.interp = interp; + lambdaAccess = lambda.realRandomAccess(); + + if( interp == Interpolators.SIMILARITY ) + interpolator = new SimilarityTransformInterpolator2D( transform, c ); + else + interpolator = new RotationTransformInterpolator2D( transform, c ); + } + + @Override + public int numSourceDimensions() { + + return transform.numSourceDimensions(); + } + + @Override + public int numTargetDimensions() { + + return transform.numTargetDimensions(); + } + + @Override + public void apply(double[] source, double[] target) { +// lambdaAccess.setPosition(source); + for( int i = 0; i < source.length; i++ ) + lambdaAccess.setPosition( source[ i ], i ); + + final double lam = lambdaAccess.get().getRealDouble(); + interpolator.get( lam ).apply( source, target ); + } + + @Override + public void apply(RealLocalizable source, RealPositionable target) { + lambdaAccess.setPosition(source); + final double lam = lambdaAccess.get().getRealDouble(); + interpolator.get( lam ).apply( source, target ); + } + + @Override + public RealTransform copy() { + + return new MaskedSimilarityTransform2D(transform.copy(), lambda, c, interp ); + } + +} diff --git a/src/main/java/net/imglib2/realtransform/RotationTransformInterpolator.java b/src/main/java/net/imglib2/realtransform/RotationTransformInterpolator.java new file mode 100644 index 00000000..78d1c84e --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/RotationTransformInterpolator.java @@ -0,0 +1,79 @@ +package net.imglib2.realtransform; + +import net.imglib2.util.LinAlgHelpers; + +import bdv.util.Affine3DHelpers; +import bdv.viewer.animate.AbstractTransformAnimator; + +public class RotationTransformInterpolator extends AbstractTransformAnimator +{ + private final double[] qStart; + + private final double[] qDiff; + + private final double[] p; + + private final double[] pDiff; + + public RotationTransformInterpolator(final AffineTransform3D transform, final double[] p) { + super(1); + AffineTransform3D transformEnd = new AffineTransform3D(); + transformEnd.set(transform); + + this.p = p; + qStart = new double[]{1, 0, 0, 0}; + final double[] qStartInv = new double[4]; + final double[] qEnd = new double[4]; + final double[] qEndInv = new double[4]; + qDiff = new double[4]; + LinAlgHelpers.quaternionInvert(qStart, qStartInv); + + Affine3DHelpers.extractRotation(transformEnd, qEnd); + LinAlgHelpers.quaternionInvert(qEnd, qEndInv); + + LinAlgHelpers.quaternionMultiply(qStartInv, qEnd, qDiff); + if (qDiff[0] < 0) + LinAlgHelpers.scale(qDiff, -1, qDiff); + + // translation needed to from reconstructed to target transformation + pDiff = new double[3]; + double[] tmp = new double[3]; + get(1).apply( p, tmp); + transform.apply(p, pDiff); + LinAlgHelpers.subtract(pDiff, tmp, pDiff); + } + + @Override + public AffineTransform3D get(final double t) { + + final double[] qDiffCurrent = new double[4]; + final double[] qCurrent = new double[4]; + LinAlgHelpers.quaternionPower(qDiff, t, qDiffCurrent); + LinAlgHelpers.quaternionMultiply(qStart, qDiffCurrent, qCurrent); + + final double[][] Rcurrent = new double[3][3]; + LinAlgHelpers.quaternionToR(qCurrent, Rcurrent); + + final double[][] m = new double[3][4]; + for (int r = 0; r < 3; ++r) { + for (int c = 0; c < 3; ++c) + m[r][c] = Rcurrent[r][c]; + } + + final AffineTransform3D transform = new AffineTransform3D(); + transform.set(m); + + double[] pCurrent = new double[3]; + double[] pTgt = new double[3]; + transform.apply(p, pCurrent); + + LinAlgHelpers.scale( pDiff, t, pTgt ); + LinAlgHelpers.add( p, pTgt, pTgt ); + LinAlgHelpers.subtract( pTgt, pCurrent, pTgt ); + transform.translate( pTgt ); + + return transform; + + } + +} \ No newline at end of file diff --git a/src/main/java/net/imglib2/realtransform/RotationTransformInterpolator2D.java b/src/main/java/net/imglib2/realtransform/RotationTransformInterpolator2D.java new file mode 100644 index 00000000..8ebb9c01 --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/RotationTransformInterpolator2D.java @@ -0,0 +1,181 @@ +package net.imglib2.realtransform; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +import org.janelia.utility.geom.GeomUtils; + +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.source.PlateauSphericalMaskRealRandomAccessible; +import bigwarp.source.PlateauSphericalMaskRealRandomAccessible.FalloffShape; +import bigwarp.transforms.BigWarpTransform; +import bigwarp.transforms.MaskedSimRotTransformSolver; +import bigwarp.transforms.TpsTransformSolver; +import net.imglib2.RealPoint; +import net.imglib2.RealRandomAccessible; +import net.imglib2.realtransform.MaskedSimilarityTransform.Interpolators; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; +import net.imglib2.type.numeric.RealType; +import net.imglib2.util.LinAlgHelpers; + +public class RotationTransformInterpolator2D implements AffineInterpolator +{ + private final double thtDiff; + + private final AffineTransform2D tform; + + private final double[] c; + + private final double[] pDiff; + + public RotationTransformInterpolator2D( final AffineTransform2D transform, final double[] c ) + { + tform = new AffineTransform2D(); + + final AffineTransform2D transformEnd = new AffineTransform2D(); + transformEnd.set( transform ); + transformEnd.translate( -c[0], -c[1] ); + + thtDiff = GeomUtils.scalesAngle( transformEnd )[ 2 ]; + + // translation needed to from reconstructed to target transformation + this.c = new double[ 2 ]; + System.arraycopy( c, 0, this.c, 0, 2 ); + + pDiff = new double[ 2 ]; + double[] tmp = new double[ 2 ]; + AffineTransform2D t1 = get( 1 ); + t1.apply( c, tmp ); + transform.apply( c, pDiff ); + LinAlgHelpers.subtract( pDiff, tmp, pDiff ); + } + + public AffineTransform2D get( final double t ) + { + // make identity + tform.set( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0 ); + + // the rotation + final double thtCurrent = t * thtDiff; + tform.rotate( thtCurrent ); + + // the translation + final double[] pCurrent = new double[ 2 ]; + final double[] pTgt = new double[ 2 ]; + tform.apply( c, pCurrent ); + + LinAlgHelpers.scale( pDiff, t, pTgt ); + LinAlgHelpers.add( pTgt, c, pTgt ); + LinAlgHelpers.subtract( pTgt, pCurrent, pTgt ); + tform.translate( pTgt ); + + return tform; + } + + public static void main( String[] args ) throws IOException + { +// final AffineTransform2D t = new AffineTransform2D(); +// t.rotate( 0.2 ); +// t.translate( 10, 20 ); +// System.out.println( t ); + +// test90deg(); + testReal2d(); + } + + public static void testReal2d() throws IOException + { + final LandmarkTableModel ltm = LandmarkTableModel.loadFromCsv( new File("/home/john/tmp/boats-bigrotation.csv"), false ); + + final RealPoint c = new RealPoint( 0.0, 0.0 ); + final PlateauSphericalMaskRealRandomAccessible lambda = new PlateauSphericalMaskRealRandomAccessible( c ); + lambda.setFalloffShape( FalloffShape.COSINE ); + lambda.setSquaredRadius( 28111.13100490283 ); + lambda.setSquaredSigma( 24251.31739665425 ); + lambda.setCenter( new double[] { 380.26234219457774, 284.5915704375881 } ); + + final RealPoint p = new RealPoint( 0.0, 0.0 ); + final RealPoint q = new RealPoint( 0.0, 0.0 ); + + final TpsTransformSolver baseSolver = new TpsTransformSolver(); + + final MaskedSimRotTransformSolver solver = new MaskedSimRotTransformSolver<>( 2, baseSolver, lambda, c.positionAsDoubleArray(), Interpolators.ROTATION ); + WrappedIterativeInvertibleRealTransform xfm = solver.solve( ltm ); + + int idx = 0; + + final int nd = ltm.getNumdims(); + final RealPoint x = new RealPoint( nd ); + final RealPoint y = new RealPoint( nd ); + + x.setPosition( Arrays.stream( ltm.getFixedPoint( idx ) ).mapToDouble( Double::doubleValue ).toArray() ); + xfm.apply( x, y ); + System.out.println("est pt : " + y ); + + final double[] ytrue = Arrays.stream( ltm.getMovingPoint( idx ) ).mapToDouble( Double::doubleValue ).toArray(); + System.out.println( "tru pt :" + new RealPoint( ytrue )); + + } + + public static > void transformLandmarkPoint( final LandmarkTableModel ltm, final int idx, + final BigWarpTransform bwTform, RealRandomAccessible< T > lambda ) + { + final int nd = ltm.getNumdims(); + final RealPoint x = new RealPoint( nd ); + final RealPoint y = new RealPoint( nd ); + + x.setPosition( Arrays.stream( ltm.getFixedPoint( idx ) ).mapToDouble( Double::doubleValue ).toArray() ); + + if( lambda != null ) + System.out.println( "l(x): " + lambda.getAt( x ) ); + + final InvertibleRealTransform xfm = bwTform.getTransformation(false); + + xfm.apply( x, y ); + System.out.println( y ); + + final double[] ytrue = Arrays.stream( ltm.getMovingPoint( idx ) ).mapToDouble( Double::doubleValue ).toArray(); + System.out.println( "true pt :" + Arrays.toString( ytrue )); + } + + + public static void test90deg() + { + final LandmarkTableModel ltm = new LandmarkTableModel(2); + ltm.add( new double[] { 0.0, 0.0 }, new double[] { 0.0, 0.0 } ); + ltm.add( new double[] { -1.0, 0.0 }, new double[] { 0.0, 1.0 } ); + ltm.add( new double[] { 0.0, 1.0 }, new double[] { 1.0, 0.0 } ); + ltm.add( new double[] { 1.0, 0.0 }, new double[] { 0.0, -1.0 } ); + ltm.add( new double[] { 0.0, -1.0 }, new double[] { -1.0, 0.0 } ); + + final RealPoint c = new RealPoint( 0.0, 0.0 ); + final PlateauSphericalMaskRealRandomAccessible lambda = new PlateauSphericalMaskRealRandomAccessible( c ); + lambda.setFalloffShape( FalloffShape.COSINE ); + lambda.setRadius( 1.0 ); + lambda.setSigma( 1.0 ); + + final RealPoint p = new RealPoint( 0.0, 0.0 ); + final RealPoint q = new RealPoint( 0.0, 0.0 ); + + final TpsTransformSolver baseSolver = new TpsTransformSolver(); + final MaskedSimRotTransformSolver solver = new MaskedSimRotTransformSolver<>( 2, baseSolver, lambda, c.positionAsDoubleArray(), Interpolators.ROTATION ); + + WrappedIterativeInvertibleRealTransform xfm = solver.solve( ltm ); + xfm.apply( p, q ); + System.out.println( p + " > " + q ); + + System.out.println( "" ); + p.setPosition( 2.0, 0 );; + xfm.apply( p, q ); + System.out.println( p + " > " + q ); + + System.out.println( "" ); + p.setPosition( 1.0, 0 ); + xfm.apply( p, q ); + System.out.println( p + " > " + q ); + + } + + +} \ No newline at end of file diff --git a/src/main/java/net/imglib2/realtransform/SimilarityTransformInterpolator.java b/src/main/java/net/imglib2/realtransform/SimilarityTransformInterpolator.java new file mode 100644 index 00000000..080e71b2 --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/SimilarityTransformInterpolator.java @@ -0,0 +1,92 @@ +package net.imglib2.realtransform; + +import net.imglib2.util.LinAlgHelpers; + +import bdv.util.Affine3DHelpers; +import bdv.viewer.animate.AbstractTransformAnimator; + +public class SimilarityTransformInterpolator extends AbstractTransformAnimator +{ + private final double[] qStart; + + private final double[] qDiff; + + private final double scaleStart; + + private final double scaleEnd; + + private final double scaleRate; + + private final double[] p; + + private final double[] pDiff; + + public SimilarityTransformInterpolator(final AffineTransform3D transform, final double[] p) { + super(1); + AffineTransform3D transformEnd = new AffineTransform3D(); + transformEnd.set(transform); + + this.p = p; + qStart = new double[]{1, 0, 0, 0}; + final double[] qStartInv = new double[4]; + final double[] qEnd = new double[4]; + final double[] qEndInv = new double[4]; + qDiff = new double[4]; + LinAlgHelpers.quaternionInvert(qStart, qStartInv); + + Affine3DHelpers.extractRotation(transformEnd, qEnd); + LinAlgHelpers.quaternionInvert(qEnd, qEndInv); + + LinAlgHelpers.quaternionMultiply(qStartInv, qEnd, qDiff); + if (qDiff[0] < 0) + LinAlgHelpers.scale(qDiff, -1, qDiff); + + scaleStart = 1.0; + scaleEnd = Affine3DHelpers.extractScale(transformEnd, 0); + scaleRate = scaleEnd / scaleStart; + + // translation needed to from reconstructed to target transformation + pDiff = new double[3]; + double[] tmp = new double[3]; + get(1).apply( p, tmp); + transform.apply(p, pDiff); + LinAlgHelpers.subtract(pDiff, tmp, pDiff); + } + + @Override + public AffineTransform3D get(final double t) { + + final double[] qDiffCurrent = new double[4]; + final double[] qCurrent = new double[4]; + LinAlgHelpers.quaternionPower(qDiff, t, qDiffCurrent); + LinAlgHelpers.quaternionMultiply(qStart, qDiffCurrent, qCurrent); + + final double alpha = Math.pow(scaleRate, t); + final double scaleCurrent = scaleStart * alpha; + + final double[][] Rcurrent = new double[3][3]; + LinAlgHelpers.quaternionToR(qCurrent, Rcurrent); + + final double[][] m = new double[3][4]; + for (int r = 0; r < 3; ++r) { + for (int c = 0; c < 3; ++c) + m[r][c] = scaleCurrent * Rcurrent[r][c]; + } + + final AffineTransform3D transform = new AffineTransform3D(); + transform.set(m); + + double[] pCurrent = new double[3]; + transform.apply(p, pCurrent); + + double[] pTgt = new double[3]; + LinAlgHelpers.scale( pDiff, t, pTgt ); + LinAlgHelpers.add( p, pTgt, pTgt ); + LinAlgHelpers.subtract( pTgt, pCurrent, pTgt ); + transform.translate( pTgt ); + + return transform; + + } + +} \ No newline at end of file diff --git a/src/main/java/net/imglib2/realtransform/SimilarityTransformInterpolator2D.java b/src/main/java/net/imglib2/realtransform/SimilarityTransformInterpolator2D.java new file mode 100644 index 00000000..943950fc --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/SimilarityTransformInterpolator2D.java @@ -0,0 +1,105 @@ +package net.imglib2.realtransform; + +import java.util.Arrays; + +import org.janelia.utility.geom.GeomUtils; + +import net.imglib2.util.LinAlgHelpers; + +public class SimilarityTransformInterpolator2D implements AffineInterpolator +{ + private final double thtDiff; + + private final AffineTransform2D tform; + + private final double[] c; + + private final double[] pDiff; + +// private final double sStart; + + private final double sDiff; + + public SimilarityTransformInterpolator2D( final AffineTransform2D transform, final double[] c ) + { + tform = new AffineTransform2D(); + + final AffineTransform2D transformEnd = new AffineTransform2D(); + transformEnd.set( transform ); + transformEnd.translate( -c[0], -c[1] ); + + double[] params = GeomUtils.scalesAngle( transformEnd ); + thtDiff = params[ 2 ]; +// double s = ( params[ 0 ] + params[ 1 ] ) / 2.0 ; +// sDiff = s - 1.0; + + sDiff = ( params[ 0 ] + params[ 1 ] ) / 2.0 - 1.0; + + // translation needed to from reconstructed to target transformation + this.c = new double[ 2 ]; + System.arraycopy( c, 0, this.c, 0, 2 ); + + pDiff = new double[ 2 ]; + final double[] tmp = new double[ 2 ]; + final AffineTransform2D t1 = get( 1 ); + t1.apply( c, tmp ); + transform.apply( c, pDiff ); + LinAlgHelpers.subtract( pDiff, tmp, pDiff ); + } + + public AffineTransform2D get( final double t ) + { + // make identity + tform.set( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0 ); + + // the rotation + tform.rotate( t * thtDiff ); + + // the scale + tform.scale( 1.0 + t * sDiff ); + + // the translation + final double[] pCurrent = new double[ 2 ]; + final double[] pTgt = new double[ 2 ]; + tform.apply( c, pCurrent ); + + LinAlgHelpers.scale( pDiff, t, pTgt ); + LinAlgHelpers.add( c, pTgt, pTgt ); + LinAlgHelpers.subtract( pTgt, pCurrent, pTgt ); + tform.translate( pTgt ); + + return tform; + } + + public static void main( String[] args ) + { + final double tht = Math.PI - 0.1; + final double scale = 2.2; + final double[] c = new double[]{ -10.0, 20.0 }; + final AffineTransform2D t = GeomUtils.centeredSimilarity( tht, scale, c ); + System.out.println( t ); + + double[] ps = GeomUtils.scalesAngle( t, c ); + System.out.println( Arrays.toString( ps )); + double sx = ps[0]; + double sy = ps[1]; + double thtEst = ps[2]; + double sEst = 0.5 * ( sx + sy ); + + + System.out.println( "\ntht: " + tht + " vs " + thtEst ); + System.out.println( "scl: " + sEst + " vs " + scale + "\n" ); + + AffineTransform2D tEst = GeomUtils.centeredSimilarity( thtEst, sEst, c ); + System.out.println( tEst ); + + + final SimilarityTransformInterpolator2D interp = new SimilarityTransformInterpolator2D( t, c ); +//// System.out.println( interp ); +// System.out.println( interp.get( 0 ) ); + + System.out.println( t ); + System.out.println( interp.get( 1 ) ); + } + +} \ No newline at end of file diff --git a/src/main/java/net/imglib2/realtransform/SpatiallyInterpolatedRealTransform.java b/src/main/java/net/imglib2/realtransform/SpatiallyInterpolatedRealTransform.java new file mode 100644 index 00000000..a25ca558 --- /dev/null +++ b/src/main/java/net/imglib2/realtransform/SpatiallyInterpolatedRealTransform.java @@ -0,0 +1,105 @@ +package net.imglib2.realtransform; + +import net.imglib2.RealLocalizable; +import net.imglib2.RealPoint; +import net.imglib2.RealPositionable; +import net.imglib2.RealRandomAccess; +import net.imglib2.RealRandomAccessible; +import net.imglib2.type.numeric.RealType; + +/** + * Spatially-varying interpolation between two {@link RealTransform}s. + *

+ * Given a {@link RealRandomAccessible} "lambda", and two transformations "a", and "b", implements the transformation + * lambda * a(x) + (1-lambda) * b(x) for a point x. + * + * @author John Bogovic + * + * @param lambda's type + */ +public class SpatiallyInterpolatedRealTransform> implements RealTransform { + + private RealRandomAccessible lambda; + + private RealRandomAccess lambdaAccess; + + private final RealTransform a; + + private final RealTransform b; + + private final RealPoint pa; + + private final RealPoint pb; + + private final double[] arrA; + + private final double[] arrB; + + public SpatiallyInterpolatedRealTransform(RealTransform a, RealTransform b, RealRandomAccessible lambda) { + + assert (a.numTargetDimensions() == b.numTargetDimensions() && + a.numSourceDimensions() == b.numSourceDimensions()); + this.a = a; + this.b = b; + this.lambda = lambda; + + if( lambda != null ) + lambdaAccess = lambda.realRandomAccess(); + + final int nd = a.numTargetDimensions(); + arrA = new double[nd]; + arrB = new double[nd]; + pa = RealPoint.wrap(arrA); + pb = RealPoint.wrap(arrB); + } + + @Override + public int numSourceDimensions() { + + return a.numSourceDimensions(); + } + + @Override + public int numTargetDimensions() { + + return a.numTargetDimensions(); + } + + @Override + public void apply(double[] source, double[] target) { + + a.apply(source, arrA); + b.apply(source, arrB); + +// lambdaAccess.setPosition(source); + for( int i = 0; i < numSourceDimensions(); i++ ) + lambdaAccess.setPosition( source[ i ], i ); + + final double am = lambdaAccess.get().getRealDouble(); + final double bm = (1 - am); + + for (int i = 0; i < numTargetDimensions(); i++) + target[i] = am * arrA[i] + bm * arrB[i]; + } + + @Override + public void apply(RealLocalizable source, RealPositionable target) { + + a.apply(source, pa); + b.apply(source, pb); + + lambdaAccess.setPosition(source); + final double am = lambdaAccess.get().getRealDouble(); + final double bm = (1 - am); + + for (int i = 0; i < numTargetDimensions(); i++) + target.setPosition(am * pa.getDoublePosition(i) + bm * pb.getDoublePosition(i), i); + } + + @Override + public RealTransform copy() { + + return new SpatiallyInterpolatedRealTransform(a.copy(), b.copy(), lambda); + } + +} diff --git a/src/main/java/net/imglib2/realtransform/Wrapped2DTransformAs3D.java b/src/main/java/net/imglib2/realtransform/Wrapped2DTransformAs3D.java index 4e6eb809..fafbef66 100644 --- a/src/main/java/net/imglib2/realtransform/Wrapped2DTransformAs3D.java +++ b/src/main/java/net/imglib2/realtransform/Wrapped2DTransformAs3D.java @@ -24,19 +24,19 @@ import net.imglib2.RealLocalizable; import net.imglib2.RealPositionable; -public class Wrapped2DTransformAs3D implements InvertibleRealTransform +public class Wrapped2DTransformAs3D implements RealTransform { - public InvertibleRealTransform transform; + public RealTransform transform; public double[] tmp; - public Wrapped2DTransformAs3D( final InvertibleRealTransform transform ) + public Wrapped2DTransformAs3D( final RealTransform transform ) { this.transform = transform; tmp = new double[ 2 ]; } - public InvertibleRealTransform getTransform() + public RealTransform getTransform() { return transform; } @@ -83,39 +83,9 @@ public void apply( RealLocalizable source, RealPositionable target ) target.setPosition( source.getDoublePosition( 2 ), 2 ); } - @Override - public void applyInverse( double[] source, double[] target ) - { - tmp[ 0 ] = target[ 0 ]; - tmp[ 1 ] = target[ 1 ]; - transform.applyInverse( tmp, tmp ); - source[ 0 ] = tmp[ 0 ]; - source[ 1 ] = tmp[ 1 ]; -// transform.applyInverse( source, target ); - source[ 2 ] = target[ 2 ]; - } - - @Override - public void applyInverse( RealPositionable source, RealLocalizable target ) - { - tmp[ 0 ] = target.getDoublePosition( 0 ); - tmp[ 1 ] = target.getDoublePosition( 1 ); - transform.applyInverse( tmp, tmp ); - source.setPosition( tmp[ 0 ], 0 ); - source.setPosition( tmp[ 1 ], 1 ); -// transform.applyInverse( source, target ); - source.setPosition( target.getDoublePosition( 2 ), 2 ); - } - - public InvertibleRealTransform copy() + public Wrapped2DTransformAs3D copy() { return new Wrapped2DTransformAs3D( transform.copy() ); } - @Override - public InvertibleRealTransform inverse() - { - return new Wrapped2DTransformAs3D( transform.inverse() ); - } - } diff --git a/src/main/java/org/janelia/utility/geom/BoundingSphereRitter.java b/src/main/java/org/janelia/utility/geom/BoundingSphereRitter.java new file mode 100644 index 00000000..95ced48a --- /dev/null +++ b/src/main/java/org/janelia/utility/geom/BoundingSphereRitter.java @@ -0,0 +1,123 @@ +package org.janelia.utility.geom; + +import java.util.List; + +/** + * Estimates the smallest sphere that bounds given points. + *

+ * Ritter, Jack (1990), "An efficient bounding sphere", in Glassner, Andrew S. (ed.), Graphics Gems, San Diego, CA, US: Academic Press Professional, Inc., pp. 301–303, + * + * @author John Bogovic + * + */ +public class BoundingSphereRitter +{ + + /** + * Returns an estimate of the smallest {@link Sphere} that contains the input points. + * + * @param points a collection of points + * @return the bounding sphere + */ + public static Sphere boundingSphere( final List points ) + { + if( points == null || points.isEmpty() ) + return null; + + final int nd = points.get( 0 ).length; + final double[] x = new double[ nd ]; + final double[] y = new double[ nd ]; + double maxSqrDist = -1; + // find pair of points with the largest distance + for ( int i = 0; i < points.size(); i++ ) + for ( int j = i + 1; j < points.size(); j++ ) + { + final double d = GeomUtils.squaredDistance( points.get( i ), points.get( j ) ); + if ( d > maxSqrDist ) + { + maxSqrDist = d; + System.arraycopy( points.get( i ), 0, x, 0, nd ); + System.arraycopy( points.get( j ), 0, y, 0, nd ); + } + } + + final double[] center = new double[ nd ]; + for ( int d = 0; d < nd; d++ ) + center[d] = 0.5 * ( x[d] + y[d] ); + + final Sphere sph = new Sphere( center, Math.sqrt( maxSqrDist ) / 2 ); + boolean allCovered = false; + int k = 0; + while( !allCovered && k < 5) + { + allCovered = false; + for( final double[] p : points ) + { + allCovered |= updateSphere( sph, p ); + } + k++; + } + return sph; + } + + /** + * return the point in point farthest from p + * + * @param points a list of points + * @param p a base point + * @return the point farthest from p + */ + public static double[] furthestFrom( final List points, double[] p ) + { + double maxSqrDist = -1; + int maxIndex = -1; + for ( int i = 0; i < points.size(); i++ ) + { + final double dist = GeomUtils.squaredDistance( p, points.get( i ) ); + if ( dist > maxSqrDist ) + { + maxSqrDist = dist; + maxIndex = i; + } + } + return points.get( maxIndex ); + } + + /** + * Changes the value of the input sphere such that it contains the given point p. + * Returns true if the sphere was modified. + * + * @param sphere + * @param p + * @return true if the sphere was changed + */ + public static boolean updateSphere( final Sphere sphere, final double[] p ) + { + final double sqrDist = GeomUtils.squaredDistance( sphere.getCenterArray(), p ); + final double r = sphere.getRadius(); + if ( sqrDist <= r*r ) + { + return false; + } else + { +// System.out.println( "update sphere" ); + final double halfDist = 0.5 * ( Math.sqrt( sqrDist ) - sphere.getRadius() ); + final double[] c = sphere.getCenterArray(); + final double[] v = new double[ c.length ]; + double mag = 0; + for( int i = 0; i < c.length; i++ ) + { + v[i] = ( p[i] - c[i]); + mag += v[i] * v[i]; + } + for( int i = 0; i < c.length; i++ ) + { + c[i] += v[i] * halfDist / Math.sqrt( mag ); + } + + sphere.setRadius( sphere.getRadius() + halfDist ); + return true; + } + } + +} diff --git a/src/main/java/org/janelia/utility/geom/GeomUtils.java b/src/main/java/org/janelia/utility/geom/GeomUtils.java new file mode 100644 index 00000000..2917be9f --- /dev/null +++ b/src/main/java/org/janelia/utility/geom/GeomUtils.java @@ -0,0 +1,197 @@ +package org.janelia.utility.geom; + +import java.util.List; + +import net.imglib2.RealLocalizable; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineGet; +import net.imglib2.realtransform.AffineTransform2D; +import net.imglib2.util.Pair; +import net.imglib2.util.ValuePair; + +public class GeomUtils { + + /** + * Finds the parameters of the smallest hypersphere containing the points. + * + * @param pts a list of points + * @return a pair containing the center and the squared distance + */ + public static Pair smallestEnclosingSphere( List pts ) + { + RealPoint p = null; + RealPoint q = null; + double maxSqrDist = Double.POSITIVE_INFINITY; + // find pair of points with the largest distance + for( int i = 0; i < pts.size(); i++) + for( int j = i+1; j < pts.size(); j++) { + final double d = squaredDistance( pts.get( i ), pts.get( j )); + if( d < maxSqrDist ) + { + maxSqrDist = d; + p = pts.get( i ); + q = pts.get( j ); + } + } + + final RealPoint center = new RealPoint( p.numDimensions()); + for( int d = 0; d < p.numDimensions(); d++ ) + { + center.setPosition( + 0.5 * p.getDoublePosition(d) + 0.5 * q.getDoublePosition(d), + d ); + } + return new ValuePair(center, maxSqrDist); + } + + final public static void scale( final RealPoint p, final double scale ) + { + for( int i = 0; i < p.numDimensions(); i++ ) + p.setPosition( p.getDoublePosition( i ) * scale , i); + } + + final public static double squaredDistance( final RealLocalizable position1, final RealLocalizable position2 ) + { + double dist = 0; + + final int n = position1.numDimensions(); + for ( int d = 0; d < n; ++d ) + { + final double pos = position2.getDoublePosition( d ) - position1.getDoublePosition( d ); + + dist += pos * pos; + } + + return dist; + } + + final public static double squaredDistance( final double[] position1, final double[] position2 ) + { + double dist = 0; + + final int n = position1.length; + for ( int d = 0; d < n; ++d ) + { + final double pos = position2[d] - position1[d]; + dist += pos * pos; + } + + return dist; + } + + public static AffineTransform2D fromScalesAngle( double... p ) + { + return fromScalesAngle( p[0], p[1], p[2] ); + } + + public static AffineTransform2D fromScalesAngle( double sx, double sy, double tht ) + { + final AffineTransform2D t = new AffineTransform2D(); + final double cosTht = Math.cos( tht ); + final double sinTht = Math.sin( tht ); + t.set( sx * cosTht, -sx * sinTht, 0.0, + sy * sinTht, sy * cosTht, 0.0 ); + + return t; + } + + /** + * Returns an array containing [sx, sy, tht], where + * sx : the x scale + * sy : the y scale + * tht : the angle + * + * @param t the transformations + * @return the scales and angle [sx, sy, tht] + */ + public static double[] scalesAngle( final AffineTransform2D t ) + { + final double a = t.get( 0, 0 ); final double b = t.get( 0, 1 ); + final double c = t.get( 1, 0 ); final double d = t.get( 1, 1 ); + + final double sa = a >= 0 ? 1 : -1; + final double sd = d >= 0 ? 1 : -1; + + final double mab = Math.sqrt( a * a + b * b ); + final double mcd = Math.sqrt( c * c + d * d ); + + final double sx = sa * mab; + final double sy = sd * mcd; + final double tht = Math.atan2( -b, a ); + return new double[] { sx, sy, tht }; + } + + /** + * Returns an array containing [sx, sy, tht], where + * sx : the x scale + * sy : the y scale + * tht : the angle + * + * @param t the transformations + * @return the scales and angle [sx, sy, tht] + */ + public static double[] scalesAngle( final AffineTransform2D t, final double[] center ) + { + // TODO why does the center arg do nothing? + + final double a = t.get( 0, 0 ); final double b = t.get( 0, 1 ); + final double c = t.get( 1, 0 ); final double d = t.get( 1, 1 ); + + // don't allow flips +// final double sa = a >= 0 ? 1 : -1; +// final double sd = d >= 0 ? 1 : -1; + final double sa = 1.0; + final double sd = 1.0; + + final double mab = Math.sqrt( a * a + b * b ); + final double mcd = Math.sqrt( c * c + d * d ); + + final double sx = sa * mab; + final double sy = sd * mcd; + final double tht = Math.atan2( -b, a ); + return new double[] { sx, sy, tht }; + +// final double tht1 = Math.atan2( -b, a ); +// final double tht2 = Math.atan2( c, d ); +// System.out.println( "tht1 : " + tht1 ); +// System.out.println( "tht2 : " + tht2 ); +// return new double[] { sx, sy, tht1, tht2 }; + } + + public static double[] evals2d( AffineGet a ) + { + final double m = trace2d( a ) / 2.0; + final double p = det2d( a ); + final double d = Math.sqrt( m * m - p ); + return new double[] { m + d, m - d }; + } + + public static double det2d( AffineGet a ) { + return a.get( 0, 0 ) * a.get( 1, 1 ) - + a.get( 1, 0 ) * a.get( 0, 1 ); + } + + public static double trace2d( AffineGet a ) { + return a.get( 0, 0 ) + a.get( 1, 1 ); + } + + public static AffineTransform2D centeredRotation( final double tht, final double[] c ) + { + final AffineTransform2D t = new AffineTransform2D(); + t.translate( -c[ 0 ], -c[ 1 ] ); + t.rotate( tht ); + t.translate( c ); + return t; + } + + public static AffineTransform2D centeredSimilarity( final double tht, final double scale, final double[] c ) + { + final AffineTransform2D t = new AffineTransform2D(); + t.translate( -c[ 0 ], -c[ 1 ] ); + t.rotate( tht ); + t.scale( scale ); + t.translate( c ); + return t; + } + +} diff --git a/src/main/java/org/janelia/utility/geom/Sphere.java b/src/main/java/org/janelia/utility/geom/Sphere.java new file mode 100644 index 00000000..793a1bb9 --- /dev/null +++ b/src/main/java/org/janelia/utility/geom/Sphere.java @@ -0,0 +1,62 @@ +package org.janelia.utility.geom; + +import net.imglib2.RealPoint; + +public class Sphere +{ + private RealPoint center; + private double[] centerArr; + private double radius; + + public Sphere( double[] center, double radius ) + { + this.centerArr = center; + this.radius = radius; + this.center = RealPoint.wrap( center ); + } + + public Sphere( final RealPoint center, double radius ) + { + this( center.positionAsDoubleArray(), radius ); + } + + public RealPoint getCenter() + { + return center; + } + + public double[] getCenterArray() + { + return centerArr; + } + + public double getRadius() + { + return radius; + } + + public void setRadius( final double radius ) + { + this.radius = radius; + } + + public void setCenter( final double[] center ) + { + System.arraycopy( center, 0, centerArr, 0, centerArr.length ); + } + + /** + * Is a point inside this sphere. + * Returns true if the distance from the given point to the center is less than or equal to + * this sphere's radius. + * + * @param p the point + * @return true if the point inside this sphere. + */ + public boolean isInside( double[] p ) + { + final double r2 = radius * radius; + return GeomUtils.squaredDistance( centerArr, p ) <= r2; + } + +} diff --git a/src/main/resources/bigwarp/ui/keymap/default.yaml b/src/main/resources/bigwarp/ui/keymap/default.yaml new file mode 100644 index 00000000..196ac448 --- /dev/null +++ b/src/main/resources/bigwarp/ui/keymap/default.yaml @@ -0,0 +1,497 @@ +--- +- !mapping + action: close dialog window + contexts: [bigwarp, bw-table] + triggers: [not mapped] +- !mapping + action: Preferences + contexts: [bigwarp, bw-table] + triggers: [ctrl COMMA, meta COMMA] +- !mapping + action: brightness settings + contexts: [bigwarp, bw-table] + triggers: [S] +- !mapping + action: go to bookmark + contexts: [bigwarp, bw-table] + triggers: [B] +- !mapping + action: go to bookmark rotation + contexts: [bigwarp, bw-table] + triggers: [shift ctrl B] +- !mapping + action: help + contexts: [bigwarp, bw-table] + triggers: [F1] +- !mapping + action: landmark mode toggle + contexts: [bigwarp, bw-table] + triggers: [SPACE] +- !mapping + action: set bookmark + contexts: [bigwarp, bw-table] + triggers: [shift B] +- !mapping + action: visibility and grouping moving + contexts: [bigwarp, bw-table] + triggers: [F3] +- !mapping + action: visibility and grouping target + contexts: [bigwarp, bw-table] + triggers: [F4] +- !mapping + action: save settings + contexts: [bigwarp, bw-table] + triggers: [not mapped] +- !mapping + action: load settings + contexts: [bigwarp, bw-table] + triggers: [not mapped] +- !mapping + action: activate selected landmarks + contexts: [bw-table] + triggers: [ctrl BACK_SPACE] +- !mapping + action: table activate selected + contexts: [bw-table] + triggers: [not mapped] +- !mapping + action: align XY plane + contexts: [bigwarp, navigation] + triggers: [shift Z] +- !mapping + action: align XZ plane + contexts: [bigwarp, navigation ] + triggers: [shift A, shift Y] +- !mapping + action: align ZY plane + contexts: [bigwarp, navigation] + triggers: [shift X] +- !mapping + action: align view transforms ACTIVE_TO_OTHER + contexts: [bigwarp, navigation] + triggers: [W] +- !mapping + action: align view transforms OTHER_TO_ACTIVE + contexts: [bigwarp, navigation] + triggers: [Q] +- !mapping + action: center on nearest landmark + contexts: [bigwarp, navigation] + triggers: [Y] +- !mapping + action: center on next landmark + contexts: [bigwarp, navigation] + triggers: [ctrl D] +- !mapping + action: center on prev landmark + contexts: [bigwarp, navigation] + triggers: [shift ctrl D] +- !mapping + action: center on selected landmark + contexts: [bigwarp, navigation] + triggers: [E] +- !mapping + action: expand and focus cards panel + contexts: [bigwarp] + triggers: [P] +- !mapping + action: collapse cards panel + contexts: [bigwarp] + triggers: [shift ESCAPE, shift P] +- !mapping + action: deactivate selected landmarks + contexts: [bw-table] + triggers: [BACK_SPACE] +- !mapping + action: delete selected landmarks + contexts: [bw-table] + triggers: [DELETE] +- !mapping + action: deselect all landmarks + contexts: [bw-table] + triggers: [ESCAPE] +- !mapping + action: export affine + contexts: [bigwarp] + triggers: [ctrl A] +- !mapping + action: export imageplus + contexts: [bigwarp] + triggers: [shift ctrl W] +- !mapping + action: export warp field + contexts: [bigwarp] + triggers: [ctrl W] +- !mapping + action: load landmarks + contexts: [bw-table, bigwarp] + triggers: [ctrl O] +- !mapping + action: next timepoint + contexts: [bigwarp, navigation] + triggers: [CLOSE_BRACKET] +- !mapping + action: previous timepoint + contexts: [bigwarp, navigation] + triggers: [OPEN_BRACKET] +- !mapping + action: quick save landmarks + contexts: [bw-table, bigwarp] + triggers: [ctrl Q] +- !mapping + action: redo + contexts: [bw-table, bigwarp] + triggers: [ctrl Y, shift ctrl Z] +- !mapping + action: reset active viewer + contexts: [bigwarp, navigation] + triggers: [R] +- !mapping + action: save landmarks + contexts: [bw-table, bigwarp] + triggers: [ctrl S] +- !mapping + action: save warped xml + contexts: [bigwarp, bw-table] + triggers: [ctrl E] +- !mapping + action: select all landmarks + contexts: [bigwarp, bw-table] + triggers: [ctrl A] +- !mapping + action: select all landmarks above + contexts: [bw-table] + triggers: [shift ctrl UP] +- !mapping + action: select all landmarks below + contexts: [bw-table] + triggers: [shift ctrl DOWN] +- !mapping + action: select landmark above + contexts: [bw-table] + triggers: [ctrl UP] +- !mapping + action: select landmark below + contexts: [bw-table] + triggers: [ctrl DOWN] +- !mapping + action: set current source 0 + contexts: [bigwarp, navigation] + triggers: ['1'] +- !mapping + action: set current source 1 + contexts: [bigwarp, navigation] + triggers: ['2'] +- !mapping + action: set current source 2 + contexts: [bigwarp, navigation] + triggers: ['3'] +- !mapping + action: set current source 3 + contexts: [bigwarp, navigation] + triggers: ['4'] +- !mapping + action: set current source 4 + contexts: [bigwarp, navigation] + triggers: ['5'] +- !mapping + action: set current source 5 + contexts: [bigwarp, navigation] + triggers: ['6'] +- !mapping + action: set current source 6 + contexts: [bigwarp, navigation] + triggers: ['7'] +- !mapping + action: set current source 7 + contexts: [bigwarp, navigation] + triggers: ['8'] +- !mapping + action: set current source 8 + contexts: [bigwarp, navigation] + triggers: ['9'] +- !mapping + action: set current source 9 + contexts: [bigwarp, navigation] + triggers: ['0'] +- !mapping + action: show warp vis dialog + contexts: [bigwarp, bw-table] + triggers: [U] +- !mapping + action: toggle fused mode + contexts: [bigwarp] + triggers: [F] +- !mapping + action: toggle grouping + contexts: [bigwarp] + triggers: [G] +- !mapping + action: toggle interpolation + contexts: [bigwarp] + triggers: [I] +- !mapping + action: toggle moving image display + contexts: [bigwarp] + triggers: [T] +- !mapping + action: toggle point names visible + contexts: [bigwarp] + triggers: [N] +- !mapping + action: toggle points visible + contexts: [bigwarp] + triggers: [V] +- !mapping + action: toggle source visibility 0 + contexts: [bigwarp] + triggers: [shift 0] +- !mapping + action: toggle source visibility 1 + contexts: [bigwarp] + triggers: [shift 1] +- !mapping + action: toggle source visibility 2 + contexts: [bigwarp] + triggers: [shift 2] +- !mapping + action: toggle source visibility 3 + contexts: [bigwarp] + triggers: [shift 3] +- !mapping + action: toggle source visibility 4 + contexts: [bigwarp] + triggers: [shift 4] +- !mapping + action: toggle source visibility 5 + contexts: [bigwarp] + triggers: [shift 5] +- !mapping + action: toggle source visibility 6 + contexts: [bigwarp] + triggers: [shift 6] +- !mapping + action: toggle source visibility 7 + contexts: [bigwarp] + triggers: [shift 7] +- !mapping + action: toggle source visibility 8 + contexts: [bigwarp] + triggers: [shift 8] +- !mapping + action: toggle source visibility 9 + contexts: [bigwarp] + triggers: [shift 9] +- !mapping + action: undo + contexts: [bigwarp, bw-table] + triggers: [ctrl Z] +- !mapping + action: drag translate + contexts: [bigwarp, navigation] + triggers: [button2, button3] +- !mapping + action: scroll zoom + contexts: [bigwarp, navigation] + triggers: [meta scroll, shift ctrl scroll] +- !mapping + action: axis x + contexts: [bigwarp, navigation] + triggers: [X] +- !mapping + action: axis y + contexts: [bigwarp, navigation] + triggers: [Y] +- !mapping + action: axis z + contexts: [bigwarp, navigation] + triggers: [Z] +- !mapping + action: drag rotate + contexts: [bigwarp, navigation] + triggers: [button1] +- !mapping + action: scroll browse z + contexts: [bigwarp, navigation] + triggers: [scroll] +- !mapping + action: rotate left + contexts: [bigwarp, navigation] + triggers: [LEFT] +- !mapping + action: rotate right + contexts: [bigwarp, navigation] + triggers: [RIGHT] +- !mapping + action: zoom in + contexts: [bigwarp, navigation] + triggers: [UP] +- !mapping + action: zoom out + contexts: [bigwarp, navigation] + triggers: [DOWN] +- !mapping + action: forward z + contexts: [bigwarp, navigation] + triggers: [COMMA] +- !mapping + action: backward z + contexts: [bigwarp, navigation] + triggers: [PERIOD] +- !mapping + action: drag rotate fast + contexts: [bigwarp, navigation] + triggers: [shift button1] +- !mapping + action: scroll browse z fast + contexts: [bigwarp, navigation] + triggers: [shift scroll] +- !mapping + action: rotate left fast + contexts: [bigwarp, navigation] + triggers: [shift LEFT] +- !mapping + action: rotate right fast + contexts: [bigwarp, navigation] + triggers: [shift RIGHT] +- !mapping + action: zoom in fast + contexts: [bigwarp, navigation] + triggers: [shift UP] +- !mapping + action: zoom out fast + contexts: [bigwarp, navigation] + triggers: [shift DOWN] +- !mapping + action: forward z fast + contexts: [bigwarp, navigation] + triggers: [shift COMMA] +- !mapping + action: backward z fast + contexts: [bigwarp, navigation] + triggers: [shift PERIOD] +- !mapping + action: drag rotate slow + contexts: [bigwarp, navigation] + triggers: [ctrl button1] +- !mapping + action: scroll browse z slow + contexts: [bigwarp, navigation] + triggers: [ctrl scroll] +- !mapping + action: rotate left slow + contexts: [bigwarp, navigation] + triggers: [ctrl LEFT] +- !mapping + action: rotate right slow + contexts: [bigwarp, navigation] + triggers: [ctrl RIGHT] +- !mapping + action: zoom in slow + contexts: [bigwarp, navigation] + triggers: [ctrl UP] +- !mapping + action: zoom out slow + contexts: [bigwarp, navigation] + triggers: [ctrl DOWN] +- !mapping + action: forward z slow + contexts: [bigwarp, navigation] + triggers: [ctrl COMMA] +- !mapping + action: backward z slow + contexts: [bigwarp, navigation] + triggers: [ctrl PERIOD] +- !mapping + action: 2d drag translate + contexts: [bigwarp, navigation] + triggers: [button2, button3] +- !mapping + action: 2d drag rotate + contexts: [bigwarp, navigation] + triggers: [button1] +- !mapping + action: 2d scroll zoom + contexts: [bigwarp, navigation] + triggers: [meta scroll, scroll, shift ctrl scroll] +- !mapping + action: 2d scroll zoom fast + contexts: [bigwarp, navigation] + triggers: [shift scroll] +- !mapping + action: 2d scroll zoom slow + contexts: [bigwarp, navigation] + triggers: [ctrl scroll] +- !mapping + action: 2d scroll translate + contexts: [bigwarp, navigation] + triggers: [not mapped] +- !mapping + action: 2d scroll translate fast + contexts: [bigwarp, navigation] + triggers: [not mapped] +- !mapping + action: 2d scroll translate slow + contexts: [bigwarp, navigation] + triggers: [not mapped] +- !mapping + action: 2d rotate left + contexts: [bigwarp, navigation] + triggers: [LEFT] +- !mapping + action: 2d rotate right + contexts: [bigwarp, navigation] + triggers: [RIGHT] +- !mapping + action: 2d zoom in + contexts: [bigwarp, navigation] + triggers: [UP] +- !mapping + action: 2d zoom out + contexts: [bigwarp, navigation] + triggers: [DOWN] +- !mapping + action: 2d rotate left fast + contexts: [bigwarp, navigation] + triggers: [shift LEFT] +- !mapping + action: 2d rotate right fast + contexts: [bigwarp, navigation] + triggers: [shift RIGHT] +- !mapping + action: 2d zoom in fast + contexts: [bigwarp, navigation] + triggers: [shift UP] +- !mapping + action: 2d zoom out fast + contexts: [bigwarp, navigation] + triggers: [shift DOWN] +- !mapping + action: 2d rotate left slow + contexts: [bigwarp, navigation] + triggers: [ctrl LEFT] +- !mapping + action: 2d rotate right slow + contexts: [bigwarp, navigation] + triggers: [ctrl RIGHT] +- !mapping + action: 2d zoom in slow + contexts: [bigwarp, navigation] + triggers: [ctrl UP] +- !mapping + action: 2d zoom out slow + contexts: [bigwarp, navigation] + triggers: [ctrl DOWN] +- !mapping + action: 2d scroll rotate + contexts: [bigwarp, navigation] + triggers: [not mapped] +- !mapping + action: 2d scroll rotate fast + contexts: [bigwarp, navigation] + triggers: [not mapped] +- !mapping + action: 2d scroll rotate slow + contexts: [bigwarp, navigation] + triggers: [not mapped] diff --git a/src/test/java/bigwarp/BigWarpTestUtils.java b/src/test/java/bigwarp/BigWarpTestUtils.java new file mode 100644 index 00000000..49e45de6 --- /dev/null +++ b/src/test/java/bigwarp/BigWarpTestUtils.java @@ -0,0 +1,275 @@ +package bigwarp; + +import bdv.gui.BigWarpViewerOptions; +import bdv.viewer.Source; +import bigwarp.source.SourceInfo; +import com.google.common.collect.MapDifference; +import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import ij.IJ; +import ij.ImagePlus; +import ij.gui.NewImage; +import ij.plugin.StackWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.Type; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; +import mpicbg.spim.data.SpimDataException; +import net.imglib2.FinalInterval; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.position.FunctionRandomAccessible; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.view.Views; +import org.junit.Assert; + +public class BigWarpTestUtils +{ + + /** + * Create a 3D image file which is deleted on exit. + * + * @param title of the temporary image file + * @param format of the temporary image file + * @return the path to the temporary image file + */ + public static String createTemp3DImage( String title, String format ) + { + + final Path tmpImgPath; + try + { + tmpImgPath = Files.createTempFile( title, "." + format ); + //noinspection ResultOfMethodCallIgnored + tmpImgPath.toFile().delete(); + } + catch ( final IOException e ) + { + throw new RuntimeException( e ); + } + + return createTemp3DImage( format, tmpImgPath ); + } + + private static String create3DImage( final String format, final Path tmpImgPath ) throws IOException + { + final ImagePlus img3d = NewImage.createByteImage( tmpImgPath.getFileName().toString(), 8, 8, 4, NewImage.FILL_RAMP ); + System.out.println( tmpImgPath.toString()); + IJ.saveAs(img3d, format, tmpImgPath.toString()); + + tmpImgPath.toFile().deleteOnExit(); + return tmpImgPath.toString(); + } + + /** + * Create a 3D image file at {@code imagePath} which is deleted on exit. + * + * @param imagePath of the temporary image file + * @return the path to the temporary image file + */ + public static String createTemp3DImage( final String format, Path imagePath ) + { + try + { + return create3DImage( format, imagePath ); + } + catch ( final Exception e ) + { + //noinspection ResultOfMethodCallIgnored + imagePath.toFile().delete(); + throw new RuntimeException( e ); + } + } + + private static String create2DImage( final String format, final Path tmpImgPath ) throws IOException + { + final ImagePlus img2d = NewImage.createByteImage( tmpImgPath.getFileName().toString(), 8, 8, 1, NewImage.FILL_RAMP ); + System.out.println( tmpImgPath.toString()); + IJ.saveAs(img2d, format, tmpImgPath.toString()); + tmpImgPath.toFile().deleteOnExit(); + return tmpImgPath.toString(); + } + + /** + * Create a 2D image file which is deleted on exit. + * + * @param title of the temporary image file + * @param format of the temporary image file + * @return the path to the temporary image file + */ + public static String createTemp2DImage( String title, String format ) + { + + Path tmpImg = null; + try + { + tmpImg = Files.createTempFile( title, "." + format ); + //noinspection ResultOfMethodCallIgnored + tmpImg.toFile().delete(); + return create2DImage( format, tmpImg ); + } + catch ( final Exception e ) + { + if (tmpImg != null) { + //noinspection ResultOfMethodCallIgnored + tmpImg.toFile().delete(); + } + throw new RuntimeException( e ); + } + } + + /** + * Create a 3D image stack which is deleted on exit. + * + * @param title of the temporary image stack + * @return the path to the temporary image stack + */ + public static String createTemp3DImageStack( String title ) + { + + final ImagePlus img3d = NewImage.createByteImage( title, 8, 8, 4, NewImage.FILL_RAMP ); + + Path tmpStackDir; + try + { + + tmpStackDir = Files.createTempDirectory( title ); + StackWriter.save( img3d ,tmpStackDir.toString() + "/", "format=tiff"); + return tmpStackDir.toString(); + } + catch ( final IOException e ) + { + throw new RuntimeException( e ); + } + } + + public static void assertJsonDiff( final JsonElement expectedJson, final JsonElement actualJson ) + { + final Gson gson = new Gson(); + //noinspection UnstableApiUsage + final Type mapType = new TypeToken< Map< String, Object > >() + { + }.getType(); + final Map< String, Object > expectedMap = gson.fromJson( expectedJson, mapType ); + final Map< String, Object > actualMap = gson.fromJson( actualJson, mapType ); + final MapDifference< String, Object > difference = Maps.difference( expectedMap, actualMap ); + if ( !difference.areEqual() ) + { + if ( difference.entriesDiffering().size() > 0 ) + { + difference.entriesDiffering().forEach( ( key, value ) -> { + final Object left = value.leftValue(); + final Object right = value.rightValue(); + + if ( left instanceof Map && right instanceof Map ) + { + final Map< ?, ? > leftMap = ( Map< ?, ? > ) value.leftValue(); + final Map< ?, ? > rightMap = ( Map< ?, ? > ) value.rightValue(); + removeEqualKeys( leftMap, rightMap ); + } + } ); + } + Assert.fail( difference.toString() ); + } + + } + + private static void removeEqualKeys( Map< ?, ? > left, Map< ?, ? > right ) + { + final Object[] leftVals = left.values().toArray(); + final boolean hasChildren = leftVals.length > 0; + final boolean childrenIsMap = hasChildren && leftVals[ 0 ] instanceof Map< ?, ? >; + + if ( childrenIsMap ) + { + /* recurse*/ + left.keySet().forEach( key -> { + final Map< ?, ? > innerLeft = ( Map< ?, ? > ) left.get( key ); + final Map< ?, ? > innerRight = ( Map< ?, ? > ) right.get( key ); + removeEqualKeys( innerLeft, innerRight ); + } ); + } + else + { + final ArrayList< Object > keysToRemove = new ArrayList<>(); + left.forEach( ( checkKey, value1 ) -> { + if ( right.containsKey( checkKey ) && right.get( checkKey ).equals( value1 ) ) + { + keysToRemove.add( checkKey ); + } + } ); + keysToRemove.forEach( keyToRemove -> { + left.remove( keyToRemove ); + right.remove( keyToRemove ); + } ); + } + + } + + private static String prettyPrint( StringWriter json ) + { + return prettyPrint( json.toString() ); + } + + private static String prettyPrint( String json ) + { + final JsonElement parse = JsonParser.parseString( json ); + final JsonObject asJsonObject = parse.getAsJsonObject(); + + return prettyPrint( asJsonObject ); + } + + public static String prettyPrint( JsonObject json ) + { + + return new GsonBuilder().setPrettyPrinting().create().toJson( json ); + } + + static < T extends NativeType >BigWarp< T > createBigWarp(boolean... moving ) throws SpimDataException, URISyntaxException, IOException + { + return createBigWarp( null, moving ); + } + + static < T extends NativeType > BigWarp< T > createBigWarp(String sourcePath, boolean... moving ) throws SpimDataException, URISyntaxException, IOException + { + final BigWarpData< T > data = BigWarpInit.initData(); + if (sourcePath != null) { + createTemp3DImage( "tif", Paths.get(sourcePath) ); + } + + final String tmpPath = sourcePath != null ? sourcePath : createTemp3DImage( "img", "tif" ); + + for ( int i = 0; i < moving.length; i++ ) + { + final LinkedHashMap< Source< T >, SourceInfo > sources = BigWarpInit.createSources( data, tmpPath, i, moving[ i ] ); + BigWarpInit.add( data, sources ); + } + final BigWarpViewerOptions opts = BigWarpViewerOptions.options( false ); + return new BigWarp<>( data, opts, null ); + } + + static ImagePlus generateImagePlus( final String title ) + { + final FunctionRandomAccessible< UnsignedByteType > fimg = new FunctionRandomAccessible<>( + 3, + ( l, v ) -> v.setOne(), + UnsignedByteType::new ); + return ImageJFunctions.wrap( Views.interval( fimg, new FinalInterval( 32, 32, 1 ) ), title ); + } + + public static void main( String[] args ) throws SpimDataException, URISyntaxException, IOException + { + createBigWarp( true, true, false, false); + } +} diff --git a/src/test/java/bigwarp/BigWarpTransformedSourcesTest.java b/src/test/java/bigwarp/BigWarpTransformedSourcesTest.java new file mode 100644 index 00000000..107a0c3b --- /dev/null +++ b/src/test/java/bigwarp/BigWarpTransformedSourcesTest.java @@ -0,0 +1,46 @@ +package bigwarp; + +import bdv.export.ProgressWriterConsole; +import ij.IJ; +import ij.ImagePlus; +import mpicbg.spim.data.SpimDataException; +import net.imglib2.realtransform.AffineTransform3D; + +public class BigWarpTransformedSourcesTest +{ + + public static void main( String[] args ) throws SpimDataException + { + final BigWarpData< ? > data = createData(); + data.applyTransformations(); + + final BigWarp bw = new BigWarp<>( data, new ProgressWriterConsole()); + bw.loadLandmarks( "/home/john/tmp/bw_tformTest_landmarks_simple.csv" ); +// bw.loadLandmarks( "/groups/saalfeld/home/bogovicj/tmp/bw_tformTest_landmarks.csv" ); + + } + + public static BigWarpData createData() + { + final ImagePlus mr = IJ.openImage("/home/john/tmp/mri-stack.tif"); + final ImagePlus t1 = IJ.openImage("/home/john/tmp/t1-head.tif"); + +// final ImagePlus mr = IJ.openImage("/groups/saalfeld/home/bogovicj/tmp/mri-stack.tif"); +// final ImagePlus t1 = IJ.openImage("/groups/saalfeld/home/bogovicj/tmp/t1-head.tif"); + + final AffineTransform3D translation0 = new AffineTransform3D(); + translation0.translate( -50, -50, -10 ); + + final AffineTransform3D translation = new AffineTransform3D(); + translation.translate( -100, -150, -50 ); + + int id = 0; + final BigWarpData< T > data = BigWarpInit.initData(); + BigWarpInit.add( data, BigWarpInit.createSources( data, mr, id++, 0, true ), translation0, null); + BigWarpInit.add( data, BigWarpInit.createSources( data, t1, id++, 0, false )); + BigWarpInit.add( data, BigWarpInit.createSources( data, t1, id++, 0, false ), translation, null); + + return data; + } + +} diff --git a/src/test/java/bigwarp/ExportTester.java b/src/test/java/bigwarp/ExportTester.java index b6db7c04..cc94527c 100644 --- a/src/test/java/bigwarp/ExportTester.java +++ b/src/test/java/bigwarp/ExportTester.java @@ -21,6 +21,7 @@ */ package bigwarp; +import bigwarp.transforms.BigWarpTransform; import java.io.File; import java.io.IOException; import java.util.List; @@ -90,7 +91,7 @@ public static void main( String[] args ) throws IOException public static void pix_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.SPECIFIED_PIXEL; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.SPECIFIED; @@ -126,7 +127,7 @@ public static void pix_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void v_spc_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.SPECIFIED_PHYSICAL; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.SPECIFIED; @@ -160,7 +161,7 @@ public static void v_spc_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel public static void spc_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.SPECIFIED_PHYSICAL; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.SPECIFIED; @@ -194,7 +195,7 @@ public static void spc_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void pspc_pspc( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.SPECIFIED_PIXEL; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.SPECIFIED; @@ -228,7 +229,7 @@ public static void pspc_pspc( ImagePlus impm, ImagePlus impt, LandmarkTableModel public static void lmk_mvg( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.LANDMARK_POINTS; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.MOVING; @@ -262,7 +263,7 @@ public static void lmk_mvg( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void lmk_tgt( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.LANDMARK_POINTS; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.TARGET; @@ -296,7 +297,7 @@ public static void lmk_tgt( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void lmk_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.LANDMARK_POINTS; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.SPECIFIED; @@ -331,7 +332,7 @@ public static void lmk_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void mvg_mvg( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.MOVING_WARPED; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.MOVING; @@ -365,7 +366,7 @@ public static void mvg_mvg( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void mvg_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.MOVING_WARPED; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.SPECIFIED; @@ -399,7 +400,7 @@ public static void mvg_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void mvg_tgt( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.MOVING_WARPED; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.TARGET; @@ -433,7 +434,7 @@ public static void mvg_tgt( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void tgt_tgt( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.TARGET; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.TARGET; @@ -467,7 +468,7 @@ public static void tgt_tgt( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void v_tgt_tgt( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.TARGET; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.TARGET; @@ -503,7 +504,7 @@ public static void v_tgt_tgt( ImagePlus impm, ImagePlus impt, LandmarkTableModel public static void tgt_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.TARGET; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.SPECIFIED; @@ -537,7 +538,7 @@ public static void tgt_spc( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void tgt_mvg( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.TARGET; String fieldOfViewPointFilter = ""; String resolutionOption = ApplyBigwarpPlugin.MOVING; @@ -571,7 +572,7 @@ public static void tgt_mvg( ImagePlus impm, ImagePlus impt, LandmarkTableModel l public static void tgt_lmpix( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.LANDMARK_POINT_CUBE_PIXEL; String fieldOfViewPointFilter = ".*5"; String resolutionOption = ApplyBigwarpPlugin.TARGET; @@ -605,7 +606,7 @@ public static void tgt_lmpix( ImagePlus impm, ImagePlus impt, LandmarkTableModel public static void tgt_lmphy( ImagePlus impm, ImagePlus impt, LandmarkTableModel landmarks ) { - String transformType = TransformTypeSelectDialog.TPS; + String transformType = BigWarpTransform.TPS; String fieldOfViewOption = ApplyBigwarpPlugin.LANDMARK_POINT_CUBE_PHYSICAL; String fieldOfViewPointFilter = ".*5"; String resolutionOption = ApplyBigwarpPlugin.TARGET; diff --git a/src/test/java/bigwarp/NgffTransformTests.java b/src/test/java/bigwarp/NgffTransformTests.java new file mode 100644 index 00000000..a4d146d7 --- /dev/null +++ b/src/test/java/bigwarp/NgffTransformTests.java @@ -0,0 +1,345 @@ +package bigwarp; + +import java.util.Arrays; + +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v05.Common; + +import net.imglib2.realtransform.RealTransform; + +public class NgffTransformTests { + + public static void main( final String[] args ) throws Exception + { +// final String url = "/home/john/projects/bigwarp/projects/jrc18-rot.zarr?/#coordinateTransformations[1]"; +// final Pair, N5Reader> ctN5 = Common.openTransformN5(url); +// System.out.println( ctN5.getA() ); +// +// final RealTransform tf = ctN5.getA().getTransform(ctN5.getB()); +// System.out.println( tf ); + +// final String dfUrl = "/home/john/projects/bigwarp/projects/jrc18-rot.zarr?/#coordinateTransformations[0]"; +// final Pair, N5Reader> ctN5 = Common.openTransformN5(dfUrl); +// System.out.println( ctN5.getA() ); +// +// final RealTransform df1 = ctN5.getA().getTransform(ctN5.getB()); +// System.out.println( df1 ); + + final String tuneUrl = "/home/john/projects/bigwarp/projects/jrc18-rot.zarr?/#coordinateTransformations[2]"; + final RealTransform tune = Common.open(tuneUrl); + + final double[] p = new double[] { 308, 122, 91 }; +// final double[] p = new double[] { 292, 116, 92 }; + final double[] q1 = new double[] { 0, 0, 0}; + final double[] q2 = new double[] { 0, 0, 0}; + + +// df1.apply(p, q1); +// System.out.println( Arrays.toString(p) ); +// System.out.println( Arrays.toString(q1) ); +// + tune.apply(p, q2); + System.out.println( " " ); + System.out.println( Arrays.toString(p) ); + System.out.println( Arrays.toString(q2) ); + + +// final String loc = "/home/john/projects/bigwarp/projects/jrc18-rot.zarr"; +// final N5Reader n5 = new N5Factory().gsonBuilder( gsonBuilder() ).openReader( loc ); +// final String dataset = ""; +// final String attribute = "coordinateTransformations[1]"; +// +// final CoordinateTransform ct = n5.getAttribute(dataset, attribute, CoordinateTransform.class); +// System.out.println( ct ); + + + +// final String urlRef = "/home/john/projects/bigwarp/projects/jrc18.zarr?/#coordinateTransformations[5]"; +// final RealTransform tform = open( urlRef ); +// System.out.println( tform ); + + +// final String lmPath = "/home/john/Desktop/bw-rot-landmarks.csv"; +// final LandmarkTableModel ltm = new LandmarkTableModel(3); +// ltm.load(new File( lmPath )); +// +// final BigWarpTransform bwTform = new BigWarpTransform(ltm); +// +// final InvertibleRealTransform tform = bwTform.getTransformation(); +// +// // tform is deformation then affine +// +// final InvertibleRealTransform tf = BigWarpToDeformationFieldPlugIn.getTransformation(null, bwTform, false, true); +// System.out.println( tf ); +// +// final AffineGet affineTps = bwTform.affinePartOfTps(); +// System.out.println( affineTps ); +// +// +// final double[] p = new double[] { 150, 200, 75 }; +// final double[] q1 = new double[] { 0, 0, 0}; +// final double[] q2 = new double[] { 0, 0, 0}; + + + // this part behaves as expected +// tform.apply(p, q1); +// System.out.println( " " ); +// System.out.println( "true: " + Arrays.toString(q1)); +// +// +// final RealTransformSequence seq = new RealTransformSequence(); +// seq.add( tf ); +// seq.add( affineTps ); +// seq.apply(p, q2); +// +// System.out.println( " " ); +// System.out.println( "othr: " + Arrays.toString(q2)); + + +// final Pair, N5Reader> ctN5 = NgffTransformations.openTransformN5("/home/john/projects/bigwarp/projects/jrc18-rot.zarr?/#coordinateTransformations[4]"); +// final RealTransform df = ctN5.getA().getTransform(ctN5.getB()); +// +// +// System.out.println( " " ); +// System.out.println( " " ); +// System.out.println( ctN5.getA().getName() ); +// +// tf.apply(p, q1); +// System.out.println( " " ); +// System.out.println( "true: " + Arrays.toString(q1)); +// +// df.apply(p, q2); +// System.out.println( " " ); +// System.out.println( "df : " + Arrays.toString(q2)); + + +//// final String jrc18DfTgt = "/home/john/projects/bigwarp/projects/bw_jrc18Down_tforms.n5?#coordinateTransformations[6]"; +// final String jrc18DfTgt = "/home/john/projects/bigwarp/projects/bw_jrc18Down_tforms.n5?#coordinateTransformations[0]"; +//// final RealTransform tform = open(jrc18DfTgt); +// +// final Pair, N5Reader> ctN5 = openTransformN5(jrc18DfTgt); +// final CoordinateTransform ct = ctN5.getA(); +// final N5Reader n5 = ctN5.getB(); +// +// System.out.println("ct: " + ct + " " + ct.getType()); +// +// final RealTransform tform = ct.getTransform( n5 ); + + +// final String p2pUri = "/home/john/Desktop/tforms.n5?/dfield2d#coordinateTransformations[1]"; +// final Pair< CoordinateTransform< ? >, N5Reader > pair = openTransformN5( p2pUri ); +// final CoordinateTransform ct = pair.getA(); +// System.out.println( ct ); +// System.out.println( ct.getType() ); +// +// final N5Reader n5 = pair.getB(); +//// final RealTransform tform = ct.getTransform(n5); +//// System.out.println( tform ); +// +// +// final SequenceCoordinateTransform sct = (SequenceCoordinateTransform)ct; +// final AffineGet affine = sct.asAffine(3); +// System.out.println( affine.getRowPackedCopy()); + +// final String dfUri = "/home/john/Desktop/tforms.n5?/dfield2d#coordinateTransformations[0]"; +// final Pair< CoordinateTransform< ? >, N5Reader > pair = openTransformN5( dfUri ); +// final CoordinateTransform ct = pair.getA(); +// System.out.println( ct ); +// +// final N5Reader n5 = pair.getB(); +// final RealTransform tform = ct.getTransform(n5); +// System.out.println( tform ); + + +// final AffineTransform affine = new AffineTransform( 2 ); +// final Translation2D t = new Translation2D( new double[] { 5, 6 } ); +// System.out.println( Arrays.toString(affine.getRowPackedCopy() )); +// affine.preConcatenate(t); +// System.out.println( Arrays.toString(affine.getRowPackedCopy() )); + + + +// // detect transformations +// final String loc = "/home/john/Desktop/tforms.n5"; +// final N5URI uri = new N5URI(loc); + +// final N5Reader n5 = new N5Factory().gsonBuilder( gsonBuilder() ).openReader( loc ); +//// final RealTransform tform = findFieldTransformFirst( n5, "/dfield2d" ); +// final RealTransform tform = findFieldTransformStrict( n5, "/dfield2d", "boats.tif_dfield" ); + + +// // detect transformations +// final String loc = "/home/john/Desktop/tforms.n5"; +// final N5URI uri = new N5URI(loc); +// +// // dfield 2d path +// final String dfUri = "/home/john/Desktop/tforms.n5?/dfield2d#coordinateTransformations[0]"; +//// final String dfUri = "/home/john/Desktop/tforms.n5?/#coordinateTransformations[2]"; +//// final String dfPartUri = "/home/john/Desktop/tforms.n5"; +// +//// final RealTransform tform = open(dfUri); +//// System.out.println(tform); +// +// final Pair< CoordinateTransform< ? >, N5Reader > pair = openTransformN5( dfUri ); +// final CoordinateTransform ct = pair.getA(); +// System.out.println( ct ); +// +// final RealTransform tform = ct.getTransform( pair.getB()); +// System.out.println( tform ); + + +// final String s = detectTransforms(dfUri); +// System.out.println( "full uri: " + s ); +// System.out.println("inferred full uri: " + detectTransforms(dfPartUri)); + + +// final CoordinateTransform[] cts = detectTransforms(loc); +// System.out.println(Arrays.toString(cts)); + +// System.out.println(detectTransforms(loc)); + +// System.out.println( uri ); +// System.out.println( uri.getURI() ); +// +// final String grp = ( uri.getGroupPath() != null ) ? uri.getGroupPath() : ""; +// System.out.println( grp ); +// +// final String attr = ( uri.getAttributePath() != null ) ? uri.getAttributePath() : ""; +// System.out.println( attr ); + +// final N5Reader n5 = new N5Factory().gsonBuilder( gsonBuilder() ).openReader( uri.getContainerPath() ); +// final JsonObject json = n5.getAttribute(grp, attr, JsonObject.class); +// final String ver = n5.getAttribute(grp, "n5", String.class); +// final JsonElement jcts = n5.getAttribute(grp, "coordinateTransformations", JsonElement.class); +// final JsonElement jct = n5.getAttribute(grp, "coordinateTransformations[0]", JsonElement.class); +// final CoordinateTransform ct = n5.getAttribute(grp, "coordinateTransformations[0]", CoordinateTransform.class); +// final CoordinateTransform[] cts = n5.getAttribute(grp, "coordinateTransformations", CoordinateTransform[].class); + +// System.out.println(""); +// System.out.println(json); +// System.out.println(""); +// System.out.println(ver); +// System.out.println(""); +// System.out.println(jcts); +// System.out.println(""); +// System.out.println(jct); +// System.out.println(""); +// System.out.println(ct); +// System.out.println(ct.getType()); +// System.out.println(""); +// System.out.println(Arrays.toString(cts)); + + +// openTransformN5( url ); + + + // full +// final String bijPath = "/home/john/projects/ngff/dfieldTest/jrc18_example.n5?/#/coordinateTransformations[0]"; + + // no dataset +// final String bijPath = "/home/john/projects/ngff/dfieldTest/jrc18_example.n5#/coordinateTransformations[0]"; + + // no dataset no attribute +// final String bijPath = "/home/john/projects/ngff/dfieldTest/jrc18_example.n5"; +// +// final N5URI url = new N5URI( bijPath ); +// System.out.println( url.getGroupPath()); +// System.out.println( url.getAttributePath()); +// +// final Pair< NgffCoordinateTransformation< ? >, N5Reader > bijN5 = openTransformN5( bijPath ); +// System.out.println( bijN5.getA() ); +// final InvertibleRealTransform bij = openInvertible( bijPath ); +// System.out.println( bij ); + + +// final String path = "/home/john/projects/ngff/dfieldTest/jrc18_example.n5"; +// final N5URL url = new N5URL( path ); +// System.out.println( url.getAttribute() ); +// System.out.println( url.getDataset()); + + +// final String bijPath = "/home/john/projects/ngff/dfieldTest/jrc18_example.n5?/#/coordinateTransformations[0]"; +// final N5URL url = new N5URL( bijPath ); +// final N5Reader n5 = new N5Factory().gsonBuilder( gsonBuilder() ).openReader( url.getLocation() ); +// final CoordinateTransformation ct = n5.getAttribute( url.getDataset(), url.getAttribute(), CoordinateTransformation.class ); +// System.out.println( ct ); +// +// final NgffCoordinateTransformation< ? > nct = NgffCoordinateTransformation.create( ct ); +// RealTransform tform = nct.getTransform( n5 ); +// System.out.println( tform ); + + + + +// final String basePath = "/home/john/projects/ngff/dfieldTest/jrc18_example.n5"; +// final String csPath = "?dfield#coordinateSystems/[0]"; +// final String namePath = "?dfield#coordinateSystems/[0]/name"; +// final String dimPath = "?dfield#/dimensions"; +// +// final N5URL baseUrl = new N5URL( basePath ); +// final N5URL nameUrl = baseUrl.getRelative( namePath ); +// final N5URL csUrl = baseUrl.getRelative( csPath ); +// final N5URL dimUrl = baseUrl.getRelative( dimPath ); +// +// final N5Reader n5 = new N5Factory().gsonBuilder( gsonBuilder() ).openReader( baseUrl.getLocation() ); +// final String name = n5.getAttribute( nameUrl.getDataset(), nameUrl.getAttribute(), String.class ); +// final CoordinateSystem cs = n5.getAttribute( csUrl.getDataset(), csUrl.getAttribute(), CoordinateSystem.class ); +// final long[] dims = n5.getAttribute( dimUrl.getDataset(), dimUrl.getAttribute(), long[].class ); +// +// System.out.println( name ); +// System.out.println( cs ); +// System.out.println( cs.getAxes()[0].getName() ); +// System.out.println( Arrays.toString( dims ) ); + + + + +//// final String path = "/home/john/projects/ngff/dfieldTest/dfield.n5"; +// final String path = "/home/john/projects/ngff/dfieldTest/jrc18_example.n5"; +// +// final String dataset = "/"; +//// final String dataset = "coordinateTransformations"; +//// final String dataset = "/dfield"; +// +// final N5FSReader n5 = new N5FSReader( path, gsonBuilder() ); +// +//// RealTransform dfieldTform = open( n5, dataset ); +//// System.out.println( dfieldTform ); +// +//// RealTransform dfieldTform = open( n5, dataset ); +//// System.out.println( dfieldTform ); +// +// TransformGraph g = openGraph( n5, dataset ); +// g.printSummary(); +// RealTransform fwdXfm = g.path( "jrc18F", "fcwb" ).get().totalTransform( n5, g ); +// RealTransform invXfm = g.path( "fcwb", "jrc18F" ).get().totalTransform( n5, g ); +// System.out.println( fwdXfm ); +// System.out.println( invXfm ); + + +// ArrayImg< IntType, IntArray > img = ArrayImgs.ints( 2, 3, 4, 5 ); +// +// int[] p = vectorAxisLastNgff( n5, dataset ); +// System.out.println( Arrays.toString( p )); +// System.out.println( "" ); +// +// IntervalView< IntType > imgP = N5DisplacementField.permute( img, p ); +// System.out.println( Intervals.toString( imgP )); + + +// try +// { +//// AffineGet p2p = N5DisplacementField.openPixelToPhysicalNgff( n5, "transform", true ); +//// System.out.println( p2p ); +// +//// int[] indexes = new int[] {1, 2, 3 }; +//// AffineGet sp2p = TransformUtils.subAffine( p2p, indexes ); +//// System.out.println( sp2p ); +// } +// catch ( Exception e ) +// { +// e.printStackTrace(); +// } + + } + +} diff --git a/src/test/java/bigwarp/SerializationTest.java b/src/test/java/bigwarp/SerializationTest.java new file mode 100644 index 00000000..bdd60f24 --- /dev/null +++ b/src/test/java/bigwarp/SerializationTest.java @@ -0,0 +1,511 @@ +package bigwarp; + +import bdv.tools.bookmarks.Bookmarks; +import bdv.tools.brightness.ConverterSetup; +import bdv.tools.brightness.MinMaxGroup; +import bdv.viewer.BigWarpViewerPanel; +import bdv.viewer.state.SourceGroup; +import bdv.viewer.state.XmlIoViewerState; +import bigwarp.loader.ImagePlusLoader; +import bigwarp.source.PlateauSphericalMaskSource; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonWriter; +import ij.ImagePlus; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PipedReader; +import java.io.PipedWriter; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import mpicbg.spim.data.SpimDataException; +import net.imglib2.FinalInterval; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.NativeType; + +import org.custommonkey.xmlunit.XMLAssert; +import org.custommonkey.xmlunit.XMLUnit; +import org.jdom2.JDOMException; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.xml.sax.SAXException; + +import static bigwarp.BigWarpTestUtils.createTemp3DImage; + +public class SerializationTest +{ + + private BigWarp< ? > bw; + + @After + public void after() { + if (bw != null) { + bw.closeAll(); + bw = null; + } + } + + @Test + public void maskTest() + { + final PlateauSphericalMaskSource mask = PlateauSphericalMaskSource.build( new RealPoint( 3 ), new FinalInterval( 5, 10, 20 ) ); + final Gson gson = BigwarpSettings.gson; + final JsonElement actual = gson.toJsonTree( mask.getRandomAccessible() ); + + final JsonObject expected = new JsonObject(); + expected.addProperty( "fallOffShape", "COSINE" ); + expected.addProperty( "squaredRadius", 64.0 ); + expected.addProperty( "squaredSigma", 100.0 ); + final JsonArray center = new JsonArray( 3 ); + center.add( 0.0 ); + center.add( 0.0 ); + center.add( 0.0 ); + expected.add( "center", center ); + + BigWarpTestUtils.assertJsonDiff( expected, actual ); + } + + @Test + public void bookmarksTest() + { + final Bookmarks bookmarks = new Bookmarks(); + final AffineTransform3D identity = new AffineTransform3D(); + bookmarks.put( "identity", identity ); + final AffineTransform3D scale = new AffineTransform3D(); + scale.scale( 1.0, 2.0, 3.0 ); + bookmarks.put( "scale", scale ); + + final AffineTransform3D translate = new AffineTransform3D(); + bookmarks.put( "translate", translate ); + translate.translate( Math.random(), Math.random(), Math.random(), Math.random() ); + + final Gson gson = BigwarpSettings.gson; + final JsonElement actual = gson.toJsonTree( bookmarks ); + + final JsonObject expected = new JsonObject(); + final JsonObject bookmarksObj = new JsonObject(); + expected.add( "bookmarks", bookmarksObj ); + final JsonArray identityArray = new JsonArray( 3 ); + for ( final double val : identity.getRowPackedCopy() ) + { + identityArray.add( val ); + } + final JsonArray scaleArray = new JsonArray( 3 ); + for ( final double val : scale.getRowPackedCopy() ) + { + scaleArray.add( val ); + } + final JsonArray translateArray = new JsonArray( 3 ); + for ( final double val : translate.getRowPackedCopy() ) + { + translateArray.add( val ); + } + bookmarksObj.add( "identity", identityArray ); + bookmarksObj.add( "scale", scaleArray ); + bookmarksObj.add( "translate", translateArray ); + + BigWarpTestUtils.assertJsonDiff( expected, actual ); + } + + @Test + public void autoSaverTest() + { + final BigWarpAutoSaver saver = new BigWarpAutoSaver( null, 1000 ); + saver.autoSaveDirectory = new File( "/tmp" ); + final JsonElement actual = BigwarpSettings.gson.toJsonTree( saver ); + + final JsonObject expected = new JsonObject(); + expected.addProperty( "period", 1000 ); + expected.addProperty( "location", "/tmp" ); + saver.stop(); + + BigWarpTestUtils.assertJsonDiff( expected, actual ); + } + + @Test + public void setupAssignmentsTest() throws SpimDataException, IOException, URISyntaxException + { + bw = BigWarpTestUtils.createBigWarp( true, false, false, false ); + + final PipedWriter writer = new PipedWriter(); + final PipedReader in = new PipedReader( writer, 1000 ); + + final JsonWriter out = new JsonWriter( writer ); + new BigwarpSettings.SetupAssignmentsAdapter( bw.setupAssignments ).write( out, bw.setupAssignments ); + + out.close(); + writer.close(); + final JsonElement actual = JsonParser.parseReader( in ); + + final JsonObject expected = new JsonObject(); + final JsonObject setups = new JsonObject(); + expected.add( "ConverterSetups", setups ); + final List< MinMaxGroup > minMaxGroups = bw.setupAssignments.getMinMaxGroups(); + for ( final ConverterSetup setup : bw.setupAssignments.getConverterSetups() ) + { + final JsonObject setupObj = new JsonObject(); + setupObj.addProperty( "min", setup.getDisplayRangeMin() ); + setupObj.addProperty( "max", setup.getDisplayRangeMax() ); + setupObj.addProperty( "color", setup.getColor().get() ); + setupObj.addProperty( "groupId", minMaxGroups.indexOf( bw.setupAssignments.getMinMaxGroup( setup ) ) ); + setups.add( String.valueOf( setup.getSetupId() ), setupObj ); + } + final JsonObject groups = new JsonObject(); + expected.add( "MinMaxGroups", groups ); + for ( int i = 0; i < minMaxGroups.size(); i++ ) + { + final MinMaxGroup minMaxGroup = minMaxGroups.get( i ); + final JsonObject groupObj = new JsonObject(); + groupObj.addProperty( "fullRangeMin", minMaxGroup.getFullRangeMin() ); + groupObj.addProperty( "fullRangeMax", minMaxGroup.getFullRangeMax() ); + groupObj.addProperty( "rangeMin", minMaxGroup.getRangeMin() ); + groupObj.addProperty( "rangeMax", minMaxGroup.getRangeMax() ); + groupObj.addProperty( "currentMin", minMaxGroup.getMinBoundedValue().getCurrentValue() ); + groupObj.addProperty( "currentMax", minMaxGroup.getMaxBoundedValue().getCurrentValue() ); + groups.add( String.valueOf( i ), groupObj ); + } + + BigWarpTestUtils.assertJsonDiff( expected, actual ); + + } + + @Test + public void viewerPanelTest() throws SpimDataException, IOException, URISyntaxException + { + bw = BigWarpTestUtils.createBigWarp( true, false, false, false ); + + final PipedWriter writer = new PipedWriter(); + final PipedReader in = new PipedReader( writer, 10000 ); + + final JsonWriter out = new JsonWriter( writer ); + new BigwarpSettings.BigWarpViewerPanelAdapter( bw.viewerP ).write( out, bw.viewerP ); + + out.close(); + writer.close(); + final JsonElement actual = JsonParser.parseReader( in ); + + final JsonObject expected = new JsonObject(); + + final JsonArray sources = new JsonArray(); + expected.add( XmlIoViewerState.VIEWERSTATE_SOURCES_TAG, sources ); + /* All sources are active */ + bw.viewerP.getState().getSources().forEach( source -> sources.add( source.isActive() ) ); + + final JsonArray groups = new JsonArray(); + expected.add( XmlIoViewerState.VIEWERSTATE_GROUPS_TAG, groups ); + + final BigWarpViewerPanel value = bw.viewerP; + final List< SourceGroup > sourceGroups = value.getState().getSourceGroups(); + for ( final SourceGroup sourceGroup : sourceGroups ) + { + final JsonObject sourceGroupObj = new JsonObject(); + sourceGroupObj.addProperty( XmlIoViewerState.VIEWERSTATE_GROUP_ACTIVE_TAG, sourceGroup.isActive() ); + sourceGroupObj.addProperty( XmlIoViewerState.VIEWERSTATE_GROUP_NAME_TAG, sourceGroup.getName() ); + final JsonArray sourceIds = new JsonArray(); + sourceGroupObj.add( XmlIoViewerState.VIEWERSTATE_GROUP_SOURCEID_TAG, sourceIds ); + for ( final Integer sourceId : sourceGroup.getSourceIds() ) + { + sourceIds.add( sourceId ); + } + groups.add( sourceGroupObj ); + } + expected.addProperty( XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_TAG, XmlIoViewerState.VIEWERSTATE_DISPLAYMODE_VALUE_SINGLE ); + expected.addProperty( XmlIoViewerState.VIEWERSTATE_INTERPOLATION_TAG, XmlIoViewerState.VIEWERSTATE_INTERPOLATION_VALUE_NEARESTNEIGHBOR ); + expected.addProperty( XmlIoViewerState.VIEWERSTATE_CURRENTSOURCE_TAG, value.getState().getCurrentSource() ); + expected.addProperty( XmlIoViewerState.VIEWERSTATE_CURRENTGROUP_TAG, value.getState().getCurrentGroup() ); + expected.addProperty( XmlIoViewerState.VIEWERSTATE_CURRENTTIMEPOINT_TAG, value.getState().getCurrentTimepoint() ); + + BigWarpTestUtils.assertJsonDiff( expected, actual ); + } + + @Test + public < T extends NativeType > void sourceFromFileTest() throws SpimDataException, URISyntaxException, IOException, JDOMException + { + final BigWarp< T > bw = BigWarpTestUtils.createBigWarp( "/tmp/img8270806677315563879.tif" ); + bw.loadSettings("src/test/resources/settings/expected.json"); + // Grab the sources + // Compare the ids, urls, isMoving status, and isActive + Assert.assertEquals("Wrong Number of Sources", 4, bw.data.sources.size()); + Assert.assertEquals("Wrong Number of Moving Sources", 2, bw.data.numMovingSources()); + Assert.assertEquals("Wrong Number of Target Sources", 2, bw.data.numTargetSources()); + final boolean[] movingById = { true, true, false, false }; + bw.data.sourceInfos.forEach( (id, info) -> { + + Assert.assertEquals("URI Mismatch", "/tmp/img8270806677315563879.tif", info.getUri()); + Assert.assertEquals("Name Mismatch", "img8270806677315563879.tif", info.getName()); + Assert.assertEquals("Moving/Target Mismatch", movingById[id], info.isMoving()); + } ); + } + + @Test + public < T extends NativeType > void sourceFromImageJTest() throws SpimDataException, URISyntaxException, IOException, JDOMException + { + final ImagePlus img = BigWarpTestUtils.generateImagePlus( "generated image" ); + img.setDisplayRange( 5, 15 ); + img.show(); + + final String imagejUri = "imagej:///generated image"; + final Path xmlSourceSettings = createNewSettingsWithReplacement( + "src/test/resources/settings/expected.json", + new HashMap< String, String >() { + /* Map filled during construction: see: https://stackoverflow.com/questions/6802483/how-to-directly-initialize-a-hashmap-in-a-literal-way */ + { + put( "/tmp/img8270806677315563879.tif", imagejUri); + put( "img8270806677315563879.tif", "generated image" ); + } + }); + + final BigWarp< T > bw = BigWarpTestUtils.createBigWarp( ); + bw.loadSettings(xmlSourceSettings.toFile().getCanonicalPath()); + // Grab the sources + // Compare the ids, urls, isMoving status, and isActive + Assert.assertEquals("Wrong Number of Sources", 4, bw.data.sources.size()); + Assert.assertEquals("Wrong Number of Moving Sources", 2, bw.data.numMovingSources()); + Assert.assertEquals("Wrong Number of Target Sources", 2, bw.data.numTargetSources()); + final boolean[] movingById = { true, true, false, false }; + bw.data.sourceInfos.forEach( (id, info) -> { + + Assert.assertEquals("URI Mismatch", imagejUri, info.getUri()); + Assert.assertEquals("Name Mismatch", "generated image", info.getName()); + Assert.assertEquals("Moving/Target Mismatch", movingById[id], info.isMoving()); + } ); + + + assertExpectedSettingsToCurrent( xmlSourceSettings, bw ); + } + + private static void assertExpectedSettingsToCurrent( final Path expectedSettings, final BigWarp< ? > bw ) throws IOException + { + /* Save the settings and compare with initial to test the deserialization */ + final Path tempSettings = Files.createTempFile( "deserialization", ".json" ); + tempSettings.toFile().delete(); + bw.saveSettingsJson(tempSettings.toFile().getCanonicalPath()); + final JsonElement expectedJson = JsonParser.parseReader( new FileReader( expectedSettings.toFile() ) ); + final JsonElement actualJson = JsonParser.parseReader( new FileReader( tempSettings.toFile() ) ); + BigWarpTestUtils.assertJsonDiff( expectedJson, actualJson ); + } + + @Test + public < T extends NativeType > void sourceFromXmlTest() throws SpimDataException, URISyntaxException, IOException, JDOMException + { + final String xmlUri = "src/test/resources/mri-stack.xml"; + final Path xmlSourceSettings = createNewSettingsWithReplacement( + "src/test/resources/settings/expected.json", + new HashMap< String, String >() { + /* Map filled during construction: see: https://stackoverflow.com/questions/6802483/how-to-directly-initialize-a-hashmap-in-a-literal-way */ + { + put( "/tmp/img8270806677315563879.tif", xmlUri); + put( "img8270806677315563879.tif", "channel 1" ); + } + }); + + final BigWarp< T > bw = BigWarpTestUtils.createBigWarp( ); + bw.loadSettings(xmlSourceSettings.toFile().getCanonicalPath()); + // Grab the sources + // Compare the ids, urls, isMoving status, and isActive + Assert.assertEquals("Wrong Number of Sources", 4, bw.data.sources.size()); + Assert.assertEquals("Wrong Number of Moving Sources", 2, bw.data.numMovingSources()); + Assert.assertEquals("Wrong Number of Target Sources", 2, bw.data.numTargetSources()); + final boolean[] movingById = { true, true, false, false }; + bw.data.sourceInfos.forEach( (id, info) -> { + + Assert.assertEquals("URI Mismatch", xmlUri, info.getUri()); + Assert.assertEquals("Name Mismatch", "channel 1", info.getName()); + Assert.assertEquals("Moving/Target Mismatch", movingById[id], info.isMoving()); + } ); + + + /* then save the settings, load it, and compare with initial to test the deserialization */ + assertExpectedSettingsToCurrent( xmlSourceSettings, bw ); + } + + @Test + public < T extends NativeType > void sourceFromN5Test() throws SpimDataException, URISyntaxException, IOException, JDOMException + { + final String n5Uri = "src/test/resources/bigwarp/url/transformTest.n5?img"; + + /* Map filled during construction: see: https://stackoverflow.com/questions/6802483/how-to-directly-initialize-a-hashmap-in-a-literal-way */ + final Path xmlSourceSettings = createNewSettingsWithReplacement( + "src/test/resources/settings/expected.json", + new HashMap< String, String >() { + { + put( "/tmp/img8270806677315563879.tif", n5Uri ); + put( "img8270806677315563879.tif", "img" ); + } + } + ); + + final BigWarp< T > bw = BigWarpTestUtils.createBigWarp( ); + bw.loadSettings(xmlSourceSettings.toFile().getCanonicalPath()); + // Grab the sources + // Compare the ids, urls, isMoving status, and isActive + Assert.assertEquals("Wrong Number of Sources", 4, bw.data.sources.size()); + Assert.assertEquals("Wrong Number of Moving Sources", 2, bw.data.numMovingSources()); + Assert.assertEquals("Wrong Number of Target Sources", 2, bw.data.numTargetSources()); + final boolean[] movingById = { true, true, false, false }; + bw.data.sourceInfos.forEach( (id, info) -> { + + Assert.assertEquals("URI Mismatch", n5Uri, info.getUri()); + Assert.assertEquals("Name Mismatch", "img", info.getName()); + Assert.assertEquals("Moving/Target Mismatch", movingById[id], info.isMoving()); + } ); + + /* then save the settings, load it, and compare with initial to test the deserialization */ + assertExpectedSettingsToCurrent( xmlSourceSettings, bw ); + + } + + private static Path createNewSettingsWithReplacement( final String baseSettings, final Map replacements ) throws IOException + { + /* Load expected.json and replace source path with desired uri */ + final Path settings = Paths.get( baseSettings ); + final List< String > newLines = Files.readAllLines( settings ).stream().map( line -> { + String out = line; + for ( final Map.Entry< String, String > replaceWith : replacements.entrySet() ) + { + out = out.replaceAll( replaceWith.getKey(), replaceWith.getValue() ); + } + return out; + } ).collect( Collectors.toList()); + final Path newSettings = Files.createTempFile( "settings", ".json" ); + return Files.write( newSettings, newLines ); + } + + @Test + public void repeatComparison() throws Exception + { + for ( int i = 0; i < 40; i++ ) + { + bw = BigWarpTestUtils.createBigWarp( true ); + + /* Load the known good*/ + final String originalXmlSettings = "src/test/resources/settings/repeatFail.settings.xml"; + bw.loadSettings( originalXmlSettings ); + + /* save it back out*/ + final File tmpXmlFile = Files.createTempFile( "xml-settings", ".xml" ).toFile(); + bw.saveSettings( tmpXmlFile.getAbsolutePath() ); + + /* compare the original and generated */ + XMLUnit.setIgnoreWhitespace( true ); + XMLUnit.setIgnoreComments( true ); + XMLAssert.assertXMLEqual( new FileReader( originalXmlSettings ), new FileReader( tmpXmlFile ) ); + + bw.closeAll(); + } + + } + + @Test + public void compareKnownXmlComparisonTest() throws SpimDataException, IOException, JDOMException, SAXException, URISyntaxException + { + final BigWarp< ? > bw = BigWarpTestUtils.createBigWarp( true, false, false, false ); + + final String originalXmlSettings = "src/test/resources/settings/compareKnownXml.bigwarp.settings.xml"; + bw.loadSettings( originalXmlSettings ); + final BigwarpSettings settings = bw.getSettings(); + + final File tmpJsonFile = Files.createTempFile( "json-settings", ".json" ).toFile(); + try ( final FileWriter fileWriter = new FileWriter( tmpJsonFile ) ) + { + final JsonWriter out = new JsonWriter( fileWriter ); + settings.write( out, settings ); + } + /* Ideally, we should close the instance, and get a new one for this test, but it's not currently safe to do this. See SerializationTest#repeatComparison*/ +// bw.closeAll(); +// bw = createBigWarp(new boolean[]{true, false, false, false}); + + bw.loadSettings( tmpJsonFile.getAbsolutePath(), true ); + final File tmpXmlFile = Files.createTempFile( "xml-settings", ".xml" ).toFile(); + bw.saveSettings( tmpXmlFile.getAbsolutePath() ); + XMLUnit.setIgnoreWhitespace( true ); + XMLUnit.setIgnoreComments( true ); + XMLAssert.assertXMLEqual( new FileReader( originalXmlSettings ), new FileReader( tmpXmlFile ) ); + + } + + @Test + public void jsonLoadSaveComparisonTest() throws SpimDataException, IOException, JDOMException, URISyntaxException + { + bw = BigWarpTestUtils.createBigWarp( "/tmp/img8270806677315563879.tif", true, true, false, false ); + + final String expectedJsonFile = "src/test/resources/settings/expected_with_dfield.json"; + bw.loadSettings( expectedJsonFile ); + final BigwarpSettings settings = bw.getSettings(); + + final PipedWriter writer = new PipedWriter(); + final PipedReader in = new PipedReader( writer, 10000 ); + + final JsonWriter out = new JsonWriter( writer ); + + settings.write( out, settings ); + out.close(); + writer.close(); + + final JsonElement jsonSettingsOut = JsonParser.parseReader( in ); + final JsonElement expectedJson = JsonParser.parseReader( new FileReader( expectedJsonFile ) ); + + BigWarpTestUtils.assertJsonDiff( expectedJson, jsonSettingsOut ); + } + + @Test + public void landmarkComparisonTest() throws SpimDataException, IOException, JDOMException, URISyntaxException + { + bw = BigWarpTestUtils.createBigWarp( "/tmp/img8270806677315563879.tif", true, true, false, false ); + + final String xmlSettings = "src/test/resources/settings/compareKnownXml.bigwarp.settings.xml"; + final String csvLandmarks = "src/test/resources/settings/landmarks.csv"; + final String expectedJsonFile = "src/test/resources/settings/expected.json"; + bw.loadSettings( xmlSettings ); + bw.loadLandmarks( csvLandmarks ); + + final BigwarpSettings settings = bw.getSettings(); + + final PipedWriter writer = new PipedWriter(); + final PipedReader in = new PipedReader( writer, 10000 ); + + final JsonWriter out = new JsonWriter( writer ); + settings.write( out, settings ); + out.close(); + writer.close(); + + final JsonElement jsonSettingsOut = JsonParser.parseReader( in ); + final JsonElement expectedJson = JsonParser.parseReader( new FileReader( expectedJsonFile ) ); + + BigWarpTestUtils.assertJsonDiff( expectedJson, jsonSettingsOut ); + + } + + public static void main( String[] args ) throws SpimDataException, URISyntaxException, IOException, JDOMException, InterruptedException + { + + final ImagePlus img = BigWarpTestUtils.generateImagePlus( "generated image" ); + img.setDisplayRange( 5, 15 ); + img.show(); + + final BigWarpData< T > data = BigWarpInit.initData(); + BigWarpInit.add(data, BigWarpInit.createSources( data, img, 123, 0, true )); + + new BigWarp<>(data, null); + + // BigWarp bw = BigWarpTestUtils.createBigWarp("/tmp/img8270806677315563879.tif", true, true, false, false); + // bw.saveSettingsJson( "/tmp/3d-settings.json" ); + // bw.closeAll(); + // Thread.sleep( 1000 ); + // bw = BigWarpTestUtils.createBigWarp(); + // bw.loadSettings("/tmp/3d-settings.json"); + } +} diff --git a/src/test/java/bigwarp/SourceTest.java b/src/test/java/bigwarp/SourceTest.java index b0bba0a4..bd5add62 100644 --- a/src/test/java/bigwarp/SourceTest.java +++ b/src/test/java/bigwarp/SourceTest.java @@ -30,7 +30,6 @@ import bdv.util.BdvFunctions; import bdv.util.BdvStackSource; import bdv.viewer.Source; -import bigwarp.BigWarp.BigWarpData; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; @@ -62,7 +61,7 @@ public static void main( String[] args ) throws IOException, SpimDataException BigWarpData< ? > datasrc = BigWarpInit.createBigWarpData( new Source[] { tsrc }, new Source[] { tsrc }, new String[] { "mvg", "tgt" } ); - BigWarp< ? > bw = new BigWarp<>( datasrc, "bw", BigWarpViewerOptions.options(), new ProgressWriterConsole() ); + BigWarp< ? > bw = new BigWarp<>( datasrc, BigWarpViewerOptions.options(), new ProgressWriterConsole() ); bw.getLandmarkPanel().getTableModel().load( new File( "src/test/resources/mr_landmarks_p2p2p4-111.csv" )); } } diff --git a/src/test/java/bigwarp/Sources2Dtests.java b/src/test/java/bigwarp/Sources2Dtests.java index adb5d9d8..d1d417d5 100644 --- a/src/test/java/bigwarp/Sources2Dtests.java +++ b/src/test/java/bigwarp/Sources2Dtests.java @@ -23,7 +23,7 @@ import bdv.util.RandomAccessibleIntervalSource; import bdv.viewer.Source; -import bigwarp.BigWarp.BigWarpData; +import bigwarp.source.SourceInfo; import ij.IJ; import ij.ImagePlus; import mpicbg.spim.data.SpimDataException; @@ -53,10 +53,18 @@ public static & RealType> void run() throws SpimData BigWarpData bwdata = BigWarpInit.initData(); BigWarpInit.add(bwdata, mvg, 0, 0, true); + final SourceInfo mvgInfo = new SourceInfo( 0, true, "mvg", () -> "https://imagej.nih.gov/ij/images/boats.gif" ); + mvgInfo.setSourceAndConverter( bwdata.sources.get( bwdata.sources.size() - 1 ) ); + bwdata.sourceInfos.put( 0, mvgInfo ); + BigWarpInit.add(bwdata, tgt, 1, 0, false); + final SourceInfo tgtInfo = new SourceInfo( 1, true, "tgt", () -> "https://imagej.nih.gov/ij/images/boats.gif" ); + bwdata.sourceInfos.put( 1, tgtInfo ); + tgtInfo.setSourceAndConverter( bwdata.sources.get( bwdata.sources.size() - 1 ) ); + bwdata.wrapUp(); - BigWarp bw = new BigWarp(bwdata, "bw test", null); + BigWarp bw = new BigWarp(bwdata, null); } public static & RealType> Source loadSource( String path, double zOffset ) diff --git a/src/test/java/bigwarp/StartupTests.java b/src/test/java/bigwarp/StartupTests.java new file mode 100644 index 00000000..3767dcd1 --- /dev/null +++ b/src/test/java/bigwarp/StartupTests.java @@ -0,0 +1,121 @@ +package bigwarp; + +import java.io.IOException; + +import bdv.gui.BigWarpInitDialog; +import ij.ImageJ; + +public class StartupTests { + + public static void main( final String[] args ) throws IOException + { +// ImageJ ij2 = new ImageJ(); +// ij2.ui().showUI(); + + final ImageJ ij = new ImageJ(); +// +// IJ.openImage( "/groups/saalfeld/home/bogovicj/tmp/boatsBlur.tif" ).show(); +// IJ.openImage( "/groups/saalfeld/home/bogovicj/tmp/boats.tif" ).show(); +// +// IJ.openImage( "/home/john/tmp/boats.tif" ).show(); +// IJ.openImage( "/home/john/tmp/boatsBlur.tif" ).show(); + +// IJ.createImage("", ImageJPrefix, DEFAULT_OUTER_PAD, DEFAULT_MID_PAD, DEFAULT_BUTTON_PAD) +// IJ.createImage("a", 32, 32, 8, 8).show(); + +// IJ.openImage( "/home/john/tmp/mri-stack.tif" ).show(); +// IJ.openImage( "/home/john/tmp/t1-head.tif" ).show(); + +// new ImageJ(); +// IJ.openImage( "/home/john/tmp/mri-stack.tif" ).show(); +// String macroOptions = "images=imagej://mri-stack.tif,imagej://mri-stack.tif moving=true,false transforms=,"; +// runMacro( macroOptions ); + +// IJ.openImage( "/home/john/tmp/t1-head.tif" ).show(); +// IJ.openImage( "/home/john/tmp/mri-stack.tif" ).show(); + + final String proj = "/home/john/Desktop/bw-boats.json"; +// final String proj = "/home/john/Desktop/bw-boats-affine2d.json"; +// final String proj = "/home/john/Desktop/bw-boats-tlation.json"; +// final String proj = "/home/john/Desktop/bw-boats-tlationImported-fineTune.json"; + +// final String proj = "/home/john/Desktop/bw-boats-affine2d-imported.json"; +// final String proj = "/home/john/Desktop/bigwarp-nomask-project.json"; +// final String proj = "/home/john/Desktop/bigwarp-affine2d.json"; +// final String proj = "/home/john/Desktop/bigwarp-vmask-project.json"; +// final String proj = "/home/john/Desktop/bigwarp-mask-project.json"; +// final String proj = "/home/john/Desktop/bigwarp-mask51k-project.json"; +// final String proj = "/home/john/Desktop/bigwarp-maskn5a-project.json"; +// final String proj = "/home/john/Desktop/bigwarp-project.json"; +// final String proj = "/home/john/Desktop/t1-bigwarp-project.json"; +// final String proj = "/home/john/Desktop/inf-bw.json"; + +// final String proj = "/home/john/projects/bigwarp/projects/bw_jrc18Down.json"; +// final String proj = "/home/john/projects/bigwarp/projects/bw_jrc18Down-vmask.json"; +// final String proj = "/home/john/Desktop/bw-jrc18-rot.json"; +// final String proj = "/home/john/Desktop/bw-jrc18-rot-maskTune.json"; + + final String boats = "/home/john/tmp/boats.tif"; + final String jrc18 = "/home/john/Documents/teaching/emboBioImage2023_registration/sampleImages/jrc18_down.nrrd"; +// final String jrc18Df2 = "/home/john/projects/bigwarp/projects/bw_jrc18Down_tforms.n5?#coordinateTransformations[3]"; +// final String jrc18Df5 = "/home/john/projects/bigwarp/projects/bw_jrc18Down_tforms.n5?#coordinateTransformations[5]"; +// final String jrc18DfTgt = "/home/john/projects/bigwarp/projects/bw_jrc18Down_tforms.n5?#coordinateTransformations[0]"; + + final String boatsTlationDf = "/home/john/projects/bigwarp/projects/boats-chain.zarr?/#coordinateTransformations[1]"; + final String boatsAffine = "/home/john/projects/bigwarp/projects/boats-chain.zarr?/#coordinateTransformations[2]"; + +// final String jrc18DfMvgWrp = "/home/john/projects/bigwarp/projects/bw_jrc18Down_tforms.n5?#coordinateTransformations[2]"; +// final String jrc18DfTgt = "/home/john/projects/bigwarp/projects/jrc18.zarr?/#coordinateTransformations[0]"; + + final String jrc18RotDfTgt = "/home/john/projects/bigwarp/projects/jrc18-rot.zarr?/#coordinateTransformations[0]"; + final String jrc18RotTotSeq = "/home/john/projects/bigwarp/projects/jrc18-rot.zarr?/#coordinateTransformations[1]"; + final String jrc18RotPartial = "/home/john/projects/bigwarp/projects/jrc18-rot.zarr?/#coordinateTransformations[2]"; + final String jrc18RotTotSeqRev = "/home/john/projects/bigwarp/projects/jrc18-rot.zarr?/#coordinateTransformations[3]"; + +// final String jrc18DfAffine = "/home/john/projects/bigwarp/projects/jrc18.zarr?/#coordinateTransformations[3]"; + +// runBigWarp(null, new String[] {boats, boats}, new String[] {"true", "false" }, new String[]{}); +// runBigWarp(proj, new String[] {boats, boats}, new String[] {"true", "false" }, null); +// runBigWarp(null, new String[] {boats, boats}, new String[] {"true", "false" }, new String[]{"/home/john/Desktop/tforms.n5?aff2d#coordinateTransformations[0]", null}); +// runBigWarp(null, new String[] {boats, boats}, new String[] {"true", "false" }, new String[]{"/home/john/projects/bigwarp/projects/boats-chain.zarr?/#coordinateTransformations[0]", null}); +// runBigWarp(null, new String[] {boats, boats}, new String[] {"true", "false" }, new String[]{boatsAffine, null}); +// runBigWarp(null, new String[] {boats}, new String[] {"true"}, new String[]{boatsAffine}); + +// runBigWarp(null, new String[] {boats, boats}, new String[] {"true", "false" }, new String[]{"/home/john/projects/bigwarp/projects/boats.n5?/#coordinateTransformations[0]", null}); +// runBigWarp(null, new String[] {boats, boats}, new String[] {"true", "false" }, new String[]{"/home/john/projects/bigwarp/projects/boats.zarr?/#coordinateTransformations[0]", null}); +// runBigWarp(null, new String[] {boats, boats}, new String[] {"true", "false" }, new String[]{"/home/john/projects/bigwarp/projects/boats.zarr?/#coordinateTransformations[2]", null}); + +// runBigWarp(null, new String[] {jrc18, jrc18}, new String[] {"true", "false" }, new String[]{ jrc18RotTotSeqRev, null }); + + BigWarpInitDialog.runBigWarp(proj, new String[] {}, new String[] {}, null); + +// BigWarpInitDialog.createAndShow(); + + + // below this are from BigWarpCommand + +// String images = Macro.getValue(options, "images", ""); +// String moving = Macro.getValue(options, "moving", ""); +// String transforms = Macro.getValue(options, "transforms", ""); +// System.out.println( images ); +// System.out.println( moving ); +// System.out.println( transforms ); + +// final ImageJ ij2 = new ImageJ(); +// ij2.ui().showUI(); + +// final Object im1 = ij2.io().open( "/home/john/tmp/mri-stack.tif" ); +// final Object im2 = ij2.io().open( "/home/john/tmp/t1-head.tif" ); +//// Object im1 = ij2.io().open( "/groups/saalfeld/home/bogovicj/tmp/mri-stack.tif" ); +//// Object im2 = ij2.io().open( "/groups/saalfeld/home/bogovicj/tmp/t1-head.tif" ); +// ij2.ui().show( im1 ); +// ij2.ui().show( im2 ); +// final Object im1 = ij2.io().open( "/home/john/tmp/boats.tif" ); +// ij2.ui().show( im1 ); +// String args = "images=[a, b, c], isMoving=[true, true, false], transforms=[,,]"; +// String imagesList = null; +// String isMovingList = null; +// String transformsList = null; + + } +} diff --git a/src/test/java/bigwarp/TransformPoints2DTest.java b/src/test/java/bigwarp/TransformPoints2DTest.java index cd945cf8..209b784a 100644 --- a/src/test/java/bigwarp/TransformPoints2DTest.java +++ b/src/test/java/bigwarp/TransformPoints2DTest.java @@ -27,17 +27,15 @@ import net.imglib2.RealPoint; import net.imglib2.realtransform.InvertibleRealTransform; -import java.io.File; - public class TransformPoints2DTest { public static void main(String... args) throws Exception { ImagePlus impBlobs = IJ.openImage("https://imagej.nih.gov/ij/images/blobs.gif"); - BigWarp.BigWarpData bwData = BigWarpInit.createBigWarpDataFromImages( impBlobs, impBlobs ); + BigWarpData bwData = BigWarpInit.createBigWarpDataFromImages( impBlobs, impBlobs ); - BigWarp bigWarp = new BigWarp(bwData, "2D points transform", null); + BigWarp bigWarp = new BigWarp(bwData, null); // bigWarp.getLandmarkPanel().getTableModel().load( new File( "src/test/resources/landmarks2d-blobs.csv" )); bigWarp.loadLandmarks( "src/test/resources/landmarks2d-blobs.csv" ); diff --git a/src/test/java/bigwarp/TransformTests.java b/src/test/java/bigwarp/TransformTests.java index eeac42e2..e73ead1f 100644 --- a/src/test/java/bigwarp/TransformTests.java +++ b/src/test/java/bigwarp/TransformTests.java @@ -25,7 +25,6 @@ import bdv.gui.BigWarpViewerOptions; import bdv.gui.TransformTypeSelectDialog; import bdv.viewer.TransformListener; -import bigwarp.BigWarp.BigWarpData; import bigwarp.landmarks.LandmarkTableModel; import ij.ImagePlus; import mpicbg.spim.data.SpimDataException; @@ -63,7 +62,7 @@ public static void test2d( boolean testTps ) throws SpimDataException BigWarpData data = BigWarpInit.createBigWarpDataFromImages( imp, imp ); @SuppressWarnings({ "unchecked", "rawtypes" }) - BigWarp bw = new BigWarp( data, "bigwarp", opts, null ); + BigWarp bw = new BigWarp( data, opts, null ); LandmarkTableModel ltm = bw.getLandmarkPanel().getTableModel(); @@ -105,7 +104,7 @@ public static void test3d( boolean testTps ) throws SpimDataException BigWarpData data = BigWarpInit.createBigWarpDataFromImages( imp, imp ); @SuppressWarnings({ "unchecked", "rawtypes" }) - BigWarp bw = new BigWarp( data, "bigwarp", opts, null ); + BigWarp bw = new BigWarp( data, opts, null ); LandmarkTableModel ltm = bw.getLandmarkPanel().getTableModel(); diff --git a/src/test/java/bigwarp/dfield/DfieldExportTest.java b/src/test/java/bigwarp/dfield/DfieldExportTest.java new file mode 100644 index 00000000..8e863e65 --- /dev/null +++ b/src/test/java/bigwarp/dfield/DfieldExportTest.java @@ -0,0 +1,237 @@ +package bigwarp.dfield; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedHashMap; + +import org.junit.Before; +import org.junit.Test; + +import bdv.ij.BigWarpToDeformationFieldPlugIn; +import bdv.viewer.Source; +import bigwarp.BigWarpData; +import bigwarp.BigWarpInit; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.source.SourceInfo; +import bigwarp.transforms.BigWarpTransform; +import ij.ImagePlus; +import net.imglib2.FinalRealInterval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealPoint; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.img.imageplus.ImagePlusImgs; +import net.imglib2.iterator.RealIntervalIterator; +import net.imglib2.realtransform.DisplacementFieldTransform; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.RealTransformSequence; +import net.imglib2.realtransform.Scale3D; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Util; +import net.imglib2.view.Views; + +public class DfieldExportTest +{ + private BigWarpData data; + private BigWarpData dataWithTransform; + private LandmarkTableModel ltm; + + @Before + public void setup() + { + final ImagePlus imp = ImagePlusImgs.bytes( 64, 64, 16 ).getImagePlus(); + data = makeData( imp, null ); + dataWithTransform = makeData( imp, new Scale3D( 0.5, 0.5, 0.5 )); + + ltm = new LandmarkTableModel( 3 ); + try + { + ltm.load( new File( "src/test/resources/mr_landmarks_p2p2p4-111.csv" )); + } + catch ( final IOException e ) + { + e.printStackTrace(); + fail(); + } + } + + private static < T extends RealType< T > > BigWarpData< T > makeData( ImagePlus imp, RealTransform tform ) + { + final int id = 0; + final boolean isMoving = true; + final BigWarpData data = BigWarpInit.initData(); + final LinkedHashMap< Source< T >, SourceInfo > infos = BigWarpInit.createSources( data, imp, id, 0, isMoving ); + BigWarpInit.add( data, infos, tform, null ); + return data; + } + + @Test + public void dfieldExportTest() + { + final BigWarpTransform bwTransform = new BigWarpTransform( ltm ); + bwTransform.setInverseTolerance( 0.05 ); + bwTransform.setInverseMaxIterations( 200 ); + + final boolean ignoreAffine = false; + final boolean flatten = true; + final boolean virtual = false; + final long[] dims = new long[] { 47, 56, 7 }; + final double[] spacing = new double[] { 0.8, 0.8, 1.6 }; + final double[] offset = new double[] { 0, 0, 0 }; + final int nThreads = 1; + + final FinalRealInterval testItvl = new FinalRealInterval( + new double[]{ 3.6, 3.6, 1.6 }, + new double[]{ 32.0, 40.0, 9.6 }); + + final RealIntervalIterator it = new RealIntervalIterator( testItvl, spacing ); + + final ImagePlus dfieldImp = BigWarpToDeformationFieldPlugIn.toImagePlus( + data, ltm, bwTransform, + ignoreAffine, flatten, false, virtual, + dims, spacing, offset, + nThreads ); + + final InvertibleRealTransform tform = bwTransform.getTransformation(); + assertTrue( "forward", compare( tform, dfieldImp, it, 1e-3 )); + + final ImagePlus dfieldInvImp = BigWarpToDeformationFieldPlugIn.toImagePlus( + data, ltm, bwTransform, + ignoreAffine, flatten, true, virtual, + dims, spacing, offset, + nThreads ); + + it.reset(); + assertTrue( "inverse", compare( tform.inverse(), dfieldInvImp, it, 0.25 )); + } + + @Test + public void dfieldIgnoreAffineExportTest() + { + final boolean ignoreAffine = true; + + final BigWarpTransform bwTransform = new BigWarpTransform( ltm ); + + // constant parameters + final boolean flatten = true; + final boolean virtual = false; + final long[] dims = new long[] { 47, 56, 7 }; + final double[] spacing = new double[] { 0.8, 0.8, 1.6 }; + final double[] offset = new double[] { 0, 0, 0 }; + final int nThreads = 1; + + final FinalRealInterval testItvl = new FinalRealInterval( + new double[]{ 3.6, 3.6, 1.6 }, + new double[]{ 32.0, 40.0, 9.6 }); + + final ImagePlus dfieldImp = BigWarpToDeformationFieldPlugIn.toImagePlus( + data, ltm, bwTransform, + ignoreAffine, flatten, false, virtual, + dims, spacing, offset, + nThreads ); + + final RealTransformSequence total = new RealTransformSequence(); + total.add( toDfield( dfieldImp ) ); + total.add( bwTransform.affinePartOfTps() ); + + final RealIntervalIterator it = new RealIntervalIterator( testItvl, spacing ); + final InvertibleRealTransform tform = bwTransform.getTransformation(); + + assertTrue( "split affine forward", compare( tform, total, it, 1e-3 )); + } + + @Test + public void dfieldConcatExportTest() + { + final BigWarpTransform bwTransform = new BigWarpTransform( ltm ); + + // constant parameters + final boolean ignoreAffine = false; + final boolean virtual = false; + final boolean inverse = false; + final long[] dims = new long[] { 47, 56, 7 }; + final double[] spacing = new double[] { 0.8, 0.8, 1.6 }; + final double[] offset = new double[] { 0, 0, 0 }; + final int nThreads = 1; + + final FinalRealInterval testItvl = new FinalRealInterval( + new double[]{ 3.6, 3.6, 1.6 }, + new double[]{ 32.0, 40.0, 9.6 }); + + // flattened + final ImagePlus dfieldImpFlat = BigWarpToDeformationFieldPlugIn.toImagePlus( + dataWithTransform, ltm, bwTransform, + ignoreAffine, true, inverse, virtual, + dims, spacing, offset, + nThreads ); + final DisplacementFieldTransform dfieldFlat = toDfield( dfieldImpFlat ); + + final InvertibleRealTransform tform = bwTransform.getTransformation(); + final RealTransform preTransform = dataWithTransform.getSourceInfo( 0 ).getTransform(); + + final RealTransformSequence totalTrueTransform = new RealTransformSequence(); + totalTrueTransform.add( tform ); + totalTrueTransform.add( preTransform ); + + final RealIntervalIterator it = new RealIntervalIterator( testItvl, spacing ); + assertTrue( "flatten forward", compare( totalTrueTransform, dfieldFlat, it, 1e-3 )); + + // not flattened + final ImagePlus dfieldImpUnFlat = BigWarpToDeformationFieldPlugIn.toImagePlus( + dataWithTransform, ltm, bwTransform, + ignoreAffine, false, inverse, virtual, + dims, spacing, offset, + nThreads ); + final DisplacementFieldTransform dfieldUnflat = toDfield( dfieldImpUnFlat ); + + it.reset(); + assertTrue( "un-flattened forward", compare( tform, dfieldUnflat, it, 1e-3 )); + } + + public static DisplacementFieldTransform toDfield( final ImagePlus dfieldImp ) + { + final double[] spacing = new double[] { + dfieldImp.getCalibration().pixelWidth, dfieldImp.getCalibration().pixelHeight, dfieldImp.getCalibration().pixelDepth + }; + final double[] offset = new double[] { + dfieldImp.getCalibration().xOrigin, dfieldImp.getCalibration().yOrigin, dfieldImp.getCalibration().zOrigin + }; + + final RandomAccessibleInterval< FloatType > img = ImageJFunctions.wrapRealNative( dfieldImp ); + final RandomAccessibleInterval< FloatType > dfimg = Views.moveAxis( img, 2, 0 ); + return new DisplacementFieldTransform( dfimg, spacing, offset ); + } + + public static boolean compare( final RealTransform tform, final ImagePlus dfieldImp, final RealIntervalIterator it, final double tol ) + { + return compare( tform, toDfield( dfieldImp ), it, tol ); + } + + public static boolean compare( final RealTransform a, final RealTransform b, final RealIntervalIterator it, final double tol ) + { + final RealPoint gt = new RealPoint( 3 ); + final RealPoint df = new RealPoint( 3 ); + while( it.hasNext()) + { + it.fwd(); + a.apply( it, gt ); + b.apply( it, df ); + final double dist = Util.distance( gt, df ); + if( dist > tol ) + { + System.out.println( "it : " + it ); + System.out.println( "dist: " + dist); + return false; + } + } + + return true; + + } + + +} diff --git a/src/test/java/bigwarp/url/UrlParseHelper.java b/src/test/java/bigwarp/url/UrlParseHelper.java new file mode 100644 index 00000000..66b67852 --- /dev/null +++ b/src/test/java/bigwarp/url/UrlParseHelper.java @@ -0,0 +1,41 @@ +package bigwarp.url; + +import java.io.IOException; + +import org.janelia.saalfeldlab.n5.DataType; +import org.janelia.saalfeldlab.n5.GzipCompression; +import org.janelia.saalfeldlab.n5.N5FSWriter; +import org.janelia.saalfeldlab.n5.hdf5.N5HDF5Writer; +import org.janelia.saalfeldlab.n5.zarr.N5ZarrWriter; + +public class UrlParseHelper +{ + + public static void main( String[] args ) throws IOException + { + + final N5HDF5Writer h5 = new N5HDF5Writer("src/test/resources/bigwarp/url/transformTest.h5", 32 ); + final String data = "[\n" + + " {\n" + + " \"type\" : \"scale\",\n" + + " \"scale\" : [1,1,1],\n" + + " \"input\" : \"ant-in\",\n" + + " \"output\" : \"ant-out\"\n" + + " }\n" + + "]" ; + + h5.createGroup( "ant" ); + h5.setAttribute( "ant", "coordinateTransformations", data ); + h5.createDataset( "img", new long[]{6, 8, 10}, new int[] {16, 16, 16}, DataType.UINT8, new GzipCompression() ); + h5.createDataset( "img2", new long[]{12, 16, 20}, new int[] {20, 20, 20}, DataType.UINT8, new GzipCompression() ); + + final N5FSWriter n5 = new N5FSWriter("src/test/resources/bigwarp/url/transformTest.n5" ); + n5.createDataset( "img", new long[]{5, 8, 9}, new int[] {16, 16, 16}, DataType.UINT8, new GzipCompression() ); + n5.createDataset( "img2", new long[]{10, 16, 18}, new int[] {18, 18, 18}, DataType.UINT8, new GzipCompression() ); + + final N5ZarrWriter zarr = new N5ZarrWriter("src/test/resources/bigwarp/url/transformTest.zarr" ); + zarr.createDataset( "/img", new long[]{4, 6, 8}, new int[] {16, 16, 16}, DataType.UINT8, new GzipCompression() ); + zarr.createDataset( "/img2", new long[]{8, 12, 16}, new int[] {16, 16, 16}, DataType.UINT8, new GzipCompression() ); + } + +} diff --git a/src/test/java/bigwarp/url/UrlParseTest.java b/src/test/java/bigwarp/url/UrlParseTest.java new file mode 100644 index 00000000..388a8e84 --- /dev/null +++ b/src/test/java/bigwarp/url/UrlParseTest.java @@ -0,0 +1,185 @@ +package bigwarp.url; + +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.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +import org.janelia.saalfeldlab.n5.N5Exception; +import org.janelia.saalfeldlab.n5.N5FSReader; +import org.janelia.saalfeldlab.n5.hdf5.N5HDF5Reader; +import org.janelia.saalfeldlab.n5.universe.N5Factory; +import org.janelia.saalfeldlab.n5.zarr.N5ZarrReader; +import org.junit.Before; +import org.junit.Test; + +import bdv.viewer.Source; +import bigwarp.BigWarpData; +import bigwarp.BigWarpInit; +import bigwarp.source.SourceInfo; +import mpicbg.spim.data.SpimDataException; +import net.imglib2.type.NativeType; + +public class UrlParseTest +{ + public static final String TIFF_FILE_3D = "src/test/resources/testdata/img3d.tif"; + + public static final String PNG_FILE_2D = "src/test/resources/testdata/img2d.png"; + + public static final String TIFF_STACK_DIR = "src/test/resources/testdata/imgDir"; + + private Class< N5FSReader > n5Clazz; + + private Class< N5ZarrReader > zarrClazz; + + private Class< N5HDF5Reader > h5Clazz; + + private HashMap< String, long[] > urlToDimensions; + + private String n5Root, zarrRoot, h5Root; + + @Before + public void before() throws IOException + { + n5Clazz = N5FSReader.class; + zarrClazz = N5ZarrReader.class; + h5Clazz = N5HDF5Reader.class; + + n5Root = new File( "src/test/resources/bigwarp/url/transformTest.n5" ).getAbsolutePath(); + zarrRoot = new File( "src/test/resources/bigwarp/url/transformTest.zarr" ).getAbsolutePath(); + h5Root = new File( "src/test/resources/bigwarp/url/transformTest.h5" ).getAbsolutePath(); + + urlToDimensions = new HashMap<>(); + final String h5Root = new File( "src/test/resources/bigwarp/url/transformTest.h5" ).getAbsolutePath(); + urlToDimensions.put( h5Root + "?img", new long[] { 6, 8, 10 } ); + urlToDimensions.put( h5Root + "?img2", new long[] { 12, 16, 20 } ); + + final String n5Root = new File( "src/test/resources/bigwarp/url/transformTest.n5" ).getAbsolutePath(); + urlToDimensions.put( n5Root + "?img", new long[] { 5, 8, 9 } ); + urlToDimensions.put( n5Root + "?img2", new long[] { 10, 16, 18 } ); + + final String zarrRoot = new File( "src/test/resources/bigwarp/url/transformTest.zarr" ).getAbsolutePath(); + urlToDimensions.put( zarrRoot + "?img", new long[] { 4, 6, 8 } ); + urlToDimensions.put( zarrRoot + "?img2", new long[] { 8, 12, 16 } ); + } + + @Test + public void testFactories() + { + final N5Factory n5Factory = new N5Factory(); + try + { + assertEquals( n5Clazz, n5Factory.openReader( n5Root ).getClass() ); + assertEquals( zarrClazz, n5Factory.openReader( zarrRoot ).getClass() ); + assertEquals( h5Clazz, n5Factory.openReader( h5Root ).getClass() ); + } + catch ( final N5Exception e ) + { + fail( e.getMessage() ); + } + } + + @Test + public void testUrlSources() throws SpimDataException, URISyntaxException, IOException + { + final String bdvXmlUrl = new File( "src/test/resources/mri-stack.xml" ).getAbsolutePath(); + + final Source< ? > bdvXmlSrc = loadSourceFromUri( bdvXmlUrl ); + assertNotNull( bdvXmlSrc ); + + final Source< ? > img3dTif = loadSourceFromUri( TIFF_FILE_3D ); + assertNotNull( img3dTif ); + assertArrayEquals( new long[]{8,8,4}, img3dTif.getSource( 0, 0 ).dimensionsAsLongArray() ); + + final Source< ? > img2dPng = loadSourceFromUri( PNG_FILE_2D ); + assertNotNull( img2dPng ); + assertArrayEquals( new long[]{8,8,1}, img2dPng.getSource( 0, 0 ).dimensionsAsLongArray() ); // TODO I wrote this to expect [8,8,1], but it might need [8,8]. + + final Source< ? > img3dTifFromDir = loadSourceFromUri( TIFF_STACK_DIR ); + assertNotNull( img3dTifFromDir ); + assertArrayEquals( new long[]{8,8,4}, img3dTifFromDir.getSource( 0, 0 ).dimensionsAsLongArray() ); + + for ( final String url : urlToDimensions.keySet() ) + { + final Source< ? > src = loadSourceFromUri( url ); + assertNotNull( src ); + assertArrayEquals( urlToDimensions.get( url ), src.getSource( 0, 0 ).dimensionsAsLongArray() ); // TODO I wrote this to expect [8,8,1], but it might need [8,8]. + } + } + +// @Test +// public void testUrlTransforms() +// { +// final String n5Path = new File( "src/test/resources/bigwarp/url/transformTest.n5" ).getAbsolutePath(); +// +// final String s0Url = n5Path + "?ant&transform=[0]"; +// final String s0DefaultUrl = n5Path + "?ant&transform=[0]"; +// + // TODO when we're ready +// final Object s0 = loadTransformFromUrl( s0Url ); +// final Object s0Default = loadTransformFromUrl( s0DefaultUrl ); +// +// assertNotNull( s0 ); +// assertNotNull( s0Default ); +// assertEquals( s0, s0Default ); +// } + + @Test + public < T extends NativeType > void n5FileUrlEquivalencyTest() throws IOException, SpimDataException, URISyntaxException + { + final String relativePath = "src/test/resources/bigwarp/url/transformTest.n5"; + final String absolutePath = Paths.get( relativePath ).toAbsolutePath().toFile().getCanonicalPath(); + final String[] variants = new String[]{ + "n5:file://" + absolutePath + "?img#coordinateTransformations[0]", + "n5:file://" + absolutePath + "?img", + "n5:file:" + absolutePath + "?img#coordinateTransformations[0]", + "n5:file:" + absolutePath + "?img", + "n5://" + absolutePath + "?img#coordinateTransformations[0]", + "n5://" + absolutePath + "?img", + "file://" + absolutePath + "?img#coordinateTransformations[0]", + "file://" + absolutePath + "?img", + "file:" + absolutePath + "?img#coordinateTransformations[0]", + "file:" + absolutePath + "?img", + absolutePath + "?img#coordinateTransformations[0]", + absolutePath + "?img", + relativePath + "?img#coordinateTransformations[0]", + relativePath + "?img" + }; + + final BigWarpData data = BigWarpInit.initData(); + final AtomicInteger id = new AtomicInteger( 1 ); + for ( final String uri : variants ) + { + final int setupId = id.getAndIncrement(); + BigWarpInit.add( data, BigWarpInit.createSources( data, uri, setupId, new Random().nextBoolean() ) ); + assertEquals( uri, data.sourceInfos.get( setupId ).getUri() ); + } + } + +// private Object loadTransformFromUrl( final String url ) +// { +// // TODO Caleb will remove me and replace calls to me with something real +// return null; +// } + + private < T extends NativeType > Source< ? > loadSourceFromUri( final String uri ) throws SpimDataException, URISyntaxException, IOException + { + + final BigWarpData< T > data = BigWarpInit.initData(); + final LinkedHashMap< Source< T >, SourceInfo > sources = BigWarpInit.createSources( data, uri, 0, true ); + BigWarpInit.add( data, sources ); + data.wrapUp(); + return (Source) sources.keySet().toArray()[ 0 ]; + + } + +} diff --git a/src/test/java/net/imglib2/realtransform/AnimatorTimeVaryingTransformation.java b/src/test/java/net/imglib2/realtransform/AnimatorTimeVaryingTransformation.java new file mode 100644 index 00000000..47cc2ecf --- /dev/null +++ b/src/test/java/net/imglib2/realtransform/AnimatorTimeVaryingTransformation.java @@ -0,0 +1,19 @@ +package net.imglib2.realtransform; + +import bdv.viewer.animate.AbstractTransformAnimator; + +public class AnimatorTimeVaryingTransformation< T extends AbstractTransformAnimator > implements TimeVaryingTransformation +{ + private T animator; + + public AnimatorTimeVaryingTransformation( T animator ) + { + this.animator = animator; + } + + public RealTransform get( double t ) + { + return animator.get( t ); + } + +} diff --git a/src/test/java/net/imglib2/realtransform/InterpolatedTimeVaryingTransformation.java b/src/test/java/net/imglib2/realtransform/InterpolatedTimeVaryingTransformation.java new file mode 100644 index 00000000..db39bbed --- /dev/null +++ b/src/test/java/net/imglib2/realtransform/InterpolatedTimeVaryingTransformation.java @@ -0,0 +1,23 @@ +package net.imglib2.realtransform; + +import bdv.viewer.animate.AbstractTransformAnimator; +import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.util.ConstantUtils; + +public class InterpolatedTimeVaryingTransformation< T extends AbstractTransformAnimator > implements TimeVaryingTransformation +{ + private RealTransform a; + private RealTransform b; + + public InterpolatedTimeVaryingTransformation( RealTransform a, RealTransform b ) + { + this.a = a; + this.b = b; + } + + public RealTransform get( double t ) + { + return new SpatiallyInterpolatedRealTransform<>( a, b, ConstantUtils.constantRealRandomAccessible( new DoubleType( t ), a.numSourceDimensions() ) ); + } + +} diff --git a/src/test/java/net/imglib2/realtransform/InterpolatedTransformTest.java b/src/test/java/net/imglib2/realtransform/InterpolatedTransformTest.java new file mode 100644 index 00000000..ac609c46 --- /dev/null +++ b/src/test/java/net/imglib2/realtransform/InterpolatedTransformTest.java @@ -0,0 +1,45 @@ +package net.imglib2.realtransform; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import net.imglib2.RealPoint; +import net.imglib2.RealRandomAccessible; +import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.util.ConstantUtils; + +public class InterpolatedTransformTest +{ + + @Test + public void simpleTest() + { + final double EPS = 1e-9; + + final Translation2D a = new Translation2D( new double[]{ 1, 0 }); + final Translation2D b = new Translation2D( new double[]{ 2, 0 }); + + final RealRandomAccessible< DoubleType > l1 = ConstantUtils.constantRealRandomAccessible( new DoubleType(0.1), 2 ); + final RealRandomAccessible< DoubleType > l5 = ConstantUtils.constantRealRandomAccessible( new DoubleType(0.5), 2 ); + final RealRandomAccessible< DoubleType > l9 = ConstantUtils.constantRealRandomAccessible( new DoubleType(0.9), 2 ); + + final SpatiallyInterpolatedRealTransform t1 = new SpatiallyInterpolatedRealTransform<>( a, b, l1 ); + final SpatiallyInterpolatedRealTransform t5 = new SpatiallyInterpolatedRealTransform<>( a, b, l5 ); + final SpatiallyInterpolatedRealTransform t9 = new SpatiallyInterpolatedRealTransform<>( a, b, l9 ); + + RealPoint src = new RealPoint( 2 ); + RealPoint dst = new RealPoint( 2 ); + + t1.apply( src, dst ); + assertEquals( "lambda 0.1", 1.9, dst.getDoublePosition( 0 ), EPS ); + + t5.apply( src, dst ); + assertEquals( "lambda 0.5", 1.5, dst.getDoublePosition( 0 ), EPS ); + + t9.apply( src, dst ); + assertEquals( "lambda 0.9", 1.1, dst.getDoublePosition( 0 ), EPS ); + + } + +} diff --git a/src/test/java/net/imglib2/realtransform/SimilarityTransformInterpolationExample.java b/src/test/java/net/imglib2/realtransform/SimilarityTransformInterpolationExample.java new file mode 100644 index 00000000..30cb7949 --- /dev/null +++ b/src/test/java/net/imglib2/realtransform/SimilarityTransformInterpolationExample.java @@ -0,0 +1,381 @@ +package net.imglib2.realtransform; + +import bdv.util.Affine3DHelpers; +import bdv.util.BdvFunctions; +import bdv.util.BdvOptions; +import bdv.util.BdvStackSource; +import bdv.viewer.animate.AbstractTransformAnimator; +import bdv.viewer.animate.SimilarityModel3D; +import bdv.viewer.animate.SimilarityTransformAnimator; +import bigwarp.BigwarpSettings; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.source.PlateauSphericalMaskRealRandomAccessible; +import bigwarp.source.PlateauSphericalMaskSource; +import bigwarp.transforms.BigWarpTransform; +import bigwarp.transforms.ModelTransformSolver; +import bigwarp.transforms.TpsTransformSolver; +import bigwarp.transforms.WrappedCoordinateTransform; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import ij.IJ; +import ij.ImagePlus; +import java.io.IOException; +import java.io.Reader; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import mpicbg.models.AbstractAffineModel3D; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealRandomAccessible; +import net.imglib2.img.Img; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.interpolation.randomaccess.NLinearInterpolatorFactory; +import net.imglib2.interpolation.randomaccess.NearestNeighborInterpolatorFactory; +import net.imglib2.realtransform.MaskedSimilarityTransform.Interpolators; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.util.Intervals; +import net.imglib2.view.Views; + + +public class SimilarityTransformInterpolationExample { + + public static void main(String[] args) { +// show( args[0] ); +// exp(); +// showSeq( args[0], args[1] ); + expSeq( args[0], args[1] ); + } + + public static void exp() { + + double[] zero = new double[]{0, 0, 0}; + double[] center = new double[]{252, 76, 70}; + double[] centerOfRotation = new double[] { 252, 150, 70 }; + AffineTransform3D offset = new AffineTransform3D(); + offset.translate(center); + + AffineTransform3D dy20 = new AffineTransform3D(); + dy20.translate(new double[]{0, 300, 0}); + + AffineTransform3D transform = new AffineTransform3D(); + transform.rotate(2, Math.PI); + transform.preConcatenate(offset); + transform.concatenate(offset.inverse()); + System.out.println(transform); + System.out.println( Affine3DHelpers.toString(transform)); + + SimilarityTransformInterpolator interpolatorOtherC = new SimilarityTransformInterpolator( transform, centerOfRotation ); + AffineTransform3D z = interpolatorOtherC.get(0); + AffineTransform3D o = interpolatorOtherC.get(1); + System.out.println( "" ); + System.out.println( Affine3DHelpers.toString( z )); + System.out.println( "" ); + System.out.println( Affine3DHelpers.toString( o )); + + } + + public static void expSeq( String imgFile, String jsonFile ) { + ImagePlus imp = IJ.openImage( imgFile ); + Img imgBase = ImageJFunctions.wrapByte(imp); + Scale3D toPhysical = new Scale3D( + imp.getCalibration().pixelWidth, + imp.getCalibration().pixelHeight, + imp.getCalibration().pixelDepth); + + Img img = imgBase; + // RandomAccessibleInterval img = Views.translateInverse( imgBase, 252, 76, 70 ); + + AffineTransform3D identity = new AffineTransform3D(); + //TODO Caleb: John, what interval should we be using here? + FinalInterval bigItvl = Intervals.createMinMax(0, -200, 0, 1000, 1000, 150); + + final Path path = Paths.get( jsonFile ); + final OpenOption[] options = new OpenOption[]{StandardOpenOption.READ}; + Reader reader; + JsonObject json = null; + try + { + reader = Channels.newReader(FileChannel.open(path, options), StandardCharsets.UTF_8.name()); + json = BigwarpSettings.gson.fromJson( reader, JsonObject.class ); + } catch ( IOException e ) + { + e.printStackTrace(); + return; + } + + LandmarkTableModel ltm = new LandmarkTableModel( 3 ); + if( json.has( "landmarks" )) + ltm.fromJson( json ); + + + + final JsonObject maskJson = json.getAsJsonObject( "mask" ); + PlateauSphericalMaskRealRandomAccessible mask = BigwarpSettings.gson.fromJson( maskJson, PlateauSphericalMaskRealRandomAccessible.class); + + + PlateauSphericalMaskSource transformMask = PlateauSphericalMaskSource.build( mask, bigItvl ); + PlateauSphericalMaskRealRandomAccessible lambda = transformMask.getRandomAccessible(); + + // build transformations + final double[][] mvgPts; + final double[][] tgtPts; + + final int numActive = ltm.numActive(); + mvgPts = new double[ 3 ][ numActive ]; + tgtPts = new double[ 3 ][ numActive ]; + ltm.copyLandmarks( mvgPts, tgtPts ); // synchronized + + WrappedCoordinateTransform sim = new ModelTransformSolver( new SimilarityModel3D() ).solve(mvgPts, tgtPts); + final AffineTransform3D transform = new AffineTransform3D(); + BigWarpTransform.affine3d( (AbstractAffineModel3D)sim.getTransform(), transform ); + + final double[] center = new double[3]; + lambda.getCenter().localize( center ); + System.out.println( "center: " + Arrays.toString( center )); + + // masked similarity + final MaskedSimilarityTransform msim = new MaskedSimilarityTransform( transform, lambda, center ); + final MaskedSimilarityTransform msimInv = new MaskedSimilarityTransform( transform.inverse(), lambda, center, Interpolators.SIMILARITY ); + final WrappedIterativeInvertibleRealTransform tpsXfm = new TpsTransformSolver().solve( ltm ); + + final Scale3D id = new Scale3D(1,1,1); + final SpatiallyInterpolatedRealTransform tpsLmsim = new SpatiallyInterpolatedRealTransform( tpsXfm, transform.inverse(), lambda ); + final SpatiallyInterpolatedRealTransform idLmsimI = new SpatiallyInterpolatedRealTransform( id, transform, lambda ); + final SpatiallyInterpolatedRealTransform msimILid = new SpatiallyInterpolatedRealTransform( id, transform, lambda ); + + final double[] x = new double[]{358.1, 171.0, 85.7 }; + + double lam = lambda.getAt( x ).getRealDouble(); + System.out.println( " x: " + Arrays.toString( x )); + System.out.println( " l: " + lam ); + + + final double[] tx = new double[3]; + final double[] sx = new double[3]; + final double[] msx = new double[3]; + + tpsXfm.apply( x, tx ); + transform.inverse().apply( x, sx ); + msim.apply( x, msx ); + + System.out.println( " t(x): " + Arrays.toString( tx )); + System.out.println( " s(x): " + Arrays.toString( sx )); + System.out.println( " ms(x): " + Arrays.toString( msx )); + + double ltx = lambda.getAt( tx ).getRealDouble(); + double lsx = lambda.getAt( sx ).getRealDouble(); + double lmsx = lambda.getAt( msx ).getRealDouble(); + System.out.println( " lam(t(x)) : " + ltx ); + System.out.println( " lam(s(x)) : " + lsx ); + System.out.println( " lam( ms(x)): " + lmsx ); + System.out.println( " " ); + + final double[] msxMinv = new double[3]; + msimInv.apply( msx, msxMinv ); + System.out.println( " minv(ms(x)): " + Arrays.toString( msxMinv )); + + /** + * Conclusion: + * msimInv ( msim ( x )) is almost the identity + * good + */ + + + double[] tlmsx = new double[3]; + tpsLmsim.apply( x, tlmsx ); + + System.out.println( " " ); + System.out.println( " tlms(x): " + Arrays.toString( tlmsx )); + + double[] msi_tlmsx = new double[3]; + msimInv.apply( tlmsx, msi_tlmsx ); + System.out.println( " msi(tlms(x)): " + Arrays.toString( msi_tlmsx )); + + /* + * CONCLUSION + * msi_tlmsx is approximately where we want it to be + * + */ + + final RealTransformSequence xfm = new RealTransformSequence(); + xfm.add( tpsLmsim ); + xfm.add( msimInv ); + + double[] y = new double[3]; + xfm.apply( x, y ); + System.out.println( " f(x): " + Arrays.toString( y )); + } + + public static void showSeq( String imgFile, String jsonFile ) { + ImagePlus imp = IJ.openImage( imgFile ); + Img imgBase = ImageJFunctions.wrapByte(imp); + Scale3D toPhysical = new Scale3D( + imp.getCalibration().pixelWidth, + imp.getCalibration().pixelHeight, + imp.getCalibration().pixelDepth); + + Img img = imgBase; + // RandomAccessibleInterval img = Views.translateInverse( imgBase, 252, 76, 70 ); + + AffineTransform3D identity = new AffineTransform3D(); + FinalInterval bigItvl = Intervals.createMinMax(0, -200, 0, 1000, 1000, 150); + + Gson gson = new Gson(); + final Path path = Paths.get( jsonFile ); + final OpenOption[] options = new OpenOption[]{StandardOpenOption.READ}; + Reader reader; + JsonObject json = null; + try + { + reader = Channels.newReader(FileChannel.open(path, options), StandardCharsets.UTF_8.name()); + json = gson.fromJson( reader, JsonObject.class ); + } catch ( IOException e ) + { + e.printStackTrace(); + return; + } + + LandmarkTableModel ltm = new LandmarkTableModel( 3 ); + if( json.has( "landmarks" )) + ltm.fromJson( json ); + + + final JsonObject mask = json.getAsJsonObject( "mask" ); + PlateauSphericalMaskRealRandomAccessible maskRA = BigwarpSettings.gson.fromJson( mask, PlateauSphericalMaskRealRandomAccessible.class); + + + PlateauSphericalMaskSource transformMask = PlateauSphericalMaskSource.build( maskRA, bigItvl ); + PlateauSphericalMaskRealRandomAccessible lambda = transformMask.getRandomAccessible(); + + + // build transformations + final double[][] mvgPts; + final double[][] tgtPts; + + final int numActive = ltm.numActive(); + mvgPts = new double[ 3 ][ numActive ]; + tgtPts = new double[ 3 ][ numActive ]; + ltm.copyLandmarks( mvgPts, tgtPts ); // synchronized + + WrappedCoordinateTransform sim = new ModelTransformSolver( new SimilarityModel3D() ).solve(mvgPts, tgtPts); + final AffineTransform3D transform = new AffineTransform3D(); + BigWarpTransform.affine3d( (AbstractAffineModel3D)sim.getTransform(), transform ); + + final double[] center = new double[3]; + lambda.getCenter().localize( center ); + System.out.println( "center: " + Arrays.toString( center )); + + // masked similarity +// final MaskedSimilarityTransform xfm = new MaskedSimilarityTransform( transform, lambda, center ); + final WrappedIterativeInvertibleRealTransform tpsXfm = new TpsTransformSolver().solve( ltm ); + + final Scale3D id = new Scale3D(1,1,1); + final SpatiallyInterpolatedRealTransform first = new SpatiallyInterpolatedRealTransform( tpsXfm, transform.inverse(), lambda ); + final SpatiallyInterpolatedRealTransform second = new SpatiallyInterpolatedRealTransform( id, transform, lambda ); + + + +// final RealTransformSequence xfm = new RealTransformSequence(); +// xfm.add( first ); +// xfm.add( second ); + + final InterpolatedTimeVaryingTransformation sim2tps = new InterpolatedTimeVaryingTransformation( tpsXfm, transform ); +// final InterpolatedTimeVaryingTransformation id2simi = new InterpolatedTimeVaryingTransformation( id, transform.inverse() ); + SimilarityTransformInterpolator id2simi = new SimilarityTransformInterpolator( transform, center ); + +// SimilarityTransformAnimator intebcwrpolator = new SimilarityTransformAnimator( new AffineTransform3D(), transform, 0, 0, 1); +// SimilarityTransformInterpolator interpolatorC = new SimilarityTransformInterpolator( transform, center ); +// SimilarityTransformInterpolator interpolatorOtherC = new SimilarityTransformInterpolator( transform, centerOfRotation ); + + +// NearestNeighborInterpolatorFactory pixInterp = new NearestNeighborInterpolatorFactory(); + NLinearInterpolatorFactory pixInterp = new NLinearInterpolatorFactory(); + RealRandomAccessible< UnsignedByteType > rimg = RealViews.affine( + Views.interpolate(Views.extendZero(img), pixInterp), + toPhysical); + + BdvOptions opts = BdvOptions.options(); + BdvStackSource bdv = makeTimeStack( rimg, bigItvl, sim2tps, "sim 2 tps", opts ); + opts = opts.addTo(bdv); + makeTimeStack( rimg, bigItvl, id2simi, "id 2 simi", opts ); + } + + public static void show( String imgFile ) { + + ImagePlus imp = IJ.openImage( imgFile ); + Img imgBase = ImageJFunctions.wrapByte(imp); + + Img img = imgBase; + // RandomAccessibleInterval img = Views.translateInverse( imgBase, 252, 76, 70 ); + + AffineTransform3D identity = new AffineTransform3D(); + FinalInterval bigItvl = Intervals.createMinMax(-1000, -1000, -1000, 1000, 1000, 1000); + + double[] zero = new double[]{0, 0, 0}; + double[] center = new double[]{252, 76, 70}; + double[] centerOfRotation = new double[] { 252, 150, 70 }; + AffineTransform3D offset = new AffineTransform3D(); + offset.translate(center); + + AffineTransform3D dy20 = new AffineTransform3D(); + dy20.translate(new double[]{0, 300, 0}); + + AffineTransform3D transform = new AffineTransform3D(); + transform.rotate(2, Math.PI); + transform.preConcatenate(offset); + transform.concatenate(offset.inverse()); + System.out.println(transform); + +//      AffineTransform3D transform = new AffineTransform3D(); +//      transform.rotate( 2, Math.PI ); +//      transform.preConcatenate( dy20 ); +//      System.out.println( transform ); + + SimilarityTransformAnimator interpolator = new SimilarityTransformAnimator( new AffineTransform3D(), transform, 0, 0, 1); + SimilarityTransformInterpolator interpolatorC = new SimilarityTransformInterpolator( transform, center ); + SimilarityTransformInterpolator interpolatorOtherC = new SimilarityTransformInterpolator( transform, centerOfRotation ); + + RealRandomAccessible< UnsignedByteType > rimg = Views.interpolate(Views.extendZero(img), new NearestNeighborInterpolatorFactory<>()); + + BdvOptions opts = BdvOptions.options(); + BdvStackSource bdv = makeTimeStack( rimg, bigItvl, interpolator, "orig", opts ); + opts = opts.addTo(bdv); + makeTimeStack( rimg, bigItvl, interpolatorC, "center", opts ); + makeTimeStack( rimg, bigItvl, interpolatorOtherC, "other C", opts ); + + } + + @SuppressWarnings( { "rawtypes", "unchecked" } ) + public static BdvStackSource makeTimeStack( RealRandomAccessible img, Interval interval, AbstractTransformAnimator interpolator, String name, BdvOptions opts ) + { + return makeTimeStack( img, interval, new AnimatorTimeVaryingTransformation( interpolator ), name, opts ); + } + + public static BdvStackSource makeTimeStack( RealRandomAccessible rimg, Interval interval, TimeVaryingTransformation interpolator, String name, BdvOptions opts ) + { + + double del = 0.01; + List> stack = new ArrayList<>(); + for (double t = 0.0; t < (1.0 + del); t += del) + { + RealRandomAccessible xfmimg = new RealTransformRandomAccessible< >( + rimg, interpolator.get(t)); + + stack.add(Views.interval(Views.raster(xfmimg), interval)); + } + + RandomAccessibleInterval stackImg = Views.stack(stack); + return BdvFunctions.show(stackImg, name, opts ); + } + +} diff --git a/src/test/java/net/imglib2/realtransform/TimeVaryingTransformation.java b/src/test/java/net/imglib2/realtransform/TimeVaryingTransformation.java new file mode 100644 index 00000000..1bd49f1d --- /dev/null +++ b/src/test/java/net/imglib2/realtransform/TimeVaryingTransformation.java @@ -0,0 +1,8 @@ +package net.imglib2.realtransform; + +public interface TimeVaryingTransformation +{ + + public RealTransform get( double t ); + +} diff --git a/src/test/resources/bigwarp/url/transformTest.h5 b/src/test/resources/bigwarp/url/transformTest.h5 new file mode 100644 index 00000000..457cbc7f Binary files /dev/null and b/src/test/resources/bigwarp/url/transformTest.h5 differ diff --git a/src/test/resources/bigwarp/url/transformTest.n5/ant/attributes.json b/src/test/resources/bigwarp/url/transformTest.n5/ant/attributes.json new file mode 100644 index 00000000..e6726bb1 --- /dev/null +++ b/src/test/resources/bigwarp/url/transformTest.n5/ant/attributes.json @@ -0,0 +1,28 @@ +{ + "coordinateTransformations" : [ + { + "type" : "scale", + "scale" : [1,1], + "input" : "ct0-in", + "output" : "ct0-out" + }, + { + "type" : "translation", + "translation" : [5,5], + "input" : "ct1-in", + "output" : "ct1-out" + } + ], + "scale" : { + "type" : "scale", + "scale" : [-1,-1], + "input" : "scale-in", + "output" : "scale-out" + }, + "translation" : { + "type" : "translation", + "translation" : [-5,-5], + "input" : "translation-in", + "output" : "translation-out" + } +} diff --git a/src/test/resources/bigwarp/url/transformTest.n5/img/attributes.json b/src/test/resources/bigwarp/url/transformTest.n5/img/attributes.json new file mode 100644 index 00000000..ad35f06f --- /dev/null +++ b/src/test/resources/bigwarp/url/transformTest.n5/img/attributes.json @@ -0,0 +1 @@ +{"compression":{"type":"gzip","useZlib":false,"level":-1},"blockSize":[16,16,16],"dataType":"uint8","dimensions":[5,8,9]} \ No newline at end of file diff --git a/src/test/resources/bigwarp/url/transformTest.n5/img2/attributes.json b/src/test/resources/bigwarp/url/transformTest.n5/img2/attributes.json new file mode 100644 index 00000000..d3c3cf2a --- /dev/null +++ b/src/test/resources/bigwarp/url/transformTest.n5/img2/attributes.json @@ -0,0 +1 @@ +{"compression":{"type":"gzip","useZlib":false,"level":-1},"blockSize":[18,18,18],"dataType":"uint8","dimensions":[10,16,18]} \ No newline at end of file diff --git a/src/test/resources/bigwarp/url/transformTest.zarr/.zgroup b/src/test/resources/bigwarp/url/transformTest.zarr/.zgroup new file mode 100644 index 00000000..0c3ab975 --- /dev/null +++ b/src/test/resources/bigwarp/url/transformTest.zarr/.zgroup @@ -0,0 +1 @@ +{"zarr_format":2} diff --git a/src/test/resources/bigwarp/url/transformTest.zarr/ant/.zgroup b/src/test/resources/bigwarp/url/transformTest.zarr/ant/.zgroup new file mode 100644 index 00000000..77531bb5 --- /dev/null +++ b/src/test/resources/bigwarp/url/transformTest.zarr/ant/.zgroup @@ -0,0 +1,10 @@ +{ + "coordinateTransformations" : [ + { + "type" : "scale", + "scale" : [1,1,1], + "input" : "ant-in", + "output" : "ant-out" + } + ] +} diff --git a/src/test/resources/bigwarp/url/transformTest.zarr/img/.zarray b/src/test/resources/bigwarp/url/transformTest.zarr/img/.zarray new file mode 100644 index 00000000..5e14ba0b --- /dev/null +++ b/src/test/resources/bigwarp/url/transformTest.zarr/img/.zarray @@ -0,0 +1 @@ +{"shape":[8,6,4],"chunks":[16,16,16],"fill_value":"0","dtype":"|u1","filters":[],"zarr_format":2,"compressor":{"id":"gzip","level":-1},"order":"C"} \ No newline at end of file diff --git a/src/test/resources/bigwarp/url/transformTest.zarr/img2/.zarray b/src/test/resources/bigwarp/url/transformTest.zarr/img2/.zarray new file mode 100644 index 00000000..dbd61f73 --- /dev/null +++ b/src/test/resources/bigwarp/url/transformTest.zarr/img2/.zarray @@ -0,0 +1 @@ +{"shape":[16,12,8],"chunks":[16,16,16],"fill_value":"0","dtype":"|u1","filters":[],"zarr_format":2,"compressor":{"id":"gzip","level":-1},"order":"C"} \ No newline at end of file diff --git a/src/test/resources/settings/compareKnownXml.bigwarp.settings.xml b/src/test/resources/settings/compareKnownXml.bigwarp.settings.xml new file mode 100644 index 00000000..04e3dceb --- /dev/null +++ b/src/test/resources/settings/compareKnownXml.bigwarp.settings.xml @@ -0,0 +1,156 @@ + + + + + + + false + + + false + + + false + + + false + + + + + true + moving images + + + true + fixed images + + + false + moving images + 0 + 1 + + + false + target images + 2 + 3 + + + sg + nearestneighbor + 0 + 3 + 0 + + + + + + + false + + + false + + + false + + + false + + + + + true + moving images + + + true + fixed images + + + sg + nearestneighbor + 0 + 1 + 0 + + + + + + 0 + 0.0 + 255.0 + -1 + 0 + + + 1 + 0.0 + 65535.0 + -1 + 1 + + + 2 + 0.0 + 255.0 + -1 + 2 + + + 3 + 10.0 + 571.0 + -1 + 3 + + + + + 0 + -2.147483648E9 + 2.147483647E9 + 0.0 + 65535.0 + 0.0 + 255.0 + + + 1 + -2.147483648E9 + 2.147483647E9 + 0.0 + 65535.0 + 0.0 + 65535.0 + + + 2 + -2.147483648E9 + 2.147483647E9 + 0.0 + 65535.0 + 0.0 + 255.0 + + + 3 + -2.147483648E9 + 2.147483647E9 + 0.0 + 65535.0 + 0.0 + 65535.0 + + + + + + /tmp/.bigwarp + 180000 + + \ No newline at end of file diff --git a/src/test/resources/settings/expected.json b/src/test/resources/settings/expected.json new file mode 100644 index 00000000..72ca0598 --- /dev/null +++ b/src/test/resources/settings/expected.json @@ -0,0 +1,234 @@ +{ + "Sources": { + "0": { + "uri": "/tmp/img8270806677315563879.tif", + "name": "img8270806677315563879.tif", + "isMoving": true + }, + "1": { + "uri": "/tmp/img8270806677315563879.tif", + "name": "img8270806677315563879.tif", + "isMoving": true + }, + "2": { + "uri": "/tmp/img8270806677315563879.tif", + "name": "img8270806677315563879.tif", + "isMoving": false + }, + "3": { + "uri": "/tmp/img8270806677315563879.tif", + "name": "img8270806677315563879.tif", + "isMoving": false + } + }, + "ViewerP": { + "Sources": [ + false, + false, + false, + false + ], + "SourceGroups": [ + { + "active": true, + "name": "moving images", + "id": [] + }, + { + "active": true, + "name": "fixed images", + "id": [] + }, + { + "active": false, + "name": "moving images", + "id": [ + 0, + 1 + ] + }, + { + "active": false, + "name": "target images", + "id": [ + 2, + 3 + ] + } + ], + "DisplayMode": "sg", + "Interpolation": "nearestneighbor", + "CurrentSource": 0, + "CurrentGroup": 3, + "CurrentTimePoint": 0 + }, + "ViewerQ": { + "Sources": [ + false, + false, + false, + false + ], + "SourceGroups": [ + { + "active": true, + "name": "moving images", + "id": [] + }, + { + "active": true, + "name": "fixed images", + "id": [] + } + ], + "DisplayMode": "sg", + "Interpolation": "nearestneighbor", + "CurrentSource": 0, + "CurrentGroup": 1, + "CurrentTimePoint": 0 + }, + "SetupAssignments": { + "ConverterSetups": { + "0": { + "min": 0.0, + "max": 255.0, + "color": -1, + "groupId": 0 + }, + "1": { + "min": 0.0, + "max": 65535.0, + "color": -1, + "groupId": 1 + }, + "2": { + "min": 0.0, + "max": 255.0, + "color": -1, + "groupId": 2 + }, + "3": { + "min": 10.0, + "max": 571.0, + "color": -1, + "groupId": 3 + } + }, + "MinMaxGroups": { + "0": { + "fullRangeMin": -2.147483648E9, + "fullRangeMax": 2.147483647E9, + "rangeMin": 0.0, + "rangeMax": 65535.0, + "currentMin": 0.0, + "currentMax": 255.0 + }, + "1": { + "fullRangeMin": -2.147483648E9, + "fullRangeMax": 2.147483647E9, + "rangeMin": 0.0, + "rangeMax": 65535.0, + "currentMin": 0.0, + "currentMax": 65535.0 + }, + "2": { + "fullRangeMin": -2.147483648E9, + "fullRangeMax": 2.147483647E9, + "rangeMin": 0.0, + "rangeMax": 65535.0, + "currentMin": 0.0, + "currentMax": 255.0 + }, + "3": { + "fullRangeMin": -2.147483648E9, + "fullRangeMax": 2.147483647E9, + "rangeMin": 0.0, + "rangeMax": 65535.0, + "currentMin": 0.0, + "currentMax": 65535.0 + } + } + }, + "Bookmarks": { + "bookmarks": {} + }, + "Autosave": { + "period": 180000, + "location": "/tmp/.bigwarp" + }, + "Transform": { + "type": "Thin Plate Spline", + "landmarks": { + "type": "BigWarpLandmarks", + "numDimensions": 3, + "movingPoints": [ + [ + 193.1527102803737, + 163.57943925233658, + 130.00000000000003 + ], + [ + 132.8910280373831, + 248.17757009345803, + 130.00000000000003 + ], + [ + 222.70411214953256, + 272.51401869158883, + 130.00000000000003 + ], + [ + 182.72280373831765, + 159.52336448598143, + 170.5607476635514 + ], + [ + 176.92841121495317, + 255.1308411214954, + 170.5607476635514 + ] + ], + "fixedPoints": [ + [ + 216.01797508417232, + 233.3341910984992, + 126.86263579832789 + ], + [ + 145.77520051769838, + 213.26482693664948, + 126.8626357983279 + ], + [ + 146.49196352347872, + 298.5596246245108, + 126.86263579832789 + ], + [ + 182.72280373831765, + 159.52336448598143, + 170.5607476635514 + ], + [ + 176.92841121495317, + 255.1308411214954, + 170.5607476635514 + ] + ], + "active": [ + true, + true, + true, + true, + true + ], + "names": [ + "Pt-0", + "Pt-1", + "Pt-2", + "Pt-3", + "Pt-4" + ] + } + } +} diff --git a/src/test/resources/settings/expected_with_dfield.json b/src/test/resources/settings/expected_with_dfield.json new file mode 100644 index 00000000..fd231465 --- /dev/null +++ b/src/test/resources/settings/expected_with_dfield.json @@ -0,0 +1,230 @@ +{ + "Sources": { + "0": { + "uri": "/tmp/img8270806677315563879.tif", + "name": "img8270806677315563879.tif", + "isMoving": true + }, + "1": { + "uri": "/tmp/img8270806677315563879.tif", + "name": "img8270806677315563879.tif", + "isMoving": true + }, + "2": { + "uri": "/tmp/img8270806677315563879.tif", + "name": "img8270806677315563879.tif", + "isMoving": false + }, + "3": { + "uri": "/tmp/img8270806677315563879.tif", + "name": "img8270806677315563879.tif", + "isMoving": false + } + }, + "ViewerP": { + "Sources": [ + false, + false, + false, + false + ], + "SourceGroups": [ + { + "active": true, + "name": "moving images", + "id": [] + }, + { + "active": true, + "name": "fixed images", + "id": [] + }, + { + "active": false, + "name": "moving images", + "id": [ + 0, + 1 + ] + }, + { + "active": false, + "name": "target images", + "id": [ + 2, + 3 + ] + } + ], + "DisplayMode": "sg", + "Interpolation": "nearestneighbor", + "CurrentSource": 0, + "CurrentGroup": 3, + "CurrentTimePoint": 0 + }, + "ViewerQ": { + "Sources": [ + false, + false, + false, + false + ], + "SourceGroups": [ + { + "active": true, + "name": "moving images", + "id": [] + }, + { + "active": true, + "name": "fixed images", + "id": [] + } + ], + "DisplayMode": "sg", + "Interpolation": "nearestneighbor", + "CurrentSource": 0, + "CurrentGroup": 1, + "CurrentTimePoint": 0 + }, + "SetupAssignments": { + "ConverterSetups": { + "0": { + "min": 0.0, + "max": 255.0, + "color": -1, + "groupId": 0 + }, + "1": { + "min": 0.0, + "max": 65535.0, + "color": -1, + "groupId": 1 + }, + "2": { + "min": 0.0, + "max": 255.0, + "color": -1, + "groupId": 2 + }, + "3": { + "min": 10.0, + "max": 571.0, + "color": -1, + "groupId": 3 + } + }, + "MinMaxGroups": { + "0": { + "fullRangeMin": -2.147483648E9, + "fullRangeMax": 2.147483647E9, + "rangeMin": 0.0, + "rangeMax": 65535.0, + "currentMin": 0.0, + "currentMax": 255.0 + }, + "1": { + "fullRangeMin": -2.147483648E9, + "fullRangeMax": 2.147483647E9, + "rangeMin": 0.0, + "rangeMax": 65535.0, + "currentMin": 0.0, + "currentMax": 65535.0 + }, + "2": { + "fullRangeMin": -2.147483648E9, + "fullRangeMax": 2.147483647E9, + "rangeMin": 0.0, + "rangeMax": 65535.0, + "currentMin": 0.0, + "currentMax": 255.0 + }, + "3": { + "fullRangeMin": -2.147483648E9, + "fullRangeMax": 2.147483647E9, + "rangeMin": 0.0, + "rangeMax": 65535.0, + "currentMin": 0.0, + "currentMax": 65535.0 + } + } + }, + "Bookmarks": { + "bookmarks": {} + }, + "Transform": { + "type": "Thin Plate Spline", + "landmarks": { + "type": "BigWarpLandmarks", + "numDimensions": 3, + "movingPoints": [ + [ + 193.1527102803737, + 163.57943925233658, + 130.00000000000003 + ], + [ + 132.8910280373831, + 248.17757009345803, + 130.00000000000003 + ], + [ + 222.70411214953256, + 272.51401869158883, + 130.00000000000003 + ], + [ + 182.72280373831765, + 159.52336448598143, + 170.5607476635514 + ], + [ + 176.92841121495317, + 255.1308411214954, + 170.5607476635514 + ] + ], + "fixedPoints": [ + [ + 216.01797508417232, + 233.3341910984992, + 126.86263579832789 + ], + [ + 145.77520051769838, + 213.26482693664948, + 126.8626357983279 + ], + [ + 146.49196352347872, + 298.5596246245108, + 126.86263579832789 + ], + [ + 182.72280373831765, + 159.52336448598143, + 170.5607476635514 + ], + [ + 176.92841121495317, + 255.1308411214954, + 170.5607476635514 + ] + ], + "active": [ + true, + true, + true, + true, + true + ], + "names": [ + "Pt-0", + "Pt-1", + "Pt-2", + "Pt-3", + "Pt-4" + ] + } + } +} diff --git a/src/test/resources/settings/landmarks.csv b/src/test/resources/settings/landmarks.csv new file mode 100644 index 00000000..707b6fee --- /dev/null +++ b/src/test/resources/settings/landmarks.csv @@ -0,0 +1,5 @@ +"Pt-0","true","193.1527102803737","163.57943925233658","130.00000000000003","216.01797508417232","233.3341910984992","126.86263579832789" +"Pt-1","true","132.8910280373831","248.17757009345803","130.00000000000003","145.77520051769838","213.26482693664948","126.8626357983279" +"Pt-2","true","222.70411214953256","272.51401869158883","130.00000000000003","146.49196352347872","298.5596246245108","126.86263579832789" +"Pt-3","true","182.72280373831765","159.52336448598143","170.5607476635514","182.72280373831765","159.52336448598143","170.5607476635514" +"Pt-4","true","176.92841121495317","255.1308411214954","170.5607476635514","176.92841121495317","255.1308411214954","170.5607476635514" diff --git a/src/test/resources/settings/repeatFail.settings.xml b/src/test/resources/settings/repeatFail.settings.xml new file mode 100644 index 00000000..900f5cf9 --- /dev/null +++ b/src/test/resources/settings/repeatFail.settings.xml @@ -0,0 +1,71 @@ + + + + + + + false + + + + + false + moving images + 0 + + + sg + nearestneighbor + 0 + 0 + 0 + + + + + + + false + + + + + true + moving images + + + sg + nearestneighbor + 0 + 0 + 0 + + + + + + 0 + 0.0 + 65535.0 + -1 + 0 + + + + + 0 + -2.147483648E9 + 2.147483647E9 + 0.0 + 65535.0 + 0.0 + 255.0 + + + + + + /tmp/.bigwarp + 180000 + + \ No newline at end of file diff --git a/src/test/resources/testdata/img2d.png b/src/test/resources/testdata/img2d.png new file mode 100644 index 00000000..a1a05303 Binary files /dev/null and b/src/test/resources/testdata/img2d.png differ diff --git a/src/test/resources/testdata/img3d.tif b/src/test/resources/testdata/img3d.tif new file mode 100644 index 00000000..cc27e12d Binary files /dev/null and b/src/test/resources/testdata/img3d.tif differ diff --git a/src/test/resources/testdata/imgDir/imgDir3d0000.tif b/src/test/resources/testdata/imgDir/imgDir3d0000.tif new file mode 100644 index 00000000..0af1d279 Binary files /dev/null and b/src/test/resources/testdata/imgDir/imgDir3d0000.tif differ diff --git a/src/test/resources/testdata/imgDir/imgDir3d0001.tif b/src/test/resources/testdata/imgDir/imgDir3d0001.tif new file mode 100644 index 00000000..0af1d279 Binary files /dev/null and b/src/test/resources/testdata/imgDir/imgDir3d0001.tif differ diff --git a/src/test/resources/testdata/imgDir/imgDir3d0002.tif b/src/test/resources/testdata/imgDir/imgDir3d0002.tif new file mode 100644 index 00000000..0af1d279 Binary files /dev/null and b/src/test/resources/testdata/imgDir/imgDir3d0002.tif differ diff --git a/src/test/resources/testdata/imgDir/imgDir3d0003.tif b/src/test/resources/testdata/imgDir/imgDir3d0003.tif new file mode 100644 index 00000000..0af1d279 Binary files /dev/null and b/src/test/resources/testdata/imgDir/imgDir3d0003.tif differ