From 84cc71a55563a279471806f29e282f6546acc7c2 Mon Sep 17 00:00:00 2001 From: Tobias Pietzsch <tobias.pietzsch@gmail.com> Date: Tue, 24 Jun 2014 16:28:44 +0200 Subject: [PATCH] Dialog to select a bounding box --- .../bdv/viewer/box/BdvBoundingBoxExample.java | 136 +++++++++++++++++ .../bdv/viewer/box/BoundingBoxDialog.java | 143 ++++++++++++++++++ .../viewer/box/BoxRealRandomAccessible.java | 84 ++++++++++ .../bdv/viewer/box/BoxSelectionPanel.java | 108 +++++++++++++ .../box/RealRandomAccessibleSource.java | 78 ++++++++++ 5 files changed, 549 insertions(+) create mode 100644 core/src/main/java/bdv/viewer/box/BdvBoundingBoxExample.java create mode 100644 core/src/main/java/bdv/viewer/box/BoundingBoxDialog.java create mode 100644 core/src/main/java/bdv/viewer/box/BoxRealRandomAccessible.java create mode 100644 core/src/main/java/bdv/viewer/box/BoxSelectionPanel.java create mode 100644 core/src/main/java/bdv/viewer/box/RealRandomAccessibleSource.java diff --git a/core/src/main/java/bdv/viewer/box/BdvBoundingBoxExample.java b/core/src/main/java/bdv/viewer/box/BdvBoundingBoxExample.java new file mode 100644 index 00000000..e2e11268 --- /dev/null +++ b/core/src/main/java/bdv/viewer/box/BdvBoundingBoxExample.java @@ -0,0 +1,136 @@ +package bdv.viewer.box; + +import java.util.ArrayList; + +import javax.swing.ActionMap; +import javax.swing.InputMap; + +import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription; +import net.imglib2.Interval; +import net.imglib2.util.Intervals; +import bdv.BigDataViewer; +import bdv.ViewerImgLoader; +import bdv.spimdata.SpimDataMinimal; +import bdv.spimdata.WrapBasicImgLoader; +import bdv.spimdata.XmlIoSpimDataMinimal; +import bdv.tools.InitializeViewerState; +import bdv.tools.ToggleDialogAction; +import bdv.tools.VisibilityAndGroupingDialog; +import bdv.tools.brightness.BrightnessDialog; +import bdv.tools.brightness.ConverterSetup; +import bdv.tools.brightness.MinMaxGroup; +import bdv.tools.brightness.RealARGBColorConverterSetup; +import bdv.tools.brightness.SetupAssignments; +import bdv.util.AbstractNamedAction.NamedActionAdder; +import bdv.util.KeyProperties; +import bdv.util.KeyProperties.KeyStrokeAdder; +import bdv.viewer.DisplayMode; +import bdv.viewer.NavigationActions; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.ViewerFrame; +import bdv.viewer.ViewerPanel; + + +public class BdvBoundingBoxExample +{ + protected BdvBoundingBoxExample( final String xmlFilename ) throws SpimDataException + { + // =============== Load spimdata and create sources for display ================== + + final SpimDataMinimal spimData = new XmlIoSpimDataMinimal().load( xmlFilename ); + if ( WrapBasicImgLoader.wrapImgLoaderIfNecessary( spimData ) ) + { + System.err.println( "WARNING:\nOpening <SpimData> dataset that is not suited for suited for interactive browsing.\nConsider resaving as HDF5 for better performance." ); + } + final AbstractSequenceDescription< ?, ?, ? > seq = spimData.getSequenceDescription(); + + final ArrayList< ConverterSetup > converterSetups = new ArrayList< ConverterSetup >(); + final ArrayList< SourceAndConverter< ? > > sources = new ArrayList< SourceAndConverter< ? > >(); + BigDataViewer.initSetups( spimData, converterSetups, sources ); + + final int width = 800; + final int height = 600; + final int numTimepoints = seq.getTimePoints().getTimePointsOrdered().size(); + final ViewerFrame viewerFrame = new ViewerFrame( width, height, sources, numTimepoints, + ( ( ViewerImgLoader< ?, ? > ) seq.getImgLoader() ).getCache() ); + final ViewerPanel viewer = viewerFrame.getViewerPanel(); + + + // =============== Create SetupAssignments, which encapsulate source color and brightness settings ================== + + for ( final ConverterSetup cs : converterSetups ) + if ( RealARGBColorConverterSetup.class.isInstance( cs ) ) + ( ( RealARGBColorConverterSetup ) cs ).setViewer( viewer ); + + final SetupAssignments setupAssignments = new SetupAssignments( converterSetups, 0, 65535 ); + if ( setupAssignments.getMinMaxGroups().size() > 0 ) + { + final MinMaxGroup group = setupAssignments.getMinMaxGroups().get( 0 ); + for ( final ConverterSetup setup : setupAssignments.getConverterSetups() ) + setupAssignments.moveSetupToGroup( setup, group ); + } + + + // =============== Dialogs for brightness and visibility&grouping ================== + + final BrightnessDialog brightnessDialog = new BrightnessDialog( viewerFrame, setupAssignments ); + final VisibilityAndGroupingDialog activeSourcesDialog = new VisibilityAndGroupingDialog( viewerFrame, viewer.getVisibilityAndGrouping() ); + + + // =============== the bounding box dialog ================== + + final int boxSetupId = 9999; // some non-existing setup id + final Interval initialInterval = Intervals.createMinMax( 500, 100, -100, 1500, 800, 300 ); // the initially selected bounding box + final Interval rangeInterval = Intervals.createMinMax( -500, -500, -500, 3000, 3000, 1000 ); // the range (bounding box of possible bounding boxes) + final BoundingBoxDialog boundingBoxDialog = new BoundingBoxDialog( viewerFrame, viewer, setupAssignments, boxSetupId, initialInterval, rangeInterval ); + + + // =============== install standard navigation shortcuts and S/F6/B for brightness/visibility&grouping/boundingbox dialogs ================== + + final KeyProperties keyProperties = KeyProperties.readPropertyFile(); + NavigationActions.installActionBindings( viewerFrame.getKeybindings(), viewer, keyProperties ); + final String BRIGHTNESS_SETTINGS = "brightness settings"; + final String VISIBILITY_AND_GROUPING = "visibility and grouping"; + final String BOUNDING_BOX_DIALOG = "bounding box"; + final ActionMap actionMap = new ActionMap(); + final NamedActionAdder amap = new NamedActionAdder( actionMap ); + amap.put( new ToggleDialogAction( BRIGHTNESS_SETTINGS, brightnessDialog ) ); + amap.put( new ToggleDialogAction( VISIBILITY_AND_GROUPING, activeSourcesDialog ) ); + amap.put( new ToggleDialogAction( BOUNDING_BOX_DIALOG, boundingBoxDialog ) ); + final InputMap inputMap = new InputMap(); + final KeyStrokeAdder imap = new KeyProperties( null ).adder( inputMap ); + imap.put( BRIGHTNESS_SETTINGS, "S" ); + imap.put( VISIBILITY_AND_GROUPING, "F6" ); + imap.put( BOUNDING_BOX_DIALOG, "B" ); + viewerFrame.getKeybindings().addActionMap( "bdvbox", actionMap ); + viewerFrame.getKeybindings().addInputMap( "bdvbox", inputMap ); + + // =============== go to fused mode (overlay all sources) such that bounding box will be visible ================== + viewer.setDisplayMode( DisplayMode.FUSED ); + + // show viewer window + viewerFrame.setVisible( true ); + + // initialize transform and brightness + InitializeViewerState.initTransform( viewer ); + InitializeViewerState.initBrightness( 0.001, 0.999, viewer, setupAssignments ); + +// ( ( Hdf5ImageLoader ) seq.imgLoader ).initCachedDimensionsFromHdf5( false ); + } + + public static void main( final String[] args ) + { + final String fn = "/Users/Pietzsch/Desktop/bdv example/drosophila 2.xml"; + try + { + System.setProperty("apple.laf.useScreenMenuBar", "true"); + new BdvBoundingBoxExample( fn ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + } + +} diff --git a/core/src/main/java/bdv/viewer/box/BoundingBoxDialog.java b/core/src/main/java/bdv/viewer/box/BoundingBoxDialog.java new file mode 100644 index 00000000..3ee971db --- /dev/null +++ b/core/src/main/java/bdv/viewer/box/BoundingBoxDialog.java @@ -0,0 +1,143 @@ +package bdv.viewer.box; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.KeyEvent; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.KeyStroke; + +import net.imglib2.Interval; +import net.imglib2.display.RealARGBColorConverter; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import bdv.tools.brightness.RealARGBColorConverterSetup; +import bdv.tools.brightness.SetupAssignments; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.ViewerPanel; + +// dialog to change bounding box +// while dialog is visible, bounding box is added as a source to the viewer +public class BoundingBoxDialog extends JDialog +{ + private final ViewerPanel viewer; + + private final BoxRealRandomAccessible< UnsignedShortType > boxRealRandomAccessible; + + private final BoxSelectionPanel boxSelectionPanel; + + protected final SourceAndConverter< UnsignedShortType > boxSourceAndConverter; + + protected final RealARGBColorConverterSetup boxConverterSetup; + + public BoundingBoxDialog( final Frame owner, final ViewerPanel viewer, final SetupAssignments setupAssignments, final int boxSetupId, final Interval initialInterval, final Interval rangeInterval ) + { + super( owner, "bounding box", false ); + this.viewer = viewer; + + + // create a procedural RealRandomAccessible that will render the bounding box + final UnsignedShortType insideValue = new UnsignedShortType( 1000 ); // inside the box pixel value is 1000 + final UnsignedShortType outsideValue = new UnsignedShortType( 0 ); // outside is 0 + boxRealRandomAccessible = new BoxRealRandomAccessible< UnsignedShortType >( initialInterval, insideValue, outsideValue ); + + // create a bdv.viewer.Source providing data from the bbox RealRandomAccessible + final RealRandomAccessibleSource< UnsignedShortType > boxSource = new RealRandomAccessibleSource< UnsignedShortType >( boxRealRandomAccessible, "selection" ) + { + @Override + public Interval getInterval( final int t, final int level ) + { + return boxRealRandomAccessible.getInterval(); + } + }; + + // set up a converter from the source type (UnsignedShortType in this case) to ARGBType + final RealARGBColorConverter< UnsignedShortType > converter = new RealARGBColorConverter.Imp1< UnsignedShortType >( 0, 3000 ); + converter.setColor( new ARGBType( ARGBType.rgba( 0, 255, 0, 255 ) ) ); // set bounding box color to green + + // create a ConverterSetup (can be used by the brightness dialog to adjust the converter settings) + boxConverterSetup = new RealARGBColorConverterSetup( boxSetupId, converter ); + boxConverterSetup.setViewer( viewer ); + + // create a SourceAndConverter (can be added to the viewer for display + boxSourceAndConverter = new SourceAndConverter< UnsignedShortType >( boxSource, converter ); + + + // create a JPanel with sliders to modify the bounding box interval (boxRealRandomAccessible.getInterval()) + boxSelectionPanel = new BoxSelectionPanel( boxRealRandomAccessible.getInterval(), rangeInterval ); + boxSelectionPanel.addSelectionUpdateListener( new BoxSelectionPanel.SelectionUpdateListener() // listen for updates on the bbox to trigger repainting + { + @Override + public void selectionUpdated() + { + viewer.requestRepaint(); + } + } ); + + + // button prints the bounding box interval + final JButton button = new JButton( "ok" ); + button.addActionListener( new AbstractAction() + { + @Override + public void actionPerformed( final ActionEvent e ) + { + System.out.println( "bounding box:" + net.imglib2.util.Util.printInterval( boxRealRandomAccessible.getInterval() ) ); + } + } ); + + + // when dialog is made visible, add bbox source + // when dialog is hidden, remove bbox source + addComponentListener( new ComponentAdapter() + { + @Override + public void componentShown( final ComponentEvent e ) + { + viewer.addSource( boxSourceAndConverter ); + setupAssignments.addSetup( boxConverterSetup ); + } + + @Override + public void componentHidden( final ComponentEvent e ) + { + viewer.removeSource( boxSourceAndConverter.getSpimSource() ); + setupAssignments.removeSetup( boxConverterSetup ); + } + } ); + + + // make ESC key hide dialog + final ActionMap am = getRootPane().getActionMap(); + final InputMap im = getRootPane().getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); + final Object hideKey = new Object(); + final Action hideAction = new AbstractAction() + { + @Override + public void actionPerformed( final ActionEvent e ) + { + setVisible( false ); + } + }; + im.put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), hideKey ); + am.put( hideKey, hideAction ); + + + // layout and show dialog + final Container content = getContentPane(); + content.add( boxSelectionPanel, BorderLayout.NORTH ); + content.add( button, BorderLayout.SOUTH ); + pack(); + setDefaultCloseOperation( JDialog.HIDE_ON_CLOSE ); + } +} diff --git a/core/src/main/java/bdv/viewer/box/BoxRealRandomAccessible.java b/core/src/main/java/bdv/viewer/box/BoxRealRandomAccessible.java new file mode 100644 index 00000000..23de2e29 --- /dev/null +++ b/core/src/main/java/bdv/viewer/box/BoxRealRandomAccessible.java @@ -0,0 +1,84 @@ +package bdv.viewer.box; + +import net.imglib2.Interval; +import net.imglib2.RealInterval; +import net.imglib2.RealPoint; +import net.imglib2.RealRandomAccess; +import net.imglib2.RealRandomAccessible; +import net.imglib2.type.Type; +import net.imglib2.util.Intervals; +import bdv.util.ModifiableInterval; + +// a simple imglib2 RealRandomAccessible that has one value inside a box and another value outside +public class BoxRealRandomAccessible< T extends Type< T > > implements RealRandomAccessible< T > +{ + private final int n; + + private final ModifiableInterval interval; + + private final T insideValue; + + private final T outsideValue; + + public BoxRealRandomAccessible( final Interval interval, final T insideValue, final T outsideValue ) + { + n = interval.numDimensions(); + this.interval = new ModifiableInterval( interval ); + this.insideValue = insideValue.copy(); + this.outsideValue = outsideValue.copy(); + } + + @Override + public int numDimensions() + { + return n; + } + + public class Access extends RealPoint implements RealRandomAccess< T > + { + public Access() + { + super( BoxRealRandomAccessible.this.n ); + } + + protected Access( final Access a ) + { + super( a ); + } + + @Override + public T get() + { + return Intervals.contains( interval, this ) ? insideValue : outsideValue; + } + + @Override + public Access copy() + { + return new Access( this ); + } + + @Override + public Access copyRealRandomAccess() + { + return copy(); + } + } + + @Override + public RealRandomAccess< T > realRandomAccess() + { + return new Access(); + } + + @Override + public RealRandomAccess< T > realRandomAccess( final RealInterval interval ) + { + return new Access(); + } + + public ModifiableInterval getInterval() + { + return interval; + } +} diff --git a/core/src/main/java/bdv/viewer/box/BoxSelectionPanel.java b/core/src/main/java/bdv/viewer/box/BoxSelectionPanel.java new file mode 100644 index 00000000..f88c8c8e --- /dev/null +++ b/core/src/main/java/bdv/viewer/box/BoxSelectionPanel.java @@ -0,0 +1,108 @@ +package bdv.viewer.box; + +import java.util.ArrayList; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JPanel; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import bdv.tools.brightness.SliderPanel; +import bdv.util.BoundedInterval; +import bdv.util.ModifiableInterval; + +// a JPanel containing X,Y,Z min/max sliders for adjusting an interval +public class BoxSelectionPanel extends JPanel +{ + public static interface SelectionUpdateListener + { + public void selectionUpdated(); + } + + private static final long serialVersionUID = 1L; + + private final BoundedInterval[] ranges; + + private final ModifiableInterval selection; + + private final ArrayList< SelectionUpdateListener > listeners; + + public BoxSelectionPanel( final ModifiableInterval selection, final Interval rangeInterval ) + { + final int n = selection.numDimensions(); + this.selection = selection; + ranges = new BoundedInterval[ n ]; + listeners = new ArrayList< SelectionUpdateListener >(); + + setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) ); + for ( int d = 0; d < n; ++d ) + { + final int rangeMin = ( int ) rangeInterval.min( d ); + final int rangeMax = ( int ) rangeInterval.max( d ); + final int initialMin = Math.max( ( int ) selection.min( d ), rangeMin ); + final int initialMax = Math.min( ( int ) selection.max( d ), rangeMax ); + final BoundedInterval range = new BoundedInterval( rangeMin, rangeMax, initialMin, initialMax, 1 ) + { + @Override + protected void updateInterval( final int min, final int max ) + { + updateSelection(); + } + }; + final JPanel sliders = new JPanel(); + sliders.setLayout( new BoxLayout( sliders, BoxLayout.PAGE_AXIS ) ); + final SliderPanel minPanel = new SliderPanel( "min", range.getMinBoundedValue(), 1 ); + minPanel.setBorder( BorderFactory.createEmptyBorder( 0, 10, 10, 10 ) ); + sliders.add( minPanel ); + final SliderPanel maxPanel = new SliderPanel( "max", range.getMaxBoundedValue(), 1 ); + maxPanel.setBorder( BorderFactory.createEmptyBorder( 0, 10, 10, 10 ) ); + sliders.add( maxPanel ); + add( sliders ); + ranges[ d ] = range; + } + } + + public void setBoundsInterval( final Interval interval ) + { + final int n = selection.numDimensions(); + for ( int d = 0; d < n; ++d ) + ranges[ d ].setRange( ( int ) interval.min( d ), ( int ) interval.max( d ) ); + } + + public void addSelectionUpdateListener( final SelectionUpdateListener l ) + { + listeners.add( l ); + } + + public void updateSelection() + { + final int n = selection.numDimensions(); + final long[] min = new long[ n ]; + final long[] max = new long[ n ]; + for ( int d = 0; d < n; ++d ) + { + min[ d ] = ranges[ d ].getMinBoundedValue().getCurrentValue(); + max[ d ] = ranges[ d ].getMaxBoundedValue().getCurrentValue(); + } + selection.set( new FinalInterval( min, max ) ); + for ( final SelectionUpdateListener l : listeners ) + l.selectionUpdated(); + } + + public void updateSliders( final Interval interval ) + { + final int n = selection.numDimensions(); + if ( interval.numDimensions() != n ) + throw new IllegalArgumentException(); + final long[] min = new long[ n ]; + final long[] max = new long[ n ]; + interval.min( min ); + interval.max( max ); + for ( int d = 0; d < n; ++d ) + { + ranges[ d ].getMinBoundedValue().setCurrentValue( ( int ) min[ d ] ); + ranges[ d ].getMaxBoundedValue().setCurrentValue( ( int ) max[ d ] ); + } + } +} diff --git a/core/src/main/java/bdv/viewer/box/RealRandomAccessibleSource.java b/core/src/main/java/bdv/viewer/box/RealRandomAccessibleSource.java new file mode 100644 index 00000000..2f06af37 --- /dev/null +++ b/core/src/main/java/bdv/viewer/box/RealRandomAccessibleSource.java @@ -0,0 +1,78 @@ +package bdv.viewer.box; + +import net.imglib2.Interval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealRandomAccessible; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.Type; +import net.imglib2.util.Util; +import net.imglib2.view.Views; +import bdv.viewer.Interpolation; +import bdv.viewer.Source; + +/** + * A {@link Source} wrapping some {@link RealRandomAccessible}. + * + * @param <T> + * + * @author Tobias Pietzsch <tobias.pietzsch@gmail.com> + */ +public abstract class RealRandomAccessibleSource< T extends Type< T > > implements Source< T > +{ + protected RealRandomAccessible< T > accessible; + + protected final T type; + + protected final String name; + + public RealRandomAccessibleSource( final RealRandomAccessible< T > accessible, final String name ) + { + this.accessible = accessible; + this.type = Util.getTypeFromRealRandomAccess( accessible ).createVariable(); + this.name = name; + } + + @Override + public boolean isPresent( final int t ) + { + return true; + } + + public abstract Interval getInterval( final int t, final int level ); + + @Override + public RandomAccessibleInterval< T > getSource( final int t, final int level ) + { + return Views.interval( Views.raster( accessible ), getInterval( t, level ) ); + } + + @Override + public RealRandomAccessible< T > getInterpolatedSource( final int t, final int level, final Interpolation method ) + { + return accessible; + } + + @Override + public AffineTransform3D getSourceTransform( final int t, final int level ) + { + return new AffineTransform3D(); + } + + @Override + public T getType() + { + return type; + } + + @Override + public String getName() + { + return name; + } + + @Override + public int getNumMipmapLevels() + { + return 1; + } +} -- GitLab