/*-
* #%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.process.fusion;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import mpicbg.spim.data.sequence.Angle;
import mpicbg.spim.data.sequence.Channel;
import mpicbg.spim.data.sequence.Illumination;
import mpicbg.spim.data.sequence.TimePoint;
import mpicbg.spim.data.sequence.ViewDescription;
import mpicbg.spim.data.sequence.ViewId;
import mpicbg.spim.io.IOFunctions;
import net.imglib2.Cursor;
import net.imglib2.IterableInterval;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.type.numeric.RealType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.view.Views;
import spim.Threads;
import spim.fiji.spimdata.SpimData2;
public class FusionHelper
{
/**
* Do not instantiate
*/
private FusionHelper() {}
public static String getIllumName( final List< Illumination > illumsToProcess )
{
String illumName = "_Ill" + illumsToProcess.get( 0 ).getName();
for ( int i = 1; i < illumsToProcess.size(); ++i )
illumName += "," + illumsToProcess.get( i ).getName();
return illumName;
}
public static String getAngleName( final List< Angle > anglesToProcess )
{
String angleName = "_Ang" + anglesToProcess.get( 0 ).getName();
for ( int i = 1; i < anglesToProcess.size(); ++i )
angleName += "," + anglesToProcess.get( i ).getName();
return angleName;
}
public static final boolean intersects( final double x, final double y, final double z, final long sx, final long sy, final long sz )
{
if ( x >= 0 && y >= 0 && z >= 0 && x < sx && y < sy && z < sz )
return true;
else
return false;
}
public static final ArrayList< ViewDescription > assembleInputData(
final SpimData2 spimData,
final TimePoint timepoint,
final Channel channel,
final List< ViewId > viewIdsToProcess )
{
final ArrayList< ViewDescription > inputData = new ArrayList< ViewDescription >();
for ( final ViewId viewId : viewIdsToProcess )
{
final ViewDescription vd = spimData.getSequenceDescription().getViewDescription(
viewId.getTimePointId(), viewId.getViewSetupId() );
if ( !vd.isPresent() || vd.getTimePointId() != timepoint.getId() || vd.getViewSetup().getChannel().getId() != channel.getId() )
continue;
// get the most recent model
spimData.getViewRegistrations().getViewRegistration( viewId ).updateModel();
inputData.add( vd );
}
return inputData;
}
public static < T extends RealType< T > > float[] minMax( final RandomAccessibleInterval< T > img )
{
final IterableInterval< T > iterable = Views.iterable( img );
// split up into many parts for multithreading
final Vector< ImagePortion > portions = FusionHelper.divideIntoPortions( iterable.size(), Threads.numThreads() * 2 );
// set up executor service
final ExecutorService taskExecutor = Executors.newFixedThreadPool( Threads.numThreads() );
final ArrayList< Callable< float[] > > tasks = new ArrayList< Callable< float[] > >();
for ( final ImagePortion portion : portions )
{
tasks.add( new Callable< float[] >()
{
@Override
public float[] call() throws Exception
{
float min = Float.MAX_VALUE;
float max = -Float.MAX_VALUE;
final Cursor< T > c = iterable.cursor();
c.jumpFwd( portion.getStartPosition() );
for ( long j = 0; j < portion.getLoopSize(); ++j )
{
final float v = c.next().getRealFloat();
min = Math.min( min, v );
max = Math.max( max, v );
}
// min & max of this portion
return new float[]{ min, max };
}
});
}
// run threads and combine results
float min = Float.MAX_VALUE;
float max = -Float.MAX_VALUE;
try
{
// invokeAll() returns when all tasks are complete
final List< Future< float[] > > futures = taskExecutor.invokeAll( tasks );
for ( final Future< float[] > future : futures )
{
final float[] minmax = future.get();
min = Math.min( min, minmax[ 0 ] );
max = Math.max( max, minmax[ 1 ] );
}
}
catch ( final Exception e )
{
IOFunctions.println( "Failed to compute min/max: " + e );
e.printStackTrace();
return null;
}
taskExecutor.shutdown();
return new float[]{ min, max };
}
/**
* Normalizes the image to the range [0...1]
*
* @param img - the image to normalize
* @return - normalized array
*/
public static boolean normalizeImage( final RandomAccessibleInterval< FloatType > img )
{
final float minmax[] = minMax( img );
final float min = minmax[ 0 ];
final float max = minmax[ 1 ];
return normalizeImage( img, min, max );
}
/**
* Normalizes the image to the range [0...1]
*
* @param img - the image to normalize
* @param min - min value
* @param max - max value
* @return - normalized array
*/
public static boolean normalizeImage( final RandomAccessibleInterval< FloatType > img, final float min, final float max )
{
final float diff = max - min;
if ( Float.isNaN( diff ) || Float.isInfinite(diff) || diff == 0 )
{
IOFunctions.println( "Cannot normalize image, min=" + min + " + max=" + max );
return false;
}
final IterableInterval< FloatType > iterable = Views.iterable( img );
// split up into many parts for multithreading
final Vector< ImagePortion > portions = FusionHelper.divideIntoPortions( iterable.size(), Threads.numThreads() * 2 );
// set up executor service
final ExecutorService taskExecutor = Executors.newFixedThreadPool( Threads.numThreads() );
final ArrayList< Callable< String > > tasks = new ArrayList< Callable< String > >();
for ( final ImagePortion portion : portions )
{
tasks.add( new Callable< String >()
{
@Override
public String call() throws Exception
{
final Cursor< FloatType > c = iterable.cursor();
c.jumpFwd( portion.getStartPosition() );
for ( long j = 0; j < portion.getLoopSize(); ++j )
{
final FloatType t = c.next();
final float norm = ( t.get() - min ) / diff;
t.set( norm );
}
return "";
}
});
}
try
{
// invokeAll() returns when all tasks are complete
taskExecutor.invokeAll( tasks );
}
catch ( final InterruptedException e )
{
IOFunctions.println( "Failed to compute min/max: " + e );
e.printStackTrace();
return false;
}
taskExecutor.shutdown();
return true;
}
public static final Vector<ImagePortion> divideIntoPortions( final long imageSize, final int numPortions )
{
final long threadChunkSize = imageSize / numPortions;
final long threadChunkMod = imageSize % numPortions;
final Vector<ImagePortion> portions = new Vector<ImagePortion>();
for ( int portionID = 0; portionID < numPortions; ++portionID )
{
// move to the starting position of the current thread
final long startPosition = portionID * threadChunkSize;
// the last thread may has to run longer if the number of pixels cannot be divided by the number of threads
final long loopSize;
if ( portionID == numPortions - 1 )
loopSize = threadChunkSize + threadChunkMod;
else
loopSize = threadChunkSize;
portions.add( new ImagePortion( startPosition, loopSize ) );
}
return portions;
}
}