package ini.trakem2.display;
import ini.trakem2.utils.Bureaucrat;
import ini.trakem2.utils.ProjectToolbar;
import ini.trakem2.utils.Utils;
import ini.trakem2.utils.Worker;
import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
import mpicbg.models.AbstractAffineModel2D;
import mpicbg.models.CoordinateTransformMesh;
import mpicbg.models.IllDefinedDataPointsException;
import mpicbg.models.NoninvertibleModelException;
import mpicbg.models.NotEnoughDataPointsException;
import mpicbg.models.Point;
import mpicbg.models.PointMatch;
import mpicbg.models.SimilarityModel2D;
import mpicbg.models.TranslationModel2D;
import mpicbg.trakem2.transform.AffineModel2D;
import mpicbg.trakem2.transform.CoordinateTransform;
import mpicbg.trakem2.transform.CoordinateTransformList;
import mpicbg.trakem2.transform.MovingLeastSquaresTransform2;
import mpicbg.trakem2.transform.TransformMeshMappingWithMasks;
import mpicbg.trakem2.transform.TransformMeshMappingWithMasks.ImageProcessorWithMasks;
public class NonLinearTransformMode extends GroupingMode {
@Override
protected void doPainterUpdate( final Rectangle r, final double m )
{
try
{
final CoordinateTransform mlst = createCT();
final SimilarityModel2D toWorld = new SimilarityModel2D();
toWorld.set( 1.0 / m, 0, r.x - ScreenPatchRange.pad / m, r.y - ScreenPatchRange.pad / m );
final mpicbg.models.CoordinateTransformList< mpicbg.models.CoordinateTransform > ctl = new mpicbg.models.CoordinateTransformList< mpicbg.models.CoordinateTransform >();
ctl.add( toWorld );
ctl.add( mlst );
ctl.add( toWorld.createInverse() );
final CoordinateTransformMesh ctm = new CoordinateTransformMesh( ctl, 32, r.width * m + 2 * ScreenPatchRange.pad, r.height * m + 2 * ScreenPatchRange.pad );
final TransformMeshMappingWithMasks< CoordinateTransformMesh > mapping = new TransformMeshMappingWithMasks< CoordinateTransformMesh >( ctm );
final HashMap<Paintable, GroupingMode.ScreenPatchRange<?>> screenPatchRanges = this.screenPatchRanges; // keep a pointer to the current list
for ( final GroupingMode.ScreenPatchRange spr : screenPatchRanges.values())
{
if (screenPatchRanges != this.screenPatchRanges) {
// List has been updated; restart painting
// TODO should it call itself: doPainterUpdate( r, m );
break;
}
spr.update( mapping );
}
}
catch ( final NotEnoughDataPointsException e ) {}
catch ( final NoninvertibleModelException e ) {}
catch ( final IllDefinedDataPointsException e ) {}
catch ( final Exception e ) { e.printStackTrace(); }
}
private class NonLinearTransformSource extends GroupingMode.GroupedGraphicsSource {
@Override
public void paintOnTop(final Graphics2D g, final Display display, final Rectangle srcRect, final double magnification) {
final Stroke original_stroke = g.getStroke();
final AffineTransform original = g.getTransform();
g.setTransform( new AffineTransform() );
g.setStroke( new BasicStroke( 1.0f ) );
for ( final Point p : points )
{
final double[] w = p.getW();
Utils.drawPoint( g, ( int )Math.round( magnification * ( w[ 0 ] - srcRect.x ) ), ( int )Math.round( magnification * ( w[ 1 ] - srcRect.y ) ) );
}
g.setTransform( original );
g.setStroke( original_stroke );
}
}
@Override
protected ScreenPatchRange createScreenPathRange(final PatchRange range, final Rectangle srcRect, final double magnification) {
return new NonLinearTransformMode.ScreenPatchRange(range, srcRect, magnification);
}
private static class ScreenPatchRange extends GroupingMode.ScreenPatchRange<TransformMeshMappingWithMasks<?>> {
ScreenPatchRange( final PatchRange range, final Rectangle srcRect, final double magnification ) {
super(range, srcRect, magnification);
}
@Override
public void update( final TransformMeshMappingWithMasks< ? > mapping )
{
ipTransformed.reset();
if ( mask == null )
{
mapping.map( ip, ipTransformed );
}
else
{
maskTransformed.reset();
final ImageProcessorWithMasks ipm = new ImageProcessorWithMasks( ip, mask, null );
final ImageProcessorWithMasks tpm = new ImageProcessorWithMasks( ipTransformed, maskTransformed, null );
mapping.map( ipm, tpm );
}
if (null != transformedImage) transformedImage.flush();
transformedImage = super.makeImage( ipTransformed, maskTransformed );
}
}
@Override
protected GroupingMode.GroupedGraphicsSource createGroupedGraphicSource() {
return new NonLinearTransformSource();
}
public NonLinearTransformMode(final Display display, final List<Displayable> selected) {
super(display, selected);
ProjectToolbar.setTool(ProjectToolbar.SELECT);
super.initThreads();
}
public NonLinearTransformMode(final Display display) {
this(display, display.getSelection().getSelected());
}
private final Collection< Point > points = new ArrayList< Point >();
private Point p_clicked = null;
@Override
public void mousePressed( final MouseEvent me, final int x_p, final int y_p, final double magnification )
{
/* find if clicked on a point */
p_clicked = null;
double min = Double.MAX_VALUE;
final Point mouse = new Point( new double[]{ x_p, y_p } );
final double a = 64.0 / magnification / magnification;
for ( final Point p : points )
{
final double sd = Point.squareDistance( p, mouse );
if ( sd < min && sd < a )
{
p_clicked = p;
min = sd;
}
}
if ( me.isShiftDown() )
{
if ( null == p_clicked )
{
/* add one */
try
{
if ( points.size() > 0 )
{
/*
* Create a pseudo-invertible (TransformMesh) for the screen.
*/
final CoordinateTransform mlst = createCT();
final SimilarityModel2D toWorld = new SimilarityModel2D();
toWorld.set( 1.0 / magnification, 0, srcRect.x, srcRect.y );
final SimilarityModel2D toScreen = toWorld.createInverse();
final mpicbg.models.CoordinateTransformList< mpicbg.models.CoordinateTransform > ctl = new mpicbg.models.CoordinateTransformList< mpicbg.models.CoordinateTransform >();
ctl.add( toWorld );
ctl.add( mlst );
ctl.add( toScreen );
final CoordinateTransformMesh ctm = new CoordinateTransformMesh(
ctl,
32,
( int )Math.ceil( srcRect.width * magnification ),
( int )Math.ceil( srcRect.height * magnification ) );
final double[] l = mouse.getL();
toScreen.applyInPlace( l );
ctm.applyInverseInPlace( l );
toWorld.applyInPlace( l );
}
points.add( mouse );
p_clicked = mouse;
}
catch ( final Exception e )
{
Utils.log( "Could not add point" );
e.printStackTrace();
}
}
else if ( Utils.isControlDown( me ) )
{
// remove it
points.remove(p_clicked);
p_clicked = null;
}
}
}
@Override
public void mouseDragged( final MouseEvent me, final int x_p, final int y_p, final int x_d, final int y_d, final int x_d_old, final int y_d_old )
{
if ( null != p_clicked )
{
final double[] w = p_clicked.getW();
w[ 0 ] += x_d - x_d_old;
w[ 1 ] += y_d - y_d_old;
painter.update();
}
}
@Override
public void mouseReleased( final MouseEvent me, final int x_p, final int y_p, final int x_d, final int y_d, final int x_r, final int y_r )
{
// bring to screen coordinates
mouseDragged( me, x_p, y_p, x_r, y_r, x_d, y_d );
p_clicked = null; // so isDragging can return the right state
}
@Override
public boolean isDragging()
{
return null != p_clicked;
}
private final void setUndoState() {
layer.getParent().addEditStep(new Displayable.DoEdits(new HashSet<Displayable>(originalPatches)).init(new String[]{"data", "at", "width", "height"}));
}
final private Future< Boolean > applyToPatch( final Patch patch ) throws Exception
{
final Rectangle pbox = patch.getCoordinateTransformBoundingBox();
final AffineTransform pat = new AffineTransform();
pat.translate( -pbox.x, -pbox.y );
pat.preConcatenate( patch.getAffineTransform() );
final AffineModel2D toWorld = new AffineModel2D();
toWorld.set( pat );
final CoordinateTransform mlst = createCT();
final CoordinateTransformList< CoordinateTransform > ctl = new CoordinateTransformList< CoordinateTransform >();
ctl.add( toWorld );
ctl.add( mlst );
ctl.add( toWorld.createInverse() );
patch.appendCoordinateTransform( ctl );
return patch.updateMipMaps();
}
@Override
public boolean apply()
{
return apply( null );
}
public boolean apply( final Set< Layer > sublist )
{
/* Set undo step to reflect initial state before any transformations */
setUndoState();
Bureaucrat.createAndStart( new Worker.Task( "Applying transformations" )
{
@Override
public void exec()
{
final ArrayList< Future< Boolean > > futures = new ArrayList< Future< Boolean > >();
synchronized ( updater )
{
/* apply to selected patches */
for ( final Paintable p : screenPatchRanges.keySet() )
{
if ( p instanceof Patch )
{
try
{
futures.add( applyToPatch( ( Patch )p ) );
}
catch ( final Exception e )
{
e.printStackTrace();
}
}
}
/* apply to other layers if there are any */
if ( !( sublist == null || sublist.isEmpty() ) )
{
for ( final Layer layer : sublist )
{
for ( final Displayable p : layer.getDisplayables( Patch.class ) )
{
try
{
futures.add( applyToPatch( ( Patch )p ) );
}
catch ( final Exception e )
{
e.printStackTrace();
}
}
}
}
}
/* Flush images */
for ( final GroupingMode.ScreenPatchRange<?> spr : new HashSet< GroupingMode.ScreenPatchRange<?> >( screenPatchRanges.values() ) )
{
spr.flush();
}
/* Wait until all mipmaps are regenerated */
for ( final Future<?> fu : futures )
try
{
fu.get();
}
catch ( final Exception ie )
{}
// Set undo step to reflect final state after applying transformations
setUndoState();
}
}, layer.getProject() );
super.quitThreads();
return true;
}
private CoordinateTransform createCT() throws Exception
{
final Collection< PointMatch > pm = new ArrayList<PointMatch>();
for ( final Point p : points )
{
pm.add( new PointMatch( new Point( p.getL() ), new Point( p.getW() ) ) );
}
/*
* TODO replace this with the desired parameters of the transformation
*/
final MovingLeastSquaresTransform2 mlst = new MovingLeastSquaresTransform2();
mlst.setAlpha( 1.0f );
Class< ? extends AbstractAffineModel2D< ? > > c = AffineModel2D.class;
switch (points.size()) {
case 1:
c = TranslationModel2D.class;
break;
case 2:
c = SimilarityModel2D.class;
break;
default:
break;
}
mlst.setModel( c );
mlst.setMatches( pm );
return mlst;
}
}