package com.indago.iddea.view.component; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import javax.swing.JFileChooser; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSlider; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.filechooser.FileFilter; import org.jhotdraw.app.action.edit.RedoAction; import org.jhotdraw.app.action.edit.UndoAction; import org.jhotdraw.draw.DefaultDrawingEditor; import org.jhotdraw.draw.Drawing; import org.jhotdraw.draw.DrawingEditor; import org.jhotdraw.draw.Figure; import org.jhotdraw.draw.QuadTreeDrawing; import org.jhotdraw.draw.action.ButtonFactory; import org.jhotdraw.draw.io.DOMStorableInputOutputFormat; import org.jhotdraw.draw.io.InputFormat; import org.jhotdraw.draw.io.OutputFormat; import org.jhotdraw.draw.tool.Tool; import org.jhotdraw.gui.Worker; import org.jhotdraw.gui.filechooser.ExtensionFileFilter; import org.jhotdraw.undo.UndoRedoManager; import org.jhotdraw.util.ResourceBundleUtil; import com.indago.iddea.controller.tool.NullTool; import com.indago.iddea.model.figure.DrawFigureFactory; import com.indago.iddea.view.display.InteractiveDrawingView; import com.indago.iddea.view.viewer.InteractiveRealViewer2D; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealRandomAccessible; import net.imglib2.converter.RealARGBConverter; import net.imglib2.img.Img; import net.imglib2.interpolation.randomaccess.NearestNeighborInterpolatorFactory; import net.imglib2.realtransform.AffineTransform2D; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.real.DoubleType; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; /** * A swing panel that can show imglib2 image data and annotate them using * JHotDraw figures. * * @author HongKee Moon, Florian Jug, Tobias Pietzsch * @since 9/4/13 */ // TODO This component should be totally generic, being able to host all image // types (not only LongType and DoubleType) public class IddeaComponent extends JPanel implements ActionListener, ChangeListener { private static final long serialVersionUID = -3808140519052170304L; // The everything containing scroll-bar private JScrollPane scrollPane; // JSlider for time series private boolean isTimeSliderVisible = false; protected JSlider timeSlider; protected int tIndex = 0; private int tMax = 0; // JSlider for volume stack private boolean isStackSliderVisible = false; private JSlider stackSlider; private int zIndex = 0; private int zMax = 0; // InteractiveViewer2D for the imglib2 data to be shown. protected InteractiveRealViewer2D< DoubleType > interactiveViewer2D; // The imglib2 image data container protected RandomAccessibleInterval ivSourceImage = null; // JHotDraw related stuff private DrawingEditor editor; private final InteractiveDrawingView view; private Drawing drawing; /** * Each DrawView uses its own undo redo manager. This allows for undoing and * redoing actions per view. */ private UndoRedoManager undo; // Toolbar setup and the toolbar itself private boolean isToolbarVisible = false; private String toolbarLocation = BorderLayout.WEST; private JToolBar tb; // Menu related stuff private final boolean isMenuVisible = false; private JMenuBar menuBar; private JMenu fileMenu; private JMenuItem menuItemOpen; private JMenuItem menuItemSaveAs; private JMenu editMenu; private JMenuItem menuItemUndo; private JMenuItem menuItemRedo; // File chooser for saving and loading private JFileChooser openChooser; private JFileChooser saveChooser; /** Holds the currently opened file. */ private File file; private HashMap< FileFilter, InputFormat > fileFilterInputFormatMap; private HashMap< FileFilter, OutputFormat > fileFilterOutputFormatMap; // ////////////////////////// CONSTUCTION ///////////////////////////////// /** * Creates an <code>IddeaComponent</code> that does not (yet) display any * image. */ public IddeaComponent() { view = buildInteractiveDrawingView(); initComponents( view ); } /** * Creates an <code>IddeaComponent</code> and adds the given * <code>soureImage</code> to it. */ public IddeaComponent( final RandomAccessibleInterval sourceImage ) { this.ivSourceImage = sourceImage; view = buildInteractiveDrawingView( ivSourceImage ); initComponents( view ); } /** * Creates an <code>IddeaComponent</code> and adds the given * <code>dimension</code> to it. Otherwise, 300x200 default screen appears. */ public IddeaComponent( final Dimension dim ) { view = buildInteractiveDrawingView( dim ); initComponents( view ); } private void initComponents( final InteractiveDrawingView view ) { editor = new DefaultDrawingEditor(); createEmptyToolbar(); scrollPane = new javax.swing.JScrollPane(); setLayout( new java.awt.BorderLayout() ); scrollPane.setHorizontalScrollBarPolicy( javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); scrollPane.setVerticalScrollBarPolicy( javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER ); scrollPane.setViewportView( view ); // Sliders initialization timeSlider = new JSlider( JSlider.HORIZONTAL, 0, 0, 0 ); timeSlider.setName( "TimeSlider" ); timeSlider.addChangeListener( this ); stackSlider = new JSlider( JSlider.VERTICAL, 0, 0, 0 ); stackSlider.setName( "StackSlider" ); stackSlider.addChangeListener( this ); menuBar = new JMenuBar(); // FileMenu fileMenu = new JMenu(); menuItemOpen = new JMenuItem(); menuItemSaveAs = new JMenuItem(); fileMenu.setText( "File" ); menuItemOpen.setText( "Open..." ); menuItemOpen.addActionListener( this ); fileMenu.add( menuItemOpen ); menuItemSaveAs.setText( "Save As..." ); menuItemSaveAs.addActionListener( this ); fileMenu.add( menuItemSaveAs ); menuBar.add( fileMenu ); // EditMenu editMenu = new JMenu(); menuItemUndo = new JMenuItem(); menuItemRedo = new JMenuItem(); editMenu.setText( "Edit" ); menuItemUndo.setText( "Undo" ); menuItemUndo.addActionListener( this ); editMenu.add( menuItemUndo ); menuItemRedo.setText( "Redo" ); menuItemRedo.addActionListener( this ); editMenu.add( menuItemRedo ); menuBar.add( editMenu ); if ( isMenuVisible ) this.add( menuBar, BorderLayout.NORTH ); if ( isTimeSliderVisible ) { this.add( timeSlider, BorderLayout.SOUTH ); timeSlider.setMaximum( tMax ); } if ( isStackSliderVisible ) { this.add( stackSlider, BorderLayout.EAST ); stackSlider.setMaximum( zMax ); } add( scrollPane, java.awt.BorderLayout.CENTER ); if ( isToolbarVisible ) { add( tb, toolbarLocation ); } setEditor( editor ); view.setDrawing( createDrawing() ); // Install undoRedoManager undo = new UndoRedoManager(); view.getDrawing().addUndoableEditListener( undo ); getActionMap().put( UndoAction.ID, undo.getUndoAction() ); getInputMap( WHEN_IN_FOCUSED_WINDOW ).put( KeyStroke.getKeyStroke( KeyEvent.VK_Z, java.awt.Event.META_MASK ), UndoAction.ID ); getActionMap().put( RedoAction.ID, undo.getRedoAction() ); getInputMap( WHEN_IN_FOCUSED_WINDOW ).put( KeyStroke.getKeyStroke( KeyEvent.VK_Z, java.awt.Event.META_MASK + java.awt.Event.SHIFT_MASK ), RedoAction.ID ); } // ////////////////////////// GETTERS AND SETTERS // ///////////////////////////////// /** * @return The <code>AffineTransform2D</code> describing the transformation * of the <code>ivSourceImage</code> on screen. */ public AffineTransform2D getViewerTransform() { return interactiveViewer2D.getViewerTransform(); } /** * @return All JHotDraw annotations made in this component. */ public Set< Figure > getAllAnnotationFigures() { return interactiveViewer2D.getJHotDrawDisplay().getAllFigures(); } /** * Returns the current screen image. * * @return */ public RandomAccessibleInterval getSourceImage() { return this.ivSourceImage; } /** * @return Returns the currently installed toolbar. */ public JToolBar getInstalledToolbar() { return this.tb; } /** * Sets the location of the toolbar. * * @param location * Either <code>BorderLayout.NORTH</code>, * <code>BorderLayout.EAST</code>, * <code>BorderLayout.SOUTH</code>, or * <code>BorderLayout.WEST</code> */ public void setToolBarLocation( final String location ) { if ( location.equals( BorderLayout.WEST ) || location.equals( BorderLayout.EAST ) ) { tb.setOrientation( JToolBar.VERTICAL ); } else { tb.setOrientation( JToolBar.HORIZONTAL ); } toolbarLocation = location; if ( isToolbarVisible ) { setToolBarVisible( false ); setToolBarVisible( true ); } } /** * Replaces the current * * @param viewImg */ public < T extends RealType< T > & NativeType< T >> void setSourceImage( final RandomAccessibleInterval< T > viewImg ) { this.ivSourceImage = viewImg; final T min = Views.iterable( viewImg ).firstElement().copy(); final T max = min.copy(); computeMinMax( viewImg, min, max ); RealRandomAccessible< T > interpolated = null; switch ( viewImg.numDimensions() ) { case 2: interpolated = Views.interpolate( Views.extendZero( viewImg ), new NearestNeighborInterpolatorFactory< T >() ); break; case 3: timeSlider.setMaximum( ( int ) viewImg.max( 2 ) ); tIndex = 0; showTimeSlider( true ); interpolated = Views.interpolate( Views.extendZero( Views.hyperSlice( viewImg, 2, tIndex ) ), new NearestNeighborInterpolatorFactory< T >() ); break; case 4: timeSlider.setMaximum( ( int ) viewImg.max( 3 ) ); tIndex = 0; showTimeSlider( true ); stackSlider.setMaximum( ( int ) viewImg.max( 2 ) ); zIndex = 0; showStackSlider( true ); interpolated = Views.interpolate( Views.extendZero( Views.hyperSlice( Views.hyperSlice( viewImg, 3, tIndex ), 2, zIndex ) ), new NearestNeighborInterpolatorFactory< T >() ); break; default: throw new IllegalArgumentException( "" + viewImg.numDimensions() + " Dimension size is not supported!" ); } final RealARGBConverter< T > converter = new RealARGBConverter< T >( min.getRealDouble(), max.getRealDouble() ); updateSourceAndConverter( interpolated, converter ); } /** * Sets the image data to be displayed. * * @param sourceImage * an IntervalView<DoubleType> containing the desired view onto * the raw image data */ public < T extends RealType< T > & NativeType< T >> void setDoubleTypeSourceImage( final IntervalView< T > sourceImage ) { this.setSourceImage( sourceImage ); } /** * Updates the only sourceImage without updating converter * * @param sourceImage * an IntervalView<T> containing the desired view onto the raw * image data */ public < T extends RealType< T > & NativeType< T >> void setOnlySourceImage( final RandomAccessibleInterval< T > raiSource ) { final IntervalView< T > sourceImage = Views.interval( raiSource, raiSource ); this.ivSourceImage = sourceImage; RealRandomAccessible< T > interpolated = null; switch ( sourceImage.numDimensions() ) { case 2: interpolated = Views.interpolate( Views.extendZero( sourceImage ), new NearestNeighborInterpolatorFactory< T >() ); break; case 3: timeSlider.setMaximum( ( int ) sourceImage.max( 2 ) ); showTimeSlider( true ); interpolated = Views.interpolate( Views.extendZero( Views.hyperSlice( sourceImage, 2, tIndex ) ), new NearestNeighborInterpolatorFactory< T >() ); break; case 4: timeSlider.setMaximum( ( int ) sourceImage.max( 3 ) ); showTimeSlider( true ); stackSlider.setMaximum( ( int ) sourceImage.max( 2 ) ); showStackSlider( true ); interpolated = Views.interpolate( Views.extendZero( Views.hyperSlice( Views.hyperSlice( sourceImage, 3, tIndex ), 2, zIndex ) ), new NearestNeighborInterpolatorFactory< T >() ); break; default: throw new IllegalArgumentException( "" + sourceImage.numDimensions() + " Dimension size is not supported!" ); } updateSource( interpolated ); } // ////////////////////////// OVERRIDDEN ///////////////////////////////// /** * Set the {@link Dimension} that contains the input image's dimension * information and propagate the dimension information to the * JHotDrawDisplay. * * @see javax.swing.JComponent#setPreferredSize(java.awt.Dimension) */ @Override public void setPreferredSize( final Dimension dim ) { interactiveViewer2D.getJHotDrawDisplay().setImageDim( dim ); } /** * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ @Override public void actionPerformed( final ActionEvent e ) { if ( e.getSource().equals( menuItemSaveAs ) ) { final JFileChooser fc = getSaveChooser(); if ( file != null ) { fc.setSelectedFile( file ); } if ( fc.showSaveDialog( this ) == JFileChooser.APPROVE_OPTION ) { this.setEnabled( false ); final File selectedFile; if ( fc.getFileFilter() instanceof ExtensionFileFilter ) { selectedFile = ( ( ExtensionFileFilter ) fc.getFileFilter() ).makeAcceptable( fc.getSelectedFile() ); } else { selectedFile = fc.getSelectedFile(); } final OutputFormat selectedFormat = fileFilterOutputFormatMap.get( fc.getFileFilter() ); new Worker< Object >() { @Override protected Object construct() throws IOException { IddeaComponent.this.write( selectedFile.toURI(), selectedFormat ); return null; } @Override protected void done( final Object value ) { file = selectedFile; } @Override protected void failed( final Throwable error ) { error.printStackTrace(); JOptionPane.showMessageDialog( IddeaComponent.this, "<html><b>Couldn't save to file \"" + selectedFile.getName() + "\"<br>" + error.toString(), "Save As File", JOptionPane.ERROR_MESSAGE ); } @Override protected void finished() { IddeaComponent.this.setEnabled( true ); } }.start(); } } else if ( e.getSource().equals( menuItemOpen ) ) { final JFileChooser fc = getOpenChooser(); if ( file != null ) { fc.setSelectedFile( file ); } if ( fc.showOpenDialog( this ) == JFileChooser.APPROVE_OPTION ) { this.setEnabled( false ); final File selectedFile = fc.getSelectedFile(); final InputFormat selectedFormat = fileFilterInputFormatMap.get( fc.getFileFilter() ); new Worker< Object >() { @Override protected Object construct() throws IOException { IddeaComponent.this.read( selectedFile.toURI(), selectedFormat ); return null; } @Override protected void done( final Object value ) { file = selectedFile; } @Override protected void failed( final Throwable error ) { error.printStackTrace(); JOptionPane.showMessageDialog( IddeaComponent.this, "<html><b>Couldn't open file \"" + selectedFile.getName() + "\"<br>" + error.toString(), "Open File", JOptionPane.ERROR_MESSAGE ); } @Override protected void finished() { IddeaComponent.this.setEnabled( true ); } }.start(); } } else if ( e.getSource().equals( menuItemUndo ) ) { getActionMap().get( UndoAction.ID ).actionPerformed( e ); } else if ( e.getSource().equals( menuItemRedo ) ) { getActionMap().get( RedoAction.ID ).actionPerformed( e ); } } // ////////////////////////// FUNCTIONS ///////////////////////////////// public void showMenu( final boolean visible ) { this.remove( menuBar ); if ( visible ) { this.add( menuBar, BorderLayout.NORTH ); } } public JMenuBar getMenuBar() { return menuBar; } public void showTimeSlider( final boolean visible ) { this.remove( timeSlider ); if ( visible ) { this.add( timeSlider, BorderLayout.SOUTH ); } this.updateUI(); } public void showStackSlider( final boolean visible ) { this.remove( stackSlider ); if ( visible ) { this.add( stackSlider, BorderLayout.EAST ); } this.updateUI(); } /** * Installs a toolbar that contains no annotation functionality at all. */ public void installDefaultToolBar() { this.tb.removeAll(); ButtonFactory.addSelectionToolTo( tb, editor, ButtonFactory.createDrawingActions( editor ), ButtonFactory.createSelectionActions( editor ) ); final ResourceBundleUtil labels = ResourceBundleUtil.getBundle( "model.Labels" ); ButtonFactory.addToolTo( tb, editor, new NullTool(), "edit.handleImageData", labels ); } /** * Shows or hides the currently installed toolbar. * * @param visible */ public void setToolBarVisible( final boolean visible ) { isToolbarVisible = visible; if ( isToolbarVisible ) { add( tb, toolbarLocation ); } else { remove( tb ); } } /** * @param loadedDrawing */ protected void setDrawing( final Drawing loadedDrawing ) { if ( view.getDrawing() != null ) { view.getDrawing().removeUndoableEditListener( undo ); } view.setDrawing( loadedDrawing ); view.getDrawing().addUndoableEditListener( undo ); undo.discardAllEdits(); this.drawing = loadedDrawing; } // ////////////////////////// PRIVATE STUFF // ///////////////////////////////// /** * Builds an Interactive Drawing view from a given <code>sourceImage</code> * Caution: this function also creates a new * <code>interactiveViewer2D</code>. * * @param sourceImage * @return */ private InteractiveDrawingView buildInteractiveDrawingView( final RandomAccessibleInterval< DoubleType > sourceImage ) { final AffineTransform2D transform = new AffineTransform2D(); final DoubleType min = new DoubleType(); final DoubleType max = new DoubleType(); computeMinMax( sourceImage, min, max ); RealRandomAccessible< DoubleType > interpolated = null; switch ( sourceImage.numDimensions() ) { case 2: interpolated = Views.interpolate( Views.extendZero( sourceImage ), new NearestNeighborInterpolatorFactory< DoubleType >() ); break; case 3: tMax = ( int ) sourceImage.max( 2 ); isTimeSliderVisible = true; tIndex = 0; interpolated = Views.interpolate( Views.extendZero( Views.hyperSlice( sourceImage, 2, tIndex ) ), new NearestNeighborInterpolatorFactory< DoubleType >() ); break; case 4: tMax = ( int ) sourceImage.max( 3 ); isTimeSliderVisible = true; tIndex = 0; zMax = ( int ) sourceImage.max( 2 ); isStackSliderVisible = true; zIndex = 0; interpolated = Views.interpolate( Views.extendZero( Views.hyperSlice( Views.hyperSlice( sourceImage, 3, tIndex ), 2, zIndex ) ), new NearestNeighborInterpolatorFactory< DoubleType >() ); break; default: throw new IllegalArgumentException( "" + sourceImage.numDimensions() + " Dimension size is not supported!" ); } final RealARGBConverter< DoubleType > converter = new RealARGBConverter< DoubleType >( min.get(), max.get() ); final int width = ( int ) sourceImage.max( 0 ); final int height = ( int ) sourceImage.max( 1 ); interactiveViewer2D = new InteractiveRealViewer2D< DoubleType >( width, height, interpolated, transform, converter ); return interactiveViewer2D.getJHotDrawDisplay(); } /** * Builds default Interactive Drawing view Caution: this function also * creates a new <code>interactiveViewer2D</code>. * * @return */ private InteractiveDrawingView buildInteractiveDrawingView() { final AffineTransform2D transform = new AffineTransform2D(); final RealRandomAccessible< DoubleType > dummy = new DummyRealRandomAccessible(); final RealARGBConverter< DoubleType > converter = new RealARGBConverter< DoubleType >( 0, 0 ); interactiveViewer2D = new InteractiveRealViewer2D< DoubleType >( 300, 200, dummy, transform, converter ); return interactiveViewer2D.getJHotDrawDisplay(); } /** * Builds an Interactive Drawing view from a given Dimension * <code>dim</code> Caution: this function also creates a new * <code>interactiveViewer2D</code>. * * @param dim * @return */ private InteractiveDrawingView buildInteractiveDrawingView( final Dimension dim ) { final AffineTransform2D transform = new AffineTransform2D(); final RealRandomAccessible< DoubleType > dummy = new DummyRealRandomAccessible(); final RealARGBConverter< DoubleType > converter = new RealARGBConverter< DoubleType >( 0, 0 ); interactiveViewer2D = new InteractiveRealViewer2D< DoubleType >( dim.width, dim.height, dummy, transform, converter ); return interactiveViewer2D.getJHotDrawDisplay(); } /** * Creates a new Drawing for this view. */ private Drawing createDrawing() { drawing = new QuadTreeDrawing(); final DOMStorableInputOutputFormat ioFormat = new DOMStorableInputOutputFormat( new DrawFigureFactory() ); drawing.addInputFormat( ioFormat ); drawing.addOutputFormat( ioFormat ); return drawing; } /** * Sets a JHotDraw drawing editor for this component. */ private void setEditor( final DrawingEditor newValue ) { if ( editor != null ) { editor.remove( view ); } editor = newValue; if ( editor != null ) { editor.add( view ); } } /** * Creates an empty toolbar. */ private void createEmptyToolbar() { this.tb = new JToolBar(); this.tb.setOrientation( JToolBar.VERTICAL ); final ResourceBundleUtil labels = ResourceBundleUtil.getBundle( "org.jhotdraw.draw.Labels" ); tb.setName( labels.getString( "window.drawToolBar.title" ) ); } /** * Adds a <code>Tool</code> to the toolbar. * * @param tool * The tool to be added to the currently installed toolbar. * @param labelKey * The key that points to the label resource. * @param labels * All the labels. */ public void addTool( final Tool tool, final String labelKey, final ResourceBundleUtil labels ) { ButtonFactory.addToolTo( tb, editor, tool, labelKey, labels ); } /** * Adds a <code>JToogleButton</code> to the toolbar. * * @param button * the button */ public void addToolBar( final JToggleButton button ) { tb.add( button ); } /** * @param strokes * An array containing all available stroke thicknesses. */ public void addToolStrokeWidthButton( final double[] strokes ) { tb.add( ButtonFactory.createStrokeWidthButton( editor, strokes, ResourceBundleUtil.getBundle( "org.jhotdraw.draw.Labels" ) ) ); } /** * Adds an separator to the currently installed toolbar. */ public void addToolBarSeparator() { tb.addSeparator(); } /** * Update the realRandomSource with new source. * * @param source */ private void updateSourceAndConverter( final RealRandomAccessible source, final RealARGBConverter converter ) { interactiveViewer2D.updateConverter( converter ); interactiveViewer2D.updateSource( source ); interactiveViewer2D.getJHotDrawDisplay().resetTransform(); } /** * Update the realRandomSource with new source. * * @param source */ private void updateSource( final RealRandomAccessible source ) { interactiveViewer2D.updateSource( source ); } /** Lazily creates a JFileChooser and returns it. */ private JFileChooser getOpenChooser() { if ( openChooser == null ) { openChooser = new JFileChooser(); fileFilterInputFormatMap = new HashMap< javax.swing.filechooser.FileFilter, InputFormat >(); javax.swing.filechooser.FileFilter firstFF = null; for ( final InputFormat format : this.drawing.getInputFormats() ) { final javax.swing.filechooser.FileFilter ff = format.getFileFilter(); if ( firstFF == null ) { firstFF = ff; } fileFilterInputFormatMap.put( ff, format ); openChooser.addChoosableFileFilter( ff ); } openChooser.setFileFilter( firstFF ); openChooser.addPropertyChangeListener( new PropertyChangeListener() { @Override public void propertyChange( final PropertyChangeEvent evt ) { if ( "fileFilterChanged".equals( evt.getPropertyName() ) ) { final InputFormat inputFormat = fileFilterInputFormatMap.get( evt.getNewValue() ); openChooser.setAccessory( ( inputFormat == null ) ? null : inputFormat.getInputFormatAccessory() ); } } } ); } return openChooser; } /** Lazily creates a JFileChooser and returns it. */ private JFileChooser getSaveChooser() { if ( saveChooser == null ) { saveChooser = new JFileChooser(); fileFilterOutputFormatMap = new HashMap< javax.swing.filechooser.FileFilter, OutputFormat >(); javax.swing.filechooser.FileFilter firstFF = null; for ( final OutputFormat format : this.drawing.getOutputFormats() ) { final javax.swing.filechooser.FileFilter ff = format.getFileFilter(); if ( firstFF == null ) { firstFF = ff; } fileFilterOutputFormatMap.put( ff, format ); saveChooser.addChoosableFileFilter( ff ); } saveChooser.setFileFilter( firstFF ); saveChooser.addPropertyChangeListener( new PropertyChangeListener() { @Override public void propertyChange( final PropertyChangeEvent evt ) { if ( "fileFilterChanged".equals( evt.getPropertyName() ) ) { final OutputFormat outputFormat = fileFilterOutputFormatMap.get( evt.getNewValue() ); saveChooser.setAccessory( ( outputFormat == null ) ? null : outputFormat.getOutputFormatAccessory() ); } } } ); } return saveChooser; } /** * Writes the drawing from the IddeaComponent into a file. * <p> * This method should be called from a worker thread. Calling it from the * Event Dispatcher Thread will block the user interface, until the drawing * is written. */ public void write( final URI uri ) throws IOException { final Drawing saveDrawing = IddeaComponent.this.drawing; if ( saveDrawing.getOutputFormats().size() == 0 ) { throw new InternalError( "Drawing object has no output formats." ); } // Try out all output formats until we find one which accepts the // filename entered by the user. final File f = new File( uri ); for ( final OutputFormat format : saveDrawing.getOutputFormats() ) { if ( format.getFileFilter().accept( f ) ) { format.write( uri, saveDrawing ); // We get here if writing was successful. // We can return since we are done. return; } } throw new IOException( "No output format for " + f.getName() ); } /** * Writes the drawing from the IddeaComponent into a file using the * specified output format. * <p> * This method should be called from a worker thread. Calling it from the * Event Dispatcher Thread will block the user interface, until the drawing * is written. */ public void write( final URI f, final OutputFormat format ) throws IOException { if ( format == null ) { write( f ); return; } // Write drawing to file final Drawing saveDrawing = IddeaComponent.this.drawing; format.write( f, saveDrawing ); } /** * Reads a drawing from the specified file into the SVGDrawingPanel. * <p> * This method should be called from a worker thread. Calling it from the * Event Dispatcher Thread will block the user interface, until the drawing * is read. */ public void read( final URI f ) throws IOException { // Create a new drawing object final Drawing newDrawing = createDrawing(); if ( newDrawing.getInputFormats().size() == 0 ) { throw new InternalError( "Drawing object has no input formats." ); } // Try out all input formats until we succeed IOException firstIOException = null; for ( final InputFormat format : newDrawing.getInputFormats() ) { try { format.read( f, newDrawing ); final Drawing loadedDrawing = newDrawing; final Runnable r = new Runnable() { @Override public void run() { // Set the drawing on the Event Dispatcher Thread setDrawing( loadedDrawing ); } }; if ( SwingUtilities.isEventDispatchThread() ) { r.run(); } else { try { SwingUtilities.invokeAndWait( r ); } catch ( final InterruptedException ex ) { // suppress silently } catch ( final InvocationTargetException ex ) { final InternalError ie = new InternalError( "Error setting drawing." ); ie.initCause( ex ); throw ie; } } // We get here if reading was successful. // We can return since we are done. return; // } catch ( final IOException e ) { // We get here if reading failed. // We only preserve the exception of the first input format, // because that's the one which is best suited for this drawing. if ( firstIOException == null ) { firstIOException = e; } } } throw firstIOException; } /** * Reads a drawing from the specified file into the SVGDrawingPanel using * the specified input format. * <p> * This method should be called from a worker thread. Calling it from the * Event Dispatcher Thread will block the user interface, until the drawing * is read. */ public void read( final URI f, final InputFormat format ) throws IOException { if ( format == null ) { read( f ); return; } // Create a new drawing object final Drawing newDrawing = createDrawing(); if ( newDrawing.getInputFormats().size() == 0 ) { throw new InternalError( "Drawing object has no input formats." ); } format.read( f, newDrawing ); final Drawing loadedDrawing = newDrawing; final Runnable r = new Runnable() { @Override public void run() { // Set the drawing on the Event Dispatcher Thread setDrawing( loadedDrawing ); } }; if ( SwingUtilities.isEventDispatchThread() ) { r.run(); } else { try { SwingUtilities.invokeAndWait( r ); } catch ( final InterruptedException ex ) { // suppress silently } catch ( final InvocationTargetException ex ) { final InternalError ie = new InternalError( "Error setting drawing." ); ie.initCause( ex ); throw ie; } } } /** * States of TimeSlider or StackSlider are changed. * * @param changeEvent * the change event */ @Override public void stateChanged( final ChangeEvent changeEvent ) { if ( timeSlider.equals( changeEvent.getSource() ) ) { tIndex = timeSlider.getValue(); updateView(); } else if ( stackSlider.equals( changeEvent.getSource() ) ) { zIndex = stackSlider.getValue(); updateView(); } } /** * Update the display view. */ private < T extends RealType< T > & NativeType< T >> void updateView() { if ( this.ivSourceImage != null ) { RandomAccessibleInterval< T > interval; switch ( ivSourceImage.numDimensions() ) { case 2: interval = ivSourceImage; break; case 3: interval = Views.hyperSlice( ivSourceImage, 2, tIndex ); break; case 4: interval = Views.hyperSlice( Views.hyperSlice( ivSourceImage, 3, tIndex ), 2, zIndex ); break; default: throw new IllegalArgumentException( "" + ivSourceImage.numDimensions() + " Dimension size is not supported!" ); } final RealRandomAccessible< T > interpolated = Views.interpolate( Views.extendZero( interval ), new NearestNeighborInterpolatorFactory< T >() ); updateSource( interpolated ); } } /** * Compute the min and max for any {@link Iterable}, like an {@link Img}. * * The only functionality we need for that is to iterate. Therefore we need * no {@link Cursor} that can localize itself, neither do we need a * {@link RandomAccess}. So we simply use the most simple interface in the * hierarchy. * * @param viewImg * - the input that has to just be {@link Iterable} * @param min * - the type that will have min * @param max * - the type that will have max */ public static < T extends RealType< T > & NativeType< T > > void computeMinMax( final RandomAccessibleInterval< T > viewImg, final T min, final T max ) { if ( viewImg == null ) { return; } // create a cursor for the image (the order does not matter) final Iterator< T > iterator = Views.iterable( viewImg ).iterator(); // initialize min and max with the first image value T type = iterator.next(); min.set( type ); max.set( type ); // loop over the rest of the data and determine min and max value while ( iterator.hasNext() ) { // we need this type more than once type = iterator.next(); if ( type.compareTo( min ) < 0 ) min.set( type ); if ( type.compareTo( max ) > 0 ) max.set( type ); } } }