/**
*
*/
package mpicbg.trakem2.align;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.NoninvertibleTransformException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import ij.IJ;
import ij.ImagePlus;
import ij.gui.GenericDialog;
import ini.trakem2.display.Display;
import ini.trakem2.display.Displayable;
import ini.trakem2.display.Layer;
import ini.trakem2.display.LayerSet;
import ini.trakem2.display.Patch;
import ini.trakem2.display.Selection;
import ini.trakem2.display.VectorData;
import ini.trakem2.display.VectorDataTransform;
import ini.trakem2.imaging.StitchingTEM;
import ini.trakem2.persistence.DBObject;
import ini.trakem2.utils.Bureaucrat;
import ini.trakem2.utils.IJError;
import ini.trakem2.utils.M;
import ini.trakem2.utils.Utils;
import ini.trakem2.utils.Worker;
import mpicbg.ij.FeatureTransform;
import mpicbg.ij.SIFT;
import mpicbg.imagefeatures.Feature;
import mpicbg.imagefeatures.FloatArray2DSIFT;
import mpicbg.models.AbstractAffineModel2D;
import mpicbg.models.AffineModel2D;
import mpicbg.models.NoninvertibleModelException;
import mpicbg.models.NotEnoughDataPointsException;
import mpicbg.models.Point;
import mpicbg.models.PointMatch;
import mpicbg.models.SimilarityModel2D;
import mpicbg.models.Tile;
import mpicbg.models.Transforms;
import mpicbg.trakem2.transform.CoordinateTransform;
import mpicbg.trakem2.transform.CoordinateTransformList;
import mpicbg.trakem2.transform.InvertibleCoordinateTransform;
import mpicbg.trakem2.transform.MovingLeastSquaresTransform2;
import mpicbg.trakem2.transform.RigidModel2D;
import mpicbg.trakem2.transform.TranslationModel2D;
/**
* Methods collection to be called from the GUI for alignment tasks.
*
*/
final public class AlignTask
{
static protected boolean tilesAreInPlace = false;
static protected boolean sloppyOverlapTest = false;
static protected boolean largestGraphOnly = false;
static protected boolean hideDisconnectedTiles = false;
static protected boolean deleteDisconnectedTiles = false;
static protected boolean deform = false;
static public final int LINEAR_PHASE_CORRELATION = 0,
LINEAR_SIFT_CORRESPONDENCES = 1,
ELASTIC_BLOCK_CORRESPONDENCES = 2;
/**
* The mode used for alignment, which defaults to LINEAR_SIFT_CORRESPONDENCES
* but can take the values ELASTIC_BLOCK_CORRESPONDENCES and LINEAR_PHASE_CORRELATION.
*/
static protected int mode = LINEAR_SIFT_CORRESPONDENCES;
final static private String[] modeStrings = new String[]{
"phase-correlation",
"least squares (linear feature correspondences)",
"elastic (non-linear block correspondences)" };
final static public Bureaucrat alignSelectionTask ( final Selection selection )
{
final Worker worker = new Worker("Aligning selected images", false, true) {
@Override
public void run() {
startedWorking();
try {
final int m = chooseAlignmentMode();
if (-1 == m)
return;
alignSelection( selection, m );
Display.repaint(selection.getLayer());
} catch (final Throwable e) {
IJError.print(e);
} finally {
finishedWorking();
}
}
@Override
public void cleanup() {
if (!selection.isEmpty())
selection.getLayer().getParent().undoOneStep();
}
};
return Bureaucrat.createAndStart( worker, selection.getProject() );
}
final static public void alignSelection( final Selection selection, final int m ) throws Exception
{
final List< Patch > patches = new ArrayList< Patch >();
for ( final Displayable d : selection.getSelected() )
if ( d instanceof Patch ) patches.add( ( Patch )d );
final HashSet< Patch > fixedPatches = new HashSet< Patch >();
// Add active Patch, if any, as the nail
final Displayable active = selection.getActive();
if ( null != active && active instanceof Patch )
fixedPatches.add( (Patch)active );
// Add all locked Patch instances to fixedPatches
for (final Patch patch : patches)
if ( patch.isLocked() )
fixedPatches.add( patch );
alignPatches( patches, fixedPatches, m );
}
final static public Bureaucrat alignPatchesTask ( final List< Patch > patches , final Set< Patch > fixedPatches )
{
if ( 0 == patches.size())
{
Utils.log("Can't align zero patches.");
return null;
}
final Worker worker = new Worker("Aligning images", false, true) {
@Override
public void run() {
startedWorking();
try {
final int m = chooseAlignmentMode();
if (-1 == m)
return;
alignPatches( patches, fixedPatches, m );
Display.repaint();
} catch (final Throwable e) {
IJError.print(e);
} finally {
finishedWorking();
}
}
@Override
public void cleanup() {
patches.get(0).getLayer().getParent().undoOneStep();
}
};
return Bureaucrat.createAndStart( worker, patches.get(0).getProject() );
}
/** @return the chosen mode, or -1 when canceled. */
final static private int chooseAlignmentMode()
{
final GenericDialog gdMode = new GenericDialog( "Montage mode" );
gdMode.addChoice( "mode :", modeStrings, modeStrings[ mode ] );
gdMode.showDialog();
if ( gdMode.wasCanceled() )
return -1;
final int m = gdMode.getNextChoiceIndex();
// Set the static for future use
mode = m;
return m;
}
/**
* @param patches: the list of Patch instances to align, all belonging to the same Layer.
* @param fixedPatches: the list of Patch instances to keep locked in place, if any.
* @param m: {@link AlignTask#LINEAR_SIFT_CORRESPONDENCES}, {@link AlignTask#LINEAR_PHASE_CORRELATION} or {@link AlignTask#ELASTIC_BLOCK_CORRESPONDENCES}.
*/
final static public void alignPatches(
final List< Patch > patches,
final Set< Patch > fixedPatches,
final int m
) throws Exception
{
if ( patches.size() < 2 )
{
Utils.log("No images to align.");
return;
}
for ( final Patch patch : fixedPatches )
{
if ( !patches.contains( patch ) )
{
Utils.log("The list of fixed patches contains at least one Patch not included in the list of patches to align!");
return;
}
}
//final Align.ParamOptimize p = Align.paramOptimize;
if ( ELASTIC_BLOCK_CORRESPONDENCES == m )
new ElasticMontage().exec( patches, fixedPatches );
else if (LINEAR_PHASE_CORRELATION == m) {
// Montage all given patches, fixedPatches is ignored!
if (!fixedPatches.isEmpty()) Utils.log("Ignoring " + fixedPatches.size() + " fixed patches.");
StitchingTEM.montageWithPhaseCorrelation(patches);
}
else if (LINEAR_SIFT_CORRESPONDENCES == m)
{
if ( !Align.paramOptimize.setup( "Montage Selection" ) )
return;
final GenericDialog gd = new GenericDialog( "Montage Selection: Miscellaneous" );
gd.addCheckbox( "tiles are roughly in place", tilesAreInPlace );
gd.addCheckbox( "sloppy overlap test (fast)", sloppyOverlapTest );
gd.addCheckbox( "consider largest graph only", largestGraphOnly );
gd.addCheckbox( "hide tiles from non-largest graph", hideDisconnectedTiles );
gd.addCheckbox( "delete tiles from non-largest graph", deleteDisconnectedTiles );
gd.showDialog();
if ( gd.wasCanceled() ) return;
tilesAreInPlace = gd.getNextBoolean();
sloppyOverlapTest = gd.getNextBoolean();
largestGraphOnly = gd.getNextBoolean();
hideDisconnectedTiles = gd.getNextBoolean();
deleteDisconnectedTiles = gd.getNextBoolean();
final Align.ParamOptimize p = Align.paramOptimize.clone();
alignPatches( p, patches, fixedPatches, tilesAreInPlace, largestGraphOnly, hideDisconnectedTiles, deleteDisconnectedTiles, sloppyOverlapTest );
}
else
Utils.log( "Don't know how to align with mode " + m );
}
/** Montage each layer independently.
* Does NOT register layers to each other.
* Considers visible Patches only. */
final static public Bureaucrat montageLayersTask(final List<Layer> layers) {
if (null == layers || layers.isEmpty()) return null;
return Bureaucrat.createAndStart(new Worker.Task("Montaging layers", true) {
@Override
public void exec()
{
final int m = chooseAlignmentMode();
if ( ELASTIC_BLOCK_CORRESPONDENCES == m )
{
final ElasticMontage.Param p = ElasticMontage.setup();
if ( p == null )
return;
else
{
try { montageLayers( p, layers ); }
catch ( final Exception e ) { e.printStackTrace(); Utils.log( "Exception during montaging layers. Operation failed." ); }
}
}
else if (LINEAR_PHASE_CORRELATION == m)
{
StitchingTEM.montageWithPhaseCorrelation(layers, this);
}
else if (LINEAR_SIFT_CORRESPONDENCES == m)
{
//final Align.ParamOptimize p = Align.paramOptimize;
if ( !Align.paramOptimize.setup( "Montage Layers" ) )
return;
final GenericDialog gd = new GenericDialog( "Montage Layers: Miscellaneous" );
gd.addCheckbox( "tiles are roughly in place", tilesAreInPlace );
gd.addCheckbox( "sloppy overlap test (fast)", sloppyOverlapTest );
gd.addCheckbox( "consider largest graph only", largestGraphOnly );
gd.addCheckbox( "hide tiles from non-largest graph", hideDisconnectedTiles );
gd.addCheckbox( "delete tiles from non-largest graph", deleteDisconnectedTiles );
gd.showDialog();
if ( gd.wasCanceled() ) return;
tilesAreInPlace = gd.getNextBoolean();
sloppyOverlapTest = gd.getNextBoolean();
largestGraphOnly = gd.getNextBoolean();
hideDisconnectedTiles = gd.getNextBoolean();
deleteDisconnectedTiles = gd.getNextBoolean();
final Align.ParamOptimize p = Align.paramOptimize.clone();
montageLayers(p, layers, tilesAreInPlace, largestGraphOnly, hideDisconnectedTiles, deleteDisconnectedTiles, sloppyOverlapTest );
}
else
Utils.log( "Don't know how to align with mode " + m );
}
}, layers.get(0).getProject());
}
@SuppressWarnings( { "rawtypes", "unchecked" } )
final static public void montageLayers(
final Align.ParamOptimize p,
final List<Layer> layers,
final boolean tilesAreInPlaceIn,
final boolean largestGraphOnlyIn,
final boolean hideDisconnectedTilesIn,
final boolean deleteDisconnectedTilesIn,
final boolean sloppyOverlapTest ) {
int i = 0;
for (final Layer layer : layers) {
if (Thread.currentThread().isInterrupted()) return;
final Collection<Displayable> patches = layer.getDisplayables(Patch.class, true);
if (patches.isEmpty()) continue;
for (final Displayable patch : patches) {
if (patch.isLinked() && !patch.isOnlyLinkedTo(Patch.class)) {
Utils.log("Cannot montage layer " + layer + "\nReason: at least one Patch is linked to non-image data: " + patch);
continue;
}
}
Utils.log("====\nMontaging layer " + layer);
Utils.showProgress(((double)i)/layers.size());
i++;
alignPatches(p, new ArrayList<Patch>((Collection<Patch>)(Collection)patches), new ArrayList<Patch>(), tilesAreInPlaceIn, largestGraphOnlyIn, hideDisconnectedTilesIn, deleteDisconnectedTilesIn, sloppyOverlapTest );
Display.repaint(layer);
}
}
final static public void montageLayers(
final Align.ParamOptimize p,
final List<Layer> layers,
final boolean tilesAreInPlaceIn,
final boolean largestGraphOnlyIn,
final boolean hideDisconnectedTilesIn,
final boolean deleteDisconnectedTilesIn ) {
montageLayers( p, layers, tilesAreInPlaceIn, largestGraphOnlyIn, hideDisconnectedTilesIn, deleteDisconnectedTilesIn, false );
}
final static public void montageLayers(
final ElasticMontage.Param p,
final List< Layer > layers ) throws Exception
{
int i = 0;
A: for ( final Layer layer : layers )
{
if ( Thread.currentThread().isInterrupted() ) return;
final Collection< Displayable > patches = layer.getDisplayables( Patch.class, true );
if ( patches.isEmpty() ) continue;
final ArrayList< Patch > patchesList = new ArrayList< Patch >();
for ( final Displayable d : patches )
{
if ( Patch.class.isInstance( d ) )
{
final Patch patch = ( Patch )d;
patchesList.add( patch );
if ( patch.isLinked() && !patch.isOnlyLinkedTo( Patch.class ) )
{
Utils.log( "Cannot montage layer " + layer + "\nReason: at least one Patch is linked to non-image data: " + patch );
continue A;
}
}
}
Utils.log("====\nMontaging layer " + layer);
Utils.showProgress(((double)i)/layers.size());
i++;
new ElasticMontage().exec( p, patchesList, new HashSet< Patch >() );
Display.repaint( layer );
}
}
final static private class InverseICT implements mpicbg.models.InvertibleCoordinateTransform {
final mpicbg.models.InvertibleCoordinateTransform ict;
/** Sets this to the inverse of ict. */
InverseICT(final mpicbg.models.InvertibleCoordinateTransform ict) {
this.ict = ict;
}
@Override
public final double[] apply(final double[] p) {
final double[] q = p.clone();
applyInPlace(q);
return q;
}
@Override
public final double[] applyInverse(final double[] p) {
final double[] q = p.clone();
applyInverseInPlace(q);
return q;
}
@Override
public final void applyInPlace(final double[] p) {
try {
ict.applyInverseInPlace(p);
} catch (final NoninvertibleModelException e) {
Utils.log2("Point outside mesh: " + p[0] + ", " + p[1]);
}
}
@Override
public final void applyInverseInPlace(final double[] p) {
ict.applyInPlace(p);
}
@Override
public final InvertibleCoordinateTransform createInverse() {
return null;
}
}
@SuppressWarnings( { "rawtypes", "unchecked" } )
final static public void transformPatchesAndVectorData(final Layer layer, final AffineTransform a) {
AlignTask.transformPatchesAndVectorData((Collection<Patch>)(Collection)layer.getDisplayables(Patch.class),
new Runnable() { @Override
public void run() {
layer.apply( Patch.class, a );
}});
}
final static public void transformPatchesAndVectorData(final Collection<Patch> patches, final AffineTransform a) {
AlignTask.transformPatchesAndVectorData(patches,
new Runnable() { @Override
public void run() {
for (final Patch p : patches) {
p.getAffineTransform().preConcatenate(a);
}
}});
}
/*
final static public Map<Long,Patch.TransformProperties> createTransformPropertiesTable(final Collection<Patch> patches) {
final Map<Long,Patch.TransformProperties> tp = new HashMap<Long,Patch.TransformProperties>();
// Parallelize! This operation can be insanely expensive
final int nproc = Runtime.getRuntime().availableProcessors();
final ExecutorService exec = Utils.newFixedThreadPool(nproc, "AlignTask-createTransformPropertiesTable");
final LinkedList<Future> tasks = new LinkedList<Future>();
final Thread current = Thread.currentThread();
final AtomicInteger counter = new AtomicInteger(0);
Utils.log2("0/" + patches.size());
try {
for (final Patch patch : patches) {
tasks.add(exec.submit(new Runnable() {
public void run() {
Patch.TransformProperties props = patch.getTransformPropertiesCopy();
synchronized (tp) {
tp.put(patch.getId(), props);
}
// report
final int i = counter.incrementAndGet();
if (0 == i % 16) {
final String msg = new StringBuilder().append(i).append('/').append(patches.size()).toString();
Utils.log2(msg);
Utils.showStatus(msg);
}
}
}));
// When reaching 2*nproc, wait for nproc to complete
if (0 == tasks.size() % (nproc+nproc)) {
if (current.isInterrupted()) return tp;
int i = 0;
while (i < nproc) {
try { tasks.removeFirst().get(); } catch (Exception e) { IJError.print(e); }
i++;
}
}
}
// Wait for remaining tasks
Utils.wait(tasks);
Utils.log2(patches.size() + "/" + patches.size() + " -- done!");
} catch (Throwable t) {
IJError.print(t);
} finally {
exec.shutdownNow();
}
return tp;
}
*/
static public final class ReferenceData {
/** Patch id vs transform */
final Map<Long,Patch.TransformProperties> tp;
/** A map of Displayable vs a map of Layer id vs list of Patch ids in that Layer that lay under the Patch, sorted by stack index. */
final Map<Displayable,Map<Long,TreeMap<Integer,Long>>> underlying;
/** A list of the Layer ids form which at least one Patch was used to determine a transform of part of a VectorData instance. I.e. the visited layers. */
final Set<Long> src_layer_lids_used;
ReferenceData(final Map<Long,Patch.TransformProperties> tp, final Map<Displayable,Map<Long,TreeMap<Integer,Long>>> underlying, final Set<Long> src_layer_lids_used) {
this.tp = tp;
this.underlying = underlying;
this.src_layer_lids_used = src_layer_lids_used;
}
}
/** Creates a map only for visible patches that intersect vdata.
* @param src_vdata represents the VectorData instances in original form, of the original project and layer set.
* @param tgt_vdata if not null, it must have the same size as src_data and their elements correspond one-to-one (as in, tgt element a clone of src element at the same index).
* @param lids_to_operate The id of the layers on which any operation will be done
* tgt_data enables transformVectorData to apply the transforms to copies of the src_vdata in another project. */
final static public ReferenceData createTransformPropertiesTable(final List<Displayable> src_vdata, final List<Displayable> tgt_vdata, final Set<Long> lids_to_operate) {
if (src_vdata.isEmpty()) return null;
final Map<Long,Patch.TransformProperties> tp = new HashMap<Long,Patch.TransformProperties>();
// A map of Displayable vs a map of Layer id vs list of Patch ids in that Layer that lay under the Patch, sorted by stack index
final Map<Displayable,Map<Long,TreeMap<Integer,Long>>> underlying = new HashMap<Displayable,Map<Long,TreeMap<Integer,Long>>>();
// The set of layers used
final Set<Long> src_layer_lids_used = new HashSet<Long>();
// Parallelize! This operation can be insanely expensive
final int nproc = Runtime.getRuntime().availableProcessors();
final ExecutorService exec = Utils.newFixedThreadPool(nproc, "AlignTask-createTransformPropertiesTable");
final List<Future<?>> dtasks = new ArrayList<Future<?>>();
final List<Future<?>> ltasks = new ArrayList<Future<?>>();
final Thread current = Thread.currentThread();
try {
for (int i=src_vdata.size()-1; i>-1; i--) {
final Displayable src_d = src_vdata.get(i);
if (!(src_d instanceof VectorData)) continue; // filter out
final Displayable tgt_d = null == tgt_vdata ? src_d : tgt_vdata.get(i); // use src_d if tgt_vdata is null
// Some checking
if (!(tgt_d instanceof VectorData)) {
Utils.log("WARNING ignoring provided tgt_vdata " + tgt_d + " which is NOT a VectorData instance!");
continue;
}
if (src_d.getClass() != tgt_d.getClass()) {
Utils.log("WARNING src_d and tgt_d are instances of different classes:\n src_d :: " + src_d + "\n tgt_d :: " + tgt_d);
}
dtasks.add(exec.submit(new Runnable() {
@SuppressWarnings( { "unchecked", "rawtypes" } )
@Override
public void run() {
final Map<Long,TreeMap<Integer,Long>> under = new HashMap<Long,TreeMap<Integer,Long>>();
synchronized (underlying) {
underlying.put(tgt_d, under);
}
if (current.isInterrupted()) return;
// Iterate the layers in which this VectorData has any data AND which have to be transformed
for (final Long olid : src_d.getLayerIds()) {
final long lid = olid.longValue();
if (!lids_to_operate.contains(lid)) continue; // layer with id 'lid' is not affected
final Layer la = src_d.getLayerSet().getLayer(lid);
final Area a = src_d.getAreaAt(la);
if (null == a || a.isEmpty()) {
continue; // does not paint in the layer
}
// The list of patches that lay under VectorData d, sorted by their stack index in the layer
final TreeMap<Integer,Long> stacked_patch_ids = new TreeMap<Integer,Long>();
synchronized (under) {
under.put(lid, stacked_patch_ids);
}
final boolean[] layer_visited = new boolean[]{false};
// Iterate source patches
for (final Patch patch : (Collection<Patch>)(Collection)la.getDisplayables(Patch.class, a, true)) { // pick visible patches only
if (current.isInterrupted()) return;
try {
ltasks.add(exec.submit(new Runnable() {
@Override
public void run() {
if (current.isInterrupted()) return;
synchronized (patch) {
Patch.TransformProperties props;
synchronized (tp) {
props = tp.get(patch.getId());
}
if (null == props) {
props = patch.getTransformPropertiesCopy();
// Cache the props
synchronized (tp) {
tp.put(patch.getId(), props);
}
}
// Cache this patch as under the VectorData d
synchronized (stacked_patch_ids) {
stacked_patch_ids.put(la.indexOf(patch), patch.getId()); // sorted by stack index
//Utils.log("Added patch for layer " + la + " with stack index " + la.indexOf(patch) + ", patch " + patch);
}
if (!layer_visited[0]) {
// synch may fail to avoid adding it twice
// but it's ok since it's a Set anyway
layer_visited[0] = true;
synchronized (src_layer_lids_used) {
src_layer_lids_used.add(la.getId());
}
}
}
}
}));
} catch (final Throwable t) {
IJError.print(t);
return;
}
}
}
}
}));
}
Utils.wait(dtasks);
Utils.wait(ltasks);
} catch (final Throwable t) {
IJError.print(t);
} finally {
exec.shutdownNow();
}
return new ReferenceData(tp, underlying, src_layer_lids_used);
}
/** For registering within the same project instance. */
final static public void transformPatchesAndVectorData(final Collection<Patch> patches, final Runnable alignment) {
if (patches.isEmpty()) {
Utils.log("No patches to align!");
return;
}
// 1 - Collect all VectorData to transform
final ArrayList<Displayable> vdata = new ArrayList<Displayable>();
final LayerSet ls = patches.iterator().next().getLayerSet();
for (final Layer layer : ls.getLayers()) {
vdata.addAll(layer.getDisplayables(VectorData.class, false, true));
}
vdata.addAll(ls.getZDisplayables(VectorData.class, true));
// Perhaps none:
if (vdata.isEmpty()) {
alignment.run();
return;
}
// 2 - Store current transformation of each Patch under any VectorData
final HashSet<Long> lids = new HashSet<Long>();
for (final Patch p : patches) lids.add(p.getLayer().getId());
final ReferenceData rd = createTransformPropertiesTable(vdata, null, lids);
// 3 - Align:
alignment.run();
// TODO check that alignTiles doesn't change the dimensions/origin of the LayerSet! That would invalidate the table of TransformProperties
// 4 - Transform VectorData instances to match the position of the Patch instances over which they were defined
if (null != rd && !vdata.isEmpty()) transformVectorData(rd, vdata, ls);
}
final static public void transformVectorData
(final ReferenceData rd, /* The transformations of patches before alignment. */
final Collection<Displayable> vdata, /* The VectorData instances to transform along with images. */
final LayerSet target_layerset) /* The LayerSet in which the vdata and the transformed images exist. */
{
final ExecutorService exec = Utils.newFixedThreadPool("AlignTask-transformVectorData");
try {
final Collection<Future<?>> fus = new ArrayList<Future<?>>();
final HashMap<Long,Layer> lidm = new HashMap<Long,Layer>();
for (final Long lid : rd.src_layer_lids_used) {
final Layer la = target_layerset.getLayer(lid.longValue());
if (null == la) {
Utils.log("ERROR layer with id " + lid + " NOT FOUND in target layerset!");
continue;
}
lidm.put(lid, la);
}
for (final Map.Entry<Displayable,Map<Long,TreeMap<Integer,Long>>> ed : rd.underlying.entrySet()) {
final Displayable d = ed.getKey(); // The VectorData instance to transform
// Process Displayables concurrently:
fus.add(exec.submit(new Runnable() { @SuppressWarnings( { "rawtypes", "unchecked" } )
@Override
public void run() {
for (final Map.Entry<Long,TreeMap<Integer,Long>> el : ed.getValue().entrySet()) {
// The entry has the id of the layer and the stack-index-ordered list of Patch that intersect VectorData d in that Layer
final Layer layer = lidm.get(el.getKey());
if (null == layer) {
Utils.log("ERROR layer with id " + el.getKey() + " NOT FOUND in target layerset!");
continue;
}
//Utils.log("Editing Displayable " + d + " at layer " + layer);
final ArrayList<Long> pids = new ArrayList<Long>(el.getValue().values()); // list of Patch ids affecting VectorData/Displayable d
Collections.reverse(pids); // so now Patch ids are sorted from top to bottom
// The area already processed in the layer
final Area used_area = new Area();
// The map of areas vs transforms for each area to apply to the VectorData, to its data within the layer only
final VectorDataTransform vdt = new VectorDataTransform(layer);
// The list of transforms to apply to each VectorData
for (final long pid : pids) {
// Find the Patch with id 'pid' in Layer 'la' of the target LayerSet:
final DBObject ob = layer.findById(pid);
if (null == ob || !(ob instanceof Patch)) {
Utils.log("ERROR layer with id " + layer.getId() + " DOES NOT CONTAIN a Patch with id " + pid);
continue;
}
final Patch patch = (Patch)ob;
final Patch.TransformProperties props = rd.tp.get(pid); // no need to synch, read only from now on
if (null == props) {
Utils.log("ERROR: could not find any Patch.TransformProperties for patch " + patch);
continue;
}
final Area a = new Area(props.area);
a.subtract(used_area);
if (M.isEmpty(a)) {
continue; // skipping fully occluded Patch
}
// Accumulate:
used_area.add(props.area);
// For the remaining area within this Layer, define a transform
// Generate a CoordinateTransformList that includes:
// 1 - an inverted transform from Patch coords to world coords
// 2 - the CoordinateTransform of the Patch, if any
// 3 - the AffineTransform of the Patch
//
// The idea is to first send the data from world to pixel space of the Patch, using the old transfroms,
// and then from pixel space of the Patch to world, using the new transforms.
final CoordinateTransformList tlist = new CoordinateTransformList();
// 1. Inverse of the old affine: from world into the old patch mipmap
final mpicbg.models.AffineModel2D aff_inv = new mpicbg.models.AffineModel2D();
try {
aff_inv.set(props.at.createInverse());
} catch (final NoninvertibleTransformException nite) {
Utils.log("ERROR: could not invert the affine transform for Patch " + patch);
IJError.print(nite);
continue;
}
tlist.add(aff_inv);
// 2. Inverse of the old coordinate transform of the Patch: from old mipmap to pixels in original image
if (null != props.ct) {
// The props.ct is a CoordinateTransform, not necessarily an InvertibleCoordinateTransform
// So the mesh is necessary to ensure the invertibility
final mpicbg.trakem2.transform.TransformMesh mesh = new mpicbg.trakem2.transform.TransformMesh(props.ct, props.meshResolution, props.o_width, props.o_height);
/* // Apparently not needed; the inverse affine in step 1 took care of it.
* // (the affine of step 1 includes the mesh translation)
Rectangle box = mesh.getBoundingBox();
AffineModel2D aff = new AffineModel2D();
aff.set(new AffineTransform(1, 0, 0, 1, box.x, box.y));
tlist.add(aff);
*/
tlist.add(new InverseICT(mesh));
}
// 3. New coordinate transform of the Patch: from original image to new mipmap
final mpicbg.trakem2.transform.CoordinateTransform ct = patch.getCoordinateTransform();
if (null != ct) {
tlist.add(ct);
final mpicbg.trakem2.transform.TransformMesh mesh = new mpicbg.trakem2.transform.TransformMesh(ct, patch.getMeshResolution(), patch.getOWidth(), patch.getOHeight());
// correct for mesh bounds -- Necessary because it comes from the other side, and the removal of the translation here is re-added by the affine in step 4!
final Rectangle box = mesh.getBoundingBox();
final AffineModel2D aff = new AffineModel2D();
aff.set(new AffineTransform(1, 0, 0, 1, -box.x, -box.y));
tlist.add(aff);
}
// 4. New affine transform of the Patch: from mipmap to world
final mpicbg.models.AffineModel2D new_aff = new mpicbg.models.AffineModel2D();
new_aff.set(patch.getAffineTransform());
tlist.add(new_aff);
/*
// TODO Consider caching the tlist for each Patch, or for a few thousand of them maximum.
// But it could blow up memory astronomically.
// The old part:
final mpicbg.models.InvertibleCoordinateTransformList old = new mpicbg.models.InvertibleCoordinateTransformList();
if (null != props.ct) {
mpicbg.trakem2.transform.TransformMesh mesh = new mpicbg.trakem2.transform.TransformMesh(props.ct, props.meshResolution, props.o_width, props.o_height);
old.add(mesh);
}
final mpicbg.models.AffineModel2D old_aff = new mpicbg.models.AffineModel2D();
old_aff.set(props.at);
old.add(old_aff);
tlist.add(new InverseICT(old));
// The new part:
final mpicbg.models.AffineModel2D new_aff = new mpicbg.models.AffineModel2D();
new_aff.set(patch.getAffineTransform());
tlist.add(new_aff);
final mpicbg.trakem2.transform.CoordinateTransform ct = patch.getCoordinateTransform();
if (null != ct) tlist.add(ct);
*/
vdt.add(a, tlist);
}
// Apply the map of area vs tlist for the data section of d within the layer:
try {
((VectorData)d).apply(vdt);
} catch (final Exception t) {
Utils.log("ERROR transformation failed for " + d + " at layer " + layer);
IJError.print(t);
}
}
}}));
}
Utils.wait(fus);
Display.repaint();
} finally {
exec.shutdown();
}
}
final static public void alignPatches(
final Align.ParamOptimize p,
final List< Patch > patches,
final Collection< Patch > fixedPatches,
final boolean tilesAreInPlaceIn,
final boolean largestGraphOnlyIn,
final boolean hideDisconnectedTilesIn,
final boolean deleteDisconnectedTilesIn,
final boolean sloppyOverlapTest )
{
final List< AbstractAffineTile2D< ? > > tiles = new ArrayList< AbstractAffineTile2D< ? > >();
final List< AbstractAffineTile2D< ? > > fixedTiles = new ArrayList< AbstractAffineTile2D< ? > > ();
Align.tilesFromPatches( p, patches, fixedPatches, tiles, fixedTiles );
transformPatchesAndVectorData(patches, new Runnable() {
@Override
public void run() {
alignTiles( p, tiles, fixedTiles, tilesAreInPlaceIn, largestGraphOnlyIn, hideDisconnectedTilesIn, deleteDisconnectedTilesIn, sloppyOverlapTest );
Display.repaint();
}
});
}
final static public void alignPatches(
final Align.ParamOptimize p,
final List< Patch > patches,
final Collection< Patch > fixedPatches,
final boolean tilesAreInPlaceIn,
final boolean largestGraphOnlyIn,
final boolean hideDisconnectedTilesIn,
final boolean deleteDisconnectedTilesIn )
{
alignPatches( p, patches, fixedPatches, tilesAreInPlaceIn, largestGraphOnlyIn, hideDisconnectedTilesIn, deleteDisconnectedTilesIn, false );
}
final static public void alignTiles(
final Align.ParamOptimize p,
final List< AbstractAffineTile2D< ? > > tiles,
final List< AbstractAffineTile2D< ? > > fixedTiles,
final boolean tilesAreInPlaceIn,
final boolean largestGraphOnlyIn,
final boolean hideDisconnectedTilesIn,
final boolean deleteDisconnectedTilesIn,
final boolean sloppyOverlapTest )
{
final List< AbstractAffineTile2D< ? >[] > tilePairs = new ArrayList< AbstractAffineTile2D< ? >[] >();
if ( tilesAreInPlaceIn )
AbstractAffineTile2D.pairOverlappingTiles( tiles, tilePairs, sloppyOverlapTest );
else
AbstractAffineTile2D.pairTiles( tiles, tilePairs );
Align.connectTilePairs( p, tiles, tilePairs, Runtime.getRuntime().availableProcessors() );
if ( Thread.currentThread().isInterrupted() ) return;
final List< Set< Tile< ? > > > graphs = AbstractAffineTile2D.identifyConnectedGraphs( tiles );
final List< AbstractAffineTile2D< ? > > interestingTiles;
if ( largestGraphOnlyIn )
{
/* find largest graph. */
Set< Tile< ? > > largestGraph = null;
for ( final Set< Tile< ? > > graph : graphs )
if ( largestGraph == null || largestGraph.size() < graph.size() )
largestGraph = graph;
interestingTiles = new ArrayList< AbstractAffineTile2D< ? > >();
for ( final Tile< ? > t : largestGraph )
interestingTiles.add( ( AbstractAffineTile2D< ? > )t );
if ( hideDisconnectedTilesIn )
for ( final AbstractAffineTile2D< ? > t : tiles )
if ( !interestingTiles.contains( t ) )
t.getPatch().setVisible( false );
if ( deleteDisconnectedTilesIn )
for ( final AbstractAffineTile2D< ? > t : tiles )
if ( !interestingTiles.contains( t ) )
t.getPatch().remove( false );
}
else
interestingTiles = tiles;
if ( Thread.currentThread().isInterrupted() ) return;
Align.optimizeTileConfiguration( p, interestingTiles, fixedTiles );
for ( final AbstractAffineTile2D< ? > t : interestingTiles )
t.getPatch().getAffineTransform().setTransform( t.getModel().createAffine() );
Utils.log( "Montage done." );
}
final static public void alignTiles(
final Align.ParamOptimize p,
final List< AbstractAffineTile2D< ? > > tiles,
final List< AbstractAffineTile2D< ? > > fixedTiles,
final boolean tilesAreInPlaceIn,
final boolean largestGraphOnlyIn,
final boolean hideDisconnectedTilesIn,
final boolean deleteDisconnectedTilesIn )
{
alignTiles( p, tiles, fixedTiles, tilesAreInPlaceIn, largestGraphOnlyIn, hideDisconnectedTilesIn, deleteDisconnectedTilesIn, false );
}
final static public Bureaucrat alignMultiLayerMosaicTask( final Layer l )
{
return alignMultiLayerMosaicTask( l, null );
}
final static public Bureaucrat alignMultiLayerMosaicTask( final Layer l, final Patch nail )
{
final Worker worker = new Worker( "Aligning multi-layer mosaic", false, true )
{
@Override
public void run()
{
startedWorking();
try { alignMultiLayerMosaic( l, nail ); }
catch ( final Throwable e ) { IJError.print( e ); }
finally { finishedWorking(); }
}
};
return Bureaucrat.createAndStart(worker, l.getProject());
}
/**
* Align a multi-layer mosaic.
*
* @param l the current layer
*/
final public static void alignMultiLayerMosaic( final Layer l, final Patch nail )
{
/* layer range and misc */
final List< Layer > layers = l.getParent().getLayers();
final String[] layerTitles = new String[ layers.size() ];
for ( int i = 0; i < layers.size(); ++i )
layerTitles[ i ] = l.getProject().findLayerThing(layers.get( i )).toString();
final GenericDialog gd1 = new GenericDialog( "Align Multi-Layer Mosaic : Layer Range" );
gd1.addMessage( "Layer Range:" );
final int sel = l.getParent().indexOf(l);
gd1.addChoice( "first :", layerTitles, layerTitles[ sel ] );
gd1.addChoice( "last :", layerTitles, layerTitles[ sel ] );
gd1.addMessage( "Miscellaneous:" );
gd1.addCheckbox( "tiles are roughly in place", tilesAreInPlace );
gd1.addCheckbox( "consider largest graph only", largestGraphOnly );
gd1.addCheckbox( "hide tiles from non-largest graph", hideDisconnectedTiles );
gd1.addCheckbox( "delete tiles from non-largest graph", deleteDisconnectedTiles );
gd1.addCheckbox( "deform layers", deform );
gd1.showDialog();
if ( gd1.wasCanceled() ) return;
final int first = gd1.getNextChoiceIndex();
final int last = gd1.getNextChoiceIndex();
final int d = first < last ? 1 : -1;
tilesAreInPlace = gd1.getNextBoolean();
largestGraphOnly = gd1.getNextBoolean();
hideDisconnectedTiles = gd1.getNextBoolean();
deleteDisconnectedTiles = gd1.getNextBoolean();
deform = gd1.getNextBoolean();
/* intra-layer parameters */
if ( !Align.paramOptimize.setup( "Align Multi-Layer Mosaic : Intra-Layer" ) )
return;
final Align.ParamOptimize p = Align.paramOptimize.clone();
final Align.ParamOptimize pcp = p.clone();
/* cross-layer parameters */
if ( !Align.param.setup( "Align Multi-Layer Mosaic : Cross-Layer" ) )
return;
final Align.Param cp = Align.param.clone();
pcp.desiredModelIndex = cp.desiredModelIndex;
final List< Layer > layerRange = new ArrayList< Layer >();
for ( int i = first; i != last + d; i += d )
layerRange.add( layers.get( i ) );
alignMultiLayerMosaicTask( layerRange, nail, cp, p, pcp, tilesAreInPlace, largestGraphOnly, hideDisconnectedTiles, deleteDisconnectedTiles, deform );
}
final static private boolean alignGraphs(
final Align.Param p,
final Layer layer1,
final Layer layer2,
final Iterable< Tile< ? > > graph1,
final Iterable< Tile< ? > > graph2 )
{
final Align.Param cp = p.clone();
final Selection selection1 = new Selection( null );
for ( final Tile< ? > tile : graph1 )
selection1.add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
final Rectangle graph1Box = selection1.getBox();
final Selection selection2 = new Selection( null );
for ( final Tile< ? > tile : graph2 )
selection2.add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
final Rectangle graph2Box = selection2.getBox();
final int maxLength = Math.max( Math.max( Math.max( graph1Box.width, graph1Box.height ), graph2Box.width ), graph2Box.height );
//final double scale = ( double )cp.sift.maxOctaveSize / maxLength;
/* rather ad hoc but we cannot just scale this to maxOctaveSize */
cp.sift.maxOctaveSize = Math.min( maxLength, 2 * p.sift.maxOctaveSize );
/* make sure that, despite rounding issues from scale, it is >= image size */
final double scale = ( double )( cp.sift.maxOctaveSize - 1 ) / maxLength;
//cp.maxEpsilon *= scale;
final FloatArray2DSIFT sift = new FloatArray2DSIFT( cp.sift );
final SIFT ijSIFT = new SIFT( sift );
final ArrayList< Feature > features1 = new ArrayList< Feature >();
final ArrayList< Feature > features2 = new ArrayList< Feature >();
final ArrayList< PointMatch > candidates = new ArrayList< PointMatch >();
final ArrayList< PointMatch > inliers = new ArrayList< PointMatch >();
long s = System.currentTimeMillis();
ijSIFT.extractFeatures(
layer1.getProject().getLoader().getFlatImage( layer1, graph1Box, scale, 0xffffffff, ImagePlus.GRAY8, Patch.class, selection1.getSelected( Patch.class ), false, Color.GRAY ).getProcessor(),
features1 );
Utils.log( features1.size() + " features extracted for graphs in layer \"" + layer1.getTitle() + "\" (took " + ( System.currentTimeMillis() - s ) + " ms)." );
ijSIFT.extractFeatures(
layer2.getProject().getLoader().getFlatImage( layer2, graph2Box, scale, 0xffffffff, ImagePlus.GRAY8, Patch.class, selection2.getSelected( Patch.class ), false, Color.GRAY ).getProcessor(),
features2 );
Utils.log( features2.size() + " features extracted for graphs in layer \"" + layer1.getTitle() + "\" (took " + ( System.currentTimeMillis() - s ) + " ms)." );
boolean modelFound = false;
if ( features1.size() > 0 && features2.size() > 0 )
{
s = System.currentTimeMillis();
FeatureTransform.matchFeatures(
features1,
features2,
candidates,
cp.rod );
final AbstractAffineModel2D< ? > model;
switch ( cp.expectedModelIndex )
{
case 0:
model = new TranslationModel2D();
break;
case 1:
model = new RigidModel2D();
break;
case 2:
model = new SimilarityModel2D();
break;
case 3:
model = new AffineModel2D();
break;
default:
return false;
}
boolean again = false;
try
{
do
{
again = false;
modelFound = model.filterRansac(
candidates,
inliers,
1000,
cp.maxEpsilon,
cp.minInlierRatio,
cp.minNumInliers,
3 );
if ( modelFound && cp.rejectIdentity )
{
final ArrayList< Point > points = new ArrayList< Point >();
PointMatch.sourcePoints( inliers, points );
if ( Transforms.isIdentity( model, points, cp.identityTolerance ) )
{
IJ.log( "Identity transform for " + inliers.size() + " matches rejected." );
candidates.removeAll( inliers );
inliers.clear();
again = true;
}
}
}
while ( again );
}
catch ( final NotEnoughDataPointsException e )
{
modelFound = false;
}
if ( modelFound )
{
Utils.log( "Model found for graphs in layer \"" + layer1.getTitle() + "\" and \"" + layer2.getTitle() + "\":\n correspondences " + inliers.size() + " of " + candidates.size() + "\n average residual error " + ( model.getCost() / scale ) + " px\n took " + ( System.currentTimeMillis() - s ) + " ms" );
final AffineTransform b = new AffineTransform();
b.translate( graph2Box.x, graph2Box.y );
b.scale( 1.0f / scale, 1.0f / scale );
b.concatenate( model.createAffine() );
b.scale( scale, scale );
b.translate( -graph1Box.x, -graph1Box.y);
for ( final Displayable d : selection1.getSelected( Patch.class ) )
d.getAffineTransform().preConcatenate( b );
/* assign patch affine transformation to the tile model */
for ( final Tile< ? > t : graph1 )
( ( AbstractAffineTile2D< ? > )t ).initModel();
Display.repaint( layer1 );
}
else
IJ.log( "No model found for graphs in layer \"" + layer1.getTitle() + "\" and \"" + layer2.getTitle() + "\":\n correspondence candidates " + candidates.size() + "\n took " + ( System.currentTimeMillis() - s ) + " ms" );
}
return modelFound;
}
@SuppressWarnings( { "unchecked", "rawtypes" } )
public static final void alignMultiLayerMosaicTask(
final List< Layer > layerRange,
final Patch nail,
final Align.Param cp,
final Align.ParamOptimize p,
final Align.ParamOptimize pcp,
final boolean tilesAreInPlaceIn,
final boolean largestGraphOnlyIn,
final boolean hideDisconnectedTilesIn,
final boolean deleteDisconnectedTilesIn,
final boolean deformIn )
{
/* register */
final List< AbstractAffineTile2D< ? > > allTiles = new ArrayList< AbstractAffineTile2D< ? > >();
final List< AbstractAffineTile2D< ? > > allFixedTiles = new ArrayList< AbstractAffineTile2D< ? > >();
final List< AbstractAffineTile2D< ? > > previousLayerTiles = new ArrayList< AbstractAffineTile2D< ? > >();
final HashMap< Patch, PointMatch > tileCenterPoints = new HashMap< Patch, PointMatch >();
final Collection< Patch > fixedPatches = new HashSet< Patch >();
if ( null != nail )
fixedPatches.add( nail );
Layer previousLayer = null;
for ( final Layer layer : layerRange )
{
/* align all tiles in the layer */
final List< Patch > patches = new ArrayList< Patch >();
for ( final Displayable a : layer.getDisplayables( Patch.class, true ) ) // ignore hidden tiles
if ( a instanceof Patch ) patches.add( ( Patch )a );
final List< AbstractAffineTile2D< ? > > currentLayerTiles = new ArrayList< AbstractAffineTile2D< ? > >();
final List< AbstractAffineTile2D< ? > > fixedTiles = new ArrayList< AbstractAffineTile2D< ? > > ();
Align.tilesFromPatches( p, patches, fixedPatches, currentLayerTiles, fixedTiles );
alignTiles( p, currentLayerTiles, fixedTiles, tilesAreInPlaceIn, false, false, false ); // Will consider graphs and hide/delete tiles when all cross-layer graphs are found.
if (Thread.currentThread().isInterrupted()) return;
/* connect to the previous layer */
/* generate tiles with the cross-section model from the current layer tiles */
/* ------------------------------------------------------------------------ */
/* TODO step back and make tiles bare containers for a patch and a model such that by changing the model the tile can be reused */
final HashMap< Patch, AbstractAffineTile2D< ? > > currentLayerPatchTiles = new HashMap< Patch, AbstractAffineTile2D<?> >();
for ( final AbstractAffineTile2D< ? > t : currentLayerTiles )
currentLayerPatchTiles.put( t.getPatch(), t );
final List< AbstractAffineTile2D< ? > > csCurrentLayerTiles = new ArrayList< AbstractAffineTile2D< ? > >();
final Set< AbstractAffineTile2D< ? > > csFixedTiles = new HashSet< AbstractAffineTile2D< ? > > ();
Align.tilesFromPatches( cp, patches, fixedPatches, csCurrentLayerTiles, csFixedTiles );
final HashMap< Tile< ? >, AbstractAffineTile2D< ? > > tileTiles = new HashMap< Tile< ? >, AbstractAffineTile2D<?> >();
for ( final AbstractAffineTile2D< ? > t : csCurrentLayerTiles )
tileTiles.put( currentLayerPatchTiles.get( t.getPatch() ), t );
for ( final AbstractAffineTile2D< ? > t : currentLayerTiles )
{
final AbstractAffineTile2D< ? > csLayerTile = tileTiles.get( t );
csLayerTile.addMatches( t.getMatches() );
for ( final Tile< ? > ct : t.getConnectedTiles() )
csLayerTile.addConnectedTile( tileTiles.get( ct ) );
}
/* add a fixed tile only if there was a Patch selected */
allFixedTiles.addAll( csFixedTiles );
/* first, align connected graphs to each other */
/* graphs in the current layer */
final List< Set< Tile< ? > > > currentLayerGraphs = AbstractAffineTile2D.identifyConnectedGraphs( csCurrentLayerTiles );
if (Thread.currentThread().isInterrupted()) return;
// /* TODO just for visualization */
// for ( final Set< Tile< ? > > graph : currentLayerGraphs )
// {
// Display.getFront().getSelection().clear();
// Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
//
// for ( final Tile< ? > tile : graph )
// {
// Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
// Display.repaint();
// }
// Utils.showMessage( "OK" );
// }
/* graphs from the whole system that are present in the previous layer */
final List< Set< Tile< ? > > > graphs = AbstractAffineTile2D.identifyConnectedGraphs( allTiles );
final HashMap< Set< Tile< ? > >, Set< Tile< ? > > > graphGraphs = new HashMap< Set<Tile<?>>, Set<Tile<?>> >();
for ( final Set< Tile< ? > > graph : graphs )
{
if (Thread.currentThread().isInterrupted()) return;
final Set< Tile< ? > > previousLayerGraph = new HashSet< Tile< ? > >();
for ( final Tile< ? > tile : previousLayerTiles )
{
if ( graph.contains( tile ) )
{
graphGraphs.put( graph, previousLayerGraph );
previousLayerGraph.add( tile );
}
}
}
final Collection< Set< Tile< ? > > > previousLayerGraphs = graphGraphs.values();
// /* TODO just for visualization */
// for ( final Set< Tile< ? > > graph : previousLayerGraphs )
// {
// Display.getFront().getSelection().clear();
// Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
//
// for ( final Tile< ? > tile : graph )
// {
// Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
// Display.repaint();
// }
// Utils.showMessage( "OK" );
// }
/* generate snapshots of the graphs and preregister them using the parameters defined in cp */
final List< AbstractAffineTile2D< ? >[] > crossLayerTilePairs = new ArrayList< AbstractAffineTile2D< ? >[] >();
for ( final Set< Tile< ? > > currentLayerGraph : currentLayerGraphs )
{
for ( final Set< Tile< ? > > previousLayerGraph : previousLayerGraphs )
{
if (Thread.currentThread().isInterrupted()) return;
alignGraphs( cp, layer, previousLayer, currentLayerGraph, previousLayerGraph );
/* TODO this is pointless data shuffling just for type incompatibility---fix this at the root */
final ArrayList< AbstractAffineTile2D< ? > > previousLayerGraphTiles = new ArrayList< AbstractAffineTile2D< ? > >();
previousLayerGraphTiles.addAll( ( Set )previousLayerGraph );
final ArrayList< AbstractAffineTile2D< ? > > currentLayerGraphTiles = new ArrayList< AbstractAffineTile2D< ? > >();
currentLayerGraphTiles.addAll( ( Set )currentLayerGraph );
AbstractAffineTile2D.pairOverlappingTiles( previousLayerGraphTiles, currentLayerGraphTiles, crossLayerTilePairs );
}
}
/* ------------------------------------------------------------------------ */
/* this is without the affine/rigid approximation per graph */
// AbstractAffineTile2D.pairTiles( previousLayerTiles, csCurrentLayerTiles, crossLayerTilePairs );
Align.connectTilePairs( cp, csCurrentLayerTiles, crossLayerTilePairs, Runtime.getRuntime().availableProcessors() );
if (Thread.currentThread().isInterrupted()) return;
// for ( final AbstractAffineTile2D< ? >[] tilePair : crossLayerTilePairs )
// {
// Display.getFront().setLayer( tilePair[ 0 ].getPatch().getLayer() );
// Display.getFront().getSelection().clear();
// Display.getFront().getSelection().add( tilePair[ 0 ].getPatch() );
// Display.getFront().getSelection().add( tilePair[ 1 ].getPatch() );
//
// Utils.showMessage( "1: OK?" );
//
// Display.getFront().setLayer( tilePair[ 1 ].getPatch().getLayer() );
// Display.getFront().getSelection().clear();
// Display.getFront().getSelection().add( tilePair[ 0 ].getPatch() );
// Display.getFront().getSelection().add( tilePair[ 1 ].getPatch() );
//
// Utils.showMessage( "2: OK?" );
// }
/* prepare the next loop */
allTiles.addAll( csCurrentLayerTiles );
previousLayerTiles.clear();
previousLayerTiles.addAll( csCurrentLayerTiles );
/* optimize */
Align.optimizeTileConfiguration( pcp, allTiles, allFixedTiles );
if (Thread.currentThread().isInterrupted()) return;
for ( final AbstractAffineTile2D< ? > t : allTiles )
t.getPatch().setAffineTransform( t.getModel().createAffine() );
previousLayer = layer;
}
final List< Set< Tile< ? > > > graphs = AbstractAffineTile2D.identifyConnectedGraphs( allTiles );
final List< AbstractAffineTile2D< ? > > interestingTiles = new ArrayList< AbstractAffineTile2D< ? > >();
if ( largestGraphOnlyIn && ( hideDisconnectedTilesIn || deleteDisconnectedTilesIn ) )
{
if ( Thread.currentThread().isInterrupted() ) return;
/* find largest graph. */
Set< Tile< ? > > largestGraph = null;
for ( final Set< Tile< ? > > graph : graphs )
if ( largestGraph == null || largestGraph.size() < graph.size() )
largestGraph = graph;
final Set<AbstractAffineTile2D<?>> tiles_to_keep = new HashSet<AbstractAffineTile2D<?>>();
for ( final Tile< ? > t : largestGraph )
tiles_to_keep.add( ( AbstractAffineTile2D< ? > )t );
if ( hideDisconnectedTilesIn )
for ( final AbstractAffineTile2D< ? > t : allTiles )
if ( !tiles_to_keep.contains( t ) )
t.getPatch().setVisible( false );
if ( deleteDisconnectedTilesIn )
for ( final AbstractAffineTile2D< ? > t : allTiles )
if ( !tiles_to_keep.contains( t ) )
t.getPatch().remove( false );
interestingTiles.addAll(tiles_to_keep);
}
else
interestingTiles.addAll( allTiles );
if ( deformIn )
{
/* ############################################ */
/* experimental: use the center points of all tiles to define a MLS deformation from the pure intra-layer registration to the globally optimal */
Utils.log( "deforming..." );
/* store the center location of each single tile for later deformation */
for ( final AbstractAffineTile2D< ? > t : interestingTiles )
{
final double[] c = new double[]{ t.getWidth() / 2.0, t.getHeight() / 2.0 };
t.getModel().applyInPlace( c );
final Point q = new Point( c );
tileCenterPoints.put( t.getPatch(), new PointMatch( q.clone(), q ) );
}
for ( final Layer layer : layerRange )
{
Utils.log( "layer" + layer );
if ( Thread.currentThread().isInterrupted() ) return;
/* again, align all tiles in the layer */
final List< Patch > patches = new ArrayList< Patch >();
for ( final Displayable a : layer.getDisplayables( Patch.class ) )
if ( a instanceof Patch ) patches.add( ( Patch )a );
final List< AbstractAffineTile2D< ? > > currentLayerTiles = new ArrayList< AbstractAffineTile2D< ? > >();
final List< AbstractAffineTile2D< ? > > fixedTiles = new ArrayList< AbstractAffineTile2D< ? > > ();
Align.tilesFromPatches( p, patches, fixedPatches, currentLayerTiles, fixedTiles );
/* add a fixed tile only if there was a Patch selected */
allFixedTiles.addAll( fixedTiles );
alignTiles( p, currentLayerTiles, fixedTiles, true, false, false, false ); // will consider graphs and hide/delete tiles when all cross-layer graphs are found
/* for each independent graph do an independent transform */
final List< Set< Tile< ? > > > currentLayerGraphs = AbstractAffineTile2D.identifyConnectedGraphs( currentLayerTiles );
for ( final Set< Tile< ? > > graph : currentLayerGraphs )
{
/* update the tile-center pointmatches */
final Collection< PointMatch > matches = new ArrayList< PointMatch >();
final Collection< AbstractAffineTile2D< ? > > toBeDeformedTiles = new ArrayList< AbstractAffineTile2D< ? > >();
for ( final AbstractAffineTile2D< ? > t : ( Collection< AbstractAffineTile2D< ? > > )( Collection )graph )
{
final PointMatch pm = tileCenterPoints.get( t.getPatch() );
if ( pm == null ) continue;
final double[] pl = pm.getP1().getL();
pl[ 0 ] = t.getWidth() / 2.0;
pl[ 1 ] = t.getHeight() / 2.0;
t.getModel().applyInPlace( pl );
matches.add( pm );
toBeDeformedTiles.add( t );
}
for ( final AbstractAffineTile2D< ? > t : toBeDeformedTiles )
{
if ( Thread.currentThread().isInterrupted() ) return;
try
{
final Patch patch = t.getPatch();
final Rectangle pbox = patch.getCoordinateTransformBoundingBox();
final AffineTransform pat = new AffineTransform();
pat.translate( -pbox.x, -pbox.y );
pat.preConcatenate( patch.getAffineTransform() );
final mpicbg.trakem2.transform.AffineModel2D toWorld = new mpicbg.trakem2.transform.AffineModel2D();
toWorld.set( pat );
final MovingLeastSquaresTransform2 mlst = Align.createMLST( matches, 1.0f );
final CoordinateTransformList< CoordinateTransform > ctl = new CoordinateTransformList< CoordinateTransform >();
ctl.add( toWorld );
ctl.add( mlst );
ctl.add( toWorld.createInverse() );
patch.appendCoordinateTransform( ctl );
patch.getProject().getLoader().regenerateMipMaps( patch );
}
catch ( final Exception e )
{
e.printStackTrace();
}
}
}
}
}
layerRange.get(0).getParent().setMinimumDimensions();
IJ.log( "Done: register multi-layer mosaic." );
return;
}
/** The ParamOptimize object containg all feature extraction and registration model parameters for the "snap" function. */
static public final Align.ParamOptimize p_snap = Align.paramOptimize.clone();
/** Find the most overlapping image to @param patch in the same layer where @param patch sits, and snap @param patch and all its linked Displayable objects.
* If a null @param p_snap is given, it will use the AlignTask.p_snap.
* If @param setup is true, it will show a dialog to adjust parameters. */
static public final Bureaucrat snap(final Patch patch, final Align.ParamOptimize p_snapIn, final boolean setup) {
return Bureaucrat.createAndStart(new Worker.Task("Snapping", true) {
@Override
public void exec() {
final Align.ParamOptimize p = null == p_snapIn ? AlignTask.p_snap : p_snapIn;
if (setup) p.setup("Snap");
// Collect Patch linked to active
final List<Displayable> linked_images = new ArrayList<Displayable>();
for (final Displayable d : patch.getLinkedGroup(null)) {
if (d.getClass() == Patch.class && d != patch) linked_images.add(d);
}
// Find overlapping images
@SuppressWarnings( { "rawtypes", "unchecked" } )
final List<Patch> overlapping = new ArrayList<Patch>( (Collection<Patch>) (Collection) patch.getLayer().getIntersecting(patch, Patch.class));
overlapping.remove(patch);
if (0 == overlapping.size()) return; // nothing overlaps
// Discard from overlapping any linked images
overlapping.removeAll(linked_images);
if (0 == overlapping.size()) {
Utils.log("Cannot snap: overlapping images are linked to the one to snap.");
return;
}
// flush
linked_images.clear();
// Find the image that overlaps the most
final Rectangle box = patch.getBoundingBox(null);
Patch most = null;
Rectangle most_inter = null;
for (final Patch other : overlapping) {
if (null == most) {
most = other;
most_inter = other.getBoundingBox();
continue;
}
final Rectangle inter = other.getBoundingBox().intersection(box);
if (inter.width * inter.height > most_inter.width * most_inter.height) {
most = other;
most_inter = inter;
}
}
// flush
overlapping.clear();
// Define two lists:
// - a list with all involved tiles: the active and the most overlapping one
final List<Patch> patches = new ArrayList<Patch>();
patches.add(most);
patches.add(patch);
// - a list with all tiles except the active, to be set as fixed, immobile
final List<Patch> fixedPatches = new ArrayList<Patch>();
fixedPatches.add(most);
// Patch as Tile
final List< AbstractAffineTile2D< ? > > tiles = new ArrayList< AbstractAffineTile2D< ? > >();
final List< AbstractAffineTile2D< ? > > fixedTiles = new ArrayList< AbstractAffineTile2D< ? > > ();
Align.tilesFromPatches( p, patches, fixedPatches, tiles, fixedTiles );
// Pair and connect overlapping tiles
final List< AbstractAffineTile2D< ? >[] > tilePairs = new ArrayList< AbstractAffineTile2D< ? >[] >();
AbstractAffineTile2D.pairOverlappingTiles( tiles, tilePairs );
Align.connectTilePairs( p, tiles, tilePairs, Runtime.getRuntime().availableProcessors() );
if ( Thread.currentThread().isInterrupted() ) return;
Align.optimizeTileConfiguration( p, tiles, fixedTiles );
for ( final AbstractAffineTile2D< ? > t : tiles ) {
if (t.getPatch() == patch) {
final AffineTransform at = t.getModel().createAffine();
try {
at.concatenate(patch.getAffineTransform().createInverse());
patch.transform(at);
} catch (final NoninvertibleTransformException nite) {
IJError.print(nite);
}
break;
}
}
Display.repaint();
}}, patch.getProject());
}
static public final Bureaucrat registerStackSlices(final Patch slice) {
return Bureaucrat.createAndStart(new Worker.Task("Registering slices", true) {
@Override
public void exec() {
// build the list
final ArrayList<Patch> slices = slice.getStackPatches();
if (slices.size() < 2) {
Utils.log2("Not a stack!");
return;
}
// check that none are linked to anything other than images
for (final Patch patch : slices) {
if (!patch.isOnlyLinkedTo(Patch.class)) {
Utils.log("Can't register: one or more slices are linked to objects other than images.");
return;
}
}
// ok proceed
final Align.ParamOptimize p = Align.paramOptimize.clone();
p.setup("Register stack slices");
final List<Patch> fixedSlices = new ArrayList<Patch>();
fixedSlices.add(slice);
alignPatches( p, slices, fixedSlices, false, false, false, false );
Display.repaint();
}}, slice.getProject());
}
}