Super Sloppy Surface Reconstruction

QR for this page

From Fiji

Jump to: navigation, search

Super sloppy surface reconstruction from planetary surface photographs or Scanning Electron Micrographs (SEM).

Motivation

Sometimes, you have a picture of a surface and you want to see how it looks in 3-D. If your picture meets a few requirements, then reconstruction of an approximation of this surface is possible and, indeed, very simple. These requirements are:

  • The surface has no variance in illumination and color (like in SEM where everything is gold or at the moon where everything is cheese).
  • The surface is illuminated by a single parallel light-source from the left (rotate it if it comes from a different side).
  • The light-source illuminates the surface from an angle steeper or as steep as the steepest slope at the surface (that means: no shadows).
  • There is no occlusion of objects.

If these requirements are met, your picture is an arbitrarily scaled x-gradient of your surface. That is, integrating it alongside x will give you the surface at an arbitrary scale.

Example

See here a photograph of the lunar crater Hohmann original, integrated, and rendered as a 3D Surface Plot.

Original image
Integral in x
3D Surface Plot

Shortcomings

  • The approach is very sensitive to noise. Noise will result in a stripy pattern, because it is accumulated independently for each pixel row.
  • Lacking the constant initializer for integration, we assume that the average height for all pixel rows is equal and that the average slope per row is 0. Rows with a large mountain without a compensating valley will thus appear lower than they should.

Code

This is BeanShell and can be executed via Script Editor or BeanShell Interpreter or by dragging it as a file with extension `.bsh' into the Fiji toolbar. This script performs per-pixel operations in an interpreted language and, therefore, is very slow. If you really need more speed, compile the source into a Java class which is straight forward for BeanShell code.

import ij.*;
import ij.process.*;

float mean( FloatProcessor source, int first, int last ) {
	double sum = 0;
	for ( int i = first; i < last; ++i )
		sum += source.getf( i );
	return ( float )( sum / ( last - first ) );
}

/** source and target are assumed to have identical dimensions. */
void integrateRow( FloatProcessor source, FloatProcessor target, int row ) {
	final int first = row * source.getWidth();
	final int last = first + source.getWidth();
	final float dxMean = mean( source, first, last );
	
	/* integrate */
	double x = 0;
	double xMean = 0;
	for ( int i = first; i < last; ++i ) {
		final float dx = source.getf( i );
		x += dx - dxMean;
		target.setf( i, ( float )x );
		xMean += x;
	}
	xMean /= last - first;
	
	/* normalize */
	for ( int i = first; i < last; ++i )
		target.setf( i, target.getf( i ) - ( float )xMean );	
}

ImagePlus impSource = IJ.getImage();
FloatProcessor source = impSource.getProcessor().convertToFloat();
FloatProcessor target = new FloatProcessor( source.getWidth(), source.getHeight() );
ImagePlus impTarget = new ImagePlus( "I " + impSource.getTitle(), target );
impTarget.show();

for ( int i = 0; i < source.getHeight(); ++i ) {
	integrateRow( source, target, i );
	impTarget.updateAndDraw();	
}

See also