README FOR SIMPLIFIED FLEXIBLE ISOSURFACES v.14

Written February 5-12, 2026

HISTORY:
========

This code was originally written for my Ph.D. thesis in 2000-2004.  I never posted it for
public download, largely because:
a)	I knew that it was alpha Ph.D. code and hacked together,
b)	I expected that I (or someone else) would rewrite it if it was useful, 
c)	I didn't want the support headaches.

While Scott Dillard did recode the contour tree algorithms (in libtourtre) and Julien 
Tierny did the same (in TTK), as did I (in PPP), the one thing which never got re-implemented
was the original user interface for flexible isosurfaces.  I have recently discovered that
it would be useful to have a version available for others to use.

In 2000-2004, vtk / Paraview were just becoming established, and were not the first choice
for writing experimental code, especially UI code.  Apple had only recently released the
beta of OSX and bought out the third party (Conix) who implemented OpenGL on OSX beta.  
The best toolkits for platform-independent GUIs were GLUI, which sat on top of GLUT, 
fltk, which at the time was very X11-focused, and QT, which wasn't then supported on OSX
and didn't yet support OpenGL. 
 
To make things worse, Apple's implementation of GLUT had a bug in its handling of 
subwindows, so GLUI didn't work. I therefore implemented a GUI toolkit myself on top of 
GLUT.  Weirdly, this decision contributed significantly to the longevity of the code, 
which can still be compiled in 2026, as it meant that the dependencies were about as 
minimal as you can get.

Later on, I built a version on QT, but their OpenGL support has changed since, and I have
not yet got around to getting this version working.

I also had a collaboration with STFC RAL in which we added extra geometric measures (v.11),
and have based this version on that code.  A further version v.12 was developed by my 
student Diana Marin to add Betti numbers, but was implemented for tetrahedral subdivisions 
rather than for the Marching Cubes cases.  Moreover, it has a dependency on Boost, and I 
would prefer not to add dependencies at this point. 

I forked a version of this in about 2018 to use as the verification / validation code for
the PPP (Parallel Peak Pruning) code now in vtk-m/viskores. This involved stripping out
*all* of the user interface code, leaving only the core data structures.  We found a few
minor bugs, and when time permits, I will track them down and transfer them to this code.

However, as of 2026, it is known to compile reliably on Unix and Mac systems. It does not
use cmake/qmake or any other build system: they too were not as ubiquitous in 2000 as they
are now, and I have never gone back to update the build.  Anyone wishing to compile will
need to adapt the makefile provided.

I fixed a crash-and-burn bug in the path seeds invoked by choosing the minimum isovalue on
the slider (involving simulation of simplicity and the Marching Cubes cases).  I also made
numerous updates to the code where old C idioms were hitting compiler warnings, and called
this v. 13.

Finally, I tracked down a bug in the UI layout code which I had hacked around by fixing
the window size: you can now resize the window. In the process, I had to update many of
the details of the UI elements, and took the opportunity to rescale and redesign the 
interface to modern monitors (when it was written, a 21" monitor at 1024x768 was 
high-resolution). I corrected a further bug in the interface related to the local 
contour code, and called the result v. 14.

ASSUMPTIONS & DESIGN:
=====================

Since this was experimental proof-of-concept code, I implemented it to accept a simple 
text float format for three-D data, with raw (integer) blocks added later.  While this 
made my job easier at the time, it has also meant that anyone who wants to use it has to 
convert their data to the format it accepts, which also contributed to my decision not to
release it.

All data is represented internally as floats, even if read in as integers.  This was an 
acceptable compromise at the time, but I would do it differently now.  Data is assumed
to be on a regular voxel grid, and the interface is based on my adaptation of contour trees
to the Marching Cubes cases (see thesis for details).

The code is structured to have one directory with the GLUT based toolkit, a second with
the application specific UI code, a third with the contour tree code, and a fourth with 
Ken Shoemake's ArcBall code (which has since had some minor edits to make it continue
to compile without warnings).  In addition, there is a 'dot' directory, for which see the
discussion below on graph layout, and a 'ppm' directory for capturing video sequences (see
below).

