Super Sloppy Surface Reconstruction
Super sloppy surface reconstruction from planetary surface photographs or Scanning Electron Micrographs (SEM).
Contents
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.
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(); }