Commit 81c79161 authored by Tobias Pietzsch's avatar Tobias Pietzsch

Initial commit

parents
.DS_Store
**/..classpath
**/.project
**/.settings/
**/target/
*.orig
.classpath
.project
.settings
/target
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.scijava</groupId>
<artifactId>pom-scijava</artifactId>
<version>21.0.0</version>
</parent>
<groupId>sc.fiji</groupId>
<artifactId>ellipsoid-projection</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>Ellipsoid Surface Projection</name>
<description>Fit ellipsoid surface to spots and project onto sphere or cylinder</description>
<url>https://git.mpi-cbg.de/tomancaklab/ellipsoid-projection</url>
<inceptionYear>2016</inceptionYear>
<organization>
<name>MPI-CBG</name>
<url>https://www.mpi-cbg.de</url>
</organization>
<licenses>
<license>
<name>Simplified BSD License</name>
<distribution>repo</distribution>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>sc.fiji</groupId>
<artifactId>bigdataviewer-core</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>sc.fiji</groupId>
<artifactId>bigdataviewer_fiji</artifactId>
</dependency>
<dependency>
<groupId>net.imglib2</groupId>
<artifactId>imglib2</artifactId>
</dependency>
<dependency>
<groupId>net.imglib2</groupId>
<artifactId>imglib2-ij</artifactId>
</dependency>
<dependency>
<groupId>net.imglib2</groupId>
<artifactId>imglib2-realtransform</artifactId>
</dependency>
<dependency>
<groupId>net.imglib2</groupId>
<artifactId>imglib2-algorithm</artifactId>
</dependency>
</dependencies>
<developers>
<developer>
<id>tpietzsch</id>
<name>Tobias Pietzsch</name>
<email>pietzsch@mpi-cbg.de</email>
<url></url>
<organization>MPI-CBG</organization>
<organizationUrl>http://www.mpi-cbg.de/</organizationUrl>
<roles>
<role>founder</role>
<role>lead</role>
<role>developer</role>
<role>debugger</role>
<role>reviewer</role>
<role>support</role>
<role>maintainer</role>
</roles>
<timezone>+1</timezone>
</developer>
</developers>
<repositories>
<repository>
<id>imagej.public</id>
<url>http://maven.imagej.net/content/groups/public</url>
</repository>
</repositories>
<properties>
<package-name>de.mpicbg.ovaries</package-name>
<license.licenseName>bsd_2</license.licenseName>
<license.projectName>Ellipsoid Surface Projection</license.projectName>
<license.organizationName>Tomancak lab, MPI-CBG</license.organizationName>
<license.copyrightOwners>Tobias Pietzsch</license.copyrightOwners>
</properties>
<mailingLists><mailingList><name>none</name></mailingList></mailingLists>
<scm><tag>none</tag></scm>
<issueManagement><system>none</system></issueManagement>
<ciManagement><system>none</system></ciManagement>
<contributors><contributor><name>none</name></contributor></contributors>
</project>
package de.mpicbg.ovaries;
import gnu.trove.list.array.TDoubleArrayList;
import gnu.trove.list.array.TIntArrayList;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.imglib2.Interval;
import net.imglib2.display.RealARGBColorConverter;
import net.imglib2.type.numeric.ARGBType;
import net.imglib2.type.numeric.integer.UnsignedShortType;
import bdv.tools.brightness.RealARGBColorConverterSetup;
import bdv.tools.brightness.SetupAssignments;
import bdv.util.RealRandomAccessibleSource;
import bdv.viewer.Source;
import bdv.viewer.SourceAndConverter;
import bdv.viewer.ViewerPanel;
public class BlobsTab extends JPanel
{
private final ViewerPanel viewer;
private final SetupAssignments setupAssignments;
private final OvariesProjectionDialog dialog;
private final DetectionsOverlay detectionsOverlay;
private int detectionsSourceIndex;
private final RealARGBColorConverterSetup detectionsConverterSetup;
private final SourceAndConverter< UnsignedShortType > detectionsSourceAndConverter;
private double sigma;
private double minPeakValue;
public BlobsTab(
final ViewerPanel viewer,
final SetupAssignments setupAssignments,
final int detectionsSetupId,
final Data data,
final OvariesProjectionDialog ovariesProjectionDialog )
{
super( new BorderLayout() );
this.viewer = viewer;
this.setupAssignments = setupAssignments;
this.dialog = ovariesProjectionDialog;
final JPanel panel = new JPanel();
panel.setLayout( new BoxLayout( panel, BoxLayout.PAGE_AXIS ) );
sigma = 3;
final JSpinner sigmaSpinner = new JSpinner( new SpinnerNumberModel( sigma, 0.01, 30, 0.01 ) );
sigmaSpinner.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( final ChangeEvent e )
{
sigma = ( ( Double ) sigmaSpinner.getValue() ).doubleValue();
}
} );
final JPanel sigmaSpinnerPanel = new JPanel();
sigmaSpinnerPanel.setLayout( new BorderLayout( 10, 10 ) );
sigmaSpinnerPanel.add( new JLabel( "sigma" ), BorderLayout.LINE_START );
sigmaSpinnerPanel.add( sigmaSpinner, BorderLayout.CENTER );
panel.add( sigmaSpinnerPanel );
minPeakValue = 250;
final JSpinner minPeakSpinner = new JSpinner( new SpinnerNumberModel( minPeakValue, 0.0, 2000.0, 1.0 ) );
minPeakSpinner.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( final ChangeEvent e )
{
minPeakValue = ( ( Double ) minPeakSpinner.getValue() ).doubleValue();
}
} );
final JPanel minPeakSpinnerPanel = new JPanel();
minPeakSpinnerPanel.setLayout( new BorderLayout( 10, 10 ) );
minPeakSpinnerPanel.add( new JLabel( "min peak value" ), BorderLayout.LINE_START );
minPeakSpinnerPanel.add( minPeakSpinner, BorderLayout.CENTER );
panel.add( minPeakSpinnerPanel );
final Source< UnsignedShortType > detectionsFakeSource = new RealRandomAccessibleSource< UnsignedShortType >( null, new UnsignedShortType(), "detections", null )
{
@Override
public boolean isPresent( final int t )
{
return false;
}
@Override
public Interval getInterval( final int t, final int level )
{
return null;
}
};
final RealARGBColorConverter< UnsignedShortType > converter = new RealARGBColorConverter.Imp1< UnsignedShortType >( 0, 5000 );
converter.setColor( new ARGBType( Color.green.getRGB() ) );
detectionsSourceAndConverter = new SourceAndConverter< UnsignedShortType >( detectionsFakeSource, converter );
detectionsConverterSetup = new RealARGBColorConverterSetup( detectionsSetupId, converter );
detectionsConverterSetup.setViewer( viewer );
detectionsOverlay = new DetectionsOverlay( viewer, detectionsConverterSetup );
final JButton bcomputeButton = new JButton( "compute" );
bcomputeButton.addActionListener( new AbstractAction()
{
@Override
public void actionPerformed( final ActionEvent e )
{
final TDoubleArrayList sigmas = new TDoubleArrayList();
sigmas.add( sigma );
final TDoubleArrayList minPeakValues = new TDoubleArrayList();
minPeakValues.add( minPeakValue );
final TIntArrayList timepoints = new TIntArrayList();
timepoints.add( 0 );
data.computeDetections( sigmas, minPeakValues, timepoints );
detectionsOverlay.setDetections( data.getDetections() );
dialog.blobsDone();
}
} );
add( panel, BorderLayout.NORTH );
add( bcomputeButton, BorderLayout.SOUTH );
}
void addOverlays()
{
viewer.addSource( detectionsSourceAndConverter );
detectionsSourceIndex = viewer.getState().numSources() - 1;
detectionsOverlay.setDetectionsSourceIndex( detectionsSourceIndex );
setupAssignments.addSetup( detectionsConverterSetup );
viewer.getDisplay().addOverlayRenderer( detectionsOverlay );
viewer.addRenderTransformListener( detectionsOverlay );
}
void removeOverlays()
{
viewer.removeSource( detectionsSourceAndConverter.getSpimSource() );
setupAssignments.removeSetup( detectionsConverterSetup );
viewer.getDisplay().removeOverlayRenderer( detectionsOverlay );
viewer.removeTransformListener( detectionsOverlay );
}
}
package de.mpicbg.ovaries;
import bdv.tools.boundingbox.BoxSelectionPanel.Box;
import bdv.util.ModifiableInterval;
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JButton;
import javax.swing.JPanel;
import net.imglib2.FinalInterval;
import net.imglib2.Interval;
import net.imglib2.RealInterval;
import net.imglib2.display.RealARGBColorConverter;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.type.numeric.ARGBType;
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.util.Intervals;
import bdv.tools.boundingbox.BoundingBoxOverlay;
import bdv.tools.boundingbox.BoundingBoxOverlay.BoundingBoxOverlaySource;
import bdv.tools.boundingbox.BoundingBoxUtil;
import bdv.tools.boundingbox.BoxRealRandomAccessible;
import bdv.tools.boundingbox.BoxSelectionPanel;
import bdv.tools.brightness.RealARGBColorConverterSetup;
import bdv.tools.brightness.SetupAssignments;
import bdv.tools.transformation.TransformedSource;
import bdv.util.RealRandomAccessibleSource;
import bdv.viewer.DisplayMode;
import bdv.viewer.SourceAndConverter;
import bdv.viewer.ViewerPanel;
import bdv.viewer.VisibilityAndGrouping;
public class BoundingBoxTab extends JPanel
{
private final ModifiableInterval interval;
private final BoxRealRandomAccessible< UnsignedShortType > boxRealRandomAccessible;
private final ViewerPanel viewer;
private final SetupAssignments setupAssignments;
private final SourceAndConverter< UnsignedShortType > boxSourceAndConverter;
private final BoundingBoxOverlay boxOverlay;
private final RealARGBColorConverterSetup boxConverterSetup;
private final OvariesProjectionDialog dialog;
private int boxSourceIndex;
public BoundingBoxTab(
final ViewerPanel viewer,
final SetupAssignments setupAssignments,
final int boxSetupId,
final Data data,
final OvariesProjectionDialog ovariesProjectionDialog )
{
super( new BorderLayout() );
this.viewer = viewer;
this.setupAssignments = setupAssignments;
this.dialog = ovariesProjectionDialog;
final RealInterval globalRangeInterval = BoundingBoxUtil.getSourcesBoundingBoxReal( viewer.getState() );
final AffineTransform3D sourceTransform = new AffineTransform3D();
viewer.getState().getSources().get( 0 ).getSpimSource().getSourceTransform( 0, 0, sourceTransform );
final Interval rangeInterval = Intervals.smallestContainingInterval( BoundingBoxUtil.transformBoundingBoxReal( globalRangeInterval, sourceTransform.inverse() ) );
// final Interval rangeInterval = BoundingBoxUtil.getSourcesBoundingBox( viewer.getState() );
final long[] min = new long[ 3 ];
final long[] max = new long[ 3 ];
for ( int d = 0; d < 3; ++d )
{
min[ d ] = rangeInterval.min( d ) + rangeInterval.dimension( d ) / 4;
max[ d ] = rangeInterval.max( d ) - rangeInterval.dimension( d ) / 4;
}
final Interval initialInterval = new FinalInterval( min, max );
// create a procedural RealRandomAccessible that will render the bounding box
final UnsignedShortType insideValue = new UnsignedShortType( 1000 ); // inside the box pixel value is 1000
final UnsignedShortType outsideValue = new UnsignedShortType( 0 ); // outside is 0
interval = new ModifiableInterval( initialInterval );
boxRealRandomAccessible = new BoxRealRandomAccessible<>( interval, insideValue, outsideValue );
// create a bdv.viewer.Source providing data from the bbox RealRandomAccessible
final RealRandomAccessibleSource< UnsignedShortType > boxSource = new RealRandomAccessibleSource< UnsignedShortType >(
boxRealRandomAccessible, new UnsignedShortType(), "selection" )
{
@Override
public void getSourceTransform( final int t, final int level, final AffineTransform3D transform )
{
transform.set( sourceTransform );
}
@Override
public Interval getInterval( final int t, final int level )
{
return interval;
}
};
// set up a converter from the source type (UnsignedShortType in this case) to ARGBType
final RealARGBColorConverter< UnsignedShortType > converter = new RealARGBColorConverter.Imp1< UnsignedShortType >( 0, 5000 );
converter.setColor( new ARGBType( 0x00994499 ) ); // set bounding box color to magenta
boxConverterSetup = new RealARGBColorConverterSetup( boxSetupId, converter );
boxConverterSetup.setViewer( viewer );
// create a SourceAndConverter (can be added to the viewer for display)
final TransformedSource< UnsignedShortType > ts = new TransformedSource< UnsignedShortType >( boxSource );
boxSourceAndConverter = new SourceAndConverter< UnsignedShortType >( ts, converter );
boxOverlay = new BoundingBoxOverlay( new BoundingBoxOverlaySource()
{
@Override
public void getIntervalTransform( final AffineTransform3D transform )
{
ts.getSourceTransform( 0, 0, transform );
}
@Override
public Interval getInterval()
{
return interval;
}
} )
{
@Override
public void drawOverlays( final Graphics g )
{
if ( viewer.getVisibilityAndGrouping().isSourceVisible( boxSourceIndex ) )
super.drawOverlays( g );
}
};
// create a JPanel with sliders to modify the bounding box interval (boxRealRandomAccessible.getInterval())
final BoxSelectionPanel boxSelectionPanel = new BoxSelectionPanel(
new Box()
{
@Override
public void setInterval( final Interval i )
{
interval.set( i );
viewer.requestRepaint();
}
@Override
public Interval getInterval()
{
return interval;
}
},
rangeInterval );
final JButton setButton = new JButton( "set" );
setButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( final ActionEvent e )
{
final AffineTransform3D transform = new AffineTransform3D();
ts.getSourceTransform( 0, 0, transform );
data.setBoundingBox( interval, transform );
dialog.bboxDone();
}
} );
addComponentListener( new ComponentAdapter()
{
@Override
public void componentShown( final ComponentEvent e )
{
final VisibilityAndGrouping vg = viewer.getVisibilityAndGrouping();
if ( vg.getDisplayMode() != DisplayMode.FUSED )
{
for ( int i = 0; i < boxSourceIndex; ++i )
vg.setSourceActive( i, vg.isSourceVisible( i ) );
vg.setDisplayMode( DisplayMode.FUSED );
}
vg.setSourceActive( boxSourceIndex, true );
vg.setCurrentSource( boxSourceIndex );
}
} );
add( boxSelectionPanel, BorderLayout.NORTH );
add( setButton, BorderLayout.SOUTH );
}
void addOverlays()
{
viewer.addSource( boxSourceAndConverter );
boxSourceIndex = viewer.getState().numSources() - 1;
setupAssignments.addSetup( boxConverterSetup );
viewer.getDisplay().addOverlayRenderer( boxOverlay );
viewer.addRenderTransformListener( boxOverlay );
}
void removeOverlays()
{
viewer.removeSource( boxSourceAndConverter.getSpimSource() );
setupAssignments.removeSetup( boxConverterSetup );
viewer.getDisplay().removeOverlayRenderer( boxOverlay );
viewer.removeTransformListener( boxOverlay );
}
}
package de.mpicbg.ovaries;
import bdv.viewer.Interpolation;
import bdv.viewer.Source;
import de.mpicbg.ovaries.ellipsoid.Ellipsoid;
import ij.IJ;
import ij.ImagePlus;
import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessible;
import net.imglib2.RealRandomAccess;
import net.imglib2.exception.ImgLibException;
import net.imglib2.img.imageplus.FloatImagePlus;
import net.imglib2.img.imageplus.ImagePlusImgs;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.realtransform.RealViews;
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.util.LinAlgHelpers;
import net.imglib2.view.IntervalView;
import net.imglib2.view.Views;
public class CylindricalProjection
{
private final Ellipsoid ellipsoid;
private final int width;
private final int height;
private final Source< UnsignedShortType > source;
private final double[][] cylAxes;
private final double minProjectDistance;
private final double maxProjectDistance;
private final double sliceDistance;
private final int minTimepoint;
private final int maxTimepoint;
public CylindricalProjection(
final Ellipsoid ellipsoid,
final int width,
final int height,
final double minProjectDistance,
final double maxProjectDistance,
final double sliceDistance,
final int minTimepoint,
final int maxTimepoint,
final boolean flipZ,
final boolean alignY,
final Source< UnsignedShortType > source )
{
this.ellipsoid = ellipsoid;
this.width = width;
this.height = height;
this.minProjectDistance = minProjectDistance;
this.maxProjectDistance = maxProjectDistance;
this.sliceDistance = sliceDistance;
this.minTimepoint = minTimepoint;
this.maxTimepoint = maxTimepoint;
this.source = source;
this.cylAxes = SphericalProjection.getCylinderAxes( ellipsoid, flipZ, alignY );
}
public ImagePlus project()
{
final StringBuilder sb = new StringBuilder("\n\nCylindricalProjection\n");
sb.append( "Ellipsoid = " + ellipsoid + "\n");
sb.append( "cylAxes = " + LinAlgHelpers.toString( cylAxes ) + "\n");
sb.append( "width = " + width + "\n");
sb.append( "height = " + height + "\n");
sb.append( "minProjectDistance = " + minProjectDistance + "\n");
sb.append( "maxProjectDistance = " + maxProjectDistance + "\n");
sb.append( "sliceDistance = " + sliceDistance + "\n");
sb.append( "minTimepoint = " + minTimepoint + "\n");
sb.append( "maxTimepoint = " + maxTimepoint + "\n\n\n");
IJ.log( sb.toString() );
ImagePlus imp = null;
try
{
final int depth = ( int ) Math.ceil( ( maxProjectDistance - minProjectDistance ) / sliceDistance ) + 1;
final int numTimepoints = maxTimepoint - minTimepoint + 1;
final FloatImagePlus< FloatType > floats = ImagePlusImgs.floats( width, height, depth, numTimepoints );
imp = floats.getImagePlus();
imp.setDimensions( 1, depth, numTimepoints );
imp.show();
for ( int t = 0; t < numTimepoints; ++t )
{
for ( int z = 0; z < depth; ++z )
{
final double distance = minProjectDistance + z * sliceDistance;
final IntervalView< FloatType > slice = Views.hyperSlice( Views.hyperSlice( floats, 3, t ), 2, z );
projectPlane( distance, t + minTimepoint, slice );
}
}
}
catch ( final ImgLibException e )
{}
return imp;
}
public void projectPlane( final double distance, final int t, final RandomAccessible< FloatType > plane )
{
final RandomAccess< FloatType > out = plane.randomAccess();
final AffineTransform3D transform = new AffineTransform3D();
source.getSourceTransform( t, 0, transform );
final RealRandomAccess< UnsignedShortType > in = RealViews.affineReal( source.getInterpolatedSource( t, 0, Interpolation.NLINEAR ), transform ).realRandomAccess();
final double[] cylindrical = new double[ 3 ];
final double[] unit = new double[ 3 ];
final double[] cartesian = new double[ 3 ];
// point on ellipsoid
final double[] pe = new double[ 3 ];
// unit normal at pe
final double[] ne = new double[ 3 ];
final double r = 1.0;
final double ly = Math.atan2( 2 * Math.PI, width ) * r;
final long t0 = System.currentTimeMillis();
cylindrical[ 0 ] = r; // radial distance
for ( int ypi = 0; ypi < height; ++ypi )
{
out.setPosition( ypi, 1 );
cylindrical[ 2 ] = ly * ( height / 2 - ypi ); // height (z)
for ( int xpi = 0; xpi < width; ++xpi )
{
out.setPosition( xpi, 0 );
cylindrical[ 1 ] = xpi * 2 * Math.PI / width; // azimuth
cylindricalToCartesian( cylindrical, cartesian );
LinAlgHelpers.multT( cylAxes, cartesian, unit );
LinAlgHelpers.normalize( unit );
LinAlgHelpers.mult( ellipsoid.getPrecision(), unit, ne );
LinAlgHelpers.scale( unit, Math.sqrt( 1.0 / LinAlgHelpers.dot( unit, ne ) ), pe );
LinAlgHelpers.mult( ellipsoid.getPrecision(), pe, ne );
LinAlgHelpers.normalize( ne );
LinAlgHelpers.add( pe, ellipsoid.getCenter(), pe );
LinAlgHelpers.scale( ne, distance, ne );
LinAlgHelpers.add( pe, ne, pe );
in.setPosition( pe );
final double v = in.get().getRealDouble();
out.get().setReal( v );
}
}
final long t1 = System.currentTimeMillis();
System.out.println( (t1 - t0) + " ms" );
}
private static void cylindricalToCartesian( final double[] cylindrical, final double[] cartesian )
{
final double r = cylindrical[ 0 ];
final double azimuth = cylindrical[ 1 ];
cartesian[ 0 ] = r * Math.cos( azimuth );
cartesian[ 1 ] = r * Math.sin( azimuth );
cartesian[ 2 ] = cylindrical[ 2 ];
}
}
package de.mpicbg.ovaries;
import gnu.trove.list.array.TDoubleArrayList;
import gnu.trove.list.array.TIntArrayList;
import java.util.ArrayList;
import java.util.List;
import net.imglib2.FinalInterval;
import net.imglib2.Interval;
import net.imglib2.Point;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.RealLocalizable;
import net.imglib2.RealPoint;
import net.imglib2.algorithm.dog.DogDetection;
import net.imglib2.algorithm.dog.DogDetection.ExtremaType;
import net.imglib2.algorithm.localextrema.RefinedPeak;
import net.imglib2.converter.Converters;
import net.imglib2.converter.RealFloatConverter;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.util.Intervals;
import net.imglib2.view.Views;
import bdv.util.Affine3DHelpers;
import bdv.util.IntervalBoundingBox;
import bdv.viewer.Source;
import bdv.viewer.ViewerPanel;
import bdv.viewer.state.SourceState;
import de.mpicbg.ovaries.ellipsoid.Ellipsoid;
import de.mpicbg.ovaries.ellipsoid.SampleEllipsoids;
public class Data
{
private int dataSourceIndex;
private final ArrayList< Integer > projectionSourceIndices;
private final ViewerPanel viewer;
private FinalInterval bbInterval;
private final AffineTransform3D bbTransform;
private ArrayList< RealPoint > detections;
private Ellipsoid ellipsoid;
public Data( final ViewerPanel viewer )
{
this.viewer = viewer;
bbTransform = new AffineTransform3D();
projectionSourceIndices = new ArrayList< Integer >();
final List< SourceState< ? > > sources = viewer.getState().getSources();
for ( int i = 0; i < sources.size(); ++i )
{
final Source< ? > s = sources.get( i ).getSpimSource();
if ( UnsignedShortType.class.isInstance( s.getType() ) )