Internally, I ended up with a single class (HeightField.h) with far too many methods, and
broke them into individual .cpp files based on the type of task involved.  I always knew
that this would need refactoring, but it never happened, and we are now stuck with it 
(unless anyone volunteers to do so, which is unlikely and probably not the best use of 
anyone's time).

When I started coding this, a 300 MHz machine was state of the art, and the GPUs were 
considerably less advanced than they are at present. Vertex Arrays and Display Lists were
the best techniques for acceleration (VBOs weren't introduced until GL 1.5 in 2003). For 
larger (128^3 and up data sets), I found it useful to be able to turn display lists on and
off, and to rotate the visible contours by proxy (i.e. to show an arcball rather than a 
set of surfaces).

The GUI toolkit was intentionally simple (it was originally written because I needed a UI
for a class assignment), but I ended up implementing an arcball using Ken Shoemake's code
and linking it so that one could rotate either the widget or the main panel.  However, it
has some minor problems - for example, it tends to lose the last pixel in a panel, so the 
boxes around UI elements aren't what they should be.  Also, the bit that has given me the
most trouble over the years is the ability to resize the window.  As a result, I set it 
to reject resizing and jump back to it's coded size.

The UI was based on the standard metaphor of "select-and-operate", and was documented in
my PhD thesis.  Essentially, the idea was to start with a single isosurface or set of local
contours, then manipulate them individually. The contour tree could be displayed as well, 
or not: and I have recently found myself thinking that for learning how topology is useful
for an end user, it is probably better *NOT* to show the tree.

Be that as it may, there are five major vertical registers, from left to right:

Panel 0:	The main panel, with associated controls.  

		Top:	The main panel shows the currently active "flexible isosurface", which
				may or may not be an isosurface.  On loading, it defaults to an isosurface
				at 90% of the values represented (this meant I usually got small surfaces
				which rotated quickly).
				
				The currently selected contour(s) are in red (if coloured contours are not
				in use) or grey (if they are).
				
				Left Button:		maps to rotation, linked to the rotation arcball
				Middle Button:		maps to translation. Used to map to rotating the light
									position (linked to the light arcball) This code is
									still present, but commented out.
				Right Button:		maps to selection, using the back-buffer rendering hack.
									has a bug that I never found so it doesn't always work
									(see below for discussion). Clicking the background
									deselects the selection.

		Controls Underneath:
		
				Left Arcball:		Rotation, linked to main panel
				Right Arcball:		Light direction
				Top Slider:			Zoom (log scale)
				Bottom Slider:		Z-scale (some of my datasets were not uniform in XYZ)
				
				Coloured Contours:	Assigns (initially) random colours to contours, which
									helps to identify different contours
				Local Contours:		Switches to local contour mode (see below)
				Minima/Maxima:		Switches between local minima & maxima (see below)

				Delete Selection:	Removes the currently selected contour from view
				Delete Complement:	Removes all unselected contours from view
				Restore Deleted:	Undoes the last delete operation

Panel 1:	An isovalue slider, plus two buttons to edit it by small increments

Panel 2:	The contour tree, displayed next to the slider shared with the main panel.
			See discussion below for the layout problems of the tree.
			The flexible isosurface is represented as a set of "tags" in the contour tree,
			one for each currently active contour, colour-coded to match the main panel.

				Left Button:		Selects "tags" on arcs and drags them vertically in
									the tree, splitting or merging as necessary.
				Middle Button:		Inactive
				Right Button:		Drags supernodes horizontally for layout
				
			Controls Underneath:
				
				Show Tree:			Turns the tree on or off.  Believe it or not, my 
									intention was that showing the tree was the "expert"
									mode.  Also, if the tree has 2,000,000 edges, drawing
									it really slowed the interface down.
			
				Colour Tree:		Shows the tree with colours corresponding to the main
									pane.  This was useful for eg classifying features 
									manually
				
				Coloured Boxes:		These are small buttons that change the colour of the
									currently selected tag

				Dot Layout:			Calls dot/graphviz externally to lay the tree out. 
									See discussion below.
									
				Save Layout:		Saves the current tree layout. Expects user to give
									the file name at the command line.
				
				Load Layout:		Loads a tree layout. Also expects a file name at the
									command line. ABSOLUTELY NO ERROR CHECKING.

Panel 3:	The Collapse Pane, to control simplification.  This shows a log-log plot of 
			the number of tree edges against the smallest measure of the ones still in 
			the tree (see discussion of simplification below).

				Mouse Controls:		None
				
				Right-Hand Slider:	Controls simplification by specifying the measure 
									value to which to simplify
									
				Lower Slider:		Controls simplification by specifying the number of 
									superarcs to retain

				++:					Simplifies exactly one superarc
				
				--:					Adds exactly one superarc back in

Panel 4:	A measure slider that chooses the smallest feature (by measure) to display.
			Note that this is linked to the tree size slider using crosshairs.

				Radio Group:		Selects between 4 different measures of importance:
										Height (Persistence (ish))
										Volume
										Approximate Hypervolume (Height * Volume)
										More accurate Hypervolume (Riemann Sum)

LOCAL CONTOURS AND ISOVALUE CONTROL:
------------------------------------

When I was doing this, a previous author had defined "local isosurfaces" to be the 
largest contours including ONLY one maximum (see thesis for reference).  I ran with this
idea, by letting the user switch between regular isosurfaces and local contours.  

ISOSURFACES:	When local contours are NOT checked, the isovalue slider controls the
				isovalue of any contour/tag that is selected. If no contour/tag is 
				selected, dragging the slider selects a new isosurface, retaining the 
				contours as the initial value of a flexible isosurface, which can then be
				edited.
				
LOCAL CONTOURS:	The isovalue slider instead puts a contour/tag on every upper leaf 
				superarc as a percentage of it's value range. If a contour/tag is selected,
				only that one is manipulated (without splitting or merging with others).
				If none is selected, a new set of local contours is selected.
				
In both cases, contour selection is based on the current simplification of the contour 
tree (see below). 

CONTOUR TREE LAYOUT:
--------------------

In 2000-2004, graph layout was less advanced than it is at present, and laying out the 
contour tree did not map well to any of the existing approaches. It still doesn't, 
because the major constraint is that the y-coordinate of each supernode should be set to
it's (scaled) isovalue.  There is no constraint on the x-coordinate.

Formally speaking, it is always possible to lay a tree out in the plane with no crossings
if you can set x and y coordinates.  For the contour tree, there are configurations that
cannot be laid out with constrained y coordinates without crossings - see the thesis for
an example (probably the earliest example of what we later came to call "W-structures").

By this point, I was focussing on writing up rather than implementing the maximum possible,
and I therefore implemented the simplest solution: manual layout.  Later on, we published
a paper on contour tree layout, but this was never incorporated into my code.

The x-coordinate of each supernode is therefore set based on the average of the x,y,z 
coordinates of the critical point in the data, and can be adjusted by dragging the 
supernode laterally with the right mouse button in the interface.  Buttons were provided
beneath to allow saving and loading the layout (but with no error checking on whether it
was a valid layout).
										
I also implemented a button which writes the contour tree to a .dot/.gv file format, then
invokes dot externally to lay the graph out.  In order to force the y-coordinate, I created
a chain of 256 vertices and set the rank of each supernode to be the same as one of them.
While this DID work, it was very slow on the machines of the day, and I hard-coded a limit
of 100 supernodes for a tree, then changed it to 256.  This can still be hand-edited in the
code if necessary.  For this feature to work, graphviz must be installed on your system.

SIMPLIFICATION:
---------------

I based my simplification on a collapse hierarchy of edges. It is basically the same as
Valerio Pascucci's branch decomposition.  However, I had identified that it wasn't always
desireable to simplify based on the height of the edge (i.e. the value range / span), and
implemented several alternatives.

Later on, my student Petar Hristov proved that the height was NOT identical to the 
persistence used in persistent homology.  Most active researchers in the field were aware
of this, but it was useful to have it confirmed.

I handled this (in my thesis) with "geometric measures" which extended one of the 
observations from the 1997 Contour Spectrum paper, by showing that they could be 
pre-computed efficiently for all contours in the tree.

Height:			The difference in isovalue between the two supernodes on the superarc

Volume:			Ideally, the volume contained by the contour just above the saddle (or 
				below, if on a lower leaf).  For features in a W-structure or features 
				that intersect the boundary, the notion of "contained" becomes tricky - 
				see thesis for more.
			
				Since we assumed a cubic lattice of data, the volume was approximated by 
				counting the number of regular vertices on the dependent sub-tree at the 
				saddle.
			
Height * Volume:	This is a straightforward hack to balance the importance of height and 
				volume.  In a 2D analogy, height is the height of a mountain peak, area
				(the equivalent of volume) is the cross-section through the peak at the
				saddle, and volume is the amount of dirt that needs shifting to get rid 
				of the peak.  This can be approximated coarsely by height * area but is 
				given more accurately as the integral of (the function - the saddle value).
				In 3D, height * volume therefore approximates the hypervolume.
				
Riemann Hypervolume:	A more accurate approximation of hypervolume on a cubic lattice 
				is given by summing the values of the vertices inside the contour, then 
				subtracting the volume multiplied by the saddle value.
				
Of course, once you can approximate the integral of the function, you can approximate the
integral of ANY function defined over the same grid, although this was never implemented.

COMPILATION:
------------

When I was working on this, cmake/qmake and similar build systems were less common, and I
simply made a makefile manually.  Sample makefiles for Linux and OS X are provided, but 
you will probably need to tweak them.

SUBDIRECTORIES:
---------------

I separated out Ken Shoemake's ArcBall code, my GLUT toolkit, the HeightField code with
the core algorithmic code, and the project-specific interface code into their own
directories.  In addition, the dot directory is where temporary files for layout are
stored, and the ppm directory is for images produced by screen capture.  This is not 
currently enabled in the interface, as it was primarily for debug purposes, and in any 
event, general purpose screencap tools are now more sophisticated than they were.	
				
FILE FORMATS:
-------------
Internally, the data is stored in a custom class holding an array of floats.  

It was primarily written to read in ASCII text files (such as the ones in the Data folder).  
Floats and ints are both supported.

.TXT	./simpflexcube filename

Later on, in order to work with specific data sets, I added support for RAW integers 
(brick of ints), in which case the CLI needs to be:

.RAW	./simpflexcube filename.raw intSize xDim yDim zDim

(note: although theoretically intSize can be up to 8, I only ever used it with 1 or 2 
given the data available in 1998-2004).  There is a compile flag for dealing with endian 
problems, but this only handles 2-byte, and I never used it much, so be warned.

I also supported a number of variations on raw integers, basically all for specific 
collaborations:

.VOL	./simpflexcube filename.vol intSize
.MHA	./simpflexcube filename.mha intSize
.MIRG	./simpflexcube filename.mirg
.BIN	./simpflexcube filename.bin

None of these got used regularly, so .txt is by far the most reliable, with .raw not far 
behind.  All of the code for this is in HeightField/HeightField_Construction.cpp, 
and it wouldn't be too difficult to make changes, such as:

1.	upgrade from float to double internally - slightly risky, as it would require 
	updating all of the code
2.	changing the CLI switches - easy
3.	adding brick-of-floats - should be pretty easy, subject to endianness problems

However, most of these would be better dealt with by rebuilding the interface inside 
an existing package, to gain access to much more sophisticated file formats and robust
software engineering.