Skip to content
Snippets Groups Projects
Commit 4905be1e authored by Tobias Pietzsch's avatar Tobias Pietzsch
Browse files

simplify thumbnail generation

parent 3449810b
No related branches found
No related tags found
No related merge requests found
......@@ -12,7 +12,7 @@ import bdv.img.remote.RemoteImageLoaderMetaData;
import bdv.spimdata.SequenceDescriptionMinimal;
import bdv.spimdata.SpimDataMinimal;
import bdv.spimdata.XmlIoSpimDataMinimal;
import bdv.util.Thumbnail;
import bdv.util.ThumbnailGenerator;
import com.google.gson.GsonBuilder;
......@@ -94,6 +94,7 @@ public class CellHandler extends ContextHandler
datasetXmlString = buildRemoteDatasetXML( io, spimData, baseUrl );
metadataJson = buildMetadataJsonString( imgLoader, seq );
settingsXmlString = buildSettingsXML( baseFilename );
createThumbnail( spimData, baseFilename );
}
@Override
......@@ -221,21 +222,6 @@ public class CellHandler extends ContextHandler
public String getThumbnailUrl()
{
final File pngFile = new File( baseFilename + ".png" );
if ( pngFile.exists() )
return dataSetURL + "png";
// No thumbnail
// Trigger to generate thumbnail here
try
{
final Thumbnail thumb = new Thumbnail( xmlFilename, 800, 600 );
}
catch ( final Exception e )
{
e.printStackTrace();
}
return dataSetURL + "png";
}
......@@ -301,6 +287,24 @@ public class CellHandler extends ContextHandler
return null;
}
/**
* Create PNG thumbnail file named "{@code <baseFilename>.png}".
*/
private static void createThumbnail( final SpimDataMinimal spimData, final String baseFilename )
{
final File thumbnailFile = new File( baseFilename + ".png" );
final BufferedImage bi = ThumbnailGenerator.makeThumbnail( spimData, baseFilename, 100, 100 );
try
{
ImageIO.write( bi, "png", thumbnailFile );
}
catch ( final IOException e )
{
LOG.warn( "Could not create thumbnail png for dataset \"" + baseFilename + "\"" );
LOG.warn( e.getMessage() );
}
}
/**
* Handle request by sending a UTF-8 string.
*/
......
......
package bdv.util;
import bdv.BigDataViewer;
import bdv.spimdata.SpimDataMinimal;
import bdv.spimdata.WrapBasicImgLoader;
import bdv.spimdata.XmlIoSpimDataMinimal;
import bdv.tools.InitializeViewerState;
import bdv.tools.brightness.ConverterSetup;
import bdv.tools.brightness.MinMaxGroup;
import bdv.tools.brightness.SetupAssignments;
import bdv.tools.transformation.TransformedSource;
import bdv.tools.transformation.XmlIoTransformedSources;
import bdv.viewer.Source;
import bdv.viewer.SourceAndConverter;
import bdv.viewer.state.SourceState;
import bdv.viewer.state.ViewerState;
import mpicbg.spim.data.SpimDataException;
import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription;
import mpicbg.spim.data.generic.sequence.BasicViewSetup;
import mpicbg.spim.data.sequence.Angle;
import mpicbg.spim.data.sequence.Channel;
import mpicbg.spim.data.sequence.TimePoint;
import net.imglib2.realtransform.AffineTransform3D;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by moon on 2/5/15.
*/
public class Thumbnail
{
protected final ThumbnailGenerator viewer;
protected final SetupAssignments setupAssignments;
protected ManualTransformation manualTransformation;
protected File proposedSettingsFile;
private static String createSetupName( final BasicViewSetup setup )
{
if ( setup.hasName() )
return setup.getName();
String name = "";
final Angle angle = setup.getAttribute( Angle.class );
if ( angle != null )
name += ( name.isEmpty() ? "" : " " ) + "a " + angle.getName();
final Channel channel = setup.getAttribute( Channel.class );
if ( channel != null )
name += ( name.isEmpty() ? "" : " " ) + "c " + channel.getName();
return name;
}
public Thumbnail( final String xmlFilename, final int width, final int height ) throws SpimDataException, InterruptedException
{
final String thumbnailFile = xmlFilename.replace( ".xml", ".png" );
final SpimDataMinimal spimData = new XmlIoSpimDataMinimal().load( xmlFilename );
if ( WrapBasicImgLoader.wrapImgLoaderIfNecessary( spimData ) )
{
System.err.println( "WARNING:\nOpening <SpimData> dataset that is not 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 List< TimePoint > timepoints = seq.getTimePoints().getTimePointsOrdered();
viewer = new ThumbnailGenerator( width, height, sources, timepoints.size() );
manualTransformation = new ManualTransformation( viewer );
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 );
}
InitializeViewerState.initTransform( viewer.getWidth(), viewer.getHeight(), viewer.getState() );
if ( !tryLoadSettings( xmlFilename ) )
InitializeViewerState.initBrightness( 0.001, 0.999, viewer.getState(), setupAssignments );
final ViewerState renderState = viewer.getState();
viewer.requestRepaint();
viewer.paint( renderState );
final Image img = viewer.getBufferedImage().getScaledInstance( 100, 100, Image.SCALE_SMOOTH );
final BufferedImage img2 = new BufferedImage( 100, 100, BufferedImage.TYPE_INT_ARGB );
img2.createGraphics().drawImage( img, 0, 0, null );
try
{
ImageIO.write( img2, "png", new File( thumbnailFile ) );
}
catch ( final Exception e )
{
}
}
protected boolean tryLoadSettings( final String xmlFilename )
{
proposedSettingsFile = null;
if ( xmlFilename.endsWith( ".xml" ) )
{
final String settings = xmlFilename.substring( 0, xmlFilename.length() - ".xml".length() ) + ".settings" + ".xml";
proposedSettingsFile = new File( settings );
if ( proposedSettingsFile.isFile() )
{
try
{
loadSettings( settings );
return true;
}
catch ( final Exception e )
{
e.printStackTrace();
}
}
}
return false;
}
protected void loadSettings( final String xmlFilename ) throws IOException, JDOMException
{
final SAXBuilder sax = new SAXBuilder();
final Document doc = sax.build( xmlFilename );
final Element root = doc.getRootElement();
viewer.stateFromXml( root );
setupAssignments.restoreFromXml( root );
manualTransformation.restoreFromXml( root );
viewer.requestRepaint();
}
public static void main( final String[] args )
{
final String fn = args[ 0 ];
try
{
final Thumbnail thumb = new Thumbnail( fn, 800, 600 );
}
catch ( final Exception e )
{
e.printStackTrace();
}
}
public class ManualTransformation
{
protected final ThumbnailGenerator viewer;
protected final XmlIoTransformedSources io;
public ManualTransformation( final ThumbnailGenerator viewer )
{
this.viewer = viewer;
io = new XmlIoTransformedSources();
}
public void restoreFromXml( final Element parent )
{
final Element elem = parent.getChild( io.getTagName() );
final List< TransformedSource< ? > > sources = getTransformedSources();
final List< AffineTransform3D > transforms = io.fromXml( elem ).getTransforms();
if ( sources.size() != transforms.size() )
System.err.println( "failed to load <" + io.getTagName() + "> source and transform count mismatch" );
else
for ( int i = 0; i < sources.size(); ++i )
sources.get( i ).setFixedTransform( transforms.get( i ) );
}
private ArrayList< TransformedSource< ? > > getTransformedSources()
{
final ViewerState state = viewer.getState();
final ArrayList< TransformedSource< ? > > list = new ArrayList< TransformedSource< ? > >();
for ( final SourceState< ? > sourceState : state.getSources() )
{
final Source< ? > source = sourceState.getSpimSource();
if ( TransformedSource.class.isInstance( source ) )
list.add( ( TransformedSource< ? > ) source );
}
return list;
}
}
}
package bdv.util;
import bdv.BigDataViewer;
import bdv.img.cache.Cache;
import bdv.viewer.Interpolation;
import bdv.spimdata.SpimDataMinimal;
import bdv.spimdata.XmlIoSpimDataMinimal;
import bdv.tools.InitializeViewerState;
import bdv.tools.brightness.ConverterSetup;
import bdv.tools.brightness.SetupAssignments;
import bdv.tools.transformation.TransformedSource;
import bdv.tools.transformation.XmlIoTransformedSources;
import bdv.viewer.Source;
import bdv.viewer.SourceAndConverter;
import bdv.viewer.VisibilityAndGrouping;
import bdv.viewer.render.MultiResolutionRenderer;
import bdv.viewer.state.SourceGroup;
import bdv.viewer.state.SourceState;
import bdv.viewer.state.ViewerState;
import bdv.viewer.state.XmlIoViewerState;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.ui.PainterThread;
import net.imglib2.ui.RenderTarget;
import net.imglib2.ui.TransformListener;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import static bdv.viewer.VisibilityAndGrouping.Event.*;
import javax.imageio.ImageIO;
/**
* Created by moon on 2/5/15.
*/
public class ThumbnailGenerator implements TransformListener< AffineTransform3D >, VisibilityAndGrouping.UpdateListener
public class ThumbnailGenerator
{
protected final int width;
protected final int height;
/**
* Currently rendered state (visible sources, transformation, timepoint,
* etc.) A copy can be obtained by {@link #getState()}.
* Create a thumbnail image for a dataset. If there is a settings.xml file
* for the dataset, these settings are used for creating the thumbnail.
*
* @param spimData
* the dataset.
* @param baseFilename
* full path of dataset xml file, without the ".xml" extension.
* this is used to derive the name of the settings.xml file.
* @param width
* width of the thumbnail image.
* @param height
* height of the thumbnail image.
* @return thumbnail image
*/
protected final ViewerState state;
public static BufferedImage makeThumbnail( final SpimDataMinimal spimData, final String baseFilename, final int width, final int height )
{
final ArrayList< ConverterSetup > converterSetups = new ArrayList< ConverterSetup >();
final ArrayList< SourceAndConverter< ? > > sources = new ArrayList< SourceAndConverter< ? > >();
BigDataViewer.initSetups( spimData, converterSetups, sources );
/**
* Renders the current state for the thumbnail.
*/
protected final MultiResolutionRenderer imageRenderer;
final int numTimepoints = spimData.getSequenceDescription().getTimePoints().size();
final ThumbnailGenerator generator = new ThumbnailGenerator( sources, numTimepoints );
final ViewerState state = generator.state;
protected final ThumbnailTarget renderTarget;
final SetupAssignments setupAssignments = new SetupAssignments( converterSetups, 0, 65535 );
final AffineTransform3D initTransform = InitializeViewerState.initTransform( width, height, false, state );
state.setViewerTransform( initTransform );
/**
* Transformation set by the interactive viewer.
*/
protected final AffineTransform3D viewerTransform;
if ( !generator.tryLoadSettings( baseFilename, setupAssignments ) )
InitializeViewerState.initBrightness( 0.001, 0.999, state, setupAssignments );
/**
* Manages visibility and currentness of sources and groups, as well as
* grouping of sources, and display mode.
*/
protected final VisibilityAndGrouping visibilityAndGrouping;
class ThumbnailTarget implements RenderTarget
{
BufferedImage bi;
@Override
public BufferedImage setBufferedImage( final BufferedImage bufferedImage )
{
bi = bufferedImage;
return null;
}
@Override
public int getWidth()
{
return width;
}
@Override
public int getHeight()
{
return height;
}
}
final ThumbnailTarget renderTarget = new ThumbnailTarget();
new MultiResolutionRenderer( renderTarget, new PainterThread( null ), new double[] { 1 }, 0, false, 1, null, false, new Cache.Dummy() ).paint( state );
return renderTarget.bi;
}
/**
* These listeners will be notified about changes to the
* {@link #viewerTransform}. This is done <em>before</em> calling
* {@link #requestRepaint()} so listeners have the chance to interfere.
* Currently rendered state (visible sources, transformation, timepoint,
* etc.)
*/
protected final CopyOnWriteArrayList< TransformListener< AffineTransform3D > > transformListeners;
private final ViewerState state;
/**
* @param sources
......@@ -67,195 +108,106 @@ public class ThumbnailGenerator implements TransformListener< AffineTransform3D
* @param numTimePoints
* number of available timepoints.
*/
public ThumbnailGenerator( final int width, final int height, final List< SourceAndConverter< ? > > sources, final int numTimePoints )
private ThumbnailGenerator( final List< SourceAndConverter< ? > > sources, final int numTimePoints )
{
this.width = width;
this.height = height;
final int numGroups = 10;
final ArrayList< SourceGroup > groups = new ArrayList< SourceGroup >( numGroups );
for ( int i = 0; i < numGroups; ++i )
{
final SourceGroup g = new SourceGroup( "group " + Integer.toString( i + 1 ) );
if ( i < sources.size() )
{
g.addSource( i );
}
groups.add( g );
}
groups.add( new SourceGroup( "" ) );
state = new ViewerState( sources, groups, numTimePoints );
if ( !sources.isEmpty() )
state.setCurrentSource( 0 );
viewerTransform = new AffineTransform3D();
renderTarget = new ThumbnailTarget( width, height );
imageRenderer = new MultiResolutionRenderer( renderTarget, new PainterThread( null ), new double[] {
1 }, 0, false, 1, null, false, new Cache.Dummy() );
visibilityAndGrouping = new VisibilityAndGrouping( state );
visibilityAndGrouping.addUpdateListener( this );
transformListeners = new CopyOnWriteArrayList< TransformListener< AffineTransform3D > >();
}
public BufferedImage getBufferedImage()
private void stateFromXml( final Element parent )
{
return renderTarget.getBufferedImage();
final XmlIoViewerState io = new XmlIoViewerState();
io.restoreFromXml( parent.getChild( io.getTagName() ), state );
}
public void paint( ViewerState state )
private static class ManualTransformation
{
imageRenderer.paint( state );
}
private final ViewerState state ;
/**
* Repaint as soon as possible.
*/
public void requestRepaint()
{
imageRenderer.requestRepaint();
}
private final XmlIoTransformedSources io;
@Override
public synchronized void transformChanged( final AffineTransform3D transform )
public ManualTransformation( final ViewerState state )
{
viewerTransform.set( transform );
state.setViewerTransform( transform );
for ( final TransformListener< AffineTransform3D > l : transformListeners )
l.transformChanged( viewerTransform );
requestRepaint();
this.state = state;
io = new XmlIoTransformedSources();
}
@Override
public void visibilityChanged( final VisibilityAndGrouping.Event e )
public void restoreFromXml( final Element parent )
{
switch ( e.id )
{
case CURRENT_SOURCE_CHANGED:
requestRepaint();
break;
case DISPLAY_MODE_CHANGED:
requestRepaint();
break;
case GROUP_NAME_CHANGED:
requestRepaint();
break;
case VISIBILITY_CHANGED:
requestRepaint();
break;
}
final Element elem = parent.getChild( io.getTagName() );
final List< TransformedSource< ? > > sources = getTransformedSources();
final List< AffineTransform3D > transforms = io.fromXml( elem ).getTransforms();
if ( sources.size() != transforms.size() )
System.err.println( "failed to load <" + io.getTagName() + "> source and transform count mismatch" );
else
for ( int i = 0; i < sources.size(); ++i )
sources.get( i ).setFixedTransform( transforms.get( i ) );
}
private final static double c = Math.cos( Math.PI / 4 );
/**
* Switch to next interpolation mode. (Currently, there are two
* interpolation modes: nearest-neighbor and N-linear.
*/
public synchronized void toggleInterpolation()
{
final Interpolation interpolation = state.getInterpolation();
if ( interpolation == Interpolation.NEARESTNEIGHBOR )
private ArrayList< TransformedSource< ? > > getTransformedSources()
{
state.setInterpolation( Interpolation.NLINEAR );
}
else
final ArrayList< TransformedSource< ? > > list = new ArrayList< TransformedSource< ? > >();
for ( final SourceState< ? > sourceState : state.getSources() )
{
state.setInterpolation( Interpolation.NEARESTNEIGHBOR );
final Source< ? > source = sourceState.getSpimSource();
if ( TransformedSource.class.isInstance( source ) )
list.add( ( TransformedSource< ? > ) source );
}
requestRepaint();
return list;
}
/**
* Set the viewer transform.
*/
public synchronized void setCurrentViewerTransform( final AffineTransform3D viewerTransform )
{
transformChanged( viewerTransform );
}
/**
* Get a copy of the current {@link ViewerState}.
*
* @return a copy of the current {@link ViewerState}.
*/
public synchronized ViewerState getState()
private boolean tryLoadSettings( final String baseFilename, final SetupAssignments setupAssignments )
{
return state.copy();
}
/**
* Add a {@link TransformListener} to notify about viewer transformation
* changes. Listeners will be notified <em>before</em> calling
* {@link #requestRepaint()} so they have the chance to interfere.
*
* @param listener
* the transform listener to add.
* @param index
* position in the list of listeners at which to insert this one.
*/
public void addTransformListener( final TransformListener< AffineTransform3D > listener, final int index )
final String settings = baseFilename + ".settings.xml";
if ( new File( settings ).isFile() )
{
synchronized ( transformListeners )
try
{
final int s = transformListeners.size();
transformListeners.add( index < 0 ? 0 : index > s ? s : index, listener );
listener.transformChanged( viewerTransform );
final SAXBuilder sax = new SAXBuilder();
final Document doc = sax.build( settings );
final Element root = doc.getRootElement();
stateFromXml( root );
setupAssignments.restoreFromXml( root );
new ManualTransformation( state ).restoreFromXml( root );
return true;
}
}
public synchronized void stateFromXml( final Element parent )
catch ( final Exception e )
{
final XmlIoViewerState io = new XmlIoViewerState();
io.restoreFromXml( parent.getChild( io.getTagName() ), state );
e.printStackTrace();
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
return false;
}
public class ThumbnailTarget implements RenderTarget
public static void main( final String[] args )
{
private BufferedImage bi;
int width, height;
public ThumbnailTarget( int width, int height )
final String fn = args[ 0 ];
try
{
this.width = width;
this.height = height;
}
final SpimDataMinimal spimData = new XmlIoSpimDataMinimal().load( fn );
final String thumbnailFile = fn.replace( ".xml", ".png" );
final BufferedImage bi = makeThumbnail( spimData, fn, 100, 100 );
@Override
public BufferedImage setBufferedImage( final BufferedImage bufferedImage )
try
{
bi = bufferedImage;
return null;
ImageIO.write( bi, "png", new File( thumbnailFile ) );
}
@Override
public int getWidth()
catch ( final Exception e )
{
return width;
}
@Override
public int getHeight()
{
return height;
}
public BufferedImage getBufferedImage()
catch ( final Exception e )
{
return bi;
e.printStackTrace();
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment