├── python ├── .gitignore ├── plot_persistence.py ├── export_mhd.py └── persistence.py ├── src ├── main │ ├── Makefile │ ├── VectorField.C │ ├── Simplify.C │ ├── Skeleton.C │ ├── Pores.C │ ├── SEDT.C │ └── PersistencePairs.C ├── util │ ├── Makefile │ ├── ncdiffstats.C │ ├── labyrinth.C │ ├── MergePhases.C │ ├── ncdump.C │ ├── checkSEDT.C │ ├── Subset.C │ └── pgmtonc.C ├── experimental │ ├── Makefile │ └── Classification.C ├── python │ ├── .gitignore │ └── Makefile ├── Makefile └── lib │ ├── OrderedMap.hpp │ ├── collections.hpp │ ├── PackedMap.hpp │ ├── stringUtils.hpp │ ├── SimpleComplex.hpp │ ├── netcdfIO.hpp │ ├── VertexMap.hpp │ ├── Partition.hpp │ ├── callables.hpp │ ├── performance.hpp │ ├── chainComplexExtraction.hpp │ ├── Set.hpp │ ├── restricted.hpp │ ├── MorseVectorField.hpp │ ├── traversals.hpp │ ├── vectorFieldExtraction.hpp │ ├── json.hpp │ └── simplification.hpp ├── .gitignore ├── Makefile ├── scripts ├── make-vector-field ├── compute-persistence └── hdf5tonc.py ├── test ├── Makefile ├── testVectorField.C ├── generative.hpp ├── testSimplification.C └── testPartition.C ├── cygwin.md └── README.md /python/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /src/main/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile 2 | -------------------------------------------------------------------------------- /src/util/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile 2 | -------------------------------------------------------------------------------- /src/experimental/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile 2 | -------------------------------------------------------------------------------- /src/python/.gitignore: -------------------------------------------------------------------------------- 1 | *.cpp 2 | *.pyc 3 | *.so.dSYM/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | # Dependency files for make 16 | *.P 17 | 18 | # Other temporary file 19 | *.gch 20 | 21 | # Directory for playing around in 22 | tmp/ 23 | 24 | # Directory for binaries 25 | bin/ 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: base main all src util python extra test clean 2 | 3 | base: main util 4 | 5 | all: base python extra 6 | 7 | main: 8 | $(MAKE) -C src/main 9 | 10 | util: 11 | $(MAKE) -C src/util 12 | 13 | python: 14 | $(MAKE) -C src/python 15 | 16 | extra: 17 | $(MAKE) -C src/experimental 18 | 19 | test: 20 | $(MAKE) -C test 21 | 22 | clean: 23 | $(MAKE) -C src/main clean 24 | $(MAKE) -C src/util clean 25 | $(MAKE) -C src/python clean 26 | $(MAKE) -C src/experimental clean 27 | $(MAKE) -C test clean 28 | -------------------------------------------------------------------------------- /scripts/make-vector-field: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | 5 | bindir=${BINDIR:-$(dirname $0)/../bin} 6 | timeCmd=${TIME:-"/usr/bin/time -v"} 7 | 8 | input=$(echo $1 | sed 's/[\._]nc$//') 9 | name=$(basename $input) 10 | shift 1 11 | 12 | if [[ $name =~ ^segmented ]] 13 | then 14 | echo "=== SignedEuclideanDistanceTransform ===" 15 | scalars=$(echo $name | sed 's/^segmented/tomo_float/')_SEDT 16 | $timeCmd $bindir/SEDT ${input}[._]nc ${scalars}.nc 17 | else 18 | scalars=$input 19 | fi 20 | 21 | output=$(basename $scalars | sed 's/^tomo_float/vector_field/')_GVF_SMP 22 | 23 | echo "=== VectorField ===" 24 | $timeCmd $bindir/VectorField ${scalars}[._]nc __field.nc 25 | echo '=== Simplify ===' 26 | $timeCmd $bindir/Simplify ${scalars}[._]nc __field[._]nc ${output}.nc $* 27 | 28 | rm -rf __* 29 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | CXXSTD = -std=c++11 2 | CXXWARN = -Wall -Wextra -pedantic 3 | CXXOPTIM = -O3 4 | CXXDEBUG = -g 5 | CXXLIBS = -I ../src/lib 6 | CXXFLAGS = $(CXXSTD) $(CXXWARN) $(CXXDEBUG) $(CXXOPTIM) $(CXXLIBS) 7 | BINDIR = bin 8 | 9 | MAKEDEPEND= $(CXX) $(CXXFLAGS) -MM -MP -MT $(BINDIR)/$* -o $*.P $< 10 | 11 | .SUFFIXES: .C .x 12 | 13 | $(BINDIR)/% : %.C 14 | @$(MAKEDEPEND); 15 | $(CXX) $(CXXFLAGS) $< -o $@ 16 | 17 | 18 | SRCS := $(wildcard *.C) 19 | 20 | PROGRAMS = $(SRCS:.C=) 21 | 22 | all: build 23 | @ \ 24 | for file in $(PROGRAMS); do \ 25 | echo; \ 26 | echo '===' $$file '==='; \ 27 | $(BINDIR)/$$file; \ 28 | done; \ 29 | echo 30 | 31 | build: $(BINDIR) $(SRCS:%.C=$(BINDIR)/%) 32 | 33 | $(BINDIR): 34 | mkdir -p $(BINDIR) 35 | 36 | clean: 37 | rm -f *.P 38 | 39 | -include $(SRCS:.C=.P) 40 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | UNAME := $(shell uname) 2 | 3 | GIT_REV = $(shell git rev-parse HEAD || "unknown") 4 | GIT_DATE = $(shell git show -s --format=%ci $(GIT_REV) || "unknown") 5 | CXXDEFS = -DGIT_REVISION="\"$(GIT_REV)\"" -DGIT_TIMESTAMP="\"$(GIT_DATE)\"" 6 | 7 | CXXSTD = -std=c++11 8 | CXXWARN = -Wall -Wextra -pedantic 9 | CXXDEBUG = -g 10 | CXXOPTIM = -O3 11 | CXXLIBS = -I ../lib 12 | CXXFLAGS = $(CXXSTD) $(CXXDEFS) $(CXXWARN) $(CXXDEBUG) $(CXXOPTIM) $(CXXLIBS) 13 | BINDIR = ../../bin 14 | 15 | MAKEDEPEND= $(CXX) $(CXXFLAGS) -MM -MP -MT $(BINDIR)/$* -o $*.P $< 16 | 17 | .PHONY: all clean 18 | 19 | .SUFFIXES: .C 20 | 21 | $(BINDIR)/% : %.C 22 | @$(MAKEDEPEND); 23 | $(CXX) $(CXXFLAGS) $< -o $@ 24 | 25 | 26 | SRCS := $(wildcard *.C) 27 | 28 | all: $(BINDIR) $(SRCS:%.C=$(BINDIR)/%) 29 | 30 | $(BINDIR): 31 | mkdir -p $(BINDIR) 32 | 33 | clean: 34 | rm -f *.P 35 | 36 | -include $(SRCS:.C=.P) 37 | -------------------------------------------------------------------------------- /scripts/compute-persistence: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | 5 | bindir=${BINDIR:-$(dirname $0)/../bin} 6 | timeCmd=${TIME:-"/usr/bin/time -v"} 7 | 8 | input=$(echo $1 | sed 's/[\._]nc$//') 9 | name=$(basename $input) 10 | shift 1 11 | 12 | rm -f $log 13 | 14 | if [[ $name =~ ^segmented ]] 15 | then 16 | echo "=== SignedEuclideanDistanceTransform ===" >&2 17 | scalars=$(echo $name | sed 's/^segmented/tomo_float/')_SEDT 18 | $timeCmd $bindir/SEDT ${input}[._]nc ${scalars}.nc 19 | echo >&2 20 | else 21 | scalars=$input 22 | fi 23 | 24 | field=$(basename $scalars | sed 's/^tomo_float/vector_field/')_GVF_SMP 25 | 26 | echo "=== VectorField ===" >&2 27 | $timeCmd $bindir/VectorField ${scalars}[._]nc __field.nc 28 | echo >&2 29 | 30 | echo '=== Simplify ===' >&2 31 | $timeCmd $bindir/Simplify ${scalars}[._]nc __field[._]nc ${field}.nc $* 32 | echo >&2 33 | 34 | pairs=$(echo $field | sed 's/^vector_field/persistence/')_PP 35 | 36 | echo "=== Persistence ===" >&2 37 | $timeCmd $bindir/PersistencePairs ${scalars}[._]nc ${field}[._]nc ${pairs}.txt 38 | echo >&2 39 | 40 | rm -rf __* 41 | -------------------------------------------------------------------------------- /cygwin.md: -------------------------------------------------------------------------------- 1 | 1) Packages required for `make` or `make main` (just the C++ programs): 2 | 3 | gcc-g++ (Devel) 4 | make (Devel) 5 | git (Devel) 6 | time (Utils) 7 | 8 | 9 | 2) Additional packages required for `make python` (Python wrappers): 10 | 11 | python-numpy (Python) 12 | python-cython (Python) 13 | 14 | After running `make python`, rename the file `bin/MorseAnalysis.so` to `bin/MorseAnalysis.dll`. 15 | 16 | 17 | 3) Additional packages required in order to make plots in python (file output only): 18 | 19 | libX11-devel (Libs) 20 | libfreetype-devel (Libs) 21 | 22 | With those packages installed, run Cygwin as administrator and issue the following commands: 23 | 24 | easy_install-2.7 pip 25 | 26 | (replace the "2.7" with the Python version you're using.) 27 | 28 | pip install matplotlib 29 | 30 | The script `plot_basins.py` in the `python/` directory has an option `-o` for writing the output to a file. The script `plot_persistence.py` does not have such an option yet, as it is essentially just a template for writing custom scripts. The only change needed is to replace the line `plt.show()` at the end of the file with something like `plot.savefig("figure.png")`. 31 | -------------------------------------------------------------------------------- /src/python/Makefile: -------------------------------------------------------------------------------- 1 | PYTHON_PREFIX = $(shell python -c "import sys; print sys.prefix") 2 | PYTHON_VERSION = $(shell python -c "import sys;\ 3 | print '%d.%d' % (sys.version_info.major, sys.version_info.minor)") 4 | 5 | PYTHON_INCL = -I ${PYTHON_PREFIX}/include/python${PYTHON_VERSION} 6 | NUMPY_INCL = -I $(shell python -c "import numpy; print numpy.get_include()") 7 | EXTRA_INCL = -I ../lib 8 | 9 | ifeq ($(CXX),c++) 10 | CXXWARN = -Wno-\#warnings 11 | else 12 | CXXWARN = -Wno-cpp 13 | endif 14 | 15 | CXXOPTS = -std=c++11 -g -O3 $(CXXWARN) 16 | CXXFLAGS = $(CXXOPTS) $(PYTHON_INCL) $(NUMPY_INCL) $(EXTRA_INCL) 17 | BINDIR = ../../bin 18 | LDFLAGS = -L${PYTHON_PREFIX}/lib -lpython${PYTHON_VERSION} 19 | 20 | MAKEDEPEND = $(CXX) $(CXXFLAGS) -MM -MP -MT $(BINDIR)/$*.so -o $*.P $< 21 | 22 | .PHONY: all clean 23 | 24 | .SUFFIXES: .pyx .cpp .so 25 | 26 | .pyx.cpp: 27 | cython --cplus $< 28 | 29 | $(BINDIR)/%.so : %.cpp 30 | @$(MAKEDEPEND) 31 | $(CXX) $(CXXFLAGS) --shared -fPIC $< -o $@ $(LDFLAGS) 32 | (cd ../../python && ln -nsf ../bin/$*.so .) 33 | 34 | 35 | SRCS := $(wildcard *.pyx) 36 | 37 | all: $(BINDIR) $(SRCS:%.pyx=$(BINDIR)/%.so) 38 | 39 | $(BINDIR): 40 | mkdir -p $(BINDIR) 41 | 42 | clean: 43 | rm -f *.cpp *.pyc 44 | 45 | 46 | -include $(SRCS:.pyx=.P) 47 | -------------------------------------------------------------------------------- /scripts/hdf5tonc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pandas as pd 3 | from netCDF4 import Dataset 4 | import os 5 | import sys 6 | 7 | import argparse 8 | 9 | parser = argparse.ArgumentParser(description='Process some integers.') 10 | parser.add_argument('input', type=str, 11 | help='The input hdf5 file') 12 | parser.add_argument('output', type=str, 13 | help='The output netCDF file') 14 | 15 | args = parser.parse_args() 16 | 17 | try: 18 | hdf = pd.read_hdf(args.input).as_matrix() 19 | except IOError: 20 | print('%s not found' % args.input) 21 | sys.exit() 22 | 23 | if os.path.isfile(args.output): 24 | r = raw_input('%s already exists on disk. Overwrite? [Y/n] ' % args.output).lower() 25 | while r not in ['', 'y', 'n']: 26 | r = raw_input('"%s" is an invalid answer. Please input "y" or "n" ' % r).lower() 27 | 28 | if r == 'n': 29 | sys.exit() 30 | 31 | nc = Dataset(args.output, 'w', format='NETCDF3_CLASSIC') 32 | 33 | if len(hdf.shape) <= 3: 34 | xyz = ['x', 'y', 'z'][:len(hdf.shape)] 35 | for i, v in enumerate(xyz): 36 | nc.createDimension(v, hdf.shape[i]) 37 | else: 38 | print('Only 1, 2 and 3D supported') 39 | sys.exit() 40 | 41 | data = nc.createVariable('data', 'f8', xyz) 42 | data[:,:,:] = hdf.copy() 43 | 44 | nc.close() 45 | -------------------------------------------------------------------------------- /src/lib/OrderedMap.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * OrderedMap.hpp 6 | * 7 | * A map that preserves insertion order. 8 | * 9 | * Olaf Delgado-Friedrichs may 15 10 | * 11 | */ 12 | 13 | 14 | #ifndef ANU_AM_DIAMORSE_ORDEREDMAP_HPP 15 | #define ANU_AM_DIAMORSE_ORDEREDMAP_HPP 16 | 17 | #include 18 | #include 19 | 20 | namespace anu_am 21 | { 22 | namespace diamorse 23 | { 24 | 25 | 26 | 27 | template 28 | class OrderedMap 29 | { 30 | std::map _map; 31 | std::vector _keysInOrder; 32 | 33 | public: 34 | OrderedMap() 35 | { 36 | } 37 | 38 | OrderedMap(K const key, V const value) 39 | { 40 | this->set(key, value); 41 | } 42 | 43 | V operator()(K const key) const 44 | { 45 | return _map.at(key); 46 | } 47 | 48 | bool hasKey(K const key) const 49 | { 50 | return _map.count(key) > 0; 51 | } 52 | 53 | size_t size() const 54 | { 55 | return _keysInOrder.size(); 56 | } 57 | 58 | K keyAt(size_t const i) const 59 | { 60 | return _keysInOrder.at(i); 61 | } 62 | 63 | V at(size_t const i) const 64 | { 65 | return _map.at(_keysInOrder.at(i)); 66 | } 67 | 68 | void set(K const key, V const value) 69 | { 70 | if (!hasKey(key)) 71 | _keysInOrder.push_back(key); 72 | _map[key] = value; 73 | } 74 | 75 | OrderedMap& operator()(K const key, V const value) 76 | { 77 | set(key, value); 78 | return *this; 79 | } 80 | }; 81 | 82 | 83 | 84 | } // namespace diamorse 85 | } // namespace anu_am 86 | 87 | #endif // !ANU_AM_DIAMORSE_ORDEREDMAP_HPP 88 | -------------------------------------------------------------------------------- /src/lib/collections.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2013 The Australian National University 4 | * 5 | * collections.hpp 6 | * 7 | * Some utility code to make working with C++ collections a little easier. 8 | * 9 | * Olaf Delgado-Friedrichs jan 14 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_COLLECTIONS_HPP 14 | #define ANU_AM_DIAMORSE_COLLECTIONS_HPP 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace anu_am 23 | { 24 | namespace diamorse 25 | { 26 | 27 | 28 | template 29 | std::ostream& print(std::ostream& out, Iter begin, Iter const end); 30 | 31 | template 32 | std::ostream& operator<<(std::ostream& out, std::pair const& p) 33 | { 34 | out << "(" << p.first << ", " << p.second << ")"; 35 | return out; 36 | } 37 | 38 | template 39 | std::ostream& operator<<(std::ostream& out, std::vector const& v) 40 | { 41 | return print(out, v.begin(), v.end()); 42 | } 43 | 44 | template 45 | std::ostream& operator<<(std::ostream& out, std::set const& v) 46 | { 47 | return print(out, v.begin(), v.end()); 48 | } 49 | 50 | template 51 | std::ostream& operator<<(std::ostream& out, std::map const& v) 52 | { 53 | return print(out, v.begin(), v.end()); 54 | } 55 | 56 | template 57 | std::ostream& print(std::ostream& out, Iter begin, Iter const end) 58 | { 59 | Iter p = begin; 60 | if (p != end) 61 | { 62 | out << *p++; 63 | while (p != end) 64 | out << " " << *p++; 65 | } 66 | return out; 67 | } 68 | 69 | 70 | 71 | } // namespace diamorse 72 | } // namespace anu_am 73 | 74 | #endif //!ANU_AM_DIAMORSE_COLLECTIONS_HPP 75 | -------------------------------------------------------------------------------- /src/lib/PackedMap.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * PackedMap.h 6 | * 7 | * A packed data structure with 4 bits for each entry. 8 | * 9 | * Olaf Delgado-Friedrichs jan 14 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_PACKEDMAP_H 14 | #define ANU_AM_DIAMORSE_PACKEDMAP_H 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | namespace anu_am 22 | { 23 | namespace diamorse 24 | { 25 | 26 | 27 | class PackedMap 28 | { 29 | typedef uint8_t Value; 30 | typedef uint32_t DataItem; 31 | 32 | public: 33 | typedef Value value_type; 34 | typedef std::shared_ptr > DataPtr; 35 | 36 | PackedMap() 37 | { 38 | } 39 | 40 | PackedMap(size_t const size, 41 | Value const defaultValue = Value()) 42 | : defaultValue_(defaultValue * 0x11111111), 43 | data_(new std::vector((size + 7)/ 8, defaultValue_)) 44 | { 45 | } 46 | 47 | PackedMap(DataPtr data, 48 | Value const defaultValue = Value()) 49 | : defaultValue_(defaultValue), 50 | data_(data) 51 | { 52 | } 53 | 54 | void clear() 55 | { 56 | for (size_t i = 0; i < data_->size(); ++i) 57 | data_->at(i) = defaultValue_; 58 | } 59 | 60 | Value get(size_t const v) const 61 | { 62 | return (data_->at(v >> 3) >> shift(v)) & 15; 63 | } 64 | 65 | void set(size_t const v, Value const x) 66 | { 67 | data_->at(v >> 3) ^= ((x & 15) ^ get(v)) << shift(v); 68 | } 69 | 70 | DataPtr const data() const 71 | { 72 | return data_; 73 | } 74 | 75 | private: 76 | DataItem defaultValue_; 77 | DataPtr data_; 78 | 79 | size_t shift(size_t const v) const 80 | { 81 | return 4 * (v & 7); 82 | } 83 | }; 84 | 85 | 86 | } // namespace diamorse 87 | } // namespace anu_am 88 | 89 | #endif // !ANU_AM_DIAMORSE_PACKEDMAP_H 90 | -------------------------------------------------------------------------------- /src/lib/stringUtils.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * stringUtils.hpp 6 | * 7 | * Some functions for processing strings. 8 | * 9 | * Olaf Delgado-Friedrichs may 16 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_STRINGUTILS_HPP 14 | #define ANU_AM_STRINGUTILS_HPP 15 | 16 | #include 17 | #include 18 | 19 | namespace anu_am 20 | { 21 | namespace stringutils 22 | { 23 | 24 | bool startsWith(std::string const str, std::string const prefix) 25 | { 26 | return str.substr(0, prefix.size()) == prefix; 27 | } 28 | 29 | bool endsWith(std::string const str, std::string const prefix) 30 | { 31 | return str.substr(str.size() - prefix.size()) == prefix; 32 | } 33 | 34 | std::string stripLeading(std::string const str, std::string const prefix) 35 | { 36 | if (startsWith(str, prefix)) 37 | return str.substr(prefix.size()); 38 | else 39 | return str; 40 | } 41 | 42 | std::vector split(std::string const str, char const sep) 43 | { 44 | std::vector result; 45 | 46 | size_t pos = 0; 47 | while (pos < str.size()) 48 | { 49 | size_t next = str.find(sep, pos); 50 | if (next == std::string::npos) 51 | next = str.size(); 52 | 53 | result.push_back(str.substr(pos, next - pos)); 54 | pos = next + 1; 55 | } 56 | 57 | return result; 58 | } 59 | 60 | std::string replaceAll(std::string const str, 61 | char const sep, 62 | std::string const replacement) 63 | { 64 | std::ostringstream ss; 65 | 66 | size_t pos = 0; 67 | while (pos < str.size()) 68 | { 69 | size_t next = str.find(sep, pos); 70 | if (next == std::string::npos) 71 | next = str.size(); 72 | 73 | ss << str.substr(pos, next - pos); 74 | if (next < str.size()) 75 | ss << replacement; 76 | 77 | pos = next + 1; 78 | } 79 | 80 | return ss.str(); 81 | } 82 | 83 | } // namespace stringutils 84 | } // namespace anu_am 85 | 86 | #endif // ANU_AM_STRINGUTILS_HPP 87 | -------------------------------------------------------------------------------- /src/lib/SimpleComplex.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2013 The Australian National University 4 | * 5 | * SimpleComplex.hpp 6 | * 7 | * A trivial chain complex implementation with scalar per-cell values. 8 | * 9 | * Olaf Delgado-Friedrichs aug 13 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_SIMPLEXCOMPLEX_HPP 14 | #define ANU_AM_DIAMORSE_SIMPLEXCOMPLEX_HPP 15 | 16 | #include 17 | 18 | #include 19 | 20 | namespace anu_am 21 | { 22 | namespace diamorse 23 | { 24 | 25 | 26 | class SimpleComplex 27 | { 28 | public: 29 | typedef size_t cell_id_type; 30 | 31 | private: 32 | typedef cell_id_type Cell; 33 | 34 | std::shared_ptr > dims_; 35 | std::shared_ptr > scalars_; 36 | std::shared_ptr > > faceLists_; 37 | 38 | public: 39 | SimpleComplex() {} 40 | 41 | SimpleComplex(std::vector const& dims, 42 | std::vector const& scalars, 43 | std::vector > const& faceLists) 44 | : dims_(new std::vector(dims)), 45 | scalars_(new std::vector(scalars)), 46 | faceLists_(new std::vector >(faceLists)) 47 | { 48 | } 49 | 50 | /// The dimension of the complex is always 3. 51 | int dimension() const 52 | { 53 | return 3; 54 | } 55 | 56 | /// The total number of cells in the complex. 57 | size_t nrCells() const 58 | { 59 | return dims_->size(); 60 | } 61 | 62 | /// The dimension of a given cell. 63 | int cellDimension(Cell const id) const 64 | { 65 | return dims_->at(id); 66 | } 67 | 68 | /// The scalar value for a given cell. 69 | float cellValue(Cell const id) const 70 | { 71 | return scalars_->at(id); 72 | } 73 | 74 | /// The list of (highest-dimensional proper) faces for a given cell. 75 | std::vector cellFaces(Cell const id) const 76 | { 77 | return faceLists_->at(id); 78 | } 79 | }; 80 | 81 | 82 | 83 | } // namespace diamorse 84 | } // namespace anu_am 85 | 86 | #endif // !ANU_AM_DIAMORSE_SIMPLEXCOMPLEX_HPP 87 | -------------------------------------------------------------------------------- /src/lib/netcdfIO.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * netcdfIO.hpp 6 | * 7 | * IO classes to be used in conjection with the generic NetCDF library. 8 | * 9 | * Olaf Delgado-Friedrichs jan 15 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_NETCDF_IO_HPP 14 | #define ANU_AM_NETCDF_IO_HPP 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | 23 | namespace anu_am 24 | { 25 | namespace netcdf 26 | { 27 | 28 | class FileBuffer 29 | { 30 | typedef std::vector Data; 31 | 32 | std::shared_ptr _data; 33 | 34 | Data* contents(std::string const path) 35 | { 36 | std::ifstream file(path.c_str(), 37 | std::ios::in|std::ios::binary|std::ios::ate); 38 | 39 | if (file.is_open()) 40 | { 41 | std::streampos const size = file.tellg(); 42 | Data* result = new Data(size); 43 | 44 | file.seekg(0, std::ios::beg); 45 | file.read((char*) &result->at(0), size); 46 | file.close(); 47 | 48 | return result; 49 | } 50 | else 51 | throw std::runtime_error("Could not open file "+path); 52 | } 53 | 54 | public: 55 | explicit FileBuffer(std::string const path) 56 | : _data(contents(path)) 57 | { 58 | } 59 | 60 | uint8_t get(size_t const offset) const 61 | { 62 | return _data->at(offset); 63 | } 64 | }; 65 | 66 | 67 | struct Writer 68 | { 69 | Writer() {} 70 | 71 | void write(uint8_t const val) 72 | { 73 | std::cout << (char) val; 74 | } 75 | }; 76 | 77 | 78 | class FileWriter 79 | { 80 | std::shared_ptr _file; 81 | 82 | public: 83 | FileWriter() {} 84 | 85 | explicit FileWriter(std::string const path) 86 | : _file(new std::ofstream(path.c_str(), std::ios::binary)) 87 | { 88 | } 89 | 90 | void write(uint8_t const val) 91 | { 92 | std::vector buf; 93 | buf.push_back(val); 94 | _file->write((char *) &buf.at(0), 1); 95 | } 96 | }; 97 | 98 | 99 | } // namespace netcdf 100 | } // namespace anu_am 101 | 102 | #endif // ANU_AM_NETCDF_IO_HPP 103 | -------------------------------------------------------------------------------- /src/lib/VertexMap.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2013 The Australian National University 4 | * 5 | * VertexMap.h 6 | * 7 | * Stores data at the vertices of a cubical complex. 8 | * 9 | * Olaf Delgado-Friedrichs jan 14 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_VERTEXMAP_H 14 | #define ANU_AM_DIAMORSE_VERTEXMAP_H 15 | 16 | #include 17 | 18 | #include 19 | 20 | namespace anu_am 21 | { 22 | namespace diamorse 23 | { 24 | 25 | 26 | template 27 | class VertexMap 28 | { 29 | typedef typename Complex::cell_id_type Cell; 30 | 31 | public: 32 | typedef Cell argument_type; 33 | typedef Value value_type; 34 | typedef Value result_type; 35 | 36 | typedef std::shared_ptr > DataPtr; 37 | 38 | VertexMap() 39 | { 40 | } 41 | 42 | VertexMap(Complex const& complex, Value const defaultValue = Value()) 43 | : complex_(complex), 44 | defaultValue_(defaultValue), 45 | data_(new std::vector( 46 | (size_t) complex_.xdim() * complex_.ydim() * complex_.zdim(), 47 | defaultValue)) 48 | { 49 | } 50 | 51 | VertexMap(Complex const& complex, 52 | DataPtr data, 53 | Value const defaultValue = Value()) 54 | : complex_(complex), 55 | defaultValue_(defaultValue), 56 | data_(data) 57 | { 58 | } 59 | 60 | void clear() 61 | { 62 | size_t const n = 63 | (size_t) complex_.xdim() * complex_.ydim() * complex_.zdim(); 64 | for (size_t i = 0; i < n; ++i) 65 | data_->at(i) = defaultValue_; 66 | } 67 | 68 | Value get(Cell const v) const 69 | { 70 | assert((v & 7) == 0); 71 | return data_->at(v >> 3); 72 | } 73 | 74 | Value operator()(Cell const v) const 75 | { 76 | return get(v); 77 | } 78 | 79 | void set(Cell const v, Value const val) 80 | { 81 | assert((v & 7) == 0); 82 | data_->at(v >> 3) = val; 83 | } 84 | 85 | DataPtr const data() const 86 | { 87 | return data_; 88 | } 89 | 90 | private: 91 | Complex complex_; 92 | Value defaultValue_; 93 | DataPtr data_; 94 | }; 95 | 96 | 97 | } // namespace diamorse 98 | } // namespace anu_am 99 | 100 | #endif // !ANU_AM_DIAMORSE_VERTEXMAP_H 101 | -------------------------------------------------------------------------------- /src/lib/Partition.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * Partition.hpp 6 | * 7 | * Partitions of integer ranges of the form [0..n). 8 | * 9 | * Olaf Delgado-Friedrichs mar 14 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_PARTITION_HPP 14 | #define ANU_AM_DIAMORSE_PARTITION_HPP 15 | 16 | #include 17 | 18 | 19 | namespace anu_am 20 | { 21 | namespace diamorse 22 | { 23 | 24 | 25 | template 26 | class Partition 27 | { 28 | mutable std::vector parent_; 29 | mutable std::vector rank_; 30 | 31 | T hasParent(T const element) const 32 | { 33 | return parent_.at(element) > 0; 34 | } 35 | 36 | T getParent(T const element) const 37 | { 38 | return parent_.at(element) - 1; 39 | } 40 | 41 | void setParent(T const element, T const parent) 42 | { 43 | parent_.at(element) = parent + 1; 44 | } 45 | 46 | void shortcutParent(T const element, T const root) const 47 | { 48 | parent_.at(element) = root + 1; 49 | } 50 | 51 | T getRank(T const element) const 52 | { 53 | return rank_.at(element); 54 | } 55 | 56 | void setRank(T const element, size_t const value) 57 | { 58 | rank_.at(element) = value; 59 | } 60 | 61 | void connect(T const a, T const b) 62 | { 63 | setParent(a, b); 64 | setRank(b, getRank(a) + getRank(b) + 1); 65 | setRank(a, 0); 66 | } 67 | 68 | public: 69 | Partition(T const size = 0) 70 | : parent_(size), 71 | rank_(size) 72 | { 73 | } 74 | 75 | T size() const 76 | { 77 | return parent_.size(); 78 | } 79 | 80 | T find(T const element) const 81 | { 82 | T root = element; 83 | while (hasParent(root)) 84 | root = getParent(root); 85 | 86 | T x = element; 87 | while (hasParent(x)) 88 | { 89 | T y = getParent(x); 90 | shortcutParent(x, root); 91 | x = y; 92 | } 93 | 94 | return root; 95 | } 96 | 97 | void unite(T const a, T const b) 98 | { 99 | T const root_a = find(a); 100 | T const root_b = find(b); 101 | 102 | if (root_a == root_b) 103 | return; 104 | 105 | if (getRank(root_b) > getRank(root_a)) 106 | connect(root_a, root_b); 107 | else 108 | connect(root_b, root_a); 109 | } 110 | }; 111 | 112 | 113 | 114 | } // namespace diamorse 115 | } // namespace anu_am 116 | 117 | #endif // !ANU_AM_DIAMORSE_PARTITION_HPP 118 | -------------------------------------------------------------------------------- /src/util/ncdiffstats.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * ncdiffstats.C 6 | * 7 | * Analyses the differences between two NetCDF volume datasets. 8 | * 9 | * Olaf Delgado-Friedrichs mar 15 10 | * 11 | */ 12 | 13 | #include 14 | 15 | #include "netcdf.hpp" 16 | #include "netcdfIO.hpp" 17 | 18 | using namespace anu_am::netcdf; 19 | 20 | 21 | Variable findVolumeVariable(NCFileInfo const info) 22 | { 23 | std::vector const vars = info.variables(); 24 | 25 | for (size_t i = 0; i < vars.size(); ++i) 26 | { 27 | Variable const v = vars.at(i); 28 | 29 | if (v.dimensions().size() == 3) 30 | return v; 31 | } 32 | 33 | throw std::runtime_error("No appropriate variable found"); 34 | } 35 | 36 | 37 | int run(int argc, char* argv[]) 38 | { 39 | if (argc < 3) 40 | { 41 | std::cerr << "Usage:" << argv[0] << " FILE1 FILE2" << std::endl; 42 | return 1; 43 | } 44 | 45 | int i = 0; 46 | char* path1 = argv[++i]; 47 | char* path2 = argv[++i]; 48 | 49 | FileBuffer data1(path1); 50 | FileBuffer data2(path2); 51 | 52 | NCFile file1(data1); 53 | NCFile file2(data2); 54 | 55 | Variable const var1 = findVolumeVariable(file1.info()); 56 | Variable const var2 = findVolumeVariable(file2.info()); 57 | 58 | std::vector const dims = var1.dimensions(); 59 | if (var2.dimensions() != dims) 60 | throw std::runtime_error("dimension mismatch"); 61 | 62 | size_t const zdim = dims.at(0); 63 | size_t const ydim = dims.at(1); 64 | size_t const xdim = dims.at(2); 65 | 66 | double mindiff = 0, maxdiff = 0; 67 | 68 | for (size_t z = 0; z < zdim; ++z) 69 | { 70 | for (size_t y = 0; y < ydim; ++y) 71 | { 72 | for (size_t x = 0; x < xdim; ++x) 73 | { 74 | double const val1 = file1.getFloat(var1, x, y, z); 75 | double const val2 = file2.getFloat(var2, x, y, z); 76 | double const diff = val1 - val2; 77 | 78 | if (diff < mindiff) mindiff = diff; 79 | if (diff > maxdiff) maxdiff = diff; 80 | } 81 | } 82 | } 83 | 84 | std::cout << mindiff << " ... " << maxdiff << std::endl; 85 | 86 | return 0; 87 | } 88 | 89 | 90 | int main(const int argc, char* argv[]) 91 | { 92 | try 93 | { 94 | run(argc, argv); 95 | } 96 | catch(std::runtime_error& e) 97 | { 98 | std::clog 99 | << "terminate called after throwing an instance of " 100 | << "'std::runtime_error'\n" 101 | << " what(): " << e.what() << '\n'; 102 | abort(); 103 | } 104 | catch(std::exception& e) 105 | { 106 | std::clog 107 | << "terminate called after throwing an exception\n" 108 | << " what(): " << e.what() << '\n'; 109 | abort(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/lib/callables.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2013 The Australian National University 4 | * 5 | * callables.hpp 6 | * 7 | * Some generic function-like objects. 8 | * 9 | * Olaf Delgado-Friedrichs mar 14 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_CALLABLES_HPP 14 | #define ANU_AM_DIAMORSE_CALLABLES_HPP 15 | 16 | #include 17 | #include 18 | 19 | namespace anu_am 20 | { 21 | namespace diamorse 22 | { 23 | 24 | 25 | template 26 | class CallableMap 27 | { 28 | public: 29 | typedef V result_type; 30 | 31 | private: 32 | typedef std::map Data; 33 | Data const& data_; 34 | V const& defaultValue_; 35 | 36 | public: 37 | CallableMap(Data const& data, V const& defaultValue = V()) 38 | : data_(data), 39 | defaultValue_(defaultValue) 40 | { 41 | } 42 | 43 | V operator()(K const& key) const 44 | { 45 | if (data_.count(key) > 0) 46 | return data_.at(key); 47 | else 48 | return defaultValue_; 49 | } 50 | }; 51 | 52 | template 53 | CallableMap 54 | callableMap(std::map const& data, V const& defaultValue = V()) 55 | { 56 | return CallableMap(data, defaultValue); 57 | } 58 | 59 | 60 | template 61 | class WithArgument 62 | { 63 | F const& evaluator_; 64 | 65 | public: 66 | typedef typename F::argument_type argument_type; 67 | typedef std::pair result_type; 68 | 69 | WithArgument(F const& evaluator) 70 | : evaluator_(evaluator) 71 | { 72 | } 73 | 74 | result_type operator()(argument_type const& arg) const 75 | { 76 | return std::make_pair(evaluator_(arg), arg); 77 | } 78 | }; 79 | 80 | template 81 | WithArgument withArgument(F const& evaluator) 82 | { 83 | return WithArgument(evaluator); 84 | } 85 | 86 | 87 | template 88 | class Maxima 89 | { 90 | F const& evaluator_; 91 | T const& sampler_; 92 | 93 | public: 94 | typedef typename F::argument_type argument_type; 95 | typedef typename F::result_type result_type; 96 | 97 | Maxima(F const& evaluator, T const& sampler) 98 | : evaluator_(evaluator), 99 | sampler_(sampler) 100 | { 101 | } 102 | 103 | result_type operator()(argument_type const arg) const 104 | { 105 | size_t const n = sampler_.count(arg); 106 | result_type val = evaluator_(sampler_(arg, 0)); 107 | 108 | for (size_t i = 1; i < n; ++i) 109 | val = std::max(val, evaluator_(sampler_(arg, i))); 110 | 111 | return val; 112 | } 113 | }; 114 | 115 | template 116 | Maxima maxima(F const& evaluator, T const& sampler) 117 | { 118 | return Maxima(evaluator, sampler); 119 | } 120 | 121 | 122 | 123 | } // namespace diamorse 124 | } // namespace anu_am 125 | 126 | #endif //!ANU_AM_DIAMORSE_CALLABLES_HPP 127 | -------------------------------------------------------------------------------- /src/util/labyrinth.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * labyrinth.C 6 | * 7 | * Makes binary images based on nodal approximations of minimal surfaces 8 | * 9 | * Olaf Delgado-Friedrichs mar 15 10 | * 11 | */ 12 | 13 | #include 14 | 15 | #include "netcdf.hpp" 16 | #include "netcdfIO.hpp" 17 | 18 | using namespace anu_am::netcdf; 19 | 20 | 21 | double const PI = 3.141592653589793; 22 | 23 | 24 | class NodalAccessor 25 | { 26 | char _type; 27 | float _scale; 28 | 29 | public: 30 | NodalAccessor() {}; 31 | 32 | NodalAccessor(char const type, float const scale) 33 | : _type(type), 34 | _scale(scale) 35 | { 36 | } 37 | 38 | int32_t getInt(size_t const x, size_t const y, size_t const z) const 39 | { 40 | double const f = 2 * PI / _scale; 41 | double const cpx = cos(x * f); 42 | double const cpy = cos(y * f); 43 | double const cpz = cos(z * f); 44 | double const spx = sin(x * f); 45 | double const spy = sin(y * f); 46 | double const spz = sin(z * f); 47 | 48 | switch (_type) 49 | { 50 | case 'G': 51 | case 'g': 52 | return (spy * cpz + spz * cpx + spx * cpy) < 0; 53 | case 'D': 54 | case 'd': 55 | return (cpx * cpy * cpz + 56 | cpx * spy * spz + 57 | spx * cpy * spz + 58 | spx * spy * cpz) < 0; 59 | case 'P': 60 | case 'p': 61 | return (cpx + cpy + cpz) < 0; 62 | default: 63 | throw new std::runtime_error("unknown type"); 64 | } 65 | } 66 | 67 | double getFloat(size_t const x, size_t const y, size_t const z) const 68 | { 69 | return getInt(x, y, z); 70 | } 71 | }; 72 | 73 | 74 | int run(int argc, char* argv[]) 75 | { 76 | if (argc < 2) 77 | { 78 | std::cerr << "Usage:" << argv[0] << " P|D|G SCALE SIZE" 79 | << std::endl; 80 | return 1; 81 | } 82 | 83 | int i = 0; 84 | char const type = argv[++i][0]; 85 | float const scale = atof(argv[++i]); 86 | int const size = atoi(argv[++i]); 87 | 88 | std::string const varname = "segmented"; 89 | 90 | std::vector vars; 91 | vars.push_back(Variable(varname, NC_BYTE, size, size, size)); 92 | 93 | std::map acc; 94 | acc[varname] = NodalAccessor(type, scale); 95 | 96 | Writer writer; 97 | writeNCFile(writer, vars, acc); 98 | 99 | return 0; 100 | } 101 | 102 | 103 | int main(const int argc, char* argv[]) 104 | { 105 | try 106 | { 107 | run(argc, argv); 108 | } 109 | catch(std::runtime_error& e) 110 | { 111 | std::clog 112 | << "terminate called after throwing an instance of " 113 | << "'std::runtime_error'\n" 114 | << " what(): " << e.what() << '\n'; 115 | abort(); 116 | } 117 | catch(std::exception& e) 118 | { 119 | std::clog 120 | << "terminate called after throwing an exception\n" 121 | << " what(): " << e.what() << '\n'; 122 | abort(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/VectorField.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * VectorField.C 6 | * 7 | * Computes the Morse vector field for a volume image. 8 | * 9 | * Olaf Delgado-Friedrichs jun 15 10 | * 11 | */ 12 | 13 | #include "CubicalComplex.hpp" 14 | #include "json.hpp" 15 | #include "vectorFieldExtraction.hpp" 16 | #include "MorseVectorField.hpp" 17 | #include "PackedMap.hpp" 18 | #include "VertexMap.hpp" 19 | #include "volume_io.hpp" 20 | 21 | using namespace anu_am::diamorse; 22 | 23 | typedef CubicalComplex::cell_id_type Cell; 24 | typedef float Value; 25 | typedef VertexMap Scalars; 26 | typedef MorseVectorField Field; 27 | 28 | 29 | int run(const int argc, char* argv[]) 30 | { 31 | namespace js = anu_am::json; 32 | 33 | if (argc < 2) 34 | { 35 | std::cerr << "Usage:" << argv[0] << " INPUT [OUTPUT]" << std::endl; 36 | return 1; 37 | } 38 | 39 | char* infile = argv[1]; 40 | 41 | // Read the data for this process. 42 | NCFileInfo const info = readFileInfo(infile); 43 | Variable const var = findVolumeVariable(info); 44 | 45 | std::vector const dims = readDimensions(info); 46 | CubicalComplex complex(dims.at(0), dims.at(1), dims.at(2)); 47 | 48 | Scalars::DataPtr scalarData = readVolumeData(infile); 49 | Scalars scalars(complex, scalarData); 50 | 51 | // Process the data. 52 | Field field = Field(complex); 53 | fillMorseVectorField(complex, scalars, field); 54 | 55 | // Generate metadata to include with the output data 56 | std::string const parentID = guessDatasetID(infile, info.attributes()); 57 | std::string const thisID = derivedID(parentID, "vector_field", "GVF"); 58 | 59 | std::string const outfile = 60 | argc > 2 ? argv[2] : (stripTimestamp(thisID) + ".nc"); 61 | 62 | js::Object const fullSpec = js::Object 63 | ("id" , thisID) 64 | ("process" , "Discrete Morse Gradient Vector Field") 65 | ("sourcefile" , __FILE__) 66 | ("revision" , js::Object("id", GIT_REVISION)("date", GIT_TIMESTAMP)) 67 | ("parent" , parentID) 68 | ("predecessors", js::Array(parentID)) 69 | ("parameters" , js::Object()); 70 | 71 | std::string const description = js::toString(fullSpec, 2); 72 | 73 | // Write the resulting gradient vector field to the output file 74 | writeVolumeData( 75 | field.data(), outfile, "vector_field", 76 | dims.at(0), dims.at(1), dims.at(2), 77 | VolumeWriteOptions() 78 | .fileAttributes(info.attributes()) 79 | .datasetID(thisID) 80 | .description(description)); 81 | 82 | return 0; 83 | } 84 | 85 | 86 | int main(const int argc, char* argv[]) 87 | { 88 | try 89 | { 90 | run(argc, argv); 91 | } 92 | catch(std::runtime_error& e) 93 | { 94 | std::clog 95 | << "terminate called after throwing an instance of " 96 | << "'std::runtime_error'\n" 97 | << " what(): " << e.what() << '\n'; 98 | abort(); 99 | } 100 | catch(std::exception& e) 101 | { 102 | std::clog 103 | << "terminate called after throwing an exception\n" 104 | << " what(): " << e.what() << '\n'; 105 | abort(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/lib/performance.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ANU_AM_PERFORMANCE_H 2 | #define ANU_AM_PERFORMANCE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class Stopwatch 11 | { 12 | private: 13 | bool const useCpuTime_; 14 | 15 | long accumulated_; 16 | long start_; 17 | bool isRunning_; 18 | 19 | tms mutable tmsCurrent_; 20 | 21 | long time() const 22 | { 23 | clock_t res = times(&tmsCurrent_); 24 | if (res < 0) 25 | throw "Cannot determine user time."; 26 | 27 | if (useCpuTime_) 28 | return tmsCurrent_.tms_utime; 29 | else 30 | return res; 31 | } 32 | 33 | public: 34 | Stopwatch(bool useCpuTime = true) 35 | : useCpuTime_(useCpuTime), 36 | accumulated_(0), 37 | start_(0), 38 | isRunning_(false) 39 | { 40 | } 41 | 42 | std::string mode() const 43 | { 44 | if (useCpuTime_) 45 | return "CPU"; 46 | else 47 | return "Real"; 48 | } 49 | 50 | void resume() 51 | { 52 | if (!isRunning_) 53 | { 54 | isRunning_ = true; 55 | start_ = time(); 56 | } 57 | } 58 | 59 | void start() 60 | { 61 | accumulated_ = 0; 62 | isRunning_ = true; 63 | start_ = time(); 64 | } 65 | 66 | void stop() 67 | { 68 | if (isRunning_) 69 | { 70 | accumulated_ += time() - start_; 71 | isRunning_ = false; 72 | } 73 | } 74 | 75 | /** 76 | * Reports the elapsed time on this timer in milliseconds. 77 | */ 78 | long elapsed() const 79 | { 80 | static long clktck = 0; 81 | 82 | if (clktck == 0 and (clktck = sysconf(_SC_CLK_TCK)) <= 0) 83 | throw "Cannot determine system clock rate"; 84 | 85 | return (accumulated_ + (isRunning_ ? time() - start_ : 0)) 86 | * 1000 / clktck; 87 | } 88 | 89 | std::string format() const 90 | { 91 | return format(elapsed()); 92 | } 93 | 94 | static std::string format(long const milliseconds) 95 | { 96 | std::stringstream ss; 97 | ss << milliseconds / 10 / 100.0 << " seconds"; 98 | return ss.str(); 99 | } 100 | }; 101 | 102 | struct memInfo 103 | { 104 | size_t total; 105 | size_t resident; 106 | size_t heap; 107 | size_t stack; 108 | }; 109 | 110 | #ifdef __linux__ 111 | #define ANU_AM_MEMORY_USAGE_DEFINED 112 | 113 | memInfo memoryUsageInKB() 114 | { 115 | std::string line; 116 | memInfo m; 117 | 118 | std::ifstream fp("/proc/self/status"); 119 | 120 | while (not fp.eof()) 121 | { 122 | std::getline(fp, line); 123 | if (line.find("VmSize:") == 0) 124 | std::stringstream(line.substr(7)) >> m.total; 125 | if (line.find("VmRSS:") == 0) 126 | std::stringstream(line.substr(6)) >> m.resident; 127 | if (line.find("VmData:") == 0) 128 | std::stringstream(line.substr(7)) >> m.heap; 129 | if (line.find("VmStk:") == 0) 130 | std::stringstream(line.substr(6)) >> m.stack; 131 | } 132 | 133 | fp.close(); 134 | 135 | return m; 136 | } 137 | 138 | #endif //__linux__ 139 | 140 | //TODO implement for other operating systems 141 | 142 | #endif // ANU_AM_PERFORMANCE_H 143 | -------------------------------------------------------------------------------- /src/lib/chainComplexExtraction.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * chainComplexExtraction.hpp 6 | * 7 | * Computes a Morse chain complex given a gradient vector field. 8 | * 9 | * Olaf Delgado-Friedrichs jan 14 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_CHAINCOMPLEXEXTRACTION_HPP 14 | #define ANU_AM_DIAMORSE_CHAINCOMPLEXEXTRACTION_HPP 15 | 16 | #include 17 | 18 | #include "CubicalComplex.hpp" 19 | #include "traversals.hpp" 20 | #include "restricted.hpp" 21 | 22 | 23 | namespace anu_am 24 | { 25 | namespace diamorse 26 | { 27 | 28 | 29 | 30 | template 31 | std::shared_ptr > 32 | connectingPaths( 33 | CubicalComplex const& complex, 34 | Field const& field, 35 | Vectors const& V, 36 | Vectors const& coV, 37 | Incidences const& I, 38 | Incidences const& coI) 39 | { 40 | typedef CubicalComplex::cell_id_type Cell; 41 | 42 | std::vector critical; 43 | for (Cell cell = 0; cell <= complex.cellIdLimit(); ++cell) 44 | if (complex.isCell(cell) and field.isCritical(cell)) 45 | critical.push_back(cell); 46 | 47 | std::vector downstreamSources; 48 | std::vector upstreamSources; 49 | std::vector downstreamTargets; 50 | std::vector upstreamTargets; 51 | 52 | for (size_t i = 0; i < critical.size(); ++i) 53 | { 54 | Cell const cell = critical.at(i); 55 | switch(complex.cellDimension(cell)) 56 | { 57 | case 0: 58 | downstreamTargets.push_back(cell); 59 | break; 60 | case 1: 61 | downstreamSources.push_back(cell); 62 | downstreamTargets.push_back(cell); 63 | break; 64 | case 2: 65 | downstreamSources.push_back(cell); 66 | upstreamSources.push_back(cell); 67 | break; 68 | case 3: 69 | upstreamTargets.push_back(cell); 70 | break; 71 | default: 72 | break; 73 | } 74 | } 75 | 76 | return connectingPaths(complex.cellIdLimit(), 77 | downstreamSources, upstreamSources, 78 | downstreamTargets, upstreamTargets, 79 | V, coV, I, coI); 80 | } 81 | 82 | 83 | template 84 | std::map > > 86 | chainComplex(CubicalComplex const& complex, Field const& field) 87 | { 88 | typedef CubicalComplex::cell_id_type Cell; 89 | typedef RestrictedIncidences Incidences; 90 | typedef std::vector > Boundary; 91 | 92 | Facets I(complex.xdim(), complex.ydim(), complex.zdim(), false); 93 | Facets coI(complex.xdim(), complex.ydim(), complex.zdim(), true); 94 | typename Field::Vectors V = field.V(); 95 | typename Field::Vectors coV = field.coV(); 96 | 97 | Incidences rI(I, connectingPaths(complex, field, V, coV, I, coI)); 98 | 99 | std::map result; 100 | 101 | for (Cell cell = 0; cell <= complex.cellIdLimit(); ++cell) 102 | if (complex.isCell(cell) and field.isCritical(cell)) 103 | result[cell] = morseBoundary(cell, V, rI); 104 | 105 | return result; 106 | } 107 | 108 | 109 | 110 | } // namespace diamorse 111 | } // namespace anu_am 112 | 113 | #endif // !ANU_AM_DIAMORSE_CHAINCOMPLEXEXTRACTION_HPP 114 | -------------------------------------------------------------------------------- /src/lib/Set.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * Set.hpp 6 | * 7 | * Sets represented as sorted vectors. 8 | * 9 | * Olaf Delgado-Friedrichs jan 15 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_SET_HPP 14 | #define ANU_AM_DIAMORSE_SET_HPP 15 | 16 | #include 17 | #include 18 | 19 | namespace anu_am 20 | { 21 | namespace diamorse 22 | { 23 | 24 | 25 | template 26 | class Set 27 | { 28 | typedef typename std::vector::const_iterator Iter; 29 | 30 | std::vector elements_; 31 | 32 | explicit Set(std::vector const& source, bool const) 33 | :elements_(source) 34 | { 35 | } 36 | 37 | public: 38 | // Create an empty set. 39 | Set() 40 | : elements_() 41 | { 42 | } 43 | 44 | // Construct a set with a single entry. 45 | Set(T const& t) 46 | : elements_() 47 | { 48 | elements_.push_back(t); 49 | } 50 | 51 | // Construct a set from a vector. 52 | explicit Set(std::vector const& source) 53 | :elements_() 54 | { 55 | std::vector tmp(source); 56 | std::sort(tmp.begin(), tmp.end()); 57 | 58 | for (Iter it = tmp.begin(); it != tmp.end(); ++it) 59 | if (elements_.size() == 0 or *it != elements_.back()) 60 | elements_.push_back(*it); 61 | } 62 | 63 | // Construct a set from an STL set. 64 | explicit Set(std::set const& source) 65 | :elements_() 66 | { 67 | typename std::set::const_iterator it; 68 | for (it = source.begin(); it != source.end(); ++it) 69 | elements_.push_back(*it); 70 | } 71 | 72 | // The size of the set. 73 | int size() const { 74 | return elements_.size(); 75 | } 76 | 77 | // Creates a new set as the union of this set and the one given. 78 | Set operator+(Set const& other) const 79 | { 80 | std::vector merged; 81 | 82 | Iter const left_end = elements_.end(); 83 | Iter const right_end = other.elements_.end(); 84 | 85 | Iter left = elements_.begin(); 86 | Iter right = other.elements_.begin(); 87 | while (left != left_end and right != right_end) 88 | { 89 | if (*left < *right) 90 | { 91 | merged.push_back(*left); 92 | ++left; 93 | } 94 | else if (*left > *right) 95 | { 96 | merged.push_back(*right); 97 | ++right; 98 | } 99 | else 100 | { 101 | merged.push_back(*right); 102 | ++left; 103 | ++right; 104 | } 105 | } 106 | if (left != left_end) 107 | merged.insert(merged.end(), left, left_end); 108 | else if (right != right_end) 109 | merged.insert(merged.end(), right, right_end); 110 | 111 | return Set(merged, true); 112 | } 113 | 114 | bool operator<(Set const& other) const 115 | { 116 | Iter const left_end = elements_.end(); 117 | Iter const right_end = other.elements_.end(); 118 | 119 | Iter left = elements_.begin(); 120 | Iter right = other.elements_.begin(); 121 | 122 | while (left != left_end and right != right_end) 123 | { 124 | if (*left < *right) 125 | return true; 126 | else if (*left > *right) 127 | return false; 128 | 129 | ++left; 130 | ++right; 131 | } 132 | 133 | return right != right_end; 134 | } 135 | 136 | std::vector elements() const 137 | { 138 | return elements_; 139 | } 140 | }; 141 | 142 | 143 | 144 | } // namespace diamorse 145 | } // namespace anu_am 146 | 147 | #endif // !ANU_AM_DIAMORSE_SET_HPP 148 | -------------------------------------------------------------------------------- /python/plot_persistence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import matplotlib.pyplot as plt 5 | import matplotlib.colors as col 6 | import numpy as np 7 | 8 | import persistence 9 | 10 | 11 | infinity = float('inf') 12 | 13 | 14 | def bars(pairs, dim, threshold): 15 | pairs = sorted(pairs) 16 | births = persistence.births(pairs, dim, threshold) 17 | deaths = persistence.deaths(pairs, dim, threshold) 18 | indexes = range(len(births)) 19 | lifetimes = list(deaths[i] - births[i] for i in indexes) 20 | 21 | return plt.bar(indexes, lifetimes, 1.0, births) 22 | 23 | 24 | def deathsVersusBirths(pairs, dim, threshold): 25 | births = persistence.births(pairs, dim, threshold) 26 | deaths = persistence.deaths(pairs, dim, threshold) 27 | return plt.plot(births, deaths, '.') 28 | 29 | 30 | def weightsVersusPersistence(pairs, dim, threshold): 31 | births = persistence.births(pairs, dim, threshold) 32 | deaths = persistence.deaths(pairs, dim, threshold) 33 | spans = tuple(d - b for (b, d) in zip(births, deaths)) 34 | weights = persistence.weights(pairs, dim, threshold) 35 | if max(weights) > 0: 36 | return plt.semilogy(spans, weights, '.') 37 | else: 38 | return plt.plot(spans, weights, '.') 39 | 40 | 41 | def deathsVersusBirthsHistogram(pairs, dim, threshold, nbins=100): 42 | pairs = [ p for p in pairs if p[1] < infinity ] 43 | births = persistence.births(pairs, dim, threshold) 44 | deaths = persistence.deaths(pairs, dim, threshold) 45 | 46 | axmin = min([min(births), min(deaths),0]) 47 | axmax = max([max(births), max(deaths), 0]) 48 | 49 | return plt.hist2d(births,deaths,[nbins,nbins],[[axmin,axmax],[axmin,axmax]],False,None,1,norm=col.LogNorm()) 50 | 51 | 52 | 53 | if __name__ == '__main__': 54 | import sys, os.path 55 | 56 | import argparse 57 | parser = argparse.ArgumentParser(description='Process and plot.') 58 | parser.add_argument('infile', help='file containing the field') 59 | parser.add_argument('-f', '--field', metavar = 'FILE', 60 | default = '', 61 | help = 'file containing a pre-computed vector field') 62 | parser.add_argument('-t', '--threshold', metavar = 'X', 63 | type = float, default = 1.0, 64 | help = 'simplification threshold (default 1.0)') 65 | parser.add_argument('-d', '--dimensions', 66 | type = int, default = 3, 67 | help = 'kind of critical points to show. E.g. 0 is for minima vs. 1-saddle point (default 2, 2-saddle point vs maxima)') 68 | args = parser.parse_args() 69 | infile = args.infile 70 | threshold = args.threshold 71 | dim = args.dimensions 72 | 73 | if infile.endswith(".nc") or infile.endswith("_nc"): 74 | pairs = persistence.fromVolumeFile(infile, args) 75 | else: 76 | pairs = persistence.fromTextFile(infile) 77 | 78 | plt.figure(1) 79 | plt.title('Cycle births and deaths, t = %.2f, d = %d' % (threshold, dim)) 80 | bars(pairs, dim, threshold) 81 | 82 | plt.figure(2) 83 | plt.title('Deaths vs Births, t = %.2f, d = %d' % (threshold, dim)) 84 | points = deathsVersusBirths(pairs, dim, threshold) 85 | plt.setp(points, color = 'black') 86 | plt.xlabel('Value at birth') 87 | plt.ylabel('Value at death') 88 | 89 | plt.figure(3) 90 | plt.title('Weight vs Persistence, t = %.2f, d = %d' % (threshold, dim)) 91 | points = weightsVersusPersistence(pairs, dim, threshold) 92 | plt.setp(points, color = 'black') 93 | plt.xlabel('Feature persistence') 94 | plt.ylabel('Feature weight') 95 | 96 | plt.figure(4) 97 | plt.title('Deaths vs Births Histogram, t = %.2f, d = %d' % (threshold, dim)) 98 | points = deathsVersusBirthsHistogram(pairs, dim, threshold) 99 | plt.xlabel('Level set value at birth') 100 | plt.ylabel('Level set value at death') 101 | plt.axhline(linewidth=1,color='black') 102 | plt.axvline(linewidth=1,color='black') 103 | 104 | plt.show() 105 | -------------------------------------------------------------------------------- /src/util/MergePhases.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * MergePhases.C 6 | * 7 | * Remaps a range of values in a NetCDF dataset. 8 | * 9 | * Olaf Delgado-Friedrichs jun 15 10 | * 11 | */ 12 | 13 | #include 14 | 15 | #include "json.hpp" 16 | #include "volume_io.hpp" 17 | 18 | using namespace anu_am::diamorse; 19 | 20 | 21 | template 22 | void extract( 23 | std::string const inpath, 24 | std::string const outpath, 25 | double const from, 26 | double const to, 27 | double const into) 28 | { 29 | namespace js = anu_am::json; 30 | 31 | // Read the data. 32 | NCFileInfo const info = readFileInfo(inpath); 33 | Variable const var = findVolumeVariable(info); 34 | std::string const name = var.name(); 35 | 36 | std::vector dims = readDimensions(info); 37 | size_t const n = dims.at(0) * dims.at(1) * dims.at(2); 38 | 39 | std::shared_ptr > const data = readVolumeData(inpath); 40 | 41 | // Do the processing. 42 | for (size_t k = 0; k < n; ++k) 43 | { 44 | T const val = data->at(k); 45 | if (val >= from and val <= to) 46 | data->at(k) = into; 47 | } 48 | 49 | // Generate metadata to include with the output data 50 | std::string const parentID = guessDatasetID(inpath, info.attributes()); 51 | std::string const thisID = derivedID(parentID, name, "PM"); 52 | 53 | std::string const outfile = 54 | outpath.size() > 0 ? outpath : (stripTimestamp(thisID) + ".nc"); 55 | 56 | js::Object const parameters = js::Object 57 | ("from", from) 58 | ("to" , to) 59 | ("into", into); 60 | 61 | js::Object const fullSpec = js::Object 62 | ("id" , thisID) 63 | ("process" , "Phase Merge") 64 | ("sourcefile" , __FILE__) 65 | ("revision" , js::Object("id", GIT_REVISION)("date", GIT_TIMESTAMP)) 66 | ("parent" , parentID) 67 | ("predecessors", js::Array(parentID)) 68 | ("parameters" , parameters); 69 | 70 | std::string const description = js::toString(fullSpec, 2); 71 | 72 | // Write the results. 73 | writeVolumeData( 74 | data, outfile, name, dims.at(0), dims.at(1), dims.at(2), 75 | VolumeWriteOptions() 76 | .fileAttributes(inheritableAttributes(info.attributes())) 77 | .datasetID(thisID) 78 | .description(description)); 79 | } 80 | 81 | 82 | int run(int argc, char* argv[]) 83 | { 84 | if (argc < 5) 85 | { 86 | std::cerr << "Usage: " << argv[0] 87 | << " FROM TO INTO INPUT [OUTPUT]" 88 | << std::endl; 89 | return 1; 90 | } 91 | 92 | double const from = atof(argv[1]); 93 | double const to = atof(argv[2]); 94 | double const into = atof(argv[3]); 95 | std::string const in = argv[4]; 96 | std::string const out = argc > 5 ? argv[5] : ""; 97 | 98 | Variable const var = findVolumeVariable(in); 99 | 100 | switch(var.type()) 101 | { 102 | case NC_BYTE : extract (in, out, from, to, into); break; 103 | case NC_SHORT : extract(in, out, from, to, into); break; 104 | case NC_LONG : extract(in, out, from, to, into); break; 105 | case NC_FLOAT : extract (in, out, from, to, into); break; 106 | case NC_DOUBLE: extract (in, out, from, to, into); break; 107 | default: break; 108 | } 109 | 110 | return 0; 111 | } 112 | 113 | 114 | int main(const int argc, char* argv[]) 115 | { 116 | try 117 | { 118 | run(argc, argv); 119 | } 120 | catch(std::runtime_error& e) 121 | { 122 | std::clog 123 | << "terminate called after throwing an instance of " 124 | << "'std::runtime_error'\n" 125 | << " what(): " << e.what() << '\n'; 126 | abort(); 127 | } 128 | catch(std::exception& e) 129 | { 130 | std::clog 131 | << "terminate called after throwing an exception\n" 132 | << " what(): " << e.what() << '\n'; 133 | abort(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/lib/restricted.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * restricted.hpp 6 | * 7 | * Code based on restricted traversal algorithms. 8 | * 9 | * Olaf Delgado-Friedrichs jan 14 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_RESTRICTED_HPP 14 | #define ANU_AM_DIAMORSE_RESTRICTED_HPP 15 | 16 | #include 17 | 18 | #include 19 | 20 | 21 | namespace anu_am 22 | { 23 | namespace diamorse 24 | { 25 | 26 | 27 | template 28 | class RestrictedIncidences 29 | { 30 | Incidences I_; 31 | std::shared_ptr > good_; 32 | 33 | public: 34 | RestrictedIncidences( 35 | Incidences const& I, 36 | std::shared_ptr > good) 37 | : I_(I), 38 | good_(good) 39 | { 40 | } 41 | 42 | RestrictedIncidences( 43 | Incidences const& I, 44 | std::vector good) 45 | : I_(I), 46 | good_(new std::vector(good)) 47 | { 48 | } 49 | 50 | int count(size_t const id) const 51 | { 52 | int n = 0; 53 | for (int i = 0; i < I_.count(id); ++i) 54 | if (good_->at(I_(id, i))) 55 | ++n; 56 | return n; 57 | } 58 | 59 | size_t operator()(size_t const id, int const n) const 60 | { 61 | int m = -1; 62 | for (int i = 0; i < I_.count(id); ++i) 63 | if (good_->at(I_(id, i)) and ++m == n) 64 | return I_(id, i); 65 | assert(false); 66 | } 67 | }; 68 | 69 | 70 | template 71 | void mark( 72 | size_t const s, 73 | std::shared_ptr >& marked, 74 | bool const value, 75 | VectorField const& V, 76 | Incidences const& I) 77 | { 78 | std::queue queue; 79 | 80 | marked->at(s) = value; 81 | queue.push(s); 82 | 83 | while (not queue.empty()) 84 | { 85 | size_t const a = queue.front(); 86 | queue.pop(); 87 | for (int i = 0; i < I.count(a); ++i) 88 | { 89 | size_t const b = I(a, i); 90 | if (V.defined(b) and V(b) != a) 91 | { 92 | size_t const c = V(b); 93 | if (c != b and marked->at(c) != value) 94 | { 95 | queue.push(c); 96 | marked->at(b) = marked->at(c) = value; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | 104 | template 105 | std::shared_ptr > connectingPaths( 106 | size_t const size, 107 | std::vector const& downstreamSources, 108 | std::vector const& upstreamSources, 109 | std::vector const& downstreamTargets, 110 | std::vector const& upstreamTargets, 111 | VectorField const& V, 112 | VectorField const& coV, 113 | Incidences const& I, 114 | Incidences const& coI) 115 | { 116 | typedef std::vector Bits; 117 | 118 | std::shared_ptr active(new Bits(size)); 119 | std::shared_ptr result(new Bits(size)); 120 | 121 | for (size_t i = 0; i < downstreamSources.size(); ++i) 122 | mark(downstreamSources.at(i), active, true, V, I); 123 | for (size_t i = 0; i < upstreamSources.size(); ++i) 124 | mark(upstreamSources.at(i), active, true, coV, coI); 125 | 126 | RestrictedIncidences rI(I, active); 127 | RestrictedIncidences rcoI(coI, active); 128 | 129 | for (size_t i = 0; i < downstreamTargets.size(); ++i) 130 | mark(downstreamTargets.at(i), result, true, coV, rcoI); 131 | for (size_t i = 0; i < upstreamTargets.size(); ++i) 132 | mark(upstreamTargets.at(i), result, true, V, rI); 133 | 134 | for (size_t i = 0; i < downstreamSources.size(); ++i) 135 | result->at(downstreamSources.at(i)) = true; 136 | for (size_t i = 0; i < upstreamSources.size(); ++i) 137 | result->at(upstreamSources.at(i)) = true; 138 | 139 | return result; 140 | } 141 | 142 | 143 | 144 | } // namespace diamorse 145 | } // namespace anu_am 146 | 147 | #endif // !ANU_AM_DIAMORSE_RESTRICTED_HPP 148 | -------------------------------------------------------------------------------- /src/util/ncdump.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * ncdump.C 6 | * 7 | * Prints the header of a NetCDF 3 file in a CDL-like format. 8 | * 9 | * Olaf Delgado-Friedrichs may 16 10 | * 11 | */ 12 | 13 | #include 14 | 15 | #include "collections.hpp" 16 | #include "netcdf.hpp" 17 | #include "netcdfIO.hpp" 18 | #include "stringUtils.hpp" 19 | 20 | using namespace anu_am::netcdf; 21 | using namespace anu_am::diamorse; 22 | using namespace anu_am::stringutils; 23 | 24 | 25 | std::string tname(Tag const type) 26 | { 27 | switch (type) 28 | { 29 | case NC_BYTE : return "byte"; 30 | case NC_CHAR : return "char"; 31 | case NC_SHORT : return "short"; 32 | case NC_LONG : return "int"; 33 | case NC_FLOAT : return "float"; 34 | case NC_DOUBLE: return "double"; 35 | default: assert(false); 36 | } 37 | } 38 | 39 | 40 | std::string stripname(std::string const path) 41 | { 42 | char *s = new char[path.size()+1]; 43 | path.copy(s, path.size()+1); 44 | 45 | std::string const base = basename(s); 46 | delete[] s; 47 | 48 | return base.substr(0, base.rfind(".")); 49 | } 50 | 51 | 52 | std::string formatValues(Attribute const a) 53 | { 54 | if (a.type() == NC_CHAR) 55 | { 56 | std::string s = a.valuesAsString(); 57 | 58 | s = replaceAll(s, '\\', "\\\\"); 59 | s = replaceAll(s, '\"', "\\\""); 60 | s = replaceAll(s, '\n', "\\n\",\n\t\t\t\""); 61 | 62 | return "\"" + s + "\""; 63 | } 64 | else 65 | { 66 | return a.valuesAsString(); 67 | } 68 | } 69 | 70 | 71 | int run(int argc, char* argv[]) 72 | { 73 | char* infile = argv[1]; 74 | 75 | if (argc < 2) 76 | { 77 | std::cerr << "Usage:" << argv[0] << " INPUT" << std::endl; 78 | return 1; 79 | } 80 | 81 | FileBuffer data(infile); 82 | NCFile file(data); 83 | 84 | std::vector const dims = file.dimensions(); 85 | std::vector const vars = file.variables(); 86 | Attributes const attrs = file.attributes(); 87 | size_t i; 88 | 89 | std::cout << "netcdf " << stripname(infile) << " {" << std::endl; 90 | 91 | std::cout << "dimensions:" << std::endl; 92 | for (i = 0; i < dims.size(); ++i) 93 | { 94 | Dimension d = dims.at(i); 95 | std::cout << "\t" << d.name << " = " << d.size 96 | << " ;" << std::endl; 97 | } 98 | std::cout << std::endl; 99 | 100 | std::cout << "variables:" << std::endl; 101 | for (i = 0; i < vars.size(); ++i) 102 | { 103 | Variable v = vars.at(i); 104 | std::cout << "\t" << tname(v.type()) << " " << v.name() 105 | << "(" << toString(v.dimensionNames()) 106 | << ") ;" << std::endl; 107 | 108 | Attributes const attrs = v.attributes(); 109 | for (size_t j = 0; j < attrs.size(); ++j) 110 | { 111 | Attribute a = attrs.at(j); 112 | std::cout << "\t\t" << v.name() << ":" << attrs.keyAt(j) << " = " 113 | << formatValues(a) 114 | << " ;" << std::endl; 115 | } 116 | } 117 | std::cout << std::endl; 118 | 119 | std::cout << "// global attributes:" << std::endl; 120 | for (i = 0; i < attrs.size(); ++i) 121 | { 122 | Attribute a = attrs.at(i); 123 | std::cout << "\t\t:" << attrs.keyAt(i) << " = " 124 | << formatValues(a) 125 | << " ;" << std::endl; 126 | } 127 | std::cout << std::endl; 128 | 129 | std::cout << "data:" << std::endl; 130 | for (i = 0; i < vars.size(); ++i) 131 | { 132 | Variable v = vars.at(i); 133 | std::cout << std::endl << " " << v.name() << " =" << std::endl; 134 | std::cout << " " << file.valueAsString(v, 0, 0, 0) 135 | << ", " << file.valueAsString(v, 1, 0, 0) 136 | << ", " << file.valueAsString(v, 2, 0, 0) 137 | << ", " << file.valueAsString(v, 3, 0, 0) 138 | << ", " << file.valueAsString(v, 4, 0, 0) 139 | << ", ... ;" 140 | << std::endl; 141 | } 142 | 143 | std::cout << "}" << std::endl; 144 | 145 | return 0; 146 | } 147 | 148 | 149 | int main(const int argc, char* argv[]) 150 | { 151 | try 152 | { 153 | run(argc, argv); 154 | } 155 | catch(std::runtime_error& e) 156 | { 157 | std::clog 158 | << "terminate called after throwing an instance of " 159 | << "'std::runtime_error'\n" 160 | << " what(): " << e.what() << '\n'; 161 | abort(); 162 | } 163 | catch(std::exception& e) 164 | { 165 | std::clog 166 | << "terminate called after throwing an exception\n" 167 | << " what(): " << e.what() << '\n'; 168 | abort(); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /test/testVectorField.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2016 The Australian National University 4 | * 5 | * testVectorField.C 6 | * 7 | * Tests gradient vector field computation. 8 | * 9 | * Olaf Delgado-Friedrichs may 16 10 | * 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "generative.hpp" 19 | #include "common.hpp" 20 | #include "stringUtils.hpp" 21 | 22 | 23 | using namespace anu_am::generative; 24 | using namespace anu_am::diamorse; 25 | using namespace anu_am::stringutils; 26 | 27 | 28 | Result alwaysTrue(VolumeData const&) 29 | { 30 | return success(); 31 | } 32 | 33 | 34 | Result failsWithMessagePrefix(std::string const& prefix, Result const& r) 35 | { 36 | if (r) 37 | { 38 | return failure("check should have failed"); 39 | } 40 | else if (!startsWith(r.cause(), prefix)) 41 | { 42 | std::stringstream msg; 43 | msg << "unexpected failure cause:" << std::endl; 44 | msg << r.cause() << std::endl; 45 | msg << "(was expected to start with '" << prefix << "')" << std::endl; 46 | 47 | return failure(msg.str()); 48 | } 49 | else 50 | { 51 | return success(); 52 | } 53 | } 54 | 55 | 56 | Result cellIsCritical(Cell const& cell, VolumeData const& candidate) 57 | { 58 | if (candidate.field.isCritical(cell)) 59 | return success(); 60 | else 61 | return failure("cell is not critical"); 62 | } 63 | 64 | 65 | VolumeData withSaturatedVectorField(VolumeData const& original) 66 | { 67 | CubicalComplex const& complex = original.complex; 68 | Facets const facets(complex.xdim(), complex.ydim(), complex.zdim(), false); 69 | 70 | Field::DataPtr const& oldData = original.field.data(); 71 | Field::DataPtr newData(new Field::DataPtr::element_type(*oldData)); 72 | Field field(complex.xdim(), complex.ydim(), complex.zdim(), newData); 73 | 74 | for (Cell start = 0; start < complex.cellIdLimit(); ++start) 75 | { 76 | if (not complex.isCell(start) or not field.isCritical(start)) 77 | continue; 78 | int const n = facets.count(start); 79 | for (int i = 0; i < n; ++i) 80 | { 81 | Cell const f = facets(start, i); 82 | if (field.isCritical(f)) 83 | field.setPartner(start, f); 84 | } 85 | } 86 | 87 | return VolumeData(complex, original.scalars, field); 88 | } 89 | 90 | 91 | Result containsNoCyclicVPathsAfterSaturation(VolumeData const& candidate) 92 | { 93 | return containsNoCyclicVPaths(withSaturatedVectorField(candidate)); 94 | } 95 | 96 | 97 | int run() 98 | { 99 | report("a passing property produces no errors", 100 | checkWithVolumeData(alwaysTrue)); 101 | 102 | report("a failing property produces an appropriate error result", 103 | failsWithMessagePrefix( 104 | "\nReason: At cell ", 105 | checkWithVolumeData(forAllCells(cellIsCritical)))); 106 | 107 | report("the vector field is complete", 108 | checkWithVolumeData(forAllCells(vectorDirectionIsDefined))); 109 | 110 | report("no vectors are marked as pointing outward", 111 | checkWithVolumeData(forAllCells(vectorIsNotOutwardPointing))); 112 | 113 | report("no vectors are actually pointing outward", 114 | checkWithVolumeData(forAllCells(directionIsNotOutwardPointing))); 115 | 116 | report("directions are consistent with cell pairings", 117 | checkWithVolumeData(forAllCells(cellPartnerMatchesDirection))); 118 | 119 | report("the call pairing is symmetrical", 120 | checkWithVolumeData(forAllCells(partnerOfPartnerIsOriginalCell))); 121 | 122 | report("the discrete vector field is a gradient vector field", 123 | checkWithVolumeData(containsNoCyclicVPaths)); 124 | 125 | report("saturation does not always preserve being a gradient vector field", 126 | failsWithMessagePrefix( 127 | "\nReason: cyclic V-path at ", 128 | checkWithVolumeData(containsNoCyclicVPathsAfterSaturation))); 129 | 130 | report("level sets are hermetic", 131 | checkWithVolumeData(forAllCells(bind(vImageHasCompatibleValue, 0)))); 132 | 133 | std::cerr << std::endl; 134 | 135 | return 0; 136 | } 137 | 138 | 139 | int main() 140 | { 141 | try 142 | { 143 | run(); 144 | } 145 | catch(std::runtime_error& e) 146 | { 147 | std::clog 148 | << "terminate called after throwing an instance of " 149 | << "'std::runtime_error'\n" 150 | << " what(): " << e.what() << '\n'; 151 | abort(); 152 | } 153 | catch(std::exception& e) 154 | { 155 | std::clog 156 | << "terminate called after throwing an exception\n" 157 | << " what(): " << e.what() << '\n'; 158 | abort(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/util/checkSEDT.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * checkSEDT.C 6 | * 7 | * Sanity check for SEDT output. 8 | * 9 | * Olaf Delgado-Friedrichs feb 15 10 | * 11 | */ 12 | 13 | 14 | #include "volume_io.hpp" 15 | #include "collections.hpp" 16 | #include "CubicalComplex.hpp" 17 | #include "VertexMap.hpp" 18 | 19 | using namespace anu_am::diamorse; 20 | 21 | typedef CubicalComplex::cell_id_type Cell; 22 | typedef int8_t PhaseValue; 23 | typedef float DistanceValue; 24 | typedef VertexMap Phases; 25 | typedef VertexMap Distances; 26 | 27 | 28 | inline double sq(double const x) { 29 | return x * x; 30 | } 31 | 32 | 33 | int run(int argc, char* argv[]) 34 | { 35 | if (argc < 3) 36 | { 37 | std::cerr << "Usage:" << argv[0] << " SEGMENTED SEDT" << std::endl; 38 | return 1; 39 | } 40 | 41 | char * segmentedFile = argv[1]; 42 | char * sedtFile = argv[2]; 43 | 44 | std::vector const dims = readDimensions(segmentedFile); 45 | size_t const xdim = dims.at(0); 46 | size_t const ydim = dims.at(1); 47 | size_t const zdim = dims.at(2); 48 | 49 | CubicalComplex complex(xdim, ydim, zdim); 50 | 51 | Phases::DataPtr phaseData = readVolumeData(segmentedFile); 52 | Phases phases(complex, phaseData); 53 | 54 | Distances::DataPtr distData = readVolumeData(sedtFile); 55 | Distances dists(complex, distData); 56 | 57 | for (size_t z = 0; z < zdim; ++z) 58 | { 59 | for (size_t y = 0; y < ydim; ++y) 60 | { 61 | for (size_t x = 0; x < xdim; ++x) 62 | { 63 | Cell const v = complex.cellAt(x, y, z); 64 | if (dists(v) == 0) 65 | std::cout << "distance is zero" << std::endl; 66 | 67 | if (phases(v) != (dists(v) > 0)) 68 | std::cout << "distance has wrong sign" << std::endl; 69 | 70 | std::vector neighbors; 71 | if (x+1 < xdim) 72 | neighbors.push_back(complex.cellAt(x+1, y, z)); 73 | if (y+1 < ydim) 74 | neighbors.push_back(complex.cellAt(x, y+1, z)); 75 | if (z+1 < zdim) 76 | neighbors.push_back(complex.cellAt(x, y, z+1)); 77 | 78 | for (size_t i = 0; i < neighbors.size(); ++i) { 79 | Cell const w = neighbors.at(i); 80 | 81 | if (fabs(dists(v) - dists(w)) > 1 + 1e-6) 82 | { 83 | std::cout << "distances " 84 | << dists(v) << " and " << dists(w) 85 | << " at " << complex.cellPosition(v) 86 | << " and " << complex.cellPosition(w) 87 | << std::endl; 88 | } 89 | 90 | if (phases(v) != phases(w) && (fabs(dists(v)) != 0.5 || 91 | fabs(dists(w)) != 0.5)) 92 | std::cout << "bad value at boundary"; 93 | } 94 | } 95 | } 96 | } 97 | 98 | for (size_t i = 0; i < 3 * xdim * ydim * zdim; ++i) { 99 | size_t const xv = rand() % xdim; 100 | size_t const yv = rand() % ydim; 101 | size_t const zv = rand() % zdim; 102 | size_t const xw = rand() % xdim; 103 | size_t const yw = rand() % ydim; 104 | size_t const zw = rand() % zdim; 105 | 106 | Cell const v = complex.cellAt(xv, yv, zv); 107 | Cell const w = complex.cellAt(xw, yw, zw); 108 | 109 | double const d1 = sqrt(sq(xw - xv) + sq(yw - yv) + sq(zw -zv)); 110 | double const d2 = fabs(dists(v) - dists(w)); 111 | 112 | if (d2 > d1 and d2 - d1 > 1e-6 * std::max(d1, d2)) 113 | std::cout << "distances " 114 | << dists(v) << " and " << dists(w) 115 | << " at " << complex.cellPosition(v) 116 | << " and " << complex.cellPosition(w) 117 | << " (d1 = " << d1 << ", d2 = " << d2 << ")" 118 | << std::endl; 119 | } 120 | 121 | return 0; 122 | } 123 | 124 | 125 | int main(const int argc, char* argv[]) 126 | { 127 | try 128 | { 129 | run(argc, argv); 130 | } 131 | catch(std::runtime_error& e) 132 | { 133 | std::clog 134 | << "terminate called after throwing an instance of " 135 | << "'std::runtime_error'\n" 136 | << " what(): " << e.what() << '\n'; 137 | abort(); 138 | } 139 | catch(std::exception& e) 140 | { 141 | std::clog 142 | << "terminate called after throwing an exception\n" 143 | << " what(): " << e.what() << '\n'; 144 | abort(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /python/export_mhd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import numpy as np 4 | 5 | 6 | def parse_options(): 7 | from optparse import OptionParser 8 | 9 | parser = OptionParser("usage: %prog [OPTIONS] INFILE [OUTPREFIX]") 10 | parser.add_option('-t', '--threshold', dest = 'threshold', metavar = 'X', 11 | type = 'float', default = 1.0, 12 | help = 'simplification threshold (default 1.0)') 13 | 14 | (options, args) = parser.parse_args() 15 | if len(args) < 1: 16 | parser.error("at least one argument needed; -h shows list of options") 17 | 18 | return options, args 19 | 20 | 21 | def ensure_dir(path): 22 | import os, os.path 23 | 24 | if not os.path.exists(path): 25 | os.makedirs(path) 26 | 27 | 28 | def scaled(data, target, symmetric = False): 29 | lo = data.min() 30 | hi = data.max() 31 | 32 | if symmetric: 33 | hi = max(abs(lo), abs(hi)) 34 | lo = -hi 35 | 36 | if hi == lo: 37 | return data - lo 38 | else: 39 | f = float(target) / (hi - lo) 40 | return (data.astype('float32') - lo) * f 41 | 42 | 43 | def write_as_mhd(data, file_name, scale = True, symmetric = False): 44 | import struct 45 | 46 | (zs, ys, xs) = data.shape 47 | 48 | mhd_filename = "%s.mhd" % file_name 49 | raw_filename = "%s.raw" % file_name 50 | 51 | mhd_text = ( 52 | """ 53 | ObjectType = Image 54 | NDims = 3 55 | BinaryData = True 56 | BinaryDataByteOrderMSB = False 57 | CompressedData = False 58 | CompressedSize = ??? 59 | TransformMatrix = 1 0 0 0 1 0 0 0 1 60 | Offset = 0 0 0 61 | CenterOfRotation = 0 0 0 62 | ElementSpacing = 1 1 1 63 | HeaderSize = -1 64 | DimSize = %d %d %d 65 | AnatomicalOrientation = ??? 66 | ElementType = MET_UCHAR 67 | ElementDataFile = %s 68 | """ % (xs, ys, zs, raw_filename)) 69 | 70 | fp = open(mhd_filename, 'w') 71 | fp.write(mhd_text) 72 | fp.close() 73 | 74 | output = scaled(data, 254, symmetric) if scale else data 75 | 76 | fp = open(raw_filename, 'wb') 77 | fp.write('\0') 78 | fp.write(struct.pack('<3i', *data.shape)) 79 | fp.write(output.astype('uint8').tostring()) 80 | fp.close() 81 | 82 | 83 | def getProcessedMorseData(infile, threshold): 84 | if infile.endswith('.nc'): 85 | img = VolumeImage(infile) 86 | morse = VectorField(img, threshold) 87 | critical = list(morse.criticalCells()) 88 | 89 | data = { 90 | 'scalars' : img.data(), 91 | 'basins' : morse.basinMap(), 92 | 'watersheds': morse.watersheds(), 93 | 'skeleton' : morse.skeleton(), 94 | 'paths' : morse.paths(), 95 | 'critical' : critical, 96 | 'critval' : map(img.scalarForCell, critical), 97 | 'critdim' : map(img.cellDimension, critical) 98 | } 99 | 100 | outfile = "%s.npz" % os.path.splitext(os.path.basename(infile))[0] 101 | np.savez(outfile, **data) 102 | else: 103 | data = np.load(infile) 104 | 105 | return data 106 | 107 | 108 | def shuffle(data): 109 | tmp = data.astype(np.uint8) 110 | out = np.zeros(data.shape, np.uint8) 111 | 112 | for i in range(6): 113 | out |= ((tmp >> i) & 1) << (6 - i) 114 | 115 | return np.where(data < data.max(), out, 254) 116 | 117 | 118 | if __name__ == '__main__': 119 | import re, os.path 120 | 121 | from MorseAnalysis import VolumeImage, VectorField 122 | 123 | inf = float('inf') 124 | 125 | (options, args) = parse_options() 126 | infile = args[0] 127 | if len(args) > 1: 128 | path = args[1] 129 | else: 130 | path = '.' 131 | 132 | data = getProcessedMorseData(infile, options.threshold) 133 | 134 | basename = re.sub('^tomo_float_', '', 135 | os.path.splitext(os.path.basename(infile))[0]) 136 | 137 | scalars = data['scalars'] 138 | basins = shuffle(data['basins']) 139 | pores = np.where(scalars > 0, 254, basins) 140 | skeleton = np.where(data['skeleton'] > 0, 254, basins) 141 | paths = data['paths'] 142 | watersheds = np.where(data['watersheds'] > 0, scalars, scalars.max() * 1.1) 143 | 144 | a = scaled(scalars, 256, symmetric = True) 145 | b = np.where(a >= 128, a, 64) 146 | c = np.where(data['skeleton'] <= 0, 32, b) 147 | d = np.where(data['watersheds'] > 0, 96, 64) 148 | combined = np.where(c != 64, c, d) 149 | 150 | def write(data, suffix, scale = False, symmetric = False): 151 | fname = os.path.join(path, "%s_%s" % (basename, suffix)) 152 | ensure_dir(os.path.dirname(fname)) 153 | write_as_mhd(data, 154 | re.sub('^\./', '', fname), 155 | scale = scale, 156 | symmetric = symmetric) 157 | 158 | write(scalars, 'scalars', symmetric = True) 159 | write(basins, 'basins') 160 | write(pores, 'pores') 161 | write(skeleton, 'skeleton') 162 | write(paths, 'paths') 163 | write(watersheds, 'watersheds', symmetric = True) 164 | write(combined, 'combined', scale = False) 165 | -------------------------------------------------------------------------------- /test/generative.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2016 The Australian National University 4 | * 5 | * generative.hpp 6 | * 7 | * A simple generative testing framework inspired by Haskell's QuickCheck. 8 | * 9 | * Olaf Delgado-Friedrichs may 16 10 | * 11 | */ 12 | 13 | 14 | #ifndef ANU_AM_GENERATIVE_HPP 15 | #define ANU_AM_GENERATIVE_HPP 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | namespace anu_am 24 | { 25 | namespace generative 26 | { 27 | 28 | std::mt19937 rng(time(0)); 29 | 30 | 31 | float randomFloat(float const sigma = 5.0, float const mean = 0.0) 32 | { 33 | return std::normal_distribution(mean, sigma)(rng); 34 | } 35 | 36 | int randomInt(int const limit) 37 | { 38 | return std::uniform_int_distribution<>(0, limit)(rng); 39 | } 40 | 41 | 42 | template 43 | struct function_traits 44 | { 45 | typedef typename G::result_type result_type; 46 | typedef typename G::argument_type argument_type; 47 | }; 48 | 49 | template 50 | struct function_traits 51 | { 52 | typedef R result_type; 53 | }; 54 | 55 | template 56 | struct function_traits 57 | { 58 | typedef R result_type; 59 | typedef A argument_type; 60 | }; 61 | 62 | template 63 | struct function_traits 64 | { 65 | typedef R result_type; 66 | }; 67 | 68 | template 69 | struct function_traits 70 | { 71 | typedef R result_type; 72 | }; 73 | 74 | template 75 | struct function_traits 76 | { 77 | typedef R result_type; 78 | }; 79 | 80 | template 81 | struct function_traits 82 | { 83 | typedef R result_type; 84 | typedef A argument_type; 85 | }; 86 | 87 | template 88 | struct function_traits 89 | { 90 | typedef R result_type; 91 | }; 92 | 93 | 94 | template 95 | struct Composition 96 | { 97 | typedef typename function_traits::result_type result_type; 98 | typedef typename function_traits::argument_type argument_type; 99 | 100 | F const& f; 101 | G const& g; 102 | 103 | Composition(F const& f, G const& g) 104 | : f(f), 105 | g(g) 106 | { 107 | } 108 | 109 | result_type operator()(argument_type const x) const 110 | { 111 | return f(g(x)); 112 | } 113 | }; 114 | 115 | template 116 | struct Composition composition(F const& f, G const& g) 117 | { 118 | return Composition(f, g); 119 | } 120 | 121 | 122 | class Result 123 | { 124 | bool successful_; 125 | std::string cause_; 126 | 127 | public: 128 | Result(bool const successful, std::string const cause = "") 129 | : successful_(successful), 130 | cause_(cause) 131 | { 132 | } 133 | 134 | std::string cause() const 135 | { 136 | return cause_; 137 | } 138 | 139 | operator bool() const 140 | { 141 | return successful_; 142 | } 143 | }; 144 | 145 | 146 | 147 | Result failure(std::string const& cause) 148 | { 149 | return Result(false, cause); 150 | } 151 | 152 | 153 | Result success(std::string const& cause = "") 154 | { 155 | return Result(true, cause); 156 | } 157 | 158 | 159 | void report(std::string const& name, Result const& result) 160 | { 161 | if (result) 162 | { 163 | std::cerr << "."; 164 | } 165 | else 166 | { 167 | std::cerr << std::endl; 168 | std::cerr << "Failed test: " << name << std::endl; 169 | std::cerr << result.cause() << std::endl; 170 | } 171 | } 172 | 173 | 174 | template 175 | std::pair::result_type> 176 | shrink(P const& predicate, C const& candidate, S const& shrinker) 177 | { 178 | C smallest = candidate; 179 | bool done = false; 180 | 181 | while (not done) 182 | { 183 | std::vector const& shrunk = shrinker(smallest); 184 | done = true; 185 | 186 | for (size_t i = 0; i < shrunk.size(); ++i) 187 | { 188 | if (!predicate(shrunk.at(i))) 189 | { 190 | smallest = shrunk.at(i); 191 | done = false; 192 | break; 193 | } 194 | } 195 | } 196 | 197 | return std::make_pair(smallest, predicate(smallest)); 198 | } 199 | 200 | 201 | template 202 | Result checkPredicate( 203 | P const& predicate, 204 | G const& generator, 205 | S const& shrinker, 206 | int const N = 100) 207 | { 208 | typedef typename function_traits::result_type Instance; 209 | 210 | for (int i = 0; i < N; ++i) 211 | { 212 | Instance const& candidate = generator(i); 213 | 214 | if (!predicate(candidate)) 215 | { 216 | std::pair const shrunk = 217 | shrink(predicate, candidate, shrinker); 218 | 219 | std::stringstream msg; 220 | 221 | msg << std::endl 222 | << "Reason: " << shrunk.second.cause() << std::endl 223 | << " in " << shrunk.first << std::endl 224 | << " (from " << candidate << ")" << std::endl; 225 | 226 | return failure(msg.str()); 227 | } 228 | } 229 | return success(); 230 | } 231 | 232 | } // namespace generative 233 | } // namespace anu_am 234 | 235 | #endif //!ANU_AM_GENERATIVE_HPP 236 | -------------------------------------------------------------------------------- /src/main/Simplify.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * Simplify.C 6 | * 7 | * Simplifies a gradient vector field via Morse cancellation. 8 | * 9 | * Olaf Delgado-Friedrichs jun 15 10 | * 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "callables.hpp" 19 | #include "collections.hpp" 20 | #include "CubicalComplex.hpp" 21 | #include "json.hpp" 22 | #include "MorseVectorField.hpp" 23 | #include "PackedMap.hpp" 24 | #include "simplification.hpp" 25 | #include "stringUtils.hpp" 26 | #include "traversals.hpp" 27 | #include "VertexMap.hpp" 28 | #include "volume_io.hpp" 29 | 30 | 31 | using namespace anu_am::diamorse; 32 | 33 | typedef CubicalComplex::cell_id_type Cell; 34 | typedef float Value; 35 | typedef VertexMap Scalars; 36 | typedef MorseVectorField Field; 37 | typedef Field::DataPtr::element_type::value_type FieldItem; 38 | 39 | 40 | void usage(char *name) 41 | { 42 | std::cerr << "Usage: " << name 43 | << " [-p FLOAT] [-s FLOAT] [-t FLOAT]" 44 | << " SCALARS FIELD [OUTPUT]" 45 | << std::endl 46 | << "Options:" 47 | << std::endl 48 | << " -p persistence limit for feature cancellation" 49 | << std::endl 50 | << " -s size limit for feature cancellation" 51 | << std::endl 52 | << " -t value level threshold to preserve" 53 | << std::endl; 54 | } 55 | 56 | 57 | int run(int argc, char* argv[]) 58 | { 59 | namespace js = anu_am::json; 60 | namespace su = anu_am::stringutils; 61 | 62 | int c; 63 | float persistenceLimit = 1; 64 | float sizeLimit = 0; 65 | float threshold = -std::numeric_limits::max(); 66 | 67 | while ((c = getopt (argc, argv, "p:s:t:")) != -1) 68 | { 69 | switch (c) 70 | { 71 | case 'p': 72 | persistenceLimit = atof(optarg); 73 | break; 74 | case 's': 75 | sizeLimit = atof(optarg); 76 | break; 77 | case 't': 78 | threshold = atof(optarg); 79 | break; 80 | default: 81 | usage(argv[0]); 82 | return 1; 83 | } 84 | } 85 | 86 | if (argc - optind < 2) 87 | { 88 | usage(argv[0]); 89 | return 1; 90 | } 91 | 92 | char* scalarPath = argv[optind]; 93 | char* fieldPath = argv[optind + 1]; 94 | 95 | // Read the data for this process. 96 | NCFileInfo const info = readFileInfo(fieldPath); 97 | Variable const var = findVolumeVariable(info); 98 | 99 | std::vector dims = readDimensions(info); 100 | CubicalComplex complex(dims.at(0), dims.at(1), dims.at(2)); 101 | Vertices vertices(dims.at(0), dims.at(1), dims.at(2)); 102 | 103 | assert(dims == readDimensions(scalarPath)); 104 | 105 | Scalars::DataPtr scalarData = readVolumeData(scalarPath); 106 | Scalars scalars(complex, scalarData); 107 | 108 | Field::DataPtr fieldData = readVolumeData(fieldPath); 109 | Field field = Field(dims.at(0), dims.at(1), dims.at(2), fieldData); 110 | 111 | // Process the data. 112 | std::ostringstream ss; 113 | simplify( 114 | complex, 115 | field, 116 | withArgument(maxima(scalars, vertices)), 117 | mayCancel(complex, scalars, field, 118 | persistenceLimit, sizeLimit, threshold), 119 | ss); 120 | std::string const output = ss.str(); 121 | 122 | // Generate metadata to include with the output data 123 | std::string const parentID = guessDatasetID(fieldPath, info.attributes()); 124 | std::string const thisID = derivedID(parentID, "vector_field", "SMP"); 125 | 126 | std::string const outfile = 127 | (argc - optind > 2) ? argv[optind+2] : (stripTimestamp(thisID) + ".nc"); 128 | 129 | js::Array const predecessors = js::Array 130 | (parentID) 131 | (guessDatasetID(scalarPath, readFileInfo(scalarPath).attributes())); 132 | 133 | js::Object const parameters = js::Object 134 | ("persistence_threshold" , persistenceLimit) 135 | ("feature_size_limit" , sizeLimit) 136 | ("simplification_barrier", threshold); 137 | 138 | js::Object const fullSpec = js::Object 139 | ("id" , thisID) 140 | ("process" , "Gradient Vector Field Simplification") 141 | ("sourcefile" , __FILE__) 142 | ("revision" , js::Object("id", GIT_REVISION)("date", GIT_TIMESTAMP)) 143 | ("parent" , parentID) 144 | ("predecessors", predecessors) 145 | ("parameters" , parameters) 146 | ("output" , su::split(output, '\n')); 147 | 148 | std::string const description = js::toString(fullSpec, 2); 149 | 150 | // Write the resulting gradient vector field to the output file 151 | writeVolumeData( 152 | field.data(), outfile, "vector_field", 153 | dims.at(0), dims.at(1), dims.at(2), 154 | VolumeWriteOptions() 155 | .fileAttributes(info.attributes()) 156 | .datasetID(thisID) 157 | .description(description)); 158 | 159 | return 0; 160 | } 161 | 162 | 163 | int main(const int argc, char* argv[]) 164 | { 165 | try 166 | { 167 | run(argc, argv); 168 | } 169 | catch(std::runtime_error& e) 170 | { 171 | std::clog 172 | << "terminate called after throwing an instance of " 173 | << "'std::runtime_error'\n" 174 | << " what(): " << e.what() << '\n'; 175 | abort(); 176 | } 177 | catch(std::exception& e) 178 | { 179 | std::clog 180 | << "terminate called after throwing an exception\n" 181 | << " what(): " << e.what() << '\n'; 182 | abort(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /test/testSimplification.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2016 The Australian National University 4 | * 5 | * testSimplification.C 6 | * 7 | * Tests the gradient vector field simplification algorithm. 8 | * 9 | * Olaf Delgado-Friedrichs may 16 10 | * 11 | */ 12 | 13 | #include "generative.hpp" 14 | #include "common.hpp" 15 | 16 | #include "simplification.hpp" 17 | 18 | using namespace anu_am::generative; 19 | using namespace anu_am::diamorse; 20 | 21 | 22 | struct Simplified 23 | { 24 | typedef VolumeData argument_type; 25 | typedef VolumeData result_type; 26 | 27 | Value const threshold; 28 | 29 | Simplified(Value const threshold) 30 | : threshold(threshold) 31 | { 32 | } 33 | 34 | VolumeData operator()(VolumeData const& original) const 35 | { 36 | CubicalComplex const& complex = original.complex; 37 | Scalars const& scalars = original.scalars; 38 | Field const& field = original.field; 39 | 40 | Vertices vertices(complex.xdim(), complex.ydim(), complex.zdim()); 41 | 42 | Field::DataPtr data(new Field::DataPtr::element_type(*field.data())); 43 | Field clonedField(complex.xdim(), complex.ydim(), complex.zdim(), data); 44 | 45 | std::stringstream devNull; 46 | 47 | simplify(complex, 48 | clonedField, 49 | withArgument(maxima(scalars, vertices)), 50 | mayCancel(complex, scalars, field, threshold), 51 | devNull); 52 | 53 | return VolumeData(complex, original.scalars, clonedField); 54 | } 55 | }; 56 | 57 | 58 | static float const THRESHOLD = 1; 59 | 60 | 61 | template 62 | anu_am::generative::Result checkWithSimplifiedVolumeData( 63 | P const& predicate, 64 | int const N = 500) 65 | { 66 | return checkWithVolumeData(composition(predicate, Simplified(THRESHOLD)), 67 | N); 68 | } 69 | 70 | 71 | // === Tests start here. 72 | 73 | 74 | Result checkOriginalVersusSimplifiedHomology(VolumeData const& candidate) 75 | { 76 | return checkPersistentHomology( 77 | convertedChainComplex(candidate), 78 | convertedChainComplex(Simplified(THRESHOLD)(candidate)), 79 | THRESHOLD); 80 | } 81 | 82 | 83 | Result checkAbsenceOfCancellableClosePairs(VolumeData const& candidate) 84 | { 85 | typedef CubicalComplex::cell_id_type Cell; 86 | typedef std::vector > Boundary; 87 | 88 | CubicalComplex const& complex = candidate.complex; 89 | Field const& field = candidate.field; 90 | Scalars const& scalars = candidate.scalars; 91 | 92 | Vertices vertices(complex.xdim(), complex.ydim(), complex.zdim()); 93 | Maxima value(scalars, vertices); 94 | 95 | std::map chains = chainComplex(complex, field); 96 | std::map cochains = reverse(chains); 97 | 98 | for (Cell cell = 0; cell < complex.cellIdLimit(); ++cell) 99 | { 100 | if (not complex.isCell(cell) or not field.isCritical(cell)) 101 | continue; 102 | 103 | std::pair const res = closePartner(cell, 104 | callableMap(chains), 105 | callableMap(cochains), 106 | withArgument(value)); 107 | 108 | if (res.second == 1 and value(cell) - value(res.first) <= THRESHOLD) 109 | { 110 | std::stringstream msg; 111 | msg << "found cancellable pair " 112 | << complex.cellPosition(cell) 113 | << " (" << value(cell) << ")" 114 | << " <-> " 115 | << complex.cellPosition(res.first) 116 | << " (" << value(res.first) << ")"; 117 | return failure(msg.str()); 118 | } 119 | } 120 | 121 | return success(); 122 | } 123 | 124 | 125 | int run() 126 | { 127 | report("the vector field is complete", 128 | checkWithSimplifiedVolumeData( 129 | forAllCells(vectorDirectionIsDefined))); 130 | 131 | report("no vectors are marked as pointing outward", 132 | checkWithSimplifiedVolumeData( 133 | forAllCells(vectorIsNotOutwardPointing))); 134 | 135 | report("no vectors are actually pointing outward", 136 | checkWithSimplifiedVolumeData( 137 | forAllCells(directionIsNotOutwardPointing))); 138 | 139 | report("directions and cell partners match", 140 | checkWithSimplifiedVolumeData( 141 | forAllCells(cellPartnerMatchesDirection))); 142 | 143 | report("the cell pairing is symmetrical", 144 | checkWithSimplifiedVolumeData( 145 | forAllCells(partnerOfPartnerIsOriginalCell))); 146 | 147 | report("the discrete vector field is a gradient vector field", 148 | checkWithSimplifiedVolumeData(containsNoCyclicVPaths)); 149 | 150 | report("original and simplified complex have the same persistent homoloy", 151 | checkWithVolumeData(checkOriginalVersusSimplifiedHomology)); 152 | 153 | report("vector Field Is Compatible With Scalars", 154 | checkWithSimplifiedVolumeData( 155 | forAllCells( 156 | bind(vImageHasCompatibleValue, THRESHOLD)))); 157 | 158 | report("simplification leaves no cancellable close pairs", 159 | checkWithSimplifiedVolumeData(checkAbsenceOfCancellableClosePairs)); 160 | 161 | std::cerr << std::endl; 162 | 163 | return 0; 164 | } 165 | 166 | 167 | int main() 168 | { 169 | try 170 | { 171 | run(); 172 | } 173 | catch(std::runtime_error& e) 174 | { 175 | std::clog 176 | << "terminate called after throwing an instance of " 177 | << "'std::runtime_error'\n" 178 | << " what(): " << e.what() << '\n'; 179 | abort(); 180 | } 181 | catch(std::exception& e) 182 | { 183 | std::clog 184 | << "terminate called after throwing an exception\n" 185 | << " what(): " << e.what() << '\n'; 186 | abort(); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/Skeleton.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * Skeleton.C 6 | * 7 | * Reads a Morse vector field and computes the Morse skeleton. 8 | * 9 | * Olaf Delgado-Friedrichs jun 14 10 | * 11 | */ 12 | 13 | #include 14 | #include 15 | 16 | #include "CubicalComplex.hpp" 17 | #include "json.hpp" 18 | #include "MorseVectorField.hpp" 19 | #include "PackedMap.hpp" 20 | #include "traversals.hpp" 21 | #include "VertexMap.hpp" 22 | #include "volume_io.hpp" 23 | 24 | 25 | using namespace anu_am::diamorse; 26 | 27 | typedef CubicalComplex::cell_id_type Cell; 28 | typedef float Value; 29 | typedef VertexMap Scalars; 30 | typedef VertexMap Mask; 31 | typedef MorseVectorField Field; 32 | typedef Field::DataPtr::element_type::value_type FieldItem; 33 | 34 | 35 | Mask skeleton( 36 | CubicalComplex const& complex, 37 | Scalars const& scalars, 38 | Field const& field, 39 | float const threshold = 0, 40 | int const dimension = 3) 41 | { 42 | Field::Vectors V = field.V(); 43 | Facets I(complex.xdim(), complex.ydim(), complex.zdim(), false); 44 | Vertices vertices(complex.xdim(), complex.ydim(), complex.zdim()); 45 | Mask skel(complex, 0); 46 | 47 | 48 | for (Cell s = 0; s <= complex.cellIdLimit(); ++s) 49 | { 50 | if (not (complex.isCell(s) and 51 | field.isCritical(s) and 52 | complex.cellDimension(s) <= dimension) 53 | ) 54 | continue; 55 | 56 | bool good = true; 57 | size_t const m = vertices.count(s); 58 | for (size_t i = 0; i < m; ++i) 59 | if (scalars.get(vertices(s, i)) > threshold) 60 | good = false; 61 | 62 | if (not good) 63 | continue; 64 | 65 | std::vector > t = flowTraversal(s, V, I); 66 | for (size_t j = 0; j < t.size(); ++j) 67 | { 68 | Cell const c = V(t.at(j).second); 69 | size_t const m = vertices.count(c); 70 | for (size_t i = 0; i < m; ++i) 71 | skel.set(vertices(c, i), 1); 72 | } 73 | } 74 | 75 | return skel; 76 | } 77 | 78 | 79 | void usage(char *name) 80 | { 81 | std::cerr << "Usage: " << name 82 | << " [-t FLOAT] SCALARS FIELD [OUTPUT]" 83 | << std::endl 84 | << "Options:" 85 | << std::endl 86 | << " -t threshold (default 0)" 87 | << std::endl; 88 | } 89 | 90 | 91 | int run(int argc, char* argv[]) 92 | { 93 | namespace js = anu_am::json; 94 | 95 | int c; 96 | float threshold = 0; 97 | int dimension = 3; 98 | 99 | while ((c = getopt (argc, argv, "d:t:")) != -1) 100 | { 101 | switch (c) 102 | { 103 | case 'd': 104 | dimension = atoi(optarg); 105 | break; 106 | case 't': 107 | threshold = atof(optarg); 108 | break; 109 | default: 110 | usage(argv[0]); 111 | return 1; 112 | } 113 | } 114 | 115 | if (argc - optind < 2) 116 | { 117 | usage(argv[0]); 118 | return 1; 119 | } 120 | 121 | char* scalarPath = argv[optind]; 122 | char* fieldPath = argv[optind + 1]; 123 | 124 | // Read the data for this process. 125 | NCFileInfo const info = readFileInfo(fieldPath); 126 | Variable const var = findVolumeVariable(info); 127 | 128 | std::vector dims = readDimensions(info); 129 | CubicalComplex complex(dims.at(0), dims.at(1), dims.at(2)); 130 | Vertices vertices(dims.at(0), dims.at(1), dims.at(2)); 131 | 132 | assert(dims == readDimensions(scalarPath)); 133 | 134 | Scalars::DataPtr scalarData = readVolumeData(scalarPath); 135 | Scalars scalars(complex, scalarData); 136 | 137 | Field::DataPtr fieldData = readVolumeData(fieldPath); 138 | Field field = Field(dims.at(0), dims.at(1), dims.at(2), fieldData); 139 | 140 | // Process the data. 141 | Mask const out = skeleton(complex, scalars, field, threshold, dimension); 142 | 143 | // Generate metadata to include with the output data 144 | std::string const parentID = guessDatasetID(fieldPath, info.attributes()); 145 | std::string const thisID = derivedID(parentID, "segmented", "SKL"); 146 | 147 | std::string const outfile = 148 | (argc - optind > 2) ? argv[optind+2] : (stripTimestamp(thisID) + ".nc"); 149 | 150 | js::Array const predecessors = js::Array 151 | (parentID) 152 | (guessDatasetID(scalarPath, readFileInfo(scalarPath).attributes())); 153 | 154 | js::Object const parameters = js::Object("threshold" , threshold); 155 | 156 | js::Object const fullSpec = js::Object 157 | ("id" , thisID) 158 | ("process" , "Skeleton") 159 | ("sourcefile" , __FILE__) 160 | ("revision" , js::Object("id", GIT_REVISION)("date", GIT_TIMESTAMP)) 161 | ("parent" , parentID) 162 | ("predecessors", predecessors) 163 | ("parameters" , parameters); 164 | 165 | std::string const description = js::toString(fullSpec, 2); 166 | 167 | // Write the resulting gradient vector field to the output file 168 | writeVolumeData( 169 | out.data(), outfile, "segmented", dims.at(0), dims.at(1), dims.at(2), 170 | VolumeWriteOptions() 171 | .fileAttributes(info.attributes()) 172 | .datasetID(thisID) 173 | .description(description)); 174 | 175 | return 0; 176 | } 177 | 178 | 179 | int main(const int argc, char* argv[]) 180 | { 181 | try 182 | { 183 | run(argc, argv); 184 | } 185 | catch(std::runtime_error& e) 186 | { 187 | std::clog 188 | << "terminate called after throwing an instance of " 189 | << "'std::runtime_error'\n" 190 | << " what(): " << e.what() << '\n'; 191 | abort(); 192 | } 193 | catch(std::exception& e) 194 | { 195 | std::clog 196 | << "terminate called after throwing an exception\n" 197 | << " what(): " << e.what() << '\n'; 198 | abort(); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /test/testPartition.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2016 The Australian National University 4 | * 5 | * testPartition.C 6 | * 7 | * Tests the Partition (union-find) class. 8 | * 9 | * Olaf Delgado-Friedrichs may 16 10 | * 11 | */ 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "generative.hpp" 22 | 23 | #include "collections.hpp" 24 | #include "Partition.hpp" 25 | 26 | 27 | using namespace anu_am::generative; 28 | using namespace anu_am::diamorse; 29 | 30 | 31 | template 32 | class Model 33 | { 34 | std::vector > setFor_; 35 | 36 | public: 37 | Model(T const size = 0) 38 | : setFor_(size) 39 | { 40 | for (T i = 0; i < size; ++i) 41 | setFor_[i].insert(i); 42 | } 43 | 44 | void unite(T const a, T const b) 45 | { 46 | std::set sa = setFor_.at(a); 47 | std::set sb = setFor_.at(b); 48 | std::set setunion; 49 | setunion.insert(sa.begin(), sa.end()); 50 | setunion.insert(sb.begin(), sb.end()); 51 | 52 | typename std::set::const_iterator iter; 53 | for (iter = setunion.begin(); iter != setunion.end(); ++iter) 54 | setFor_.at(*iter) = setunion; 55 | } 56 | 57 | std::vector > sets() const 58 | { 59 | std::vector > result; 60 | 61 | for (T i = 0; i < setFor_.size(); ++i) 62 | { 63 | std::set s = setFor_.at(i); 64 | if (s.size() > 1 and *s.begin() == i) 65 | result.push_back(s); 66 | } 67 | return result; 68 | } 69 | }; 70 | 71 | 72 | template 73 | class Implementation 74 | { 75 | Partition partition_; 76 | 77 | public: 78 | Implementation(T const size = 0) 79 | : partition_(size) 80 | { 81 | } 82 | 83 | void unite(T const a, T const b) 84 | { 85 | partition_.unite(a, b); 86 | } 87 | 88 | std::vector > sets() const 89 | { 90 | std::map > tmp; 91 | 92 | for (T i = 0; i < partition_.size(); ++i) 93 | tmp[partition_.find(i)].insert(i); 94 | 95 | std::vector > result; 96 | 97 | for (T i = 0; i < partition_.size(); ++i) 98 | if (tmp[i].size() > 1) 99 | result.push_back(tmp.at(i)); 100 | std::sort(result.begin(), result.end()); 101 | 102 | return result; 103 | } 104 | }; 105 | 106 | 107 | template 108 | class Session 109 | { 110 | Model model_; 111 | Implementation implementation_; 112 | 113 | T size_; 114 | std::vector > unions_; 115 | 116 | public: 117 | Session(T const size, std::vector > const unions) 118 | : model_(Model(size)), 119 | implementation_(Implementation(size)), 120 | size_(size), 121 | unions_(unions) 122 | { 123 | } 124 | 125 | T size() const 126 | { 127 | return size_; 128 | } 129 | 130 | std::vector > unions() const 131 | { 132 | return unions_; 133 | } 134 | 135 | Result operator()() 136 | { 137 | for (size_t i = 0; i < unions_.size(); ++i) 138 | { 139 | T const a = unions_.at(i).first; 140 | T const b = unions_.at(i).second; 141 | 142 | model_.unite(a, b); 143 | implementation_.unite(a, b); 144 | } 145 | 146 | std::vector > expected = model_.sets(); 147 | std::vector > found = implementation_.sets(); 148 | 149 | if (expected == found) 150 | { 151 | return success(); 152 | } 153 | else 154 | { 155 | std::stringstream msg; 156 | msg << "expected " << expected << ", got " << found 157 | << std::endl; 158 | return failure(msg.str()); 159 | } 160 | } 161 | }; 162 | 163 | 164 | template 165 | std::ostream& operator<<(std::ostream& out, Session const& session) 166 | { 167 | return out << session.size() << ", " << session.unions(); 168 | } 169 | 170 | 171 | template 172 | Session randomSession(T const size) 173 | { 174 | T const n = randomInt(size); 175 | size_t const m = randomInt(size); 176 | std::vector > unions; 177 | 178 | if (n > 0) 179 | for (size_t i = 0; i < m; ++i) 180 | unions.push_back(std::make_pair(randomInt(n-1), randomInt(n-1))); 181 | 182 | return Session(n, unions); 183 | } 184 | 185 | 186 | template 187 | std::vector > shrinkSession(Session const session) 188 | { 189 | T const size = session.size(); 190 | std::vector > const unions = session.unions(); 191 | std::vector > result; 192 | 193 | std::vector > tmp; 194 | for (size_t i = 0; i < unions.size(); ++i) 195 | if (unions.at(i).first < size-1 and unions.at(i).second < size-1) 196 | tmp.push_back(unions.at(i)); 197 | result.push_back(Session(size-1, tmp)); 198 | 199 | for (size_t i = 0; i < unions.size(); ++i) 200 | { 201 | std::vector > tmp(unions); 202 | tmp.erase(tmp.begin() + i); 203 | result.push_back(Session(size, tmp)); 204 | } 205 | 206 | return result; 207 | } 208 | 209 | 210 | template 211 | Result call(F fun) 212 | { 213 | return fun(); 214 | } 215 | 216 | 217 | template 218 | Result checkPartition() 219 | { 220 | return checkPredicate(call >, 221 | randomSession, 222 | shrinkSession); 223 | } 224 | 225 | 226 | int run() 227 | { 228 | report("partitions work properly", checkPartition()); 229 | 230 | std::cerr << std::endl; 231 | 232 | return 0; 233 | } 234 | 235 | 236 | int main() 237 | { 238 | try 239 | { 240 | run(); 241 | } 242 | catch(std::runtime_error& e) 243 | { 244 | std::clog 245 | << "terminate called after throwing an instance of " 246 | << "'std::runtime_error'\n" 247 | << " what(): " << e.what() << '\n'; 248 | abort(); 249 | } 250 | catch(std::exception& e) 251 | { 252 | std::clog 253 | << "terminate called after throwing an exception\n" 254 | << " what(): " << e.what() << '\n'; 255 | abort(); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /python/persistence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | from MorseAnalysis import VolumeImage, VectorField 5 | 6 | 7 | infinity = float('inf') 8 | 9 | 10 | def fromVolumeFile(filename, options): 11 | img = VolumeImage(filename) 12 | morse = VectorField(img, 13 | threshold = options.threshold, 14 | filename = options.field) 15 | 16 | dim = lambda v: img.cellDimension(v) 17 | val = lambda v: img.scalarForCell(v) if v else infinity 18 | 19 | weights = dict((tuple(v), x) for v, x in morse.weights()) 20 | 21 | return tuple((val(v), val(w), dim(v), v, w, weights.get(tuple(v), 0)) 22 | for v, w in morse.birthsAndDeaths()) 23 | 24 | 25 | def fromText(input): 26 | res = [] 27 | for line in input.readlines(): 28 | text = line.strip() 29 | if len(text) == 0 or text.startswith('#'): 30 | continue 31 | try: 32 | birth, death, dim, v0, v1, v2, w0, w1, w2, wt = text.split() 33 | except ValueError: 34 | birth, death, dim, v0, v1, v2, w0, w1, w2 = text.split() 35 | wt = 0 36 | 37 | v = map(float, [v0, v1, v2]) 38 | w = map(float, [w0, w1, w2]) if w0 != '-' else None 39 | dim = int(dim) 40 | birth = float(birth) 41 | death = float(death) 42 | weight = int(wt) 43 | 44 | res.append((birth, death, dim, v, w, weight)) 45 | 46 | return tuple(res) 47 | 48 | 49 | def fromTextFile(filename): 50 | fp = open(filename) 51 | result = fromText(fp) 52 | fp.close() 53 | return result 54 | 55 | 56 | def toText(output, pairs, source): 57 | output.write("# Persistence pairs for %s\n" % (source,)) 58 | output.write("# format: ") 59 | output.write(" ") 60 | output.write("\n") 61 | for birth, death, dim, v, w, wt in pairs: 62 | w = w or '---' 63 | output.write( 64 | "% 14.10f % 14.10f %d % 6s % 6s % 6s % 6s % 6s % 6s %d\n" % 65 | (birth, death, dim, v[0], v[1], v[2], w[0], w[1], w[2], wt)) 66 | 67 | 68 | def toTextFile(filename, pairs, source): 69 | fp = open(filename, "w") 70 | toText(fp, pairs, source) 71 | fp.close() 72 | 73 | 74 | def births(pairs, dim, threshold): 75 | return tuple(birth for birth, death, d, v, w, weight in pairs 76 | if d == dim and death - birth > threshold) 77 | 78 | def deaths(pairs, dim, threshold): 79 | return tuple(death for birth, death, d, v, w, weight in pairs 80 | if d == dim and death - birth > threshold) 81 | 82 | def weights(pairs, dim, threshold): 83 | return tuple(weight for birth, death, d, v, w, weight in pairs 84 | if d == dim and death - birth > threshold) 85 | 86 | def locations(pairs, dim, threshold): 87 | return tuple( (v, w) for birth, death, d, v, w, weight in pairs 88 | if d == dim and death - birth > threshold) 89 | 90 | 91 | def bettiNumbers(pairs, dim, threshold): 92 | bs = tuple((birth, 1) for birth in births(pairs, dim, threshold)) 93 | ds = tuple((death, -1) for death in deaths(pairs, dim, threshold)) 94 | events = sorted(bs + ds) 95 | 96 | result = [] 97 | 98 | if events: 99 | (x, n, n0) = (events[0][0], 0, -1) 100 | for (y, m) in events: 101 | if y != x: 102 | if n != n0: 103 | result.append((x, n)) 104 | n0 = n 105 | x = y 106 | n += m 107 | if n != n0: 108 | result.append((x, n)) 109 | 110 | return tuple(result) 111 | 112 | def rankHomAB(pairs,dim,a,b): 113 | count = 0 114 | for birth, death, d, v, w, weight in pairs: 115 | if (d == dim and birth < a and death > b): 116 | count += 1 117 | return count 118 | 119 | def rankHomFunction(pairs,dim,bvals,dvals): 120 | countKeys = [(b,d) for b in bvals for d in dvals if d >= b ] 121 | count = { ck: 0 for ck in countKeys } 122 | for birth, death, d, v, w, wt in pairs: 123 | if d == dim: 124 | for (b,d) in countKeys: 125 | if (b >= birth and d <= death): 126 | count[(b,d)] += 1 127 | 128 | return count 129 | 130 | 131 | def printStats(pairs): 132 | for dim in range(3): 133 | counts = defaultdict(int) 134 | for birth, death, d, v, w, wt in pairs: 135 | if d == dim: 136 | counts[death - birth] += 1 137 | 138 | cum = 0 139 | for x in reversed(sorted(counts.keys())): 140 | cum += counts[x] 141 | counts[x] = cum 142 | 143 | print ("# Numbers of cells of dimension " + str(d) + 144 | " by lower persistence thresholds") 145 | for x in sorted(counts.keys()): 146 | print "%10.5f %6d" % (x, counts[x]) 147 | print 148 | 149 | 150 | if __name__ == '__main__': 151 | import argparse 152 | from collections import defaultdict 153 | 154 | parser = argparse.ArgumentParser("usage: %prog [OPTIONS] INFILE") 155 | parser.add_argument('infile', help='file containing the field') 156 | parser.add_argument('-f', '--field', dest = 'field', metavar = 'FILE', 157 | default = '', 158 | help = 'file containing a pre-computed vector field') 159 | parser.add_argument('-t', '--threshold', dest = 'threshold', metavar = 'X', 160 | type = float, default = 1.0, 161 | help = 'simplification threshold (default 1.0)') 162 | parser.add_argument("-b", "--betti", dest = "betti", default = False, 163 | action = "store_true", help = "output Betti numbers") 164 | parser.add_argument("-r", "--raw", dest = "raw", default = False, 165 | action = "store_true", help = "output persistence pairs") 166 | parser.add_argument("-s", "--stats", dest = "stats", default = False, 167 | action = "store_true", help = "output some statistics") 168 | options = parser.parse_args() 169 | 170 | infile = options.infile 171 | threshold = options.threshold 172 | 173 | if infile.endswith(".nc") or infile.endswith("_nc"): 174 | pairs = fromVolumeFile(infile, options) 175 | else: 176 | pairs = fromTextFile(infile) 177 | 178 | xth = [ "zeroth", "first", "second", "third" ] 179 | 180 | if options.raw: 181 | toText(sys.stdout, pairs, infile) 182 | 183 | if options.stats: 184 | printStats(pairs) 185 | 186 | if options.betti: 187 | for i in range(len(xth)): 188 | print "# The %f-persistent %s Betti numbers:" % (threshold, xth[i]) 189 | for (val, count) in bettiNumbers(pairs, i, threshold): 190 | print "%10.5f %6d" % (val, count) 191 | print 192 | -------------------------------------------------------------------------------- /src/lib/MorseVectorField.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * MorseVectorField.hpp 6 | * 7 | * Gradient vector fields on cubical complexes. 8 | * 9 | * Olaf Delgado-Friedrichs jan 14 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_MORSEVECTORFIELD_HPP 14 | #define ANU_AM_DIAMORSE_MORSEVECTORFIELD_HPP 15 | 16 | #include 17 | 18 | #include 19 | 20 | #include "CubicalComplex.hpp" 21 | 22 | namespace anu_am 23 | { 24 | namespace diamorse 25 | { 26 | 27 | 28 | /// This class represents a gradient vector fields on cubical complexes. 29 | template 30 | class MorseVectorField 31 | { 32 | typedef S Storage; 33 | typedef CubicalComplex Complex; 34 | typedef Complex::cell_id_type Cell; 35 | 36 | public: 37 | typedef typename Storage::DataPtr DataPtr; 38 | 39 | typedef enum { 40 | UNDEFINED = 0, 41 | SELF = 1, 42 | XUP = 2, 43 | XDOWN = 3, 44 | YUP = 4, 45 | YDOWN = 5, 46 | ZUP = 6, 47 | ZDOWN = 7 48 | } Directions; 49 | 50 | class Vectors 51 | { 52 | Complex complex_; 53 | MorseVectorField field_; 54 | bool dual_; 55 | 56 | public: 57 | Vectors(Complex const& complex, 58 | MorseVectorField const& field, 59 | bool const dual) 60 | : complex_(complex), 61 | field_(field), 62 | dual_(dual) 63 | { 64 | } 65 | 66 | bool defined(Cell const& cell) const 67 | { 68 | if (field_.isCritical(cell)) 69 | return true; 70 | else if (field_.pointsOutward(cell)) 71 | return false; 72 | else if (dual_) 73 | return not field_.forward(cell, field_.getDirection(cell)); 74 | else 75 | return field_.forward(cell, field_.getDirection(cell)); 76 | } 77 | 78 | Cell operator()(Cell const& cell) const 79 | { 80 | assert(defined(cell)); 81 | return field_.getPartner(cell); 82 | } 83 | }; 84 | 85 | MorseVectorField() 86 | { 87 | } 88 | 89 | MorseVectorField(Complex const& complex) 90 | : xdim_(complex.xdim()), 91 | ydim_(complex.ydim()), 92 | zdim_(complex.zdim()), 93 | complex_(xdim_, ydim_, zdim_), 94 | storage_(xdim_ * ydim_ * zdim_ * 8, 0) 95 | { 96 | } 97 | 98 | MorseVectorField(size_t xdim, size_t ydim, size_t zdim, DataPtr data) 99 | : xdim_(xdim), 100 | ydim_(ydim), 101 | zdim_(zdim), 102 | complex_(xdim_, ydim_, zdim_), 103 | storage_(data, 0) 104 | { 105 | } 106 | 107 | Vectors const V() const 108 | { 109 | return Vectors(complex_, *this, false); 110 | } 111 | 112 | Vectors const coV() const 113 | { 114 | return Vectors(complex_, *this, true); 115 | } 116 | 117 | Directions getDirection(Cell const v) const 118 | { 119 | return (Directions) (storage_.get(v) & 7); 120 | } 121 | 122 | void setDirection(Cell const v, Directions const d) 123 | { 124 | int const out = outward(v, d) ? 8 : 0; 125 | storage_.set(v, (typename Storage::value_type) (d | out)); 126 | } 127 | 128 | bool isCritical(Cell const n) const 129 | { 130 | return getDirection(n) == SELF; 131 | } 132 | 133 | Cell getPartner(Cell const n) const 134 | { 135 | assert(not pointsOutward(n)); 136 | return neighbor(n, getDirection(n)); 137 | } 138 | 139 | void setPartner(Cell const v, Cell const w) 140 | { 141 | for (int i = 1; i <= 7; ++i) 142 | { 143 | if (neighbor(v, i) == w) 144 | { 145 | setDirection(v, (Directions) i); 146 | setDirection(w, (Directions) (i == 1 ? i : i ^ 1)); 147 | assert(getPartner(v) == w); 148 | assert(getPartner(w) == v); 149 | return; 150 | } 151 | } 152 | assert(false); 153 | } 154 | 155 | bool pointsOutward(Cell const v) const 156 | { 157 | return storage_.get(v) > 7; 158 | } 159 | 160 | DataPtr const data() const 161 | { 162 | return storage_.data(); 163 | } 164 | 165 | private: 166 | size_t xdim_; 167 | size_t ydim_; 168 | size_t zdim_; 169 | CubicalComplex complex_; 170 | Storage storage_; 171 | 172 | Cell neighbor(Cell const n, int const direction) const 173 | { 174 | switch (direction) 175 | { 176 | case XUP: 177 | return (complex_.cellDX(n) ? n + 8 : n) ^ 1; 178 | case XDOWN: 179 | return (complex_.cellDX(n) ? n : n - 8) ^ 1; 180 | case YUP: 181 | return (complex_.cellDY(n) ? n + 8 * xdim_ : n) ^ 2; 182 | case YDOWN: 183 | return (complex_.cellDY(n) ? n : n - 8 * xdim_) ^ 2; 184 | case ZUP: 185 | return (complex_.cellDZ(n) ? n + 8 * xdim_ * ydim_ : n) ^ 4; 186 | case ZDOWN: 187 | return (complex_.cellDZ(n) ? n : n - 8 * xdim_ * ydim_) ^ 4; 188 | default: 189 | return n; 190 | } 191 | } 192 | 193 | bool outward(Cell const v, Directions const d) const 194 | { 195 | switch (d) 196 | { 197 | case XUP: 198 | return complex_.cellX(v) == xdim_ - 1 and complex_.cellDX(v) == 0; 199 | case XDOWN: 200 | return complex_.cellX(v) == 0 and complex_.cellDX(v) == 0; 201 | case YUP: 202 | return complex_.cellY(v) == ydim_ - 1 and complex_.cellDY(v) == 0; 203 | case YDOWN: 204 | return complex_.cellY(v) == 0 and complex_.cellDY(v) == 0; 205 | case ZUP: 206 | return complex_.cellZ(v) == zdim_ - 1 and complex_.cellDZ(v) == 0; 207 | case ZDOWN: 208 | return complex_.cellZ(v) == 0 and complex_.cellDZ(v) == 0; 209 | default: 210 | return false; 211 | } 212 | } 213 | 214 | bool forward(Cell const v, Directions const d) const 215 | { 216 | switch (d) 217 | { 218 | case XUP: 219 | return not complex_.cellDX(v); 220 | case XDOWN: 221 | return not complex_.cellDX(v); 222 | case YUP: 223 | return not complex_.cellDY(v); 224 | case YDOWN: 225 | return not complex_.cellDY(v); 226 | case ZUP: 227 | return not complex_.cellDZ(v); 228 | case ZDOWN: 229 | return not complex_.cellDZ(v); 230 | default: 231 | return false; 232 | } 233 | } 234 | }; 235 | 236 | 237 | } // namespace diamorse 238 | } // namespace anu_am 239 | 240 | #endif // !ANU_AM_DIAMORSE_MORSEVECTORFIELD_HPP 241 | -------------------------------------------------------------------------------- /src/util/Subset.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * Subset.C 6 | * 7 | * Extracts a subvolume from a NetCDF volume file. 8 | * 9 | * Olaf Delgado-Friedrichs jun 15 10 | * 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "json.hpp" 18 | #include "volume_io.hpp" 19 | #include "stringUtils.hpp" 20 | 21 | using namespace anu_am::diamorse; 22 | 23 | 24 | struct Range 25 | { 26 | size_t start; 27 | size_t stop; 28 | size_t stride; 29 | 30 | Range(size_t const start = 0, size_t const stop = 0, size_t const stride = 1) 31 | : start(start), 32 | stop(stop), 33 | stride(std::max((size_t)1, stride)) 34 | { 35 | } 36 | 37 | Range(std::string const spec) 38 | : start(0), 39 | stop(0), 40 | stride(1) 41 | { 42 | std::vector const parts = anu_am::stringutils::split(spec, ':'); 43 | if (parts.size() >= 3) 44 | stride = std::max(1, atoi(parts.at(2).c_str())); 45 | if (parts.size() >= 2) 46 | stop = atoi(parts.at(1).c_str()); 47 | start = atoi(parts.at(0).c_str()); 48 | } 49 | }; 50 | 51 | 52 | size_t stop(Range const& range, size_t const srcSize) 53 | { 54 | return range.stop ? range.stop : srcSize; 55 | } 56 | 57 | 58 | size_t size(Range const& range, size_t const srcSize) 59 | { 60 | size_t const end = stop(range, srcSize); 61 | return (end + range.stride - 1 - range.start) / range.stride; 62 | } 63 | 64 | 65 | template 66 | void extract( 67 | std::string const inpath, 68 | std::string const outpath, 69 | Range const& xrange, 70 | Range const& yrange, 71 | Range const& zrange) 72 | { 73 | namespace js = anu_am::json; 74 | 75 | // Read the data. 76 | NCFileInfo const info = readFileInfo(inpath); 77 | Variable const var = findVolumeVariable(info); 78 | std::string const name = var.name(); 79 | 80 | std::vector dims = readDimensions(info); 81 | size_t const xdim = dims.at(0); 82 | size_t const ydim = dims.at(1); 83 | size_t const zdim = dims.at(2); 84 | 85 | size_t const xsize = size(xrange, xdim); 86 | size_t const ysize = size(yrange, ydim); 87 | size_t const zsize = size(zrange, zdim); 88 | 89 | size_t const xmax = stop(xrange, xdim); 90 | size_t const ymax = stop(yrange, ydim); 91 | size_t const zmax = stop(zrange, zdim); 92 | 93 | std::shared_ptr > const data = readVolumeData(inpath); 94 | 95 | // Do the processing. 96 | std::shared_ptr > const 97 | output(new std::vector(xsize * ysize * zsize)); 98 | 99 | size_t k = 0; 100 | for (size_t z = zrange.start; z < zmax; z += zrange.stride) 101 | { 102 | for (size_t y = yrange.start; y < ymax; y += yrange.stride) 103 | { 104 | for (size_t x = xrange.start; x < xmax; x += xrange.stride) 105 | { 106 | output->at(k) = data->at((z * ydim + y) * xdim + x); 107 | ++k; 108 | } 109 | } 110 | } 111 | 112 | // Generate metadata to include with the output data 113 | std::string const parentID = guessDatasetID(inpath, info.attributes()); 114 | std::string const thisID = derivedID(parentID, name, "SS"); 115 | 116 | std::string const outfile = 117 | outpath.size() > 0 ? outpath : (stripTimestamp(thisID) + ".nc"); 118 | 119 | js::Object const parameters = js::Object 120 | ("start_x" , xrange.start) 121 | ("stop_x" , stop(xrange, xdim)) 122 | ("stride_x", xrange.stride) 123 | ("start_y" , yrange.start) 124 | ("stop_y" , stop(yrange, ydim)) 125 | ("stride_y", yrange.stride) 126 | ("start_z" , zrange.start) 127 | ("stop_z" , stop(zrange, zdim)) 128 | ("stride_z", zrange.stride); 129 | 130 | js::Object const fullSpec = js::Object 131 | ("id" , thisID) 132 | ("process" , "Subset") 133 | ("sourcefile" , __FILE__) 134 | ("revision" , js::Object("id", GIT_REVISION)("date", GIT_TIMESTAMP)) 135 | ("parent" , parentID) 136 | ("predecessors", js::Array(parentID)) 137 | ("parameters" , parameters); 138 | 139 | std::string const description = js::toString(fullSpec, 2); 140 | 141 | // Write the results. 142 | writeVolumeData( 143 | output, outfile, name, xsize, ysize, zsize, 144 | VolumeWriteOptions() 145 | .fileAttributes(inheritableAttributes(info.attributes())) 146 | .datasetID(thisID) 147 | .description(description)); 148 | } 149 | 150 | 151 | void usage(char *name) 152 | { 153 | std::cerr << "Usage: " << name 154 | << " [-x RANGE] [-y RANGE] [-z RANGE] INPUT [OUTPUT]" << std::endl 155 | << "where" << std::endl 156 | << " RANGE = [start[:stop[:stride]]]" << std::endl; 157 | } 158 | 159 | 160 | int run(int argc, char* argv[]) 161 | { 162 | namespace js = anu_am::json; 163 | 164 | Range xrange, yrange, zrange; 165 | 166 | char c; 167 | while ((c = getopt (argc, argv, "x:y:z:")) != -1) 168 | { 169 | switch (c) 170 | { 171 | case 'x': 172 | xrange = Range(optarg); 173 | break; 174 | case 'y': 175 | yrange = Range(optarg); 176 | break; 177 | case 'z': 178 | zrange = Range(optarg); 179 | break; 180 | default: 181 | usage(argv[0]); 182 | return 1; 183 | } 184 | } 185 | 186 | if (argc < optind + 1) 187 | { 188 | usage(argv[0]); 189 | return 1; 190 | } 191 | 192 | std::string const in = argv[optind]; 193 | std::string const out = argc > optind+1 ? argv[optind+1] : ""; 194 | 195 | Variable const var = findVolumeVariable(in); 196 | 197 | switch(var.type()) 198 | { 199 | case NC_BYTE : extract (in, out, xrange, yrange, zrange); break; 200 | case NC_SHORT : extract(in, out, xrange, yrange, zrange); break; 201 | case NC_LONG : extract(in, out, xrange, yrange, zrange); break; 202 | case NC_FLOAT : extract (in, out, xrange, yrange, zrange); break; 203 | case NC_DOUBLE: extract (in, out, xrange, yrange, zrange); break; 204 | default: break; 205 | } 206 | 207 | return 0; 208 | } 209 | 210 | 211 | int main(const int argc, char* argv[]) 212 | { 213 | try 214 | { 215 | run(argc, argv); 216 | } 217 | catch(std::runtime_error& e) 218 | { 219 | std::clog 220 | << "terminate called after throwing an instance of " 221 | << "'std::runtime_error'\n" 222 | << " what(): " << e.what() << '\n'; 223 | abort(); 224 | } 225 | catch(std::exception& e) 226 | { 227 | std::clog 228 | << "terminate called after throwing an exception\n" 229 | << " what(): " << e.what() << '\n'; 230 | abort(); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diamorse 2 | 3 | Digital image analysis using discrete Morse theory and persistent homology. 4 | 5 | ## References 6 | 7 | Delgado-Friedrichs, O., Robins, V., & Sheppard, A. (2015). Skeletonization and partitioning of digital images using discrete Morse theory. *Pattern Analysis and Machine Intelligence, IEEE Transactions on*, 37(3), 654-666. 8 | 9 | Robins, V., Wood, P.J., Sheppard, A.P. (2011). Theory and algorithms for constructing discrete Morse complexes from grayscale digital images. *Pattern Analysis and Machine Intelligence, IEEE Transactions on*, 33(8), 1646-1658. 10 | 11 | 12 | # Documentation 13 | 14 | Below is some minimal information that should help you get started. We plan to add more detail over time. Please contact us if you have any questions. 15 | 16 | ## Installation 17 | 18 | In order to compile diamorse, you will need *git*, a GNU-compatible *make*, and a C++ compiler that supports the C++11 standard. Please contact us if any of these requirements poses a problem. The python scripts use python2.7. 19 | 20 | * Clone this repository to your machine: `git clone https://github.com/AppliedMathematicsANU/diamorse.git` 21 | * Change into the newly created directory: `cd diamorse` 22 | * Run `make main` to compile only the main analysis programs, or `make` to also compile some utility programs. 23 | * Run `make python` to compile the python wrappers (optional, requires *cython* and *numpy*). Some of the Python scripts provided require *matplotlib*. 24 | 25 | We have installed and run the code successfully under Linux and OS X, and expect that it will work out of the box on most Unix/Posix compatible systems. It is also possible to use diamorse under Windows via *Cygwin* (see the file [cygwin.md](https://github.com/AppliedMathematicsANU/diamorse/blob/master/cygwin.md) for details). 26 | 27 | ## File formats 28 | 29 | All diamorse programs use the NetCDF3 file format for input and output of image data. Please note that NetCDF4 (a.k.a. HDF5) is not supported. To make the conversion into NetCDF easier, we provide a utility that converts portable graymap (.pgm) files as introduced by the Netpbm library (see http://netpbm.sourceforge.net/doc/pgm.html) into NetCDF. The .pgm format was chosen because it is already being supported by a number of libraries and tools, and is also simple enough to easily write out from within a program without having to rely on a particular library. It allows a single file to contain a sequence of (2D) images, and the conversion program uses this feature to encode a 3D dataset. In other words, if the input .pgm file contains multiple images, each one is taken as a layer in the 3D output dataset, with the first image representing the layer at z=0, the next one that at z=1, and so on. If only a single image is present, the output will effectively be a 2D dataset. 30 | 31 | * diamorse/util/pgmtonc.C 32 | 33 | Converts a portable graymap (.pgm) file into NetCDF data. Both 8-bit and 16-bit images are supported. 34 | 35 | OPTION: -b (create a segmented (black and white) image with all nonzero voxels set to 1) 36 | 37 | OPTION: -t (create a segmented image with all voxels larger or equal to the specified value set to 1) 38 | 39 | INPUT: file.pgm (a file with either a single 2d image or a stack of images of equal width and height) 40 | 41 | OUTPUT: segmentedfile.nc OR tomo_floatfile.nc (the corresponding NetCDF file) 42 | 43 | USAGE: `diamorse $ ./bin/pgmtonc image.pgm` OR `diamorse $ ./bin/pgmtonc image.pgm -t 128` 44 | 45 | * diamorse/main/SEDT.C 46 | 47 | Takes a segmented image and computes the signed Euclidean distance for each voxel using the Hirata/Meijster algorithm. 48 | 49 | INPUT: file.nc (a binary image in NetCDF3 format) 50 | 51 | OUTPUT: tomo_float_file_SEDT.nc (the SEDT of the input image) 52 | 53 | USAGE: `diamorse $ ./bin/SEDT segmented_sample.nc tomo_float_sample_SEDT.nc` 54 | 55 | 56 | ## Usage 57 | 58 | Once you have a greyscale image in NetCDF3 format you can generate the persistence pairs, Morse skeleton and basins using the following. 59 | 60 | * diamorse/python/persistence.py 61 | 62 | Python2.7 wrapper for the vector field and persistence computations 63 | 64 | INPUT: file.nc (greyscale NetCDF image) 65 | 66 | OPTION: -t (specify the simplification threshold for the vector field computations, default is 1.0 ) 67 | 68 | OPTION: -r (tells the script to write out the persistence pairs to stdout, pipe to pairs.txt) 69 | 70 | check the source code for other options for input, output, and usage. 71 | 72 | USAGE: `diamorse $ ./python/persistence.py -t 1.0 -r file.nc > pairs.txt` 73 | 74 | pairs.txt contains the persistence pairing results listed as 75 | 76 | ` ` 77 | 78 | The persistence diagram for homology in dimension k is extracted by grabbing lines with ` = k` 79 | 80 | The locations of creator and destroyer critical cells are specified by the geometric center of the cell. This means that vertices in the cubical complex will have coordinates that are all integers, edges will have exactly one coordinate that is an integer plus 0.5, 2d faces (squares) will have exactly two half-integer coordinates, and 3d faces (cubes) will have three half-integer coordinates. For example, the cell represented by the coordinate pair (205.5, 169.0) is the edge connecting vertices (205,169) and (206,169). 81 | 82 | the `` information is an experimental feature - please ignore for now. 83 | 84 | 85 | * diamorse/python/plot_persistence.py 86 | 87 | Python2.7 scripts to provide basic plots of persistence diagrams. 88 | 89 | 90 | * diamorse/python/plot_basins.py 91 | 92 | For a 2D image this script can create figures such as Figure 4 in our 2015 IEEE TPAMI paper (reference above). 93 | 94 | USAGE: `diamorse $ ./python/plot_basins -h` will display the full list of options. 95 | 96 | 97 | 98 | * diamorse/bin/VectorField 99 | 100 | * diamorse/bin/Simplify 101 | 102 | * diamorse/bin/Skeleton 103 | 104 | * diamorse/bin/Pores 105 | 106 | The above programs provide lower level functionality for 3d images. These compute the Morse vector field from a NetCDF image, simplify it to a desired threshold, output the Morse Skeleton and pore labels as NetCDF files for visualisation. 107 | 108 | 3D visualisation is not currently provided as part of diamorse. 109 | 110 | 111 | # License 112 | 113 | The MIT License (MIT) 114 | 115 | Copyright (c) 2015 The Australian National University 116 | 117 | Permission is hereby granted, free of charge, to any person obtaining a copy 118 | of this software and associated documentation files (the "Software"), to deal 119 | in the Software without restriction, including without limitation the rights 120 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 121 | copies of the Software, and to permit persons to whom the Software is 122 | furnished to do so, subject to the following conditions: 123 | 124 | The above copyright notice and this permission notice shall be included in all 125 | copies or substantial portions of the Software. 126 | 127 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 128 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 129 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 130 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 131 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 132 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 133 | SOFTWARE. 134 | -------------------------------------------------------------------------------- /src/main/Pores.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * Pores.C 6 | * 7 | * Reads a Morse vector field and computes pore labels. 8 | * 9 | * Olaf Delgado-Friedrichs jun 15 10 | * 11 | */ 12 | 13 | #include 14 | #include 15 | 16 | #include "CubicalComplex.hpp" 17 | #include "json.hpp" 18 | #include "MorseVectorField.hpp" 19 | #include "PackedMap.hpp" 20 | #include "traversals.hpp" 21 | #include "VertexMap.hpp" 22 | #include "volume_io.hpp" 23 | 24 | 25 | using namespace anu_am::diamorse; 26 | 27 | typedef CubicalComplex::cell_id_type Cell; 28 | typedef float Value; 29 | typedef VertexMap Scalars; 30 | typedef VertexMap Labels; 31 | typedef MorseVectorField Field; 32 | typedef Field::DataPtr::element_type::value_type FieldItem; 33 | 34 | 35 | Value cellValue(Cell const v, Scalars const scalars, Vertices const vertices) 36 | { 37 | Value val = scalars.get(vertices(v, 0)); 38 | for (size_t i = 1; i < (size_t) vertices.count(v); ++i) 39 | val = std::max(val, scalars.get(vertices(v, i))); 40 | 41 | return val; 42 | } 43 | 44 | 45 | struct Comparator 46 | { 47 | Comparator(CubicalComplex const& complex, Scalars const& scalars) 48 | : _complex(complex), 49 | _vertices(complex.xdim(), complex.ydim(), complex.zdim()), 50 | _scalars(scalars) 51 | { 52 | } 53 | 54 | bool operator()(Cell const v, Cell const w) 55 | { 56 | size_t const dv = _complex.cellDimension(v); 57 | size_t const dw = _complex.cellDimension(w); 58 | Value const sv = cellValue(v, _scalars, _vertices); 59 | Value const sw = cellValue(w, _scalars, _vertices); 60 | 61 | return dv < dw or (dv == dw and sv < sw); 62 | } 63 | 64 | private: 65 | CubicalComplex const& _complex; 66 | Vertices const _vertices; 67 | Scalars const& _scalars; 68 | }; 69 | 70 | 71 | std::vector criticalCellsSorted( 72 | CubicalComplex const& complex, 73 | Scalars const& scalars, 74 | Field const& field, 75 | float const threshold, 76 | int const dimension) 77 | { 78 | Vertices vertices(complex.xdim(), complex.ydim(), complex.zdim()); 79 | std::vector critical; 80 | 81 | for (Cell cell = 0; cell <= complex.cellIdLimit(); ++cell) 82 | if (complex.isCell(cell) and 83 | field.isCritical(cell) and 84 | complex.cellDimension(cell) <= dimension and 85 | cellValue(cell, scalars, vertices) <= threshold 86 | ) 87 | critical.push_back(cell); 88 | 89 | std::stable_sort(critical.begin(), critical.end(), 90 | Comparator(complex, scalars)); 91 | 92 | return critical; 93 | } 94 | 95 | 96 | Labels pores( 97 | CubicalComplex const& complex, 98 | Scalars const& scalars, 99 | Field const& field, 100 | float const threshold = 0) 101 | { 102 | Field::Vectors coV = field.coV(); 103 | Facets coI(complex.xdim(), complex.ydim(), complex.zdim(), true); 104 | Labels pores(complex, 0x7fffffff); 105 | 106 | std::vector const sources = 107 | criticalCellsSorted(complex, scalars, field, threshold, 0); 108 | 109 | for (size_t i = 0; i < sources.size(); ++i) 110 | { 111 | Cell const s = sources.at(i); 112 | pores.set(s, i+1); 113 | 114 | std::vector > t = flowTraversal(s, coV, coI); 115 | for (size_t j = 0; j < t.size(); ++j) 116 | { 117 | Cell const b = t.at(j).second; 118 | Cell const c = coV(b); 119 | if (b != c and scalars(c) <= threshold) 120 | pores.set(c, i+1); 121 | } 122 | } 123 | 124 | return pores; 125 | } 126 | 127 | 128 | void usage(char *name) 129 | { 130 | std::cerr << "Usage: " << name 131 | << " [-t FLOAT] SCALARS FIELD [OUTPUT]" 132 | << std::endl 133 | << "Options:" 134 | << std::endl 135 | << " -t pore inclusion threshold (default 0)" 136 | << std::endl; 137 | } 138 | 139 | 140 | int run(int argc, char* argv[]) 141 | { 142 | namespace js = anu_am::json; 143 | 144 | int c; 145 | float threshold = 0; 146 | 147 | while ((c = getopt (argc, argv, "t:")) != -1) 148 | { 149 | switch (c) 150 | { 151 | case 't': 152 | threshold = atof(optarg); 153 | break; 154 | default: 155 | usage(argv[0]); 156 | return 1; 157 | } 158 | } 159 | 160 | if (argc - optind < 2) 161 | { 162 | usage(argv[0]); 163 | return 1; 164 | } 165 | 166 | char* scalarPath = argv[optind]; 167 | char* fieldPath = argv[optind + 1]; 168 | 169 | // Read the data for this process. 170 | NCFileInfo const info = readFileInfo(fieldPath); 171 | Variable const var = findVolumeVariable(info); 172 | 173 | std::vector dims = readDimensions(info); 174 | CubicalComplex complex(dims.at(0), dims.at(1), dims.at(2)); 175 | Vertices vertices(dims.at(0), dims.at(1), dims.at(2)); 176 | 177 | assert(dims == readDimensions(scalarPath)); 178 | 179 | Scalars::DataPtr scalarData = readVolumeData(scalarPath); 180 | Scalars scalars(complex, scalarData); 181 | 182 | Field::DataPtr fieldData = readVolumeData(fieldPath); 183 | Field field = Field(dims.at(0), dims.at(1), dims.at(2), fieldData); 184 | 185 | // Process the data. 186 | Labels const out = pores(complex, scalars, field, threshold); 187 | 188 | // Generate metadata to include with the output data 189 | std::string const parentID = guessDatasetID(fieldPath, info.attributes()); 190 | std::string const thisID = derivedID(parentID, "labels", "POR"); 191 | 192 | std::string const outfile = 193 | (argc - optind > 2) ? argv[optind+2] : (stripTimestamp(thisID) + ".nc"); 194 | 195 | js::Array const predecessors = js::Array 196 | (parentID) 197 | (guessDatasetID(scalarPath, readFileInfo(scalarPath).attributes())); 198 | 199 | js::Object const parameters = js::Object("threshold" , threshold); 200 | 201 | js::Object const fullSpec = js::Object 202 | ("id" , thisID) 203 | ("process" , "Pores") 204 | ("sourcefile" , __FILE__) 205 | ("revision" , js::Object("id", GIT_REVISION)("date", GIT_TIMESTAMP)) 206 | ("parent" , parentID) 207 | ("predecessors", predecessors) 208 | ("parameters" , parameters); 209 | 210 | std::string const description = js::toString(fullSpec, 2); 211 | 212 | // Write the resulting gradient vector field to the output file 213 | writeVolumeData( 214 | out.data(), outfile, "labels", dims.at(0), dims.at(1), dims.at(2), 215 | VolumeWriteOptions() 216 | .fileAttributes(info.attributes()) 217 | .datasetID(thisID) 218 | .description(description)); 219 | 220 | return 0; 221 | } 222 | 223 | 224 | int main(const int argc, char* argv[]) 225 | { 226 | try 227 | { 228 | run(argc, argv); 229 | } 230 | catch(std::runtime_error& e) 231 | { 232 | std::clog 233 | << "terminate called after throwing an instance of " 234 | << "'std::runtime_error'\n" 235 | << " what(): " << e.what() << '\n'; 236 | abort(); 237 | } 238 | catch(std::exception& e) 239 | { 240 | std::clog 241 | << "terminate called after throwing an exception\n" 242 | << " what(): " << e.what() << '\n'; 243 | abort(); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/lib/traversals.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2013 The Australian National University 4 | * 5 | * traversals.hpp 6 | * 7 | * Stable/unstable set traversal and derived algorithms. 8 | * 9 | * Olaf Delgado-Friedrichs jan 15 10 | * 11 | */ 12 | 13 | #ifndef ANU_AM_DIAMORSE_TRAVERSALS_HPP 14 | #define ANU_AM_DIAMORSE_TRAVERSALS_HPP 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace anu_am 23 | { 24 | namespace diamorse 25 | { 26 | 27 | 28 | template 29 | std::vector > flowTraversal( 30 | Cell const& s, 31 | VectorField const& V, 32 | Incidences const& I, 33 | bool const coordinated = false) 34 | { 35 | std::map k; 36 | std::queue queue; 37 | std::vector > result; 38 | 39 | if (coordinated) 40 | { 41 | std::vector > t = flowTraversal(s, V, I); 42 | for (size_t i = 0; i < t.size(); ++i) 43 | ++k[V(t.at(i).second)]; 44 | } 45 | 46 | queue.push(s); 47 | --k[s]; 48 | 49 | while (not queue.empty()) 50 | { 51 | Cell const a = queue.front(); 52 | queue.pop(); 53 | 54 | size_t const n = I.count(a); 55 | for (size_t i = 0; i < n; ++i) 56 | { 57 | Cell const b = I(a, i); 58 | 59 | if (V.defined(b) and V(b) != a) 60 | { 61 | result.push_back(std::make_pair(a, b)); 62 | 63 | Cell const c = V(b); 64 | if ((k.count(c) == 0 or k[c] > 0) and c != b) 65 | { 66 | if (coordinated and --k[c] != 0) 67 | continue; 68 | queue.push(c); 69 | --k[c]; 70 | } 71 | } 72 | } 73 | } 74 | 75 | return result; 76 | } 77 | 78 | template 79 | std::set unstableSet( 80 | Cell const& s, 81 | VectorField const& V, 82 | Incidences const& I) 83 | { 84 | std::set seen; 85 | seen.insert(s); 86 | 87 | std::vector > t = flowTraversal(s, V, I); 88 | for (size_t i = 0; i < t.size(); ++i) 89 | { 90 | Cell const b = t.at(i).second; 91 | Cell const c = V(b); 92 | if (b != c and seen.count(c) == 0) 93 | seen.insert(c); 94 | } 95 | 96 | return seen; 97 | } 98 | 99 | template 100 | std::vector > morseBoundary( 101 | Cell const& s, 102 | VectorField const& V, 103 | Incidences const& I) 104 | { 105 | std::map counts; 106 | std::set boundary; 107 | 108 | counts[s] = 1; 109 | 110 | std::vector > t = flowTraversal(s, V, I, true); 111 | for (size_t i = 0; i < t.size(); ++i) 112 | { 113 | Cell const a = t.at(i).first; 114 | Cell const b = t.at(i).second; 115 | Cell const c = V(b); 116 | 117 | int const n = counts[a] + (counts.count(c) > 0 ? counts[c] : 0); 118 | counts[c] = (n <= 3) ? n : n % 2 + 2; 119 | if (b == c) 120 | boundary.insert(c); 121 | } 122 | 123 | std::vector > result; 124 | typename std::set::const_iterator iter; 125 | for (iter = boundary.begin(); iter != boundary.end(); ++iter) 126 | result.push_back(std::make_pair(*iter, counts[*iter])); 127 | 128 | return result; 129 | } 130 | 131 | template 132 | std::vector > morseBoundaryFast( 133 | Cell const& s, 134 | VectorField const& V, 135 | Incidences const& I) 136 | { 137 | std::queue queue; 138 | std::map counts; 139 | 140 | queue.push(s); 141 | 142 | while (not queue.empty()) 143 | { 144 | Cell const a = queue.front(); 145 | queue.pop(); 146 | 147 | size_t const n = I.count(a); 148 | for (size_t i = 0; i < n; ++i) 149 | { 150 | Cell const b = I(a, i); 151 | 152 | if (V.defined(b) and V(b) != a ) 153 | { 154 | Cell const c = V(b); 155 | 156 | if (c != b) 157 | { 158 | queue.push(c); 159 | } 160 | else 161 | { 162 | int const n = ++counts[b]; 163 | if (n > 3) 164 | counts[b] = n % 2 + 2; 165 | } 166 | } 167 | } 168 | } 169 | 170 | return std::vector >(counts.begin(), counts.end()); 171 | } 172 | 173 | template 174 | std::vector > connections( 175 | Cell const& s, 176 | Cell const& t, 177 | VectorField const& V, 178 | VectorField const& coV, 179 | Incidences const& I, 180 | Incidences const& coI) 181 | { 182 | std::set active; 183 | active.insert(t); 184 | 185 | std::vector > back = flowTraversal(t, coV, coI); 186 | for (size_t i = 0; i < back.size(); ++i) 187 | active.insert(coV(back.at(i).second)); 188 | 189 | std::vector > result; 190 | std::vector > forw = flowTraversal(s, V, I); 191 | for (size_t i = 0; i < forw.size(); ++i) 192 | { 193 | Cell const a = forw.at(i).first; 194 | Cell const b = forw.at(i).second; 195 | if (active.count(b) > 0) 196 | result.push_back(std::make_pair(a, b)); 197 | } 198 | 199 | return result; 200 | } 201 | 202 | template 203 | std::pair closePartner( 204 | Cell const s, 205 | Boundary const& B, 206 | Boundary const& coB, 207 | Key const& K) 208 | { 209 | typename Boundary::result_type bnd = B(s); 210 | 211 | if (bnd.size() == 0) 212 | return std::make_pair(s, 0); 213 | 214 | std::pair best = *bnd.begin(); 215 | 216 | typename std::vector >::const_iterator iter; 217 | for (iter = bnd.begin(); iter != bnd.end(); ++iter) 218 | if (K(iter->first) > K(best.first)) 219 | best = *iter; 220 | 221 | typename Boundary::result_type cob = coB(best.first); 222 | for (iter = cob.begin(); iter != cob.end(); ++iter) 223 | if (K(iter->first) < K(s)) 224 | return std::make_pair(s, 0); 225 | 226 | return best; 227 | } 228 | 229 | template 230 | std::vector > pairsToCancel( 231 | std::set const& S, 232 | Boundary const& B, 233 | Boundary const& coB, 234 | Key const& K, 235 | Predicate const& P) 236 | { 237 | std::vector > result; 238 | 239 | typename std::set::const_iterator outer; 240 | for (outer = S.begin(); outer != S.end(); ++outer) 241 | { 242 | Cell const s = *outer; 243 | std::pair res = closePartner(s, B, coB, K); 244 | if (res.second == 1) { 245 | Cell const t = res.first; 246 | if (P(s, t)) 247 | { 248 | result.push_back(std::make_pair(s, t)); 249 | } 250 | } 251 | } 252 | 253 | return result; 254 | } 255 | 256 | 257 | 258 | } // namespace diamorse 259 | } // namespace anu_am 260 | 261 | #endif // !ANU_AM_DIAMORSE_TRAVERSALS_HPP 262 | -------------------------------------------------------------------------------- /src/lib/vectorFieldExtraction.hpp: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2014 The Australian National University 4 | * 5 | * vectorFieldExtraction.hpp 6 | * 7 | * Computes a gradient vector field for a cell complex with scalar values 8 | * defined at its vertices. 9 | * 10 | * Olaf Delgado-Friedrichs jan 15 11 | * 12 | */ 13 | 14 | #ifndef ANU_AM_DIAMORSE_VECTORFIELDEXTRACTION_HPP 15 | #define ANU_AM_DIAMORSE_VECTORFIELDEXTRACTION_HPP 16 | 17 | #include 18 | #include 19 | 20 | #include "collections.hpp" 21 | 22 | namespace anu_am 23 | { 24 | namespace diamorse 25 | { 26 | 27 | 28 | int const MAX_STAR = 32; 29 | 30 | class StarException: public std::exception 31 | { 32 | virtual const char* what() const throw() 33 | { 34 | return "vertex star has too many cells (> 32)"; 35 | } 36 | } starException; 37 | 38 | 39 | /// Helper class used in fillMorseVectorField(). 40 | template 41 | class StarField 42 | { 43 | typedef typename Scalars::value_type Value; 44 | 45 | int size_; 46 | Cell cells_[MAX_STAR]; 47 | Value weight_[MAX_STAR]; 48 | int ranked_[MAX_STAR]; 49 | int defined_[MAX_STAR]; 50 | int incidences_[MAX_STAR][MAX_STAR]; 51 | int incidence_counts_[MAX_STAR]; 52 | 53 | public: 54 | StarField( 55 | Cell const v, 56 | Facets const& cofacets, 57 | Vertices const& vertices, 58 | Scalars const& scalars) 59 | { 60 | extract(v, cofacets, vertices, scalars); 61 | } 62 | 63 | int size() const 64 | { 65 | return size_; 66 | } 67 | 68 | bool defined(int const cell) const 69 | { 70 | return defined_[cell]; 71 | } 72 | 73 | void set(int const v, int const w) 74 | { 75 | defined_[v] = true; 76 | defined_[w] = true; 77 | } 78 | 79 | int indexForRank(int const i) 80 | { 81 | return ranked_[i]; 82 | } 83 | 84 | Cell cell(int const i) 85 | { 86 | return cells_[i]; 87 | } 88 | 89 | /// Returns the first face of the given cell that the vector field does 90 | /// not yet assign a partner to, but only if this is the case for exactly 91 | /// k faces of that cell. As a special case, if k = 0 and the cell has 0 92 | /// unused faces, the cell itself is returned. 93 | /// 94 | /// If the cell does not have k unused faces, the function returns a 95 | /// negative value. 96 | int firstFreeFaceIfKFree(int const cell, int const k) 97 | { 98 | int partner = cell; 99 | int count = 0; 100 | 101 | int const n = incidence_counts_[cell]; 102 | for (int j = 0; j < n; ++j) 103 | { 104 | int const f = incidences_[cell][j]; 105 | if (not defined(f)) 106 | { 107 | if (++count > k) 108 | break; 109 | partner = f; 110 | } 111 | } 112 | 113 | if (count == k) 114 | return partner; 115 | else 116 | return -1; 117 | } 118 | 119 | private: 120 | void extract( 121 | Cell const v, 122 | Facets const& cofacets, 123 | Vertices const& vertices, 124 | Scalars const& scalars) 125 | { 126 | Value value = scalars.get(v); 127 | 128 | cells_[0] = v; 129 | defined_[0] = false; 130 | weight_[0] = value; 131 | ranked_[0] = 0; 132 | incidence_counts_[0] = 0; 133 | size_ = 1; 134 | 135 | int mark = size_; 136 | int next = 0; 137 | while (next < size_) 138 | { 139 | Cell const cell = cells_[next]; 140 | 141 | int const n = cofacets.count(cell); 142 | 143 | for (int i = n - 1; i >= 0; --i) 144 | { 145 | Cell const coface = cofacets(cell, i); 146 | int k; 147 | for (k = mark; k < size_; ++k) 148 | if (coface == cells_[k]) 149 | break; 150 | if (k < size_) 151 | { 152 | incidences_[k][incidence_counts_[k]] = next; 153 | ++incidence_counts_[k]; 154 | continue; 155 | } 156 | 157 | int const m = vertices.count(coface); 158 | bool add = true; 159 | Value sum = 0.0; 160 | for (int j = 0; j < m; ++j) 161 | { 162 | Cell const w = vertices(coface, j); 163 | Value const d = scalars.get(w); 164 | if (d > value or (d == value and w > v)) 165 | { 166 | add = false; 167 | break; 168 | } 169 | sum += d; 170 | } 171 | 172 | if (add) 173 | { 174 | cells_[size_] = coface; 175 | defined_[size_] = false; 176 | weight_[size_] = sum; 177 | ranked_[size_] = size_; 178 | incidences_[size_][0] = next; 179 | incidence_counts_[size_] = 1; 180 | ++size_; 181 | 182 | if (size_ > MAX_STAR) 183 | throw starException; 184 | } 185 | } 186 | 187 | ++next; 188 | if (next == mark) 189 | mark = size_; 190 | } 191 | 192 | for (int i = 0; i < size_; ++i) { 193 | int j; 194 | Value x = weight_[i]; 195 | for (j = i; j > 0 && x < weight_[j-1]; --j) { 196 | ranked_[j] = ranked_[j-1]; 197 | weight_[j] = weight_[j-1]; 198 | } 199 | ranked_[j] = i; 200 | weight_[j] = x; 201 | } 202 | } 203 | }; 204 | 205 | 206 | /// Implements a slight variation of the ProcessLowerStar algorithm by 207 | /// Robbins, Sheppard and Wood (TPAMI 33(8), 2011, pp. 1646-1658). 208 | template 209 | void processLowerStar(Cell const v, 210 | Scalars const& scalars, 211 | VectorField& outputField, 212 | Facets const& cofacets, 213 | Vertices const& vertices) 214 | { 215 | // The lower star with the current partial field. 216 | StarField star(v, cofacets, vertices, scalars); 217 | 218 | // Determines whether to look for edges or singular cells next. Alternates 219 | // between 0 and 1. 220 | int k = 1; 221 | 222 | while (true) 223 | { 224 | int cell; 225 | int partner = -1; 226 | 227 | // Find an unused cell with k unused faces and assign the first such 228 | // face to partner if k > 0, or the cell itself otherwise. 229 | for (int i = 0; i < star.size(); ++i) 230 | { 231 | cell = star.indexForRank(i); 232 | if (not star.defined(cell)) 233 | partner = star.firstFreeFaceIfKFree(cell, k); 234 | 235 | if (partner >= 0) 236 | break; 237 | } 238 | 239 | if (partner >= 0) 240 | { 241 | // New pairing found; record and resume scanning. 242 | star.set(cell, partner); 243 | outputField.setPartner(star.cell(cell), star.cell(partner)); 244 | k = 1; 245 | } 246 | else if (k == 1) // Scan for singular cells next. 247 | k = 0; 248 | else // Nothing found; terminate inner loop. 249 | break; 250 | } 251 | } 252 | 253 | 254 | /// Computes a Morse vector field for a cell complex. 255 | template 256 | void fillMorseVectorField(Complex const& source, 257 | Scalars const& scalars, 258 | VectorField& outputField) 259 | { 260 | Facets cofacets(source.xdim(), source.ydim(), source.zdim(), true); 261 | Vertices vertices(source.xdim(), source.ydim(), source.zdim()); 262 | 263 | for (size_t x = 0; x < source.xdim(); ++x) 264 | for (size_t y = 0; y < source.ydim(); ++y) 265 | for (size_t z = 0; z < source.zdim(); ++z) 266 | processLowerStar(source.cellAt(x, y, z), 267 | scalars, outputField, 268 | cofacets, vertices); 269 | } 270 | 271 | 272 | } // namespace diamorse 273 | } // namespace anu_am 274 | 275 | #endif // !ANU_AM_DIAMORSE_VECTORFIELDEXTRACTION_HPP 276 | -------------------------------------------------------------------------------- /src/experimental/Classification.C: -------------------------------------------------------------------------------- 1 | /** -*-c++-*- 2 | * 3 | * Copyright 2015 The Australian National University 4 | * 5 | * Classification.C 6 | * 7 | * Classifies cells in a cubical complex by their stable set membership. 8 | * 9 | * Olaf Delgado-Friedrichs jun 15 10 | * 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "collections.hpp" 20 | #include "CubicalComplex.hpp" 21 | #include "json.hpp" 22 | #include "MorseVectorField.hpp" 23 | #include "PackedMap.hpp" 24 | #include "Set.hpp" 25 | #include "VertexMap.hpp" 26 | #include "volume_io.hpp" 27 | 28 | using namespace anu_am::diamorse; 29 | 30 | typedef CubicalComplex::cell_id_type Cell; 31 | typedef float Value; 32 | typedef MorseVectorField Field; 33 | typedef Field::DataPtr::element_type::value_type FieldItem; 34 | 35 | typedef uint32_t Label; 36 | typedef VertexMap Labelling; 37 | 38 | 39 | struct Legend { 40 | std::map, Label> label; 41 | std::vector > lookup; 42 | 43 | Set operator()(Label const lbl) const 44 | { 45 | return lookup.at(lbl); 46 | } 47 | 48 | Label labelFor(Set const& s) { 49 | if (lookup.size() == 0) 50 | lookup.push_back(Set()); 51 | 52 | if (label.count(s) == 0) 53 | { 54 | label[s] = lookup.size(); 55 | lookup.push_back(s); 56 | } 57 | return label[s]; 58 | } 59 | 60 | Label labelFor(Cell const c) { 61 | return labelFor(Set(c)); 62 | } 63 | 64 | size_t size() const 65 | { 66 | return lookup.size(); 67 | } 68 | }; 69 | 70 | 71 | std::pair, Legend> markStable( 72 | CubicalComplex const& complex, 73 | Field const& field) 74 | { 75 | Cell const n = complex.cellIdLimit(); 76 | Field::Vectors coV = field.coV(); 77 | Facets I(complex.xdim(), complex.ydim(), complex.zdim(), false); 78 | Facets coI(complex.xdim(), complex.ydim(), complex.zdim(), true); 79 | 80 | std::queue q; 81 | std::vector