/*-
* #%L
* Fiji distribution of ImageJ for the life sciences.
* %%
* Copyright (C) 2007 - 2017 Fiji developers.
* %%
* 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
* <http://www.gnu.org/licenses/gpl-2.0.html>.
* #L%
*/
package spim.fiji.spimdata.explorer;
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.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import bdv.BigDataViewer;
import bdv.img.hdf5.Hdf5ImageLoader;
import bdv.tools.InitializeViewerState;
import bdv.tools.brightness.ConverterSetup;
import bdv.util.Affine3DHelpers;
import bdv.viewer.DisplayMode;
import bdv.viewer.Source;
import bdv.viewer.ViewerOptions;
import bdv.viewer.VisibilityAndGrouping;
import bdv.viewer.state.ViewerState;
import mpicbg.spim.data.SpimDataException;
import mpicbg.spim.data.generic.AbstractSpimData;
import mpicbg.spim.data.generic.XmlIoAbstractSpimData;
import mpicbg.spim.data.generic.sequence.BasicViewDescription;
import mpicbg.spim.data.generic.sequence.BasicViewSetup;
import mpicbg.spim.data.sequence.TimePoint;
import mpicbg.spim.data.sequence.ViewId;
import mpicbg.spim.io.IOFunctions;
import net.imglib2.Interval;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.type.numeric.ARGBType;
import net.imglib2.util.LinAlgHelpers;
import spim.fiji.spimdata.SpimData2;
import spim.fiji.spimdata.explorer.popup.ApplyTransformationPopup;
import spim.fiji.spimdata.explorer.popup.BDVPopup;
import spim.fiji.spimdata.explorer.popup.BakeManualTransformationPopup;
import spim.fiji.spimdata.explorer.popup.BoundingBoxPopup;
import spim.fiji.spimdata.explorer.popup.DetectInterestPointsPopup;
import spim.fiji.spimdata.explorer.popup.DisplayViewPopup;
import spim.fiji.spimdata.explorer.popup.FusionPopup;
import spim.fiji.spimdata.explorer.popup.InterestPointsExplorerPopup;
import spim.fiji.spimdata.explorer.popup.LabelPopUp;
import spim.fiji.spimdata.explorer.popup.MaxProjectPopup;
import spim.fiji.spimdata.explorer.popup.RegisterInterestPointsPopup;
import spim.fiji.spimdata.explorer.popup.RegistrationExplorerPopup;
import spim.fiji.spimdata.explorer.popup.RemoveDetectionsPopup;
import spim.fiji.spimdata.explorer.popup.RemoveTransformationPopup;
import spim.fiji.spimdata.explorer.popup.ReorientSamplePopup;
import spim.fiji.spimdata.explorer.popup.ResavePopup;
import spim.fiji.spimdata.explorer.popup.Separator;
import spim.fiji.spimdata.explorer.popup.SpecifyCalibrationPopup;
import spim.fiji.spimdata.explorer.popup.ViewExplorerSetable;
import spim.fiji.spimdata.explorer.popup.VisualizeDetectionsPopup;
import spim.fiji.spimdata.explorer.util.ColorStream;
import spim.fiji.spimdata.interestpoints.InterestPointList;
import spim.fiji.spimdata.interestpoints.ViewInterestPointLists;
import spim.fiji.spimdata.interestpoints.ViewInterestPoints;
public class ViewSetupExplorerPanel< AS extends AbstractSpimData< ? >, X extends XmlIoAbstractSpimData< ?, AS > > extends JPanel
{
final static ArrayList< ViewExplorerSetable > staticPopups = new ArrayList< ViewExplorerSetable >();
static
{
IOFunctions.printIJLog = true;
staticPopups.add( new LabelPopUp( " Displaying" ) );
staticPopups.add( new BDVPopup() );
staticPopups.add( new DisplayViewPopup() );
staticPopups.add( new MaxProjectPopup() );
staticPopups.add( new Separator() );
staticPopups.add( new LabelPopUp( " Processing" ) );
staticPopups.add( new DetectInterestPointsPopup() );
staticPopups.add( new RegisterInterestPointsPopup() );
staticPopups.add( new BoundingBoxPopup() );
staticPopups.add( new FusionPopup() );
staticPopups.add( new Separator() );
staticPopups.add( new LabelPopUp( " Calibration/Transformations" ) );
staticPopups.add( new RegistrationExplorerPopup() );
staticPopups.add( new SpecifyCalibrationPopup() );
staticPopups.add( new ApplyTransformationPopup() );
staticPopups.add( new BakeManualTransformationPopup() );
staticPopups.add( new RemoveTransformationPopup() );
staticPopups.add( new ReorientSamplePopup() );
staticPopups.add( new Separator() );
staticPopups.add( new LabelPopUp( " Interest Points" ) );
staticPopups.add( new InterestPointsExplorerPopup() );
staticPopups.add( new VisualizeDetectionsPopup() );
staticPopups.add( new RemoveDetectionsPopup() );
staticPopups.add( new Separator() );
staticPopups.add( new LabelPopUp( " Modifications" ) );
staticPopups.add( new ResavePopup() );
}
private static final long serialVersionUID = -3767947754096099774L;
protected JTable table;
protected ViewSetupTableModel< AS > tableModel;
protected ArrayList< SelectedViewDescriptionListener< AS > > listeners;
protected AS data;
protected ViewSetupExplorer< AS, X > explorer;
final String xml;
final X io;
final boolean isMac;
protected boolean colorMode = true;
final protected HashSet< BasicViewDescription< ? extends BasicViewSetup > > selectedRows;
protected BasicViewDescription< ? extends BasicViewSetup > firstSelectedVD;
public ViewSetupExplorerPanel( final ViewSetupExplorer< AS, X > explorer, final AS data, final String xml, final X io )
{
this.explorer = explorer;
this.listeners = new ArrayList< SelectedViewDescriptionListener< AS > >();
this.data = data;
this.xml = xml.replace( "\\", "/" ).replace( "//", "/" ).replace( "/./", "/" );
this.io = io;
this.isMac = System.getProperty( "os.name" ).toLowerCase().contains( "mac" );
this.selectedRows = new HashSet< BasicViewDescription< ? extends BasicViewSetup > >();
this.firstSelectedVD = null;
initComponent();
if ( Hdf5ImageLoader.class.isInstance( data.getSequenceDescription().getImgLoader() ) )
{
final BDVPopup bdvpopup = bdvPopup();
if ( bdvpopup != null )
{
bdvpopup.bdv = BigDataViewer.open( getSpimData(), xml(), IOFunctions.getProgressWriter(), ViewerOptions.options() );
// if ( !bdv.tryLoadSettings( panel.xml() ) ) TODO: this should work, but currently tryLoadSettings is protected. fix that.
InitializeViewerState.initBrightness( 0.001, 0.999, bdvpopup.bdv.getViewer(), bdvpopup.bdv.getSetupAssignments() );
// do not rotate BDV view by default
BDVPopup.initTransform( bdvpopup.bdv.getViewer() );
setFusedModeSimple( bdvpopup.bdv, data );
}
}
}
public static BDVPopup bdvPopup()
{
for ( final ViewExplorerSetable s : staticPopups )
if ( BDVPopup.class.isInstance( s ) )
return ((BDVPopup)s);
return null;
}
public boolean colorMode() { return colorMode; }
public BasicViewDescription< ? extends BasicViewSetup > firstSelectedVD() { return firstSelectedVD; }
public ViewSetupTableModel< AS > getTableModel() { return tableModel; }
public AS getSpimData() { return data; }
public String xml() { return xml; }
public X io() { return io; }
public ViewSetupExplorer< AS, X > explorer() { return explorer; }
@SuppressWarnings("unchecked")
public void setSpimData( final Object data ) { this.data = (AS)data; }
public void updateContent()
{
this.getTableModel().fireTableDataChanged();
for ( final SelectedViewDescriptionListener< AS > l : listeners )
l.updateContent( this.data );
}
public List< BasicViewDescription< ? extends BasicViewSetup > > selectedRows()
{
final ArrayList< BasicViewDescription< ? extends BasicViewSetup > > list = new ArrayList< BasicViewDescription< ? extends BasicViewSetup > >();
list.addAll( selectedRows );
Collections.sort( list );
return list;
}
public List< ViewId > selectedRowsViewId()
{
final ArrayList< ViewId > list = new ArrayList< ViewId >();
list.addAll( selectedRows );
Collections.sort( list );
return list;
}
public void addListener( final SelectedViewDescriptionListener< AS > listener )
{
this.listeners.add( listener );
// update it with the currently selected row
if ( table.getSelectedRow() != -1 )
listener.seletedViewDescription( tableModel.getElements().get( table.getSelectedRow() ) );
}
public ArrayList< SelectedViewDescriptionListener< AS > > getListeners() { return listeners; }
public void initComponent()
{
tableModel = new ViewSetupTableModel< AS >( this );
table = new JTable();
table.setModel( tableModel );
table.setSurrendersFocusOnKeystroke( true );
table.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
final DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
centerRenderer.setHorizontalAlignment( JLabel.CENTER );
// center all columns
for ( int column = 0; column < tableModel.getColumnCount(); ++column )
table.getColumnModel().getColumn( column ).setCellRenderer( centerRenderer );
// add listener to which row is selected
table.getSelectionModel().addListSelectionListener( getSelectionListener() );
// check out if the user clicked on the column header and potentially sorting by that
table.getTableHeader().addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent mouseEvent)
{
int index = table.convertColumnIndexToModel(table.columnAtPoint(mouseEvent.getPoint()));
if (index >= 0)
{
int row = table.getSelectedRow();
tableModel.sortByColumn( index );
table.clearSelection();
table.getSelectionModel().setSelectionInterval( row, row );
}
};
});
if ( isMac )
addAppleA();
addColorMode();
table.setPreferredScrollableViewportSize( new Dimension( 750, 300 ) );
table.getColumnModel().getColumn( 0 ).setPreferredWidth( 20 );
table.getColumnModel().getColumn( 1 ).setPreferredWidth( 15 );
table.getColumnModel().getColumn( tableModel.registrationColumn() ).setPreferredWidth( 25 );
if ( tableModel.interestPointsColumn() >= 0 )
table.getColumnModel().getColumn( tableModel.interestPointsColumn() ).setPreferredWidth( 30 );
this.setLayout( new BorderLayout() );
final JButton save = new JButton( "Save" );
save.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( final ActionEvent e )
{
if ( save.isEnabled() )
saveXML();
}
});
final JButton info = new JButton( "Info" );
info.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( final ActionEvent e )
{
if ( info.isEnabled() )
showInfoBox();
}
});
final JPanel buttons = new JPanel( new BorderLayout() );
buttons.add( info, BorderLayout.WEST );
buttons.add( save, BorderLayout.EAST );
final JPanel header = new JPanel( new BorderLayout() );
header.add( new JLabel( "XML: " + xml ), BorderLayout.WEST );
header.add( buttons, BorderLayout.EAST );
this.add( header, BorderLayout.NORTH );
this.add( new JScrollPane( table ), BorderLayout.CENTER );
table.getSelectionModel().setSelectionInterval( 0, 0 );
addPopupMenu( table );
}
protected ListSelectionListener getSelectionListener()
{
return new ListSelectionListener()
{
int lastRow = -1;
@Override
public void valueChanged( final ListSelectionEvent arg0 )
{
BDVPopup b = bdvPopup();
if ( table.getSelectedRowCount() != 1 )
{
lastRow = -1;
for ( int i = 0; i < listeners.size(); ++i )
listeners.get( i ).seletedViewDescription( null );
selectedRows.clear();
firstSelectedVD = null;
for ( final int row : table.getSelectedRows() )
{
if ( firstSelectedVD == null )
firstSelectedVD = tableModel.getElements().get( row );
selectedRows.add( tableModel.getElements().get( row ) );
}
}
else
{
final int row = table.getSelectedRow();
if ( ( row != lastRow ) && row >= 0 && row < tableModel.getRowCount() )
{
lastRow = row;
// not using an iterator allows that listeners can close the frame and remove all listeners while they are called
final BasicViewDescription< ? extends BasicViewSetup > vd = tableModel.getElements().get( row );
for ( int i = 0; i < listeners.size(); ++i )
listeners.get( i ).seletedViewDescription( vd );
selectedRows.clear();
selectedRows.add( vd );
firstSelectedVD = vd;
}
}
if ( b != null && b.bdv != null )
updateBDV( b.bdv, colorMode, data, firstSelectedVD, selectedRows );
}
};
}
public static void updateBDV(
final BigDataViewer bdv,
final boolean colorMode,
final AbstractSpimData< ? > data,
BasicViewDescription< ? extends BasicViewSetup > firstVD,
final Collection< ? extends BasicViewDescription< ? extends BasicViewSetup > > selectedRows )
{
// we always set the fused mode
setFusedModeSimple( bdv, data );
if ( selectedRows == null || selectedRows.size() == 0 )
return;
if ( firstVD == null )
firstVD = selectedRows.iterator().next();
// always use the first timepoint
final TimePoint firstTP = firstVD.getTimePoint();
bdv.getViewer().setTimepoint( getBDVTimePointIndex( firstTP, data ) );
final boolean[] active = new boolean[ data.getSequenceDescription().getViewSetupsOrdered().size() ];
for ( final BasicViewDescription< ? > vd : selectedRows )
if ( vd.getTimePointId() == firstTP.getId() )
active[ getBDVSourceIndex( vd.getViewSetup(), data ) ] = true;
if ( selectedRows.size() > 1 && colorMode )
colorSources( bdv.getSetupAssignments().getConverterSetups(), 0 );
else
whiteSources( bdv.getSetupAssignments().getConverterSetups() );
setVisibleSources( bdv.getViewer().getVisibilityAndGrouping(), active );
}
public static void setFusedModeSimple( final BigDataViewer bdv, final AbstractSpimData< ? > data )
{
if ( bdv == null )
return;
if ( bdv.getViewer().getVisibilityAndGrouping().getDisplayMode() != DisplayMode.FUSED )
{
final boolean[] active = new boolean[ data.getSequenceDescription().getViewSetupsOrdered().size() ];
active[ 0 ] = true;
setVisibleSources( bdv.getViewer().getVisibilityAndGrouping(), active );
bdv.getViewer().getVisibilityAndGrouping().setDisplayMode( DisplayMode.FUSED );
}
}
public static void colorSources( final List< ConverterSetup > cs, final long j )
{
for ( int i = 0; i < cs.size(); ++i )
cs.get( i ).setColor( new ARGBType( ColorStream.get( i + j ) ) );
}
public static void whiteSources( final List< ConverterSetup > cs )
{
for ( int i = 0; i < cs.size(); ++i )
cs.get( i ).setColor( new ARGBType( ARGBType.rgba( 255, 255, 255, 0 ) ) );
}
public static void setVisibleSources( final VisibilityAndGrouping vag, final boolean[] active )
{
for ( int i = 0; i < active.length; ++i )
vag.setSourceActive( i, active[ i ] );
}
public static int getBDVTimePointIndex( final TimePoint t, final AbstractSpimData< ? > data )
{
final List< TimePoint > list = data.getSequenceDescription().getTimePoints().getTimePointsOrdered();
for ( int i = 0; i < list.size(); ++i )
if ( list.get( i ).getId() == t.getId() )
return i;
return 0;
}
public static int getBDVSourceIndex( final BasicViewSetup vs, final AbstractSpimData< ? > data )
{
final List< ? extends BasicViewSetup > list = data.getSequenceDescription().getViewSetupsOrdered();
for ( int i = 0; i < list.size(); ++i )
if ( list.get( i ).getId() == vs.getId() )
return i;
return 0;
}
public HashSet< BasicViewDescription< ? extends BasicViewSetup > > getSelectedRows() { return selectedRows; }
public void showInfoBox()
{
new ViewSetupExplorerInfoBox< AS >( data, xml );
}
public void saveXML()
{
try
{
io.save( data, xml );
for ( final SelectedViewDescriptionListener< AS > l : listeners )
l.save();
if ( SpimData2.class.isInstance( data ) )
{
final ViewInterestPoints vip = ( (SpimData2)data ).getViewInterestPoints();
for ( final ViewInterestPointLists vipl : vip.getViewInterestPoints().values() )
{
for ( final String label : vipl.getHashMap().keySet() )
{
final InterestPointList ipl = vipl.getInterestPointList( label );
if ( ipl.getInterestPoints() == null )
ipl.loadInterestPoints();
ipl.saveInterestPoints();
if ( ipl.getCorrespondingInterestPoints() == null )
ipl.loadCorrespondingInterestPoints();
ipl.saveCorrespondingInterestPoints();
}
}
}
IOFunctions.println( "Saved XML '" + xml + "'." );
}
catch ( SpimDataException e )
{
IOFunctions.println( "Failed to save XML '" + xml + "': " + e );
e.printStackTrace();
}
}
protected void addPopupMenu( final JTable table )
{
final JPopupMenu popupMenu = new JPopupMenu();
for ( final ViewExplorerSetable item : staticPopups )
popupMenu.add( item.setViewExplorer( this ) );
table.setComponentPopupMenu( popupMenu );
}
protected void addColorMode()
{
table.addKeyListener( new KeyListener()
{
@Override
public void keyPressed( final KeyEvent arg0 )
{
if ( arg0.getKeyChar() == 'c' || arg0.getKeyChar() == 'C' )
{
colorMode = !colorMode;
System.out.println( "colormode" );
final BDVPopup p = bdvPopup();
if ( p != null && p.bdv != null && p.bdv.getViewerFrame().isVisible() )
updateBDV( p.bdv, colorMode, data, null, selectedRows );
}
}
@Override
public void keyReleased( final KeyEvent arg0 ) {}
@Override
public void keyTyped( final KeyEvent arg0 ) {}
} );
}
protected void addAppleA()
{
table.addKeyListener( new KeyListener()
{
boolean appleKeyDown = false;
@Override
public void keyTyped( KeyEvent arg0 )
{
if ( appleKeyDown && arg0.getKeyChar() == 'a' )
table.selectAll();
}
@Override
public void keyReleased( KeyEvent arg0 )
{
if ( arg0.getKeyCode() == 157 )
appleKeyDown = false;
}
@Override
public void keyPressed(KeyEvent arg0)
{
if ( arg0.getKeyCode() == 157 )
appleKeyDown = true;
}
});
}
}