######################################################################################################
#
# MorphContour.sml (Stand Alone)
#
######################################################################################################
#
# Author:
# Dave Breitwisch, MicroImages, Inc.
# with contributions from Randy Smith, MicroImages, Inc.
#
# Started:
# 17 November 2005
#
# Updated:
# 18 July 2006
#
######################################################################################################
#
# Documentation: MorphContour.sml
#
# Purpose:
#
# The purpose of this Stand Alone SML script is to demonstrate the results of a surface-fitting
# process from contour lines using morphological functions as described in the paper "An Image Space
# Algorithm for Morphological Contour Interpolation" by Barrett, Mortensen and Taylor.
#
# The basic concept of this algorithm is to grow two known contour lines until their medial line is
# reached. This line is then given the average value of the two lines and is added to the resulting
# image. This new line is then used in the next iteration to find additional medial lines. The
# process continues until there are no new cell values being added to the final image.
#
# Some limitations of this algorithm include: 1) no filling of areas that are in between contour lines of
# the same value, such as ridges, saddles, peaks, or depressions; 2) there are also limitations based
# on the element structure used in the morphological functions, which lead to some unfilled cells if
# the output DEM is set to be an integer data type.
#
######################################################################################################
# Function Prototypes
func MyErosion(class RASTER Rin, class RASTER Rtemp, numeric maskValue);
proc MyDilation(class RASTER Rin, class RASTER Rout, class RASTER Rmask, numeric maskValue, numeric minValue);
proc MyRasterXOR(class RASTER Rin1, class RASTER Rin2, class RASTER Rout);
proc MyMedialLine(class RASTER Rin1, class RASTER Rin2, class RASTER Rmask);
func MyAddToAccumulator(class RASTER Rin, class RASTER Rmask, class RASTER Rout, numeric intContValue);
proc Main();
# Global Variables
class VECTOR Vect; # the input vector containing the 3D contours
class RASTER A; # raster with initial contour z values transfered from input vector
class RASTER T, U, V, W, Temp; # intermediate working rasters
class RASTER DEM; # the final output DEM raster
numeric numlins, numcols;
array numeric element[3,3];
string table$, field$; # names of line table and field containing elevation values
# Used as global rather then passing by reference since they are used in recursive functions
class RASTER FILL; #intermediate raster for filling holes
numeric value;
numeric currsize = 64;
numeric currpos = 0;
array numeric rowlist[currsize];
array numeric collist[currsize];
# Start process here
######################################################################################################
clear();
Main();
# Additional functions called
######################################################################################################
######################################################################################################
# This is the main function for the script.
proc Main() {
numeric done = 1, numRuns = 0;
# Create the Morphological element
element[1,1] = null;
element[1,2] = 0;
element[1,3] = null;
element[2,1] = 0;
element[2,2] = 0;
element[2,3] = 0;
element[3,1] = null;
element[3,2] = 0;
element[3,3] = null;
local class GEOREF vGeoref, rGeoref;
local numeric dbErr;
local class DATABASE vlineDB; # vector line database
local class DBTABLEINFO tInfo; # class instance for selected line table
local class DBFIELDINFO fInfo; # class instance for selected field with elevation values
local string outFilename$, outRastname$, outRastdesc$, tempFilename$;
local string prompt$;
local numeric rType; # numeric flag for output data type for DEM
local string rastType$; # raster data type string
local numeric cellSize;
local numeric k, elev;
# select the input vector object
PopupMessage("Select a vector object containing the vector contours with elevations:");
GetInputVector(Vect);
vGeoref = GetLastUsedGeorefObject(Vect);
if (vGeoref == 0) {
CloseVector(Vect);
PopupMessage("The vector you selected does not contain a valid georeference. Exiting the script....");
return;
}
# select the table and field in the line database that contains the elevation values
vlineDB = OpenVectorLineDatabase(Vect);
PopupMessage("Select line database table and field containing the contour line elevations:");
dbErr = PopupSelectTableField(vlineDB, table$, field$);
if (dbErr < 1) { # exit script if user cancels the table selection dialog
printf("No database table selected; exiting script.");
Exit();
}
tInfo = DatabaseGetTableInfo(vlineDB, table$);
fInfo = FieldGetInfoByName(tInfo, field$);
# prompt for the output project file and name and description for the output DEM
outFilename$ = GetOutputFileName("","Select or create a project file for the output DEM:", "rvc");
outRastname$ = PopupString("Enter a name for the output DEM raster:", "DEM");
outRastdesc$ = PopupString("Enter a description for the output DEM raster:", "");
# prompt for and set the data type for the output DEM raster
prompt$ = "Choose bit-depth of output elevation raster:\n" +
"Press Yes to accept the default: 16-bit signed integer.\n" +
"Press No to change to 32-bit floating point values.";
rType = PopupYesNo(prompt$, 1);
if (rType == 1) then
rastType$ = "16-bit signed";
else
rastType$ = "32-bit float";
# prompt for the cell size for the output DEM
cellSize = PopupNum("Enter desired cell size of output raster in meters", 30, 0);
# create temporary file for the contour raster R
tempFilename$ = CreateTempFileName();
# create a blank contour raster A using the extents of the input vector and the specified cell size
CreateRasterFromObject(Vect, A, tempFilename$, "RastCon", "", cellSize, cellSize, rastType$, vGeoref);
rGeoref = GetLastUsedGeorefObject(A);
numlins = NumLins(A);
numcols = NumCols(A);
# tranfer contour line elevation values to the contour raster
for k = 1 to Vect.$Info.NumLines {
elev = TableReadFieldNum(tInfo, field$, k);
VectorElementToRaster(Vect, A, "line", k, elev, vGeoref, rGeoref);
}
# Use Max and Min values outside the boundaries of the contour line values -or- use the Max and Min values of the raster
numeric max = GlobalMax(A) + 1;
#numeric Max = Vect.$Info.MaxZ + 1;
numeric min = GlobalMin(A);
#numeric Min = Vect.$Info.MinZ - 1;
# Create the necessary working objects
CreateRaster(T, tempFilename$, "T", "T", numlins, numcols, rastType$);
CreateRaster(U, tempFilename$, "U", "U", numlins, numcols, rastType$);
CreateRaster(V, tempFilename$, "V", "V", numlins, numcols, "binary");
CopyGeorefToObject(V, rGeoref);
CreateRaster(W, tempFilename$, "W", "W", numlins, numcols, rastType$);
CreateTempRaster(Temp, numlins, numcols, rastType$, 0);
while (done) {
done = 0;
numRuns++;
# Filling all inter-contour cells in mask rasters with the maximum value
RasterCopy(A, W);
RasterCopy(A, T);
numeric i, j;
for i = 1 to numlins {
for j = 1 to numcols {
if (A[i,j] == min) {
W[i,j] = max;
T[i,j] = max;
}
}
}
# use morphological functions to compute a set of medial lines for all contour pairs
while ( MyErosion(W, Temp, max) > 0) { }
MyDilation(W, U, T, max, min);
MyRasterXOR(W, U, V);
MyMedialLine(W, U, V);
done = MyAddToAccumulator(W, V, A, min);
print("Accumulator Count:", done, "\nRun Completed:", numRuns, "\n");
}
# close intermediate work rasters used in the morphological interpolation
CloseRaster(T);
CloseRaster(W);
CloseRaster(U);
CloseRaster(V);
# Fill blank areas left after morphological interpolation
CreateRaster(FILL, tempFilename$, "FILL", "FILL", numlins, numcols, rastType$);
RasterCopy(A, FILL);
# Set min values to maximum before filling by erosion
for i = 1 to numlins {
for j = 1 to numcols {
if (FILL[i,j] == min) FILL[i,j] = max;
}
}
while ( MyErosion(FILL, Temp, max) > 0 ) { }
CloseRaster(Temp);
CreateRaster(DEM, outFilename$, outRastname$, outRastdesc$, numlins, numcols, rastType$);
RasterCopy(FILL, DEM);
CreatePyramid(DEM);
CreateHistogram(DEM);
CloseRaster(DEM);
CloseRaster(A);
CloseRaster(FILL);
}
######################################################################################################
# This function copies the input raster to the output raster (Rtemp) and morphologically
# erodes the output raster cells that have a starting value equal to the maskValue (inter-contour
# cells). The function returns the number of cells that were eroded.
func MyErosion(class RASTER Rin, class RASTER Rtemp, numeric maskValue) {
RasterCopy(Rin, Rtemp);
local numeric r, c, er, ec, tempValue, newValue, count=0;
for r = 1 to NumLins(Rin) {
for c = 1 to NumCols(Rin) {
newValue = maskValue;
if (Rin[r,c] == maskValue) {
count++;
# For each cell in the element
for er = -1 to 1 {
for ec = -1 to 1 {
# If the element value isn't null
if (!IsNull(element[er+2,ec+2])) {
# If the element is within the bounds of Rin
if ( ((r + er) >= 1) and ((r + er) <= NumLins(W)) and ((c + ec) >= 1) and ((c + ec) <= NumCols(W)) ) {
tempValue = Rin[r + er, c + ec];
if (tempValue < newValue) {
newValue = tempValue;
} # if
} # if
} # if
} # for ec
} # for er
if (newValue != maskValue) {
count--;
Rtemp[r,c] = newValue;
} # if
} # for if
} # for c
} # for r
RasterCopy(Rtemp, Rin);
print("Erosion Count:", count);
return count;
} # end MyErosion
######################################################################################################
# This procedure copies the input raster to an output raster and then morphologically
# dilates the masked cells in output raster.
proc MyDilation(class RASTER Rin, class RASTER Rout, class RASTER Rmask, numeric maskValue, numeric minValue) {
RasterCopy(Rin, Rout);
local numeric r, c, er, ec, tempValue, newValue, count=0;
for r = 1 to NumLins(Rin) {
for c = 1 to NumCols(Rin) {
newValue = minValue;
if (Rmask[r,c] == maskValue) {
# For each cell in the element
for er = -1 to 1 {
for ec = -1 to 1 {
# If the element value isn't null
if (!IsNull(element[er+2,ec+2])) {
# If the element is within the bounds of Rin
if ( ((r + er) >= 1) and ((r + er) <= NumLins(W)) and ((c + ec) >= 1) and ((c + ec) <= NumCols(W)) ) {
tempValue = Rin[r + er, c + ec];
if (tempValue > newValue) {
newValue = tempValue;
} # if
} # if
} # if
} # for ec
} # for er
if (newValue != minValue) {
Rout[r,c] = newValue;
} # if
} # if
} # for c
} # for r
} # end of MyDilation
######################################################################################################
# This function compares the two input rasters and creates a binary output
# raster where a value of 1 means that the input raster cell values are different.
proc MyRasterXOR(class RASTER Rin1, class RASTER Rin2, class RASTER Rout) {
numeric i, j;
for i = 1 to NumLins(Rin1) {
for j = 1 to NumCols(Rin1) {
if (Rin1[i,j] != Rin2[i,j]) {
Rout[i,j] = 1;
} else {
Rout[i,j] = 0;
}
}
}
} # end MyRasterXOR()
######################################################################################################
# This function sets the cells in the raster that symbolizes the new medial lines
# (Rin1) to the average value of the two contour values that created it.
proc MyMedialLine(class RASTER Rin1, class RASTER Rin2, class RASTER Rmask) {
numeric i, j;
for i = 1 to NumLins(Rin1) {
for j = 1 to NumCols(Rin1) {
if (Rmask[i,j]) {
Rin1[i,j] = (Rin2[i,j] + Rin1[i,j]) / 2;
}
}
}
}
######################################################################################################
# This function adds the medial lines that were created to the final output raster
# (also used as the starting raster for the next iteration through the process). It returns the
# number of new cell values added to the output raster.
func MyAddToAccumulator(class RASTER Rin, class RASTER Rmask, class RASTER Rout, numeric intContValue) {
numeric i, j, count = 0;
for i = 1 to NumLins(Rin) {
for j = 1 to NumCols(Rin) {
if ( (Rmask[i,j]) and (Rout[i,j] == intContValue) ) {
Rout[i,j] = Rin[i,j];
count++;
}
}
}
return count;
}
printf ("%s %.2f %.2f %.2f", "Global Mean, Median and Variance values are:", GlobalMean (DEM), GlobalMedian (DEM),GlobalVariance(DEM));