├── src ├── util.h ├── dnn.h ├── graph.h ├── kdtree │ ├── README │ ├── LICENSE │ ├── kdtree.hpp │ └── kdtree.cpp ├── output.h ├── util.cpp ├── triplet.h ├── cluster.h ├── hclust │ ├── LICENSE │ ├── fastcluster.h │ ├── README │ ├── fastcluster_R_dm.cpp │ └── fastcluster.cpp ├── option.h ├── pointcloud.h ├── dnn.cpp ├── graph.cpp ├── triplet.cpp ├── main.cpp ├── option.cpp ├── pointcloud.cpp ├── cluster.cpp └── output.cpp ├── .gitignore ├── maketgz ├── CMakeLists.txt ├── CHANGES ├── data ├── README.md ├── attpc.dat ├── synthetic-clean.dat ├── synthetic-noise.dat ├── tennis.dat └── radar.dat ├── cdist-plot.r ├── LICENSE ├── test.dat └── README.md /src/util.h: -------------------------------------------------------------------------------- 1 | // 2 | // util.h 3 | // Utility functions needed here and there. 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2018-08-30 7 | // License: see ../LICENSE 8 | // 9 | 10 | #ifndef UTIL_H 11 | #define UTIL_H 12 | 13 | enum Linkage { SINGLE, COMPLETE, AVERAGE }; 14 | 15 | // converts *str* to double. 16 | double stod(const char* str); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /src/dnn.h: -------------------------------------------------------------------------------- 1 | // 2 | // dnn.h 3 | // Functions for computing the characteristic length dnn 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2018-08-30 7 | // License: see ../LICENSE 8 | // 9 | 10 | #ifndef DNN_H 11 | #define DNN_H 12 | #include "pointcloud.h" 13 | 14 | // compute first quartile of the mean squared distance from the points 15 | double first_quartile(const PointCloud &cloud); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /maketgz: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # maketgz - creates TGZ archive of source code 5 | # 6 | 7 | VERSION="" 8 | 9 | 10 | # 11 | # parse command line arguments 12 | # 13 | USAGEMSG="USAGE: `basename $0` -v version" 14 | while [ $# -gt 0 ] 15 | do 16 | case "$1" in 17 | -v) VERSION="$2"; shift;; 18 | *) echo "$USAGEMSG" 1>&2; exit 1;; 19 | esac 20 | shift 21 | done 22 | 23 | if [ -z "$VERSION" ] 24 | then 25 | echo "$USAGEMSG" 1>&2 26 | exit 1 27 | fi 28 | 29 | # 30 | # build archive 31 | # 32 | DIR=triplclust-$VERSION 33 | 34 | ln -s . $DIR 35 | 36 | tar czvf $DIR.tgz \ 37 | $DIR/README.md $DIR/CMakeLists.txt $DIR/CHANGES $DIR/LICENSE \ 38 | $DIR/test.dat $DIR/cdist-plot.r $DIR/src $DIR/data 39 | 40 | # clean up 41 | rm $DIR 42 | -------------------------------------------------------------------------------- /src/graph.h: -------------------------------------------------------------------------------- 1 | // 2 | // graph.h 3 | // Classes and functions for computing the MST and for 4 | // splitting up clusters at gaps 5 | // 6 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 7 | // Date: 2018-08-30 8 | // License: see ../LICENSE 9 | // 10 | 11 | #ifndef MSD_H 12 | #define MSD_H 13 | #include 14 | #include 15 | 16 | #include "pointcloud.h" 17 | 18 | // Split *cluster* in multiple new clusters and return the result in 19 | // *new_clusters". The mst of the cluster is created and all edges are 20 | // removed with a wheigth > *dmax*. The connected comonents are computed 21 | // and returned as new clusters if their size is >= *min_size*. 22 | void max_step(std::vector > &new_clusters, 23 | const std::vector &cluster, const PointCloud &cloud, 24 | double dmax, size_t min_size); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8 FATAL_ERROR) 2 | 3 | project(triplclust) 4 | 5 | set (CMAKE_CXX_FLAGS "-O2") 6 | 7 | # with MS Visual C++ we must explicity switch on proper exception handling 8 | if (MSVC) 9 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") 10 | endif (MSVC) 11 | 12 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0") 13 | 14 | # all source files 15 | set(SRC src/cluster.cpp src/triplet.cpp src/main.cpp src/dnn.cpp src/hclust/fastcluster.cpp src/kdtree/kdtree.cpp src/pointcloud.cpp src/output.cpp src/option.cpp src/util.cpp src/graph.cpp) 16 | 17 | # default target (created with "make") 18 | add_executable (triplclust ${SRC}) 19 | 20 | # webdemo target (created with "make demo") 21 | add_executable (triplclust-demo ${SRC}) 22 | set_target_properties(triplclust-demo PROPERTIES EXCLUDE_FROM_ALL TRUE COMPILE_FLAGS "-DWEBDEMO") 23 | add_custom_target(demo DEPENDS triplclust-demo) 24 | -------------------------------------------------------------------------------- /src/kdtree/README: -------------------------------------------------------------------------------- 1 | C++ Kd-Tree Library 2 | =================== 3 | 4 | This is a standalone C++ version of the kd-tree implementation in the 5 | Gamera framework that has been extended by a range search and has 6 | been relicensed by the original author under a BSD style license. 7 | 8 | Usage of the library 9 | -------------------- 10 | 11 | Design and usage of the library are described in detail in 12 | 13 | C. Dalitz: Kd-Trees for Document Layout Analysis. 14 | In C. Dalitz (Ed.): "Document Image Analysis with the Gamera Framework." 15 | Schriftenreihe des Fachbereichs Elektrotechnik und Informatik, 16 | Hochschule Niederrhein, vol. 8, pp. 39-52, Shaker Verlag (2009) 17 | 18 | 19 | Authors & Copyright 20 | ------------------- 21 | 22 | Christoph Dalitz, 2018-2022, 23 | Jens Wilberg, 2018, 24 | 25 | For licensing information, see the file LICENSE for details. 26 | -------------------------------------------------------------------------------- /src/output.h: -------------------------------------------------------------------------------- 1 | // 2 | // output.h 3 | // Functions for writing output files. 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2018-08-30 7 | // License: see ../LICENSE 8 | // 9 | 10 | #ifndef OUTPUT_H 11 | #define OUTPUT_H 12 | #include "cluster.h" 13 | #include "pointcloud.h" 14 | 15 | // saves a PointCloud *cloud* as csv file. 16 | bool cloud_to_csv(const PointCloud &cloud, 17 | const char *fname = "debug_smoothed.csv"); 18 | // saves smoothen cloud as gnuplot script. 19 | bool debug_gnuplot(const PointCloud &cloud, const PointCloud &cloud_smooth, 20 | const char *fname = "debug_smoothed.gnuplot"); 21 | // prints gnuplot script to stdout. 22 | void clusters_to_gnuplot(const PointCloud &cloud, 23 | const std::vector &clusters); 24 | // saves the PointCloud *cloud* with clusters *cluster* as csv file. 25 | void clusters_to_csv(const PointCloud &cloud); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // util.cpp 3 | // Utility functions needed here and there. 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2019-04-02 7 | // License: see ../LICENSE 8 | // 9 | 10 | #include "util.h" 11 | #include 12 | #include 13 | 14 | //------------------------------------------------------------------- 15 | // converts *str* to double. 16 | // If str is not a number a invalid_argument exception is thrown. 17 | //------------------------------------------------------------------- 18 | double stod(const char* s) { 19 | double result; 20 | 21 | // remove leading and trailing white space 22 | std::string str(s); 23 | str.erase(0, str.find_first_not_of("\t\r\n ")); 24 | str.erase(str.find_last_not_of("\t\r\n ") + 1); 25 | 26 | // use istream for conversion 27 | std::istringstream iss(str); 28 | 29 | iss >> std::ws >> result; 30 | if (iss.fail() || !iss.eof()) { 31 | throw std::invalid_argument("not a number"); 32 | } 33 | return result; 34 | } 35 | -------------------------------------------------------------------------------- /src/triplet.h: -------------------------------------------------------------------------------- 1 | // 2 | // triplet.h 3 | // Classes and functions for triplets of three points and 4 | // computing their dissimilarity. 5 | // 6 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 7 | // Date: 2018-08-30 8 | // License: see ../LICENSE 9 | // 10 | 11 | #ifndef TRIPLET_H 12 | #define TRIPLET_H 13 | 14 | #include 15 | #include 16 | 17 | #include "pointcloud.h" 18 | 19 | // triplet of three points 20 | struct triplet { 21 | size_t point_index_a; 22 | size_t point_index_b; 23 | size_t point_index_c; 24 | Point center; 25 | Point direction; 26 | double error; 27 | friend bool operator<(const triplet &t1, const triplet &t2) { 28 | return (t1.error < t2.error); 29 | }; 30 | }; 31 | 32 | // dissimilarity for triplets. 33 | // scale is an external scale factor. 34 | class ScaleTripletMetric { 35 | private: 36 | double scale; 37 | 38 | public: 39 | ScaleTripletMetric(double s); 40 | double operator()(const triplet &lhs, const triplet &rhs); 41 | }; 42 | 43 | // generates triplets from PointCloud 44 | void generate_triplets(const PointCloud &cloud, std::vector &triplets, 45 | size_t k, size_t n, double a); 46 | #endif 47 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Change log of triplclust 2 | ========================= 3 | 4 | Version 1.4 from 2024-02-16 5 | --------------------------- 6 | 7 | - new option -ordered for point clouds that are in chronological order, 8 | which can improve trajctory detection (thanks to Juliane Arning) 9 | 10 | - step 3) of the pruning method described at the end of section 2.4 11 | in the IPOL article was missing and has now been added 12 | 13 | - removed unused variable warning from graph.cpp 14 | 15 | - ported new kdtree library version 1.2 to triplclust 16 | 17 | - all exceptions now caught as reference 18 | 19 | 20 | Version 1.3 from 2019-04-02 21 | --------------------------- 22 | 23 | - floats no longer converted to int by ostream 24 | 25 | - windows line endings ("\r\n") now possible in input file, too 26 | 27 | 28 | Version 1.2 from 2019-03-22 29 | --------------------------- 30 | 31 | - error message when dnn is computed as zero due to too many identical points 32 | 33 | 34 | Version 1.1 from 2018-12-12 35 | --------------------------- 36 | 37 | - cdist-plot.r now also works if no threshold is found 38 | 39 | - data directory added 40 | 41 | 42 | Version 1.0 from 2018-09-03 43 | --------------------------- 44 | 45 | - first creation 46 | -------------------------------------------------------------------------------- /src/cluster.h: -------------------------------------------------------------------------------- 1 | // 2 | // cluster.h 3 | // Functions for triplet clustering and for propagating 4 | // the triplet cluster labels to points 5 | // 6 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 7 | // Date: 2018-08-30 8 | // License: see ../LICENSE 9 | // 10 | 11 | #ifndef CLUSTER_H 12 | #define CLUSTER_H 13 | 14 | #include 15 | #include 16 | 17 | #include "triplet.h" 18 | #include "util.h" 19 | 20 | typedef std::vector cluster_t; 21 | 22 | typedef std::vector cluster_group; 23 | 24 | // compute hierarchical clustering 25 | void compute_hc(const PointCloud &cloud, cluster_group &result, 26 | const std::vector &triplets, double s, double t, 27 | bool tauto = false, double dmax = 0, bool is_dmax = false, 28 | Linkage method = SINGLE, int opt_verbose = 0); 29 | // remove all small clusters 30 | void cleanup_cluster_group(cluster_group &cg, size_t m, int opt_verbose = 0); 31 | // convert the triplet indices ind *cl_group* to point indices. 32 | void cluster_triplets_to_points(const std::vector &triplets, 33 | cluster_group &cl_group); 34 | // adds the cluster ids to the points in *cloud* 35 | void add_clusters(PointCloud &cloud, cluster_group &cl_group, 36 | bool gnuplot = false); 37 | #endif 38 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | TriplClust Reference Data Files 2 | =============================== 3 | 4 | These files contain point clouds that can be used as input for the 5 | TriplClust algorithm that is described in (cited as "IPOL paper" below): 6 | 7 | > C. Dalitz, J. Wilberg, L. Aymans: "TriplClust: An Algorithm 8 | > for Curve Detection in 3D Point Clouds." 9 | > Image Processing Online 9, pp. 26-46 (2019) 10 | > https://doi.org/10.5201/ipol.2019.234 11 | 12 | 13 | Description 14 | ----------- 15 | 16 | See the IPOL paper for a detailed description of the files and for 17 | their origin. The data files have the exension ".dat". 18 | 19 | Depending on the file, the following command line paramters should 20 | be used: 21 | 22 | triplclust attpc.dat 23 | triplclust lidar.dat -k 12 -t 12 24 | triplclust radar.dat -a 0.003 25 | triplclust synthetic-clean.dat 26 | triplclust synthetic-noise.dat -r 1.0 27 | triplclust tennis.dat -k 12 28 | 29 | For visualization of the results, you can use gnuplot, e.g.: 30 | 31 | triplclust attpc.dat -gnuplot | gnuplot 32 | 33 | 34 | Author & Copyright 35 | ------------------ 36 | 37 | Christoph Dalitz, 2018 38 | Institute for Pattern Recognition 39 | Niederrhein University of Applied Sciences 40 | Krefeld, Germany 41 | 42 | These files can be freely used, copied, modified and distributed under 43 | the terms of Creative Commons license CC BY-SA 3.0. Attribution should be 44 | done by referencing the IPOL paper. 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/kdtree/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright: 2 | * 2018 Christoph Dalitz and Jens Wilberg 3 | Niederrhein University of Applied Sciences, 4 | Institute for Pattern Recognition, 5 | Reinarzstr. 49, 47805 Krefeld, Germany 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /src/hclust/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright: 2 | * fastcluster_dm.cpp & fastcluster_R_dm.cpp: 3 | © 2011 Daniel Müllner 4 | * fastcluster.(h|cpp) & demo.cpp & plotresult.r: 5 | © 2018 Christoph Dalitz 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /cdist-plot.r: -------------------------------------------------------------------------------- 1 | # 2 | # R script for plotting cdist as function of the number of clusters 3 | # 4 | 5 | infile <- "debug_cdist.csv" 6 | plotfilename <- "debug_cdist.pdf" 7 | 8 | message(sprintf("cdist plot is written to '%s'", plotfilename)) 9 | 10 | cyan <- rgb(007,161,226,maxColorValue=255) 11 | 12 | x <- rev(read.csv(infile, header=FALSE)[,1]) 13 | n <- length(x) 14 | k <- min(c(80,n-1)) 15 | x.tail <- x[1:k] 16 | 17 | # two sigma expected jump width 18 | expjump <- function(x,k) { 19 | y <- rep(0,k) # preallocation 20 | for (i in 1:k) { 21 | y[i] <- x[i+1] + 2*sd(x[i:n], na.rm=TRUE) 22 | } 23 | return(y) 24 | } 25 | 26 | 27 | # set plot parameters 28 | pdf(plotfilename, width=6, height=4) 29 | par(lwd=1, ps=14, mar=c(3.5,3.5,0.5,0.5), cex.lab=1.0, family="Times") 30 | 31 | # plot of cdist valaues 32 | plot(1:k, x.tail, type='l', xlab=expression(italic('number of clusters, i.e. ') (m-i)), ylab=expression(italic('cdist')), mgp=c(2.5,1,0)) 33 | points(1:k, x.tail) 34 | 35 | # plot comparison line of automatic threshold 36 | z <- expjump(x,k) 37 | lines(1:k, z, col="red") 38 | if (any(z[1:k] < x[1:k])) { 39 | t.index <- max(which(z[1:k] < x[1:k])) 40 | t <- z[t.index] 41 | lines(c(-1,k+4), c(t,t), col=cyan) 42 | text(expression(italic('automatic threshold')), x=k-15, y=t, pos=3, col=cyan) 43 | } 44 | legend("topright", c(expression(italic("cdist")[i]), expression(italic("cdist")[i-1] + sigma^{(i)})), col=c("black","red"), lty=c(1,1)) 45 | 46 | # close pdf outfile 47 | grabage <- dev.off() 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright: 2 | * (C) 2018 Christoph Dalitz, Jens Wilberg, Lukas Aymans 3 | Niederrhein University of Applied Sciences, 4 | Institute for Pattern Recognition 5 | * the clustering uses the fastcluster implementation by Daniel Müllner 6 | see the file src/hclust/LICENSE for its copyright and licensing information 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /src/option.h: -------------------------------------------------------------------------------- 1 | // 2 | // option.h 3 | // Class and functions for parsing and storing command line options. 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2024-02-02 7 | // License: see ../LICENSE 8 | // 9 | 10 | #ifndef OPTION_H 11 | #define OPTION_H 12 | #include 13 | #include 14 | 15 | #include "util.h" 16 | 17 | // class for handling all command line option 18 | class Opt { 19 | private: 20 | char *infile_name, *outfile_prefix; 21 | // output as gnuplot 22 | bool gnuplot; 23 | // csv file delimiter 24 | char delimiter; 25 | // csv file with header 26 | size_t skip; 27 | // verbosity level 28 | int verbose; 29 | 30 | // neighbour distance for smoothing 31 | double r; 32 | bool rdnn; // compute r with dnn 33 | 34 | // tested neighbours of triplet mid point 35 | size_t k; 36 | // max number of triplets to one mid point 37 | size_t n; 38 | // 1 - cos alpha, where alpha is the angle between the two triplet branches 39 | double a; 40 | 41 | // distance scale factor in metric 42 | double s; 43 | bool sdnn; // compute s with dnn 44 | // threshold for cdist in clustering 45 | double t; 46 | bool tauto; // auto generate t 47 | // maximum gap width 48 | double dmax; 49 | bool isdmax; // dmax != none 50 | bool dmax_dnn; // use dnn for dmax 51 | bool ordered; // points are in chronological order 52 | // linkage method for clustering 53 | Linkage link; 54 | 55 | // min number of triplets per cluster 56 | size_t m; 57 | 58 | std::pair parse_argument(const char *str); 59 | 60 | public: 61 | Opt(); 62 | int parse_args(int argc, char **argv); 63 | // compute attributes which depend on dnn. 64 | void set_dnn(double dnn); 65 | 66 | // read access functions 67 | const char *get_ifname(); 68 | // get outfile name 69 | const char *get_ofprefix(); 70 | bool needs_dnn(); 71 | bool is_gnuplot(); 72 | char get_delimiter(); 73 | size_t get_skip(); 74 | int get_verbosity(); 75 | double get_r(); 76 | size_t get_k(); 77 | size_t get_n(); 78 | double get_a(); 79 | double get_s(); 80 | bool is_tauto(); 81 | double get_t(); 82 | bool is_dmax(); 83 | double get_dmax(); 84 | bool get_ordered(); //! 85 | Linkage get_linkage(); 86 | size_t get_m(); 87 | }; 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /src/pointcloud.h: -------------------------------------------------------------------------------- 1 | // 2 | // pointcloud.h 3 | // Classes and functions for 3D points and clouds thereof. 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2024-02-02 7 | // License: see ../LICENSE 8 | // 9 | 10 | #ifndef POINTCLOUD_H 11 | #define POINTCLOUD_H 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // 3D point class. 20 | class Point { 21 | public: 22 | double x; 23 | double y; 24 | double z; 25 | std::set cluster_ids; 26 | size_t index; // only used for chronological order 27 | 28 | Point(){}; 29 | Point(const std::vector& point); 30 | Point(const std::vector& point, const std::set& cluster_ids); 31 | Point(double x, double y, double z); 32 | Point(double x, double y, double z, const std::set& cluster_ids); 33 | Point(double x, double y, double z, size_t index); 34 | 35 | // representation of 3D point as std::vector 36 | std::vector as_vector() const; 37 | // Euclidean norm 38 | double norm() const; 39 | // squared norm 40 | double squared_norm() const; 41 | 42 | friend std::ostream& operator<<(std::ostream& os, const Point& p); 43 | bool operator==(const Point& p) const; 44 | Point& operator=(const Point& other); 45 | // vector addition 46 | Point operator+(const Point& p) const; 47 | // vector subtraction 48 | Point operator-(const Point& p) const; 49 | // scalar product 50 | double operator*(const Point& p) const; 51 | // scalar division 52 | Point operator/(double c) const; 53 | }; 54 | 55 | // scalar multiplication 56 | Point operator*(Point x, double c); 57 | Point operator*(double c, Point x); 58 | 59 | // The Pointcloud is a vector of points 60 | class PointCloud : public std::vector { 61 | private: 62 | bool points2d; 63 | bool ordered; //! 64 | 65 | public: 66 | bool is2d() const; 67 | void set2d(bool is2d); 68 | bool isOrdered() const; //! 69 | void setOrdered(bool isOrdered); //! 70 | PointCloud(); 71 | }; 72 | 73 | 74 | // Load csv file. 75 | void load_csv_file(const char* fname, PointCloud& cloud, const char delimiter, 76 | size_t skip = 0); 77 | // Smoothing of the PointCloud *cloud*. The result is returned in *result_cloud* 78 | void smoothen_cloud(const PointCloud& cloud, PointCloud& result_cloud, 79 | double radius); 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/dnn.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // dnn.cpp 3 | // Functions for computing the characteristic length dnn 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2018-08-30 7 | // License: see ../LICENSE 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "dnn.h" 15 | #include "kdtree/kdtree.hpp" 16 | 17 | //------------------------------------------------------------------- 18 | // Compute mean squared distances. 19 | // the distances is computed for every point in *cloud* to its *k* 20 | // nearest neighbours. The distances are returned in *msd*. 21 | //------------------------------------------------------------------- 22 | void compute_mean_square_distance(const PointCloud &cloud, 23 | std::vector &msd, int k) { 24 | // compute mean square distances for every point to its k nearest neighbours 25 | Kdtree::KdNodeVector nodes, result; 26 | double sum; 27 | 28 | // build kdtree 29 | for (size_t i = 0; i < cloud.size(); ++i) { 30 | nodes.push_back(cloud[i].as_vector()); 31 | } 32 | Kdtree::KdTree kdtree(&nodes); 33 | 34 | k++; // k must be one higher because the first point found by the kdtree is 35 | // the point itself 36 | 37 | for (size_t i = 0; i < cloud.size(); ++i) { 38 | // compute 39 | std::vector squared_distances; 40 | kdtree.k_nearest_neighbors(cloud[i].as_vector(), k, &result, 41 | &squared_distances); 42 | 43 | squared_distances.erase(squared_distances.begin()); // The first value 44 | // must be deleted 45 | // because it is 46 | // the distance 47 | // with the point 48 | // itself 49 | 50 | sum = std::accumulate(squared_distances.begin(), squared_distances.end(), 51 | 0.0); 52 | msd.push_back(sum / squared_distances.size()); 53 | } 54 | } 55 | 56 | //------------------------------------------------------------------- 57 | // Compute first quartile of the mean squared distance of all points 58 | // in *cloud* 59 | //------------------------------------------------------------------- 60 | double first_quartile(const PointCloud &cloud) { 61 | std::vector msd; 62 | compute_mean_square_distance(cloud, msd, 1); 63 | const double q1 = msd.size() / 4; 64 | std::nth_element(msd.begin(), msd.begin() + q1, msd.end()); 65 | return msd[q1]; 66 | } 67 | -------------------------------------------------------------------------------- /src/hclust/fastcluster.h: -------------------------------------------------------------------------------- 1 | // 2 | // C++ standalone verion of fastcluster by Daniel Muellner 3 | // 4 | // Copyright: Daniel Muellner, 2011 5 | // Christoph Dalitz, 2018 6 | // License: BSD style license 7 | // (see the file LICENSE for details) 8 | // 9 | 10 | #ifndef fastclustercpp_H 11 | #define fastclustercpp_H 12 | 13 | // 14 | // Assigns cluster labels (0, ..., nclust-1) to the n points such 15 | // that the cluster result is split into nclust clusters. 16 | // 17 | // Input arguments: 18 | // n = number of observables 19 | // merge = clustering result in R format 20 | // nclust = number of clusters 21 | // Output arguments: 22 | // labels = allocated integer array of size n for result 23 | // 24 | void cutree_k(int n, const int* merge, int nclust, int* labels); 25 | 26 | // 27 | // Assigns cluster labels (0, ..., nclust-1) to the n points such 28 | // that the hierarchical clsutering is stopped at cluster distance cdist 29 | // 30 | // Input arguments: 31 | // n = number of observables 32 | // merge = clustering result in R format 33 | // height = cluster distance at each merge step 34 | // cdist = cutoff cluster distance 35 | // Output arguments: 36 | // labels = allocated integer array of size n for result 37 | // 38 | void cutree_cdist(int n, const int* merge, double* height, double cdist, int* labels); 39 | 40 | // 41 | // Hierarchical clustering with one of Daniel Muellner's fast algorithms 42 | // 43 | // Input arguments: 44 | // n = number of observables 45 | // distmat = condensed distance matrix, i.e. an n*(n-1)/2 array representing 46 | // the upper triangle (without diagonal elements) of the distance 47 | // matrix, e.g. for n=4: 48 | // d00 d01 d02 d03 49 | // d10 d11 d12 d13 -> d02 d02 d03 d12 d13 d23 50 | // d20 d21 d22 d23 51 | // d30 d31 d32 d33 52 | // method = cluster metric (see enum method_code) 53 | // Output arguments: 54 | // merge = allocated (n-1)x2 matrix (2*(n-1) array) for storing result. 55 | // Result follows R hclust convention: 56 | // - observabe indices start with one 57 | // - merge[i][] contains the merged nodes in step i 58 | // - merge[i][j] is negative when the node is an atom 59 | // height = allocated (n-1) array with distances at each merge step 60 | // Return code: 61 | // 0 = ok 62 | // 1 = invalid method 63 | // 64 | int hclust_fast(int n, double* distmat, int method, int* merge, double* height); 65 | enum hclust_fast_methods { 66 | HCLUST_METHOD_SINGLE = 0, 67 | HCLUST_METHOD_COMPLETE = 1, 68 | HCLUST_METHOD_AVERAGE = 2, 69 | HCLUST_METHOD_MEDIAN = 3 70 | }; 71 | 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /src/hclust/README: -------------------------------------------------------------------------------- 1 | C++ interface to fast hierarchical clustering algorithms 2 | ======================================================== 3 | 4 | This is simplified C++ interface to fast implementations of hierarchical 5 | clustering by Daniel Müllner. The original library with interfaces to R 6 | and Python is described in: 7 | 8 | Daniel Müllner: "fastcluster: Fast Hierarchical, Agglomerative Clustering 9 | Routines for R and Python." Journal of Statistical Software 53 (2013), 10 | no. 9, pp. 1–18, http://www.jstatsoft.org/v53/i09/ 11 | 12 | 13 | Usage of the library 14 | -------------------- 15 | 16 | For using the library, the following source files are needed: 17 | 18 | fastcluster_dm.cpp, fastcluster_R_dm.cpp 19 | original code by Daniel Müllner 20 | these are included by fastcluster.cpp via #include, and therefore 21 | need not be compiled to object code 22 | 23 | fastcluster.[h|cpp] 24 | simplified C++ interface 25 | fastcluster.cpp is the only file that must be compiled 26 | 27 | The library provides the clustering function *hclust_fast* for 28 | creating the dendrogram information in an encoding as used by the 29 | R function *hclust*. For a description of the parameters, see fastcluster.h. 30 | Its parameter *method* can be one of 31 | 32 | HCLUST_METHOD_SINGLE 33 | single link with the minimum spanning tree algorithm (Rohlf, 1973) 34 | 35 | HHCLUST_METHOD_COMPLETE 36 | complete link with the nearest-neighbor-chain algorithm (Murtagh, 1984) 37 | 38 | HCLUST_METHOD_AVERAGE 39 | complete link with the nearest-neighbor-chain algorithm (Murtagh, 1984) 40 | 41 | HCLUST_METHOD_MEDIAN 42 | median link with the generic algorithm (Müllner, 2011) 43 | 44 | For splitting the dendrogram into clusters, the two functions *cutree_k* 45 | and *cutree_cdist* are provided. 46 | 47 | Note that output parameters must be allocated beforehand, e.g. 48 | int* merge = new int[2*(npoints-1)]; 49 | For a complete usage example, see lines 135-142 of demo.cpp. 50 | 51 | 52 | Demonstration program 53 | --------------------- 54 | 55 | A simple demo is implemented in demo.cpp, which can be compiled and run with 56 | 57 | make 58 | ./hclust-demo -m complete lines.csv 59 | 60 | It creates two clusters of line segments such that the segment angle between 61 | line segments of different clusters have a maximum (cosine) dissimilarity. 62 | For visualizing the result, plotresult.r can be used as follows 63 | (requires R to be installed): 64 | 65 | ./hclust-demo -m complete lines.csv | Rscript plotresult.r 66 | 67 | 68 | Authors & Copyright 69 | ------------------- 70 | 71 | Daniel Müllner, 2011, 72 | Christoph Dalitz, 2018, 73 | 74 | 75 | License 76 | ------- 77 | 78 | This code is provided under a BSD-style license. 79 | See the file LICENSE for details. 80 | -------------------------------------------------------------------------------- /test.dat: -------------------------------------------------------------------------------- 1 | # Test data file for triplclust 2 | # Source: AT-TPC at the NSCL, Michigan State University 3 | # (courtesy of Yassid Ayyad) 4 | 107.981 2.7999 64 5 | 105.527 1.45075 110.8 6 | 105.527 -1.45075 172 7 | 103.073 -2.7999 175.6 8 | 34.3576 167.362 175.6 9 | 100.619 -7.05056 294.4 10 | 41.72 103.467 298 11 | 98.1647 -5.70141 298 12 | 49.0824 28.3038 308.8 13 | 39.2659 104.816 308.8 14 | 36.8118 128.97 319.6 15 | 36.8118 100.565 334 16 | 34.3577 96.3143 362.8 17 | -147.247 90.85 373.6 18 | 31.9035 92.0637 388 19 | 98.1647 -14.2027 424 20 | 115.344 24.0532 442 21 | 115.344 26.9547 442 22 | 24.5412 82.2132 445.6 23 | 110.435 24.0532 445.6 24 | 112.889 22.704 445.6 25 | 107.981 22.704 449.2 26 | 107.981 19.8025 449.2 27 | 120.252 26.9547 449.2 28 | 105.527 18.4534 452.8 29 | 103.073 19.8025 452.8 30 | 100.619 18.4534 456.4 31 | -49.0824 28.3038 460 32 | 95.7106 15.5519 460 33 | 24.5412 79.3117 460 34 | 93.2565 14.2027 463.6 35 | 90.8024 15.5519 463.6 36 | 95.7106 -15.5519 463.6 37 | 88.3483 14.2027 467.2 38 | 26.9953 75.0611 470.8 39 | 120.252 24.0532 478 40 | 22.0871 75.0611 481.6 41 | 24.5412 73.7119 481.6 42 | 2.4541 52.4586 488.8 43 | 95.7106 -18.4534 488.8 44 | 24.5412 70.8104 496 45 | 22.0871 69.4613 503.2 46 | 22.0871 66.5598 514 47 | 24.5412 65.2106 521.2 48 | 24.5412 62.3091 535.6 49 | 22.0871 60.9599 539.2 50 | 117.798 22.704 542.8 51 | 26.9953 60.9599 546.4 52 | 22.0871 58.0584 553.6 53 | 26.9953 58.0584 557.2 54 | 24.5412 56.7093 560.8 55 | 24.5412 53.8078 575.2 56 | 26.9953 52.4586 582.4 57 | 115.344 9.95207 593.2 58 | 26.9953 49.5571 596.8 59 | 24.5412 48.208 600.4 60 | 117.798 19.8025 604 61 | 29.4494 48.208 607.6 62 | 29.4494 45.3065 618.4 63 | 26.9953 43.9573 622 64 | 26.9953 41.0558 636.4 65 | 31.9035 41.0558 643.6 66 | 29.4494 39.7067 647.2 67 | 29.4494 36.8052 658 68 | 115.344 18.4534 661.6 69 | 31.9035 35.456 668.8 70 | 31.9035 32.5545 683.2 71 | 34.3577 31.2053 694 72 | 112.889 19.8025 697.6 73 | 34.3577 28.3038 708.4 74 | 115.344 15.5519 708.4 75 | 36.8118 26.9547 719.2 76 | 36.8118 24.0532 730 77 | 34.3577 22.704 730 78 | 39.2659 22.704 744.4 79 | 112.889 14.2027 751.6 80 | 36.8118 18.4534 755.2 81 | 39.2659 19.8025 758.8 82 | 41.72 18.4534 766 83 | 110.435 15.5519 769.6 84 | 41.72 15.5519 780.4 85 | 39.2659 14.2027 780.4 86 | 112.889 11.3012 787.6 87 | 44.1741 14.2027 794.8 88 | 44.1741 11.3012 809.2 89 | 46.6282 9.95207 820 90 | 49.0824 11.3012 827.2 91 | 46.6282 7.05056 834.4 92 | 110.435 9.95207 841.6 93 | 107.981 11.3012 848.8 94 | 49.0824 5.70141 852.4 95 | 51.5365 7.05056 856 96 | 110.435 7.05056 870.4 97 | 53.9906 5.70141 870.4 98 | 53.9906 2.7999 881.2 99 | 105.527 9.95207 892 100 | 107.981 5.70141 899.2 101 | 56.4447 1.45075 899.2 102 | 56.4447 -1.45075 902.8 103 | 58.8988 -2.7999 917.2 104 | 61.3529 -1.45075 924.4 105 | 105.527 7.05056 928 106 | 63.8071 -2.7999 942.4 107 | 63.8071 -5.70141 953.2 108 | 103.073 5.70141 953.2 109 | 66.2612 -7.05056 971.2 110 | 68.7153 -5.70141 978.4 111 | 103.073 2.7999 985.6 112 | 71.1694 -7.05056 996.4 113 | 71.1694 -9.95207 1003.6 114 | 100.619 1.45075 1014.4 115 | 73.6235 -11.3012 1021.6 116 | 76.0776 -9.95207 1028.8 117 | 100.619 -1.45075 1046.8 118 | 78.5318 -11.3012 1054 119 | 80.9859 -9.95207 1064.8 120 | 83.44 -11.3012 1075.6 121 | 83.44 -14.2027 1090 122 | 98.1647 -2.7999 1104.4 123 | 88.3483 -14.2027 1111.6 124 | 93.2565 -14.2027 1147.6 125 | 95.7106 -7.05056 1180 126 | 95.7106 -9.95207 1201.6 127 | 93.2565 -19.8025 1338.4 128 | -------------------------------------------------------------------------------- /data/attpc.dat: -------------------------------------------------------------------------------- 1 | # Test data file for triplclust 2 | # Source: AT-TPC at the NSCL, Michigan State University 3 | # (courtesy of Yassid Ayyad) 4 | # x y z 5 | 2.4541 52.4586 64 6 | 137.431 -96.3821 71.2 7 | 34.3576 167.362 179.2 8 | -66.2612 15.5519 679.6 9 | -51.5365 7.05056 697.6 10 | -49.0824 5.70141 701.2 11 | -46.6282 7.05056 701.2 12 | -44.1741 2.7999 708.4 13 | -44.1741 5.70141 708.4 14 | -39.2659 2.7999 712 15 | -41.72 1.45075 712 16 | -36.8118 1.45075 715.6 17 | -34.3577 2.7999 719.2 18 | -31.9035 1.45075 722.8 19 | -31.9035 -1.45075 722.8 20 | -26.9953 -1.45075 730 21 | -51.5365 9.95207 733.6 22 | -19.6329 -5.70141 737.2 23 | -12.2706 -7.05056 748 24 | -9.81648 -5.70141 751.6 25 | -7.36236 -7.05056 755.2 26 | -4.90825 -5.70141 758.8 27 | -2.45413 -7.05056 762.4 28 | -1.33514e-05 -5.70141 762.4 29 | 2.4541 -7.05056 766 30 | 4.90825 -5.70141 769.6 31 | 7.36237 -7.05056 773.2 32 | 9.81648 -5.70141 776.8 33 | 12.2706 -7.05056 780.4 34 | 17.1788 -7.05056 787.6 35 | 19.6329 -5.70141 791.2 36 | 22.0871 -7.05056 794.8 37 | 24.5412 -5.70141 798.4 38 | 26.9953 -7.05056 802 39 | 29.4494 -2.7999 805.6 40 | 29.4494 -5.70141 805.6 41 | 31.9035 -7.05056 809.2 42 | 34.3577 -5.70141 812.8 43 | 34.3577 -2.7999 812.8 44 | 36.8118 -1.45075 816.4 45 | 39.2659 -2.7999 820 46 | 41.72 -1.45075 823.6 47 | 46.6282 -1.45075 827.2 48 | 44.1741 -2.7999 827.2 49 | 49.0824 -2.7999 830.8 50 | 51.5365 -1.45075 834.4 51 | 51.5365 1.45075 838 52 | 53.9906 2.7999 841.6 53 | 56.4447 1.45075 845.2 54 | 58.8988 5.70141 848.8 55 | 58.8988 2.7999 848.8 56 | 61.3529 1.45075 848.8 57 | 61.3529 7.05056 852.4 58 | 63.8071 2.7999 856 59 | 63.8071 5.70141 856 60 | -147.247 90.85 863.2 61 | 68.7153 5.70141 863.2 62 | 71.1694 7.05056 866.8 63 | 71.1694 9.95207 866.8 64 | 73.6235 11.3012 870.4 65 | 76.0776 9.95207 874 66 | 78.5318 11.3012 877.6 67 | 78.5318 14.2027 881.2 68 | 80.9859 15.5519 884.8 69 | 83.44 14.2027 884.8 70 | 85.8941 15.5519 888.4 71 | 85.8941 18.4534 892 72 | 100.619 26.9547 895.6 73 | 88.3483 19.8025 895.6 74 | 103.073 28.3038 895.6 75 | 103.073 31.2053 895.6 76 | 100.619 32.5545 895.6 77 | 98.1647 28.3038 895.6 78 | 95.7106 26.9547 895.6 79 | 88.3483 22.704 899.2 80 | 90.8024 24.0532 899.2 81 | 95.7106 24.0532 899.2 82 | 93.2565 22.704 899.2 83 | 98.1647 31.2053 899.2 84 | 93.2565 19.8025 924.4 85 | 90.8024 18.4534 935.2 86 | 90.8024 15.5519 964 87 | 93.2565 14.2027 982 88 | 93.2565 11.3012 1021.6 89 | 90.8024 9.95207 1028.8 90 | 95.7106 9.95207 1039.6 91 | 90.8024 7.05056 1061.2 92 | 95.7106 7.05056 1079.2 93 | 93.2565 5.70141 1097.2 94 | 93.2565 2.7999 1129.6 95 | 95.7106 1.45075 1162 96 | 95.7106 -1.45075 1205.2 97 | 93.2565 -2.7999 1223.2 98 | 93.2565 -5.70141 1291.6 99 | 95.7106 -7.05056 1298.8 100 | 137.431 31.2053 1342 101 | 137.431 28.3038 1367.2 102 | 95.7106 -9.95207 1374.4 103 | 93.2565 -11.3012 1406.8 104 | 134.976 26.9547 1414 105 | 95.7106 -15.5519 1478.8 106 | 132.522 28.3038 1532.8 107 | 93.2565 -19.8025 1568.8 108 | 130.068 26.9547 1579.6 109 | 130.068 24.0532 1604.8 110 | 127.614 28.3038 1619.2 111 | 93.2565 -22.704 1622.8 112 | 125.16 26.9547 1637.2 113 | 127.614 22.704 1640.8 114 | 125.16 24.0532 1666 115 | 122.706 22.704 1691.2 116 | 120.252 24.0532 1705.6 117 | 122.706 19.8025 1705.6 118 | 117.798 22.704 1727.2 119 | 120.252 18.4534 1727.2 120 | 117.798 19.8025 1741.6 121 | 115.344 18.4534 1770.4 122 | 112.889 19.8025 1781.2 123 | 115.344 15.5519 1788.4 124 | 112.889 14.2027 1788.4 125 | 110.435 15.5519 1788.4 126 | -------------------------------------------------------------------------------- /src/hclust/fastcluster_R_dm.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Excerpt from fastcluster_R.cpp 3 | // 4 | // Copyright: Daniel Müllner, 2011 5 | // 6 | 7 | struct pos_node { 8 | t_index pos; 9 | int node; 10 | }; 11 | 12 | void order_nodes(const int N, const int * const merge, const t_index * const node_size, int * const order) { 13 | /* Parameters: 14 | N : number of data points 15 | merge : (N-1)×2 array which specifies the node indices which are 16 | merged in each step of the clustering procedure. 17 | Negative entries -1...-N point to singleton nodes, while 18 | positive entries 1...(N-1) point to nodes which are themselves 19 | parents of other nodes. 20 | node_size : array of node sizes - makes it easier 21 | order : output array of size N 22 | 23 | Runtime: Θ(N) 24 | */ 25 | auto_array_ptr queue(N/2); 26 | 27 | int parent; 28 | int child; 29 | t_index pos = 0; 30 | 31 | queue[0].pos = 0; 32 | queue[0].node = N-2; 33 | t_index idx = 1; 34 | 35 | do { 36 | --idx; 37 | pos = queue[idx].pos; 38 | parent = queue[idx].node; 39 | 40 | // First child 41 | child = merge[parent]; 42 | if (child<0) { // singleton node, write this into the 'order' array. 43 | order[pos] = -child; 44 | ++pos; 45 | } 46 | else { /* compound node: put it on top of the queue and decompose it 47 | in a later iteration. */ 48 | queue[idx].pos = pos; 49 | queue[idx].node = child-1; // convert index-1 based to index-0 based 50 | ++idx; 51 | pos += node_size[child-1]; 52 | } 53 | // Second child 54 | child = merge[parent+N-1]; 55 | if (child<0) { 56 | order[pos] = -child; 57 | } 58 | else { 59 | queue[idx].pos = pos; 60 | queue[idx].node = child-1; 61 | ++idx; 62 | } 63 | } while (idx>0); 64 | } 65 | 66 | #define size_(r_) ( ((r_ 69 | void generate_R_dendrogram(int * const merge, double * const height, int * const order, cluster_result & Z2, const int N) { 70 | // The array "nodes" is a union-find data structure for the cluster 71 | // identites (only needed for unsorted cluster_result input). 72 | union_find nodes(sorted ? 0 : N); 73 | if (!sorted) { 74 | std::stable_sort(Z2[0], Z2[N-1]); 75 | } 76 | 77 | t_index node1, node2; 78 | auto_array_ptr node_size(N-1); 79 | 80 | for (t_index i=0; inode1; 85 | node2 = Z2[i]->node2; 86 | } 87 | else { 88 | node1 = nodes.Find(Z2[i]->node1); 89 | node2 = nodes.Find(Z2[i]->node2); 90 | // Merge the nodes in the union-find data structure by making them 91 | // children of a new node. 92 | nodes.Union(node1, node2); 93 | } 94 | // Sort the nodes in the output array. 95 | if (node1>node2) { 96 | t_index tmp = node1; 97 | node1 = node2; 98 | node2 = tmp; 99 | } 100 | /* Conversion between labeling conventions. 101 | Input: singleton nodes 0,...,N-1 102 | compound nodes N,...,2N-2 103 | Output: singleton nodes -1,...,-N 104 | compound nodes 1,...,N 105 | */ 106 | merge[i] = (node1(node1)-1 107 | : static_cast(node1)-N+1; 108 | merge[i+N-1] = (node2(node2)-1 109 | : static_cast(node2)-N+1; 110 | height[i] = Z2[i]->dist; 111 | node_size[i] = size_(node1) + size_(node2); 112 | } 113 | 114 | order_nodes(N, merge, node_size, order); 115 | } 116 | -------------------------------------------------------------------------------- /src/kdtree/kdtree.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __kdtree_HPP 2 | #define __kdtree_HPP 3 | 4 | // 5 | // Kd-Tree implementation. 6 | // 7 | // Copyright: Christoph Dalitz, 2018-2022 8 | // Jens Wilberg, 2018 9 | // Version: 1.2 10 | // License: BSD style license 11 | // (see the file LICENSE for details) 12 | // 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace Kdtree { 19 | 20 | typedef std::vector CoordPoint; 21 | typedef std::vector DoubleVector; 22 | 23 | // for passing points to the constructor of kdtree 24 | struct KdNode { 25 | CoordPoint point; 26 | void* data; 27 | int index; 28 | KdNode(const CoordPoint& p, void* d = NULL, int i = -1) { 29 | point = p; 30 | data = d; 31 | index = i; 32 | } 33 | KdNode() { data = NULL; } 34 | }; 35 | typedef std::vector KdNodeVector; 36 | 37 | // base function object for search predicate in knn search 38 | // returns true when the given KdNode is an admissible neighbor 39 | // To define an own search predicate, derive from this class 40 | // and overwrite the call operator operator() 41 | struct KdNodePredicate { 42 | virtual ~KdNodePredicate() {} 43 | virtual bool operator()(const KdNode&) const { return true; } 44 | }; 45 | 46 | //-------------------------------------------------------- 47 | // private helper classes used internally by KdTree 48 | // 49 | // the internal node structure used by kdtree 50 | class kdtree_node; 51 | // base class for different distance computations 52 | class DistanceMeasure; 53 | // helper class for priority queue in k nearest neighbor search 54 | class nn4heap { 55 | public: 56 | size_t dataindex; // index of actual kdnode in *allnodes* 57 | double distance; // distance of this neighbor from *point* 58 | nn4heap(size_t i, double d) { 59 | dataindex = i; 60 | distance = d; 61 | } 62 | }; 63 | class compare_nn4heap { 64 | public: 65 | bool operator()(const nn4heap& n, const nn4heap& m) { 66 | return (n.distance < m.distance); 67 | } 68 | }; 69 | typedef std::priority_queue, compare_nn4heap> SearchQueue; 70 | //-------------------------------------------------------- 71 | 72 | // kdtree class 73 | class KdTree { 74 | private: 75 | // recursive build of tree 76 | kdtree_node* build_tree(size_t depth, size_t a, size_t b); 77 | // helper variable for keeping track of subtree bounding box 78 | CoordPoint lobound, upbound; 79 | // helper variable to check the distance method 80 | int distance_type; 81 | bool neighbor_search(const CoordPoint& point, kdtree_node* node, size_t k, SearchQueue* neighborheap); 82 | void range_search(const CoordPoint& point, kdtree_node* node, double r, std::vector* range_result); 83 | bool bounds_overlap_ball(const CoordPoint& point, double dist, 84 | kdtree_node* node); 85 | bool ball_within_bounds(const CoordPoint& point, double dist, 86 | kdtree_node* node); 87 | // class implementing the distance computation 88 | DistanceMeasure* distance; 89 | // search predicate in knn searches 90 | KdNodePredicate* searchpredicate; 91 | 92 | public: 93 | KdNodeVector allnodes; 94 | size_t dimension; 95 | kdtree_node* root; 96 | // distance_type can be 0 (max), 1 (city block), or 2 (euklid [squared]) 97 | KdTree(const KdNodeVector* nodes, int distance_type = 2); 98 | ~KdTree(); 99 | void set_distance(int distance_type, const DoubleVector* weights = NULL); 100 | void k_nearest_neighbors(const CoordPoint& point, size_t k, 101 | KdNodeVector* result, 102 | std::vector* distances, 103 | KdNodePredicate* pred = NULL); 104 | void range_nearest_neighbors(const CoordPoint& point, double r, 105 | KdNodeVector* result); 106 | }; 107 | 108 | } // end namespace Kdtree 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /src/graph.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // graph.cpp 3 | // Classes and functions for computing the MST and for 4 | // splitting up clusters at gaps 5 | // 6 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 7 | // Date: 2018-08-30 8 | // License: see ../LICENSE 9 | // 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "graph.h" 16 | 17 | struct Edge { 18 | size_t src, dest; 19 | double weight; 20 | bool operator<(const Edge &e2) const { return this->weight < e2.weight; }; 21 | }; 22 | 23 | // Create edges with weights between all point indices in *cluster*. the 24 | // weights are the distances of the points in *cloud*. The edges are returned 25 | // in *edges*. 26 | void create_edges(std::vector &edges, const PointCloud &cloud, 27 | const std::vector &cluster) { 28 | for (size_t vertex1 = 0; vertex1 < cluster.size(); ++vertex1) { 29 | for (size_t vertex2 = vertex1 + 1; vertex2 < cluster.size(); ++vertex2) { 30 | size_t point_index1 = cluster[vertex1], point_index2 = cluster[vertex2]; 31 | const Point &p = cloud[point_index1]; 32 | const Point &q = cloud[point_index2]; 33 | 34 | // compute squared distance 35 | double distance = (q - p).squared_norm(); 36 | 37 | Edge e = {vertex1, vertex2, distance}; 38 | edges.push_back(e); 39 | } 40 | } 41 | std::sort(edges.begin(), edges.end()); 42 | } 43 | 44 | // Create a adjacent list for every vertex from the edges in *edges*. 45 | // The adjacent list is returned in *adj* which be resized to the number of 46 | // vertices a priori. 47 | void create_adj(std::vector > &adj, 48 | const std::vector edges) { 49 | for (std::vector::const_iterator edge = edges.begin(); 50 | edge != edges.end(); ++edge) { 51 | adj[edge->src].push_back(edge->dest); 52 | adj[edge->dest].push_back(edge->src); 53 | } 54 | } 55 | 56 | // Remove all edges of *edges* with weight smaller *dmax*. *edges* will be 57 | // modified. 58 | void remove_edge(std::vector &edges, double dmax) { 59 | std::vector::iterator edge = edges.begin(); 60 | double dmax2 = dmax * dmax; 61 | while (edge != edges.end()) { 62 | if (edge->weight > dmax2) { 63 | edge = edges.erase(edge); 64 | } else { 65 | ++edge; 66 | } 67 | } 68 | } 69 | 70 | // Remove all edges from *edges* which don't belong to the mst. *vcount* is the 71 | // number of verteicies. *edges* will be modified. 72 | void mst(const std::vector &edges, std::vector &mst_edges, 73 | size_t vcount) { 74 | std::vector groups(vcount); 75 | for (size_t i = 0; i < groups.size(); i++) { 76 | groups[i] = i; 77 | } 78 | 79 | for (std::vector::const_iterator e = edges.begin(); e != edges.end(); 80 | ++e) { 81 | size_t group_a = groups[e->src]; 82 | size_t group_b = groups[e->dest]; 83 | 84 | // look if there is no circle 85 | if (group_a != group_b) { 86 | // merge groups 87 | for (std::vector::iterator it = groups.begin(); 88 | it != groups.end(); ++it) { 89 | if (*it == group_b) *it = group_a; 90 | } 91 | mst_edges.push_back(*e); 92 | } 93 | } 94 | } 95 | 96 | // create a cluster of connected components which is returned in *new_cluster*. 97 | // *vertex* is the index of the start vertex. *visited* is a list of the visted 98 | // states from every vertecy. *cluster* is used to get the original point index 99 | // of a vertex. *adj* are the adjacent lists of all vertices. 100 | void dfs_util(std::vector &new_cluster, const size_t vertex, 101 | std::vector &visited, const std::vector &cluster, 102 | const std::vector > &adj) { 103 | std::stack stack; 104 | stack.push(vertex); 105 | while (!stack.empty()) { 106 | size_t v = stack.top(); 107 | stack.pop(); 108 | visited[v] = true; 109 | new_cluster.push_back(cluster[v]); 110 | 111 | for (std::vector::const_iterator it = adj[v].begin(); 112 | it != adj[v].end(); ++it) { 113 | if (!visited[*it]) stack.push(*it); 114 | } 115 | } 116 | } 117 | 118 | //------------------------------------------------------------------- 119 | // Split *cluster* in multiple new clusters and return the result in 120 | // *new_clusters". The mst of the cluster is created and all edges are 121 | // removed with a wheigth > *dmax*. The connected components are computed 122 | // and returned as new clusters if their size is >= *min_size*. 123 | //------------------------------------------------------------------- 124 | void max_step(std::vector > &new_clusters, 125 | const std::vector &cluster, const PointCloud &cloud, 126 | double dmax, size_t min_size) { 127 | size_t vcount = cluster.size(); 128 | size_t n_removed; 129 | std::vector > adj(vcount); 130 | std::vector edges, mst_edges; 131 | std::vector visited(vcount); 132 | create_edges(edges, cloud, cluster); 133 | mst(edges, mst_edges, vcount); 134 | n_removed = mst_edges.size(); 135 | remove_edge(mst_edges, dmax); 136 | n_removed = n_removed - mst_edges.size(); 137 | create_adj(adj, mst_edges); 138 | 139 | for (size_t v = 0; v < vcount; ++v) { 140 | if (!visited[v]) { 141 | std::vector new_cluster; 142 | dfs_util(new_cluster, v, visited, cluster, adj); 143 | if ((new_cluster.size() >= min_size) || (n_removed == 0)) 144 | new_clusters.push_back(new_cluster); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/hclust/fastcluster.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // C++ standalone verion of fastcluster by Daniel Müllner 3 | // 4 | // Copyright: Christoph Dalitz, 2018 5 | // Daniel Müllner, 2011 6 | // License: BSD style license 7 | // (see the file LICENSE for details) 8 | // 9 | 10 | 11 | #include 12 | #include 13 | 14 | #include "fastcluster.h" 15 | 16 | // Code by Daniel Müllner 17 | // workaround to make it usable as a standalone version (without R) 18 | bool fc_isnan(double x) { return false; } 19 | #include "fastcluster_dm.cpp" 20 | #include "fastcluster_R_dm.cpp" 21 | 22 | // 23 | // Assigns cluster labels (0, ..., nclust-1) to the n points such 24 | // that the cluster result is split into nclust clusters. 25 | // 26 | // Input arguments: 27 | // n = number of observables 28 | // merge = clustering result in R format 29 | // nclust = number of clusters 30 | // Output arguments: 31 | // labels = allocated integer array of size n for result 32 | // 33 | void cutree_k(int n, const int* merge, int nclust, int* labels) { 34 | 35 | int k,m1,m2,j,l; 36 | 37 | if (nclust > n || nclust < 2) { 38 | for (j=0; j last_merge(n, 0); 45 | for (k=1; k<=(n-nclust); k++) { 46 | // (m1,m2) = merge[k,] 47 | m1 = merge[k-1]; 48 | m2 = merge[n-1+k-1]; 49 | if (m1 < 0 && m2 < 0) { // both single observables 50 | last_merge[-m1-1] = last_merge[-m2-1] = k; 51 | } 52 | else if (m1 < 0 || m2 < 0) { // one is a cluster 53 | if(m1 < 0) { j = -m1; m1 = m2; } else j = -m2; 54 | // merging single observable and cluster 55 | for(l = 0; l < n; l++) 56 | if (last_merge[l] == m1) 57 | last_merge[l] = k; 58 | last_merge[j-1] = k; 59 | } 60 | else { // both cluster 61 | for(l=0; l < n; l++) { 62 | if( last_merge[l] == m1 || last_merge[l] == m2 ) 63 | last_merge[l] = k; 64 | } 65 | } 66 | } 67 | 68 | // assign cluster labels 69 | int label = 0; 70 | std::vector z(n,-1); 71 | for (j=0; j= cdist 86 | // 87 | // Input arguments: 88 | // n = number of observables 89 | // merge = clustering result in R format 90 | // height = cluster distance at each merge step 91 | // cdist = cutoff cluster distance 92 | // Output arguments: 93 | // labels = allocated integer array of size n for result 94 | // 95 | void cutree_cdist(int n, const int* merge, double* height, double cdist, int* labels) { 96 | 97 | int k; 98 | 99 | for (k=0; k<(n-1); k++) { 100 | if (height[k] >= cdist) { 101 | break; 102 | } 103 | } 104 | cutree_k(n, merge, n-k, labels); 105 | } 106 | 107 | 108 | // 109 | // Hierarchical clustering with one of Daniel Muellner's fast algorithms 110 | // 111 | // Input arguments: 112 | // n = number of observables 113 | // distmat = condensed distance matrix, i.e. an n*(n-1)/2 array representing 114 | // the upper triangle (without diagonal elements) of the distance 115 | // matrix, e.g. for n=4: 116 | // d00 d01 d02 d03 117 | // d10 d11 d12 d13 -> d02 d02 d03 d12 d13 d23 118 | // d20 d21 d22 d23 119 | // d30 d31 d32 d33 120 | // method = cluster metric (see enum method_code) 121 | // Output arguments: 122 | // merge = allocated (n-1)x2 matrix (2*(n-1) array) for storing result. 123 | // Result follows R hclust convention: 124 | // - observabe indices start with one 125 | // - merge[i][] contains the merged nodes in step i 126 | // - merge[i][j] is negative when the node is an atom 127 | // height = allocated (n-1) array with distances at each merge step 128 | // Return code: 129 | // 0 = ok 130 | // 1 = invalid method 131 | // 132 | int hclust_fast(int n, double* distmat, int method, int* merge, double* height) { 133 | 134 | // call appropriate culstering function 135 | cluster_result Z2(n-1); 136 | if (method == HCLUST_METHOD_SINGLE) { 137 | // single link 138 | MST_linkage_core(n, distmat, Z2); 139 | } 140 | else if (method == HCLUST_METHOD_COMPLETE) { 141 | // complete link 142 | NN_chain_core(n, distmat, NULL, Z2); 143 | } 144 | else if (method == HCLUST_METHOD_AVERAGE) { 145 | // best average distance 146 | double* members = new double[n]; 147 | for (int i=0; i(n, distmat, members, Z2); 149 | delete[] members; 150 | } 151 | else if (method == HCLUST_METHOD_MEDIAN) { 152 | // best median distance (beware: O(n^3)) 153 | generic_linkage(n, distmat, NULL, Z2); 154 | } 155 | else { 156 | return 1; 157 | } 158 | 159 | int* order = new int[n]; 160 | if (method == HCLUST_METHOD_MEDIAN) { 161 | generate_R_dendrogram(merge, height, order, Z2, n); 162 | } else { 163 | generate_R_dendrogram(merge, height, order, Z2, n); 164 | } 165 | 166 | delete[] order; // only needed for visualization 167 | 168 | return 0; 169 | } 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TriplClust: Curve Detection in 3D Point Clouds 2 | ============================================== 3 | 4 | TriplClust reads a 3D or 2D point cloud from a CSV file and assigns 5 | each point cluster labels representing curves or noise. 6 | 7 | This code implements the algorithm described in (cited as "IPOL paper" below): 8 | 9 | > C. Dalitz, J. Wilberg, L. Aymans: "TriplClust: An Algorithm 10 | > for Curve Detection in 3D Point Clouds." 11 | > Image Processing Online 9, pp. 26-46 (2019) 12 | > https://doi.org/10.5201/ipol.2019.234 13 | 14 | Please cite this article when using the code. The article was published 15 | on IPOL with version 1.3 of this code. For changes since then, see the file 16 | *CHANGES*. 17 | 18 | 19 | Compilation 20 | ----------- 21 | 22 | Building the code requires cmake and a standard C++98 (or later) compiler. 23 | We have tested the code with gcc 5.4.0, LLVM 9.0.0, and MSVC 15.7.5. 24 | 25 | Starting from the root directory (i.e., the directory, in which this 26 | Readme file is located), the code is compiled with ($ is the shell prompt): 27 | 28 | $ mkdir build 29 | $ cd build 30 | $ cmake .. 31 | $ make 32 | 33 | This will create the executable "triplclust". 34 | 35 | 36 | Usage 37 | ----- 38 | 39 | Calling triplclust without any or with an unknown option (e.g. "-?") 40 | will print a usage message. The meaning of the parameters controlling 41 | the algorithm is explained in the IPOL paper. 42 | 43 | The input file can contain 3D or 2D point coordinates, one point per line 44 | with the coordinates separated by a delimiter character. The default delimiter 45 | is the space character, but a different character can be specified with the 46 | command line option "-delim ". Lines starting with a hash (#) are 47 | ignored. 48 | 49 | If the points are in chronological order, the option "-ordered" improves 50 | track detection, because some impossible triplet combinations are ruled out. 51 | 52 | Unless the option "-oprefix " is given, the output is printed to 53 | stdout. The default output format is a comma separated file with two header 54 | lines (starting with #) and one point per line followed by the cluster label. 55 | For points belonging to more than one cluster, all labels are given separated 56 | by semicolons. 57 | 58 | With the option "-gnuplot", the output is instead a gnuplot command that 59 | can directly be used for visualizing the result. When the options "-gnuplot" 60 | and "-oprefix " are given, the result is not printed to stdout, 61 | but into two files: CSV format to ".csv", gnuplot command to 62 | ".gnuplot". 63 | 64 | When the option "-v" is given, automatically computed default values are 65 | additionally printed to stdout with the prefix "[Info]". 66 | 67 | Example calls with the provided test file 'test.dat': 68 | 69 | 1) direct visualization with gnuplot: 70 | ``$ triplclust test.dat -gnuplot | gnuplot -persist`` 71 | 72 | 2) write result to files result.csv and result.gnuplot and print 73 | automatically computed parameters: 74 | ``$ triplclust test.dat -v -oprefix result -gnuplot`` 75 | 76 | When the option "-vv" is given, data files documenting intermediate steps 77 | of the algorithm are written: "debug_smoothed.csv" and "debug_smoothed.gnuplot" 78 | contain the points after smoothing (Figure 2 in the IPOL paper), and 79 | "debug_cdist.csv" contains the sequence of cluster distances of all merge 80 | steps in the hierarchical clustering. This file can be important for finding 81 | good stopping thresholds for the clustering. A plot similar to Figure 4 in 82 | the IPOL paper can be created with the R script "cdist-plot.r". 83 | Example ("$" is the shell prompt):: 84 | 85 | $ triplclust test.dat -vv 86 | $ Rscript cdist-plot.r 87 | cdist plot is written to 'debug_cdist.pdf' 88 | 89 | 90 | Source Files 91 | ------------ 92 | 93 | - ``main.cpp`` 94 | Main program that calls the four steps of the algorithm 95 | (beginning of section 2 in the IPOL paper) 96 | 97 | - ``pointcloud.[h|cpp]`` 98 | Implementation of 3D points and clouds thereof, 99 | and the position smoothing described in section 2.1 of the IPOL paper 100 | 101 | - ``triplet.[h|cpp]`` 102 | Implementation of triplets of three points and their grouping 103 | (section 2.2 of the IPOL paper), and of the triplet distance 104 | (section 2.3.1 of the IPOL paper) 105 | 106 | - ``cluster.[h|cpp]`` 107 | Implementation of the hierarchical clustering and the stopping 108 | criterion (section 2.3.2 of the IPOL paper) 109 | 110 | - ``graph.[h|cpp]`` 111 | Implementation of the optional split up of clusters at gaps 112 | (section 2.4 of the IPOL paper) 113 | 114 | - ``dnn.[h|cpp]`` 115 | Implementation of the characteristic length computation 116 | (section 3.1 of the IPOL paper) 117 | 118 | - ``option.[h|cpp]`` 119 | Utilities for handling and storing command line options. 120 | 121 | - ``util.[h|cpp]`` 122 | Convenience functions not fitting elsewhere. 123 | 124 | The subdirectories ``hclust/`` and ``kdtree/`` contain the fastcluster 125 | implementation by Daniel Müllner and the kd-tree implementation by 126 | Christoph Dalitz. 127 | 128 | The subdirectory ``data/`` contains the six reference point clouds discussed 129 | in the IPOL paper. 130 | 131 | 132 | Authors & Copyright 133 | ------------------- 134 | 135 | Christoph Dalitz, 2017-2024 136 | Jens Wilberg, Lukas Aymans, 2017-2018 137 | Institute for Pattern Recognition 138 | Niederrhein University of Applied Sciences 139 | Krefeld, Germany 140 | 141 | The software includes parts of the fastcluster library by Daniel Müllner, 142 | available from http://danifold.net. See the directory ``src/hclust`` 143 | for details. 144 | -------------------------------------------------------------------------------- /src/triplet.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // triplet.cpp 3 | // Classes and functions for triplets of three points and 4 | // computing their dissimilarity. 5 | // 6 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 7 | // Date: 2024-02-02 8 | // License: see ../LICENSE 9 | // 10 | 11 | #include 12 | #include 13 | 14 | #include "kdtree/kdtree.hpp" 15 | #include "triplet.h" 16 | 17 | 18 | //------------------------------------------------------------------- 19 | // Generates triplets from the PointCloud *cloud*. 20 | // The resulting triplets are returned in *triplets*. *k* is the number 21 | // of neighbores from a point, which are used for triplet generation. 22 | // *n* is the number of the best triplet candidates to use. This can 23 | // be lesser than *n*. *a* is the max error (1-angle) for the triplet 24 | // to be a triplet candidate. If the cloud is ordered, only triplets 25 | // with a.index < b.index < c.index are considered. 26 | //------------------------------------------------------------------- 27 | void generate_triplets(const PointCloud &cloud, std::vector &triplets, 28 | size_t k, size_t n, double a) { 29 | std::vector distances; 30 | Kdtree::KdNodeVector nodes, result; 31 | std::vector indices; // save the indices so that they can be used 32 | // for the KdNode constructor 33 | indices.resize(cloud.size(), 0); 34 | 35 | // build kdtree 36 | for (size_t i = 0; i < cloud.size(); ++i) { 37 | indices[i] = i; 38 | Kdtree::KdNode n = Kdtree::KdNode(cloud[i].as_vector(), (void *)&indices[i]); 39 | n.index = cloud[i].index; 40 | nodes.push_back(n);//, NULL, (int)cloud[i].index); 41 | } 42 | Kdtree::KdTree kdtree(&nodes); 43 | 44 | for (size_t point_index_b = 0; point_index_b < cloud.size(); 45 | ++point_index_b) { 46 | distances.clear(); 47 | Point point_b = cloud[point_index_b]; 48 | std::vector triplet_candidates; 49 | kdtree.k_nearest_neighbors(cloud[point_index_b].as_vector(), k, &result, 50 | &distances); 51 | 52 | for (size_t result_index_a = 1; result_index_a < result.size(); 53 | ++result_index_a) { 54 | // When the distance is 0, we have the same point as point_b 55 | if (distances[result_index_a] == 0) continue; 56 | Point point_a(result[result_index_a].point); 57 | point_a.index = result[result_index_a].index; 58 | if (cloud.isOrdered() && (point_a.index > point_b.index)) continue; 59 | size_t point_index_a = *(size_t *)result[result_index_a].data; 60 | 61 | Point direction_ab = point_b - point_a; 62 | double ab_norm = direction_ab.norm(); 63 | direction_ab = direction_ab / ab_norm; 64 | 65 | for (size_t result_index_c = result_index_a + 1; 66 | result_index_c < result.size(); ++result_index_c) { 67 | // When the distance is 0, we have the same point as point_b 68 | if (distances[result_index_c] == 0) continue; 69 | Point point_c = Point(result[result_index_c].point); 70 | point_c.index = result[result_index_c].index; 71 | if (cloud.isOrdered() && (point_b.index > point_c.index)) continue; 72 | size_t point_index_c = *(size_t *)result[result_index_c].data; 73 | 74 | Point direction_bc = point_c - point_b; 75 | double bc_norm = direction_bc.norm(); 76 | direction_bc = direction_bc / bc_norm; 77 | 78 | const double angle = direction_ab * direction_bc; 79 | 80 | // calculate error 81 | const double error = 1.0f - angle; 82 | 83 | if (error <= a) { 84 | // calculate center 85 | Point center = (point_a + point_b + point_c) / 3.0f; 86 | 87 | // calculate direction 88 | Point direction = point_c - point_b; 89 | direction = direction / direction.norm(); 90 | 91 | triplet new_triplet; 92 | 93 | new_triplet.point_index_a = point_index_a; 94 | new_triplet.point_index_b = point_index_b; 95 | new_triplet.point_index_c = point_index_c; 96 | new_triplet.center = center; 97 | new_triplet.direction = direction; 98 | new_triplet.error = error; 99 | 100 | triplet_candidates.push_back(new_triplet); 101 | } 102 | } 103 | } 104 | 105 | // order triplet candidates 106 | std::sort(triplet_candidates.begin(), triplet_candidates.end()); 107 | 108 | // use the n best candidates 109 | for (size_t i = 0; i < std::min(n, triplet_candidates.size()); ++i) { 110 | triplets.push_back(triplet_candidates[i]); 111 | } 112 | } 113 | } 114 | 115 | // initialization of scale factor for triplet dissimilarity 116 | ScaleTripletMetric::ScaleTripletMetric(double s) { 117 | this->scale = s; 118 | } 119 | 120 | 121 | // dissimilarity measure for triplets 122 | double ScaleTripletMetric::operator()(const triplet &lhs, const triplet &rhs) { 123 | const double perpendicularDistanceA = 124 | (rhs.center - lhs.center + 125 | lhs.direction * (lhs.center - rhs.center) * lhs.direction).squared_norm(); 126 | const double perpendicularDistanceB = 127 | (lhs.center - rhs.center + 128 | rhs.direction * (rhs.center - lhs.center) * rhs.direction).squared_norm(); 129 | 130 | double anglecos = lhs.direction * rhs.direction; 131 | if (anglecos > 1.0) anglecos = 1.0; 132 | if (anglecos < -1.0) anglecos = -1.0; 133 | if (std::fabs(anglecos) < 1.0e-8) { 134 | return 1.0e+8; 135 | } else { 136 | return ( 137 | std::sqrt(std::max(perpendicularDistanceA, perpendicularDistanceB)) / 138 | this->scale + 139 | std::fabs(std::tan(std::acos(anglecos))) ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // Main file for reference implementation of the TriplClust algorithm. 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2024-02-16 7 | // License: see ../LICENSE 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "cluster.h" 17 | #include "dnn.h" 18 | #include "graph.h" 19 | #include "option.h" 20 | #include "output.h" 21 | #include "pointcloud.h" 22 | 23 | // usage message 24 | const char *usage = 25 | "Usage:\n" 26 | "\ttriplclust [options] \n" 27 | "Options (defaults in brackets):\n" 28 | "\t-r radius for point smoothing [2dNN]\n" 29 | "\t (can be numeric or multiple of dNN)\n" 30 | "\t-k number of neighbours in triplet creation [19]\n" 31 | "\t-n number of the best triplets to use [2]\n" 32 | "\t-a maximum value for the angle between the\n" 33 | "\t triplet branches [0.03]\n" 34 | "\t-s scalingfactor for clustering [0.33dNN]\n" 35 | "\t (can be numeric or multiple of dNN)\n" 36 | "\t-t best cluster distance [auto]\n" 37 | "\t (can be numeric or 'auto')\n" 38 | "\t-m minimum number of triplets for a cluster [5]\n" 39 | "\t-dmax max gapwidth within a triplet [none]\n" 40 | "\t (can be numeric, multiple of dNN or 'none')\n" 41 | "\t-link linkage method for clustering [single]\n" 42 | "\t (can be 'single', 'complete', 'average')\n" 43 | "\t-ordered interpret infile as ordered\n" 44 | "\t (i.e. points are in chronological order)\n" 45 | "\t-oprefix \n" 46 | "\t write result not to stdout, but to .csv\n" 47 | "\t and (if -gnuplot is set) to .gnuplot\n" 48 | "\t-gnuplot print result as a gnuplot command\n" 49 | "\t-delim single char delimiter for csv input [' ']\n" 50 | "\t-skip number of lines skipped at head of infile [0]\n" 51 | "\t-v be verbose\n" 52 | "\t-vv be more verbose and write debug trace files\n" 53 | "Version:\n" 54 | "\t1.4 from 2024-02-16"; 55 | 56 | int main(int argc, char **argv) { 57 | // parse commandline 58 | Opt opt_params; 59 | if (opt_params.parse_args(argc, argv) != 0) { 60 | std::cerr << usage << std::endl; 61 | return 1; 62 | } 63 | const char *infile_name = opt_params.get_ifname(); 64 | const char *outfile_prefix = opt_params.get_ofprefix(); 65 | int opt_verbose = opt_params.get_verbosity(); 66 | bool opt_ordered = opt_params.get_ordered(); 67 | 68 | // plausibility checks 69 | if (!infile_name) { 70 | std::cerr << "[Error] no infile given!\n" << usage << std::endl; 71 | return 1; 72 | } 73 | 74 | // load data 75 | PointCloud cloud_xyz; 76 | cloud_xyz.setOrdered(opt_ordered); 77 | 78 | try { 79 | load_csv_file(infile_name, cloud_xyz, opt_params.get_delimiter(), 80 | opt_params.get_skip()); 81 | } catch (const std::invalid_argument &e) { 82 | std::cerr << "[Error] in file'" << infile_name << "': " << e.what() 83 | << std::endl; 84 | return 2; 85 | } 86 | #ifdef WEBDEMO 87 | // maximum pointcloud size error for webdemo 88 | catch (const std::length_error &e) { 89 | std::cerr << "[Error] in file'" << infile_name << "': " << e.what() 90 | << std::endl; 91 | return 3; 92 | } 93 | #endif 94 | catch (const std::exception &e) { 95 | std::cerr << "[Error] cannot read infile '" << infile_name << "'! " 96 | << e.what() << std::endl; 97 | return 2; 98 | } 99 | if (cloud_xyz.size() == 0) { 100 | std::cerr << "[Error] empty cloud in file '" << infile_name << "'" 101 | << std::endl 102 | << "maybe you used the wrong delimiter" << std::endl; 103 | return 2; 104 | } 105 | 106 | // compute characteristic length dnn if needed 107 | if (opt_params.needs_dnn()) { 108 | double dnn = std::sqrt(first_quartile(cloud_xyz)); 109 | if (opt_verbose > 0) { 110 | std::cout << "[Info] computed dnn: " << dnn << std::endl; 111 | } 112 | opt_params.set_dnn(dnn); 113 | if (dnn == 0.0) { 114 | std::cerr << "[Error] dnn computed as zero. " 115 | << "Suggestion: remove doublets, e.g. with 'sort -u'" 116 | << std::endl; 117 | return 3; 118 | } 119 | } 120 | 121 | // Step 1) smoothing by position averaging of neighboring points 122 | PointCloud cloud_xyz_smooth; 123 | smoothen_cloud(cloud_xyz, cloud_xyz_smooth, opt_params.get_r()); 124 | 125 | if (opt_verbose > 1) { 126 | bool rc; 127 | rc = cloud_to_csv(cloud_xyz_smooth); 128 | if (!rc) 129 | std::cerr << "[Error] can't write debug_smoothed.csv" << std::endl; 130 | rc = debug_gnuplot(cloud_xyz, cloud_xyz_smooth); 131 | if (!rc) 132 | std::cerr << "[Error] can't write debug_smoothed.gnuplot" << std::endl; 133 | } 134 | 135 | // Step 2) finding triplets of approximately collinear points 136 | std::vector triplets; 137 | generate_triplets(cloud_xyz_smooth, triplets, opt_params.get_k(), 138 | opt_params.get_n(), opt_params.get_a()); 139 | 140 | // Step 3) single link hierarchical clustering of the triplets 141 | cluster_group cl_group; 142 | compute_hc(cloud_xyz_smooth, cl_group, triplets, opt_params.get_s(), 143 | opt_params.get_t(), opt_params.is_tauto(), opt_params.get_dmax(), 144 | opt_params.is_dmax(), opt_params.get_linkage(), opt_verbose); 145 | 146 | // Step 4) pruning by removal of small clusters ... 147 | cleanup_cluster_group(cl_group, opt_params.get_m(), opt_verbose); 148 | cluster_triplets_to_points(triplets, cl_group); 149 | // .. and (optionally) by splitting up clusters at gaps > dmax 150 | if (opt_params.is_dmax()) { 151 | cluster_group cleaned_up_cluster_group; 152 | for (cluster_group::iterator cl = cl_group.begin(); cl != cl_group.end(); 153 | ++cl) { 154 | max_step(cleaned_up_cluster_group, *cl, cloud_xyz, opt_params.get_dmax(), 155 | opt_params.get_m() + 2); 156 | } 157 | cl_group = cleaned_up_cluster_group; 158 | } 159 | 160 | // store cluster labels in points 161 | add_clusters(cloud_xyz, cl_group, opt_params.is_gnuplot()); 162 | 163 | if (outfile_prefix) { 164 | // redirect cout to outfile if requested 165 | std::streambuf *backup = std::cout.rdbuf(); 166 | std::ofstream of; 167 | of.open((std::string(outfile_prefix) + ".csv").c_str()); 168 | // replace the stream buffer from cout with the stream buffer from the 169 | // opened file, so that everythin printed to cout is printed to the file. 170 | std::cout.rdbuf(of.rdbuf()); 171 | clusters_to_csv(cloud_xyz); 172 | of.close(); 173 | if (opt_params.is_gnuplot()) { 174 | of.open((std::string(outfile_prefix) + ".gnuplot").c_str()); 175 | clusters_to_gnuplot(cloud_xyz, cl_group); 176 | of.close(); 177 | } 178 | // restore cout's default stream buffer 179 | std::cout.rdbuf(backup); 180 | } else if (opt_params.is_gnuplot()) { 181 | clusters_to_gnuplot(cloud_xyz, cl_group); 182 | } else { 183 | clusters_to_csv(cloud_xyz); 184 | } 185 | 186 | return 0; 187 | } 188 | -------------------------------------------------------------------------------- /src/option.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // option.cpp 3 | // Class and functions for parsing and storing command line options. 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2024-02-02 7 | // License: see ../LICENSE 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "option.h" 16 | 17 | // initialize default values 18 | Opt::Opt() { 19 | this->infile_name = NULL; 20 | this->outfile_prefix = NULL; 21 | this->gnuplot = false; 22 | this->delimiter = ' '; 23 | this->skip = 0; 24 | this->verbose = 0; 25 | 26 | // neighbourship smoothing 27 | this->r = 2; 28 | this->rdnn = true; 29 | 30 | // triplet building 31 | this->k = 19; 32 | this->n = 2; 33 | this->a = 0.03; 34 | 35 | // triplet clustering 36 | this->s = 0.3; 37 | this->sdnn = true; 38 | this->t = 0; 39 | this->tauto = true; 40 | this->dmax = 0.0; 41 | this->isdmax = false; 42 | this->dmax_dnn = false; 43 | this->ordered = false; 44 | this->link = SINGLE; 45 | 46 | this->m = 5; 47 | } 48 | 49 | //------------------------------------------------------------------- 50 | // parse commandline arguments. 51 | // Parses the commandline arguments in *argv* and returns 0 if no error 52 | // occurred. *argc* is the count off arguments. 53 | //------------------------------------------------------------------- 54 | int Opt::parse_args(int argc, char** argv) { 55 | // parse command line 56 | std::pair tmp; 57 | try { 58 | for (int i = 1; i < argc; i++) { 59 | if (0 == strcmp(argv[i], "-v")) { 60 | if (this->verbose < 1) this->verbose = 1; 61 | } else if (0 == strcmp(argv[i], "-vv")) { 62 | if (this->verbose < 2) this->verbose = 2; 63 | } else if (0 == strcmp(argv[i], "-s")) { 64 | ++i; 65 | if (i >= argc) { 66 | return 1; 67 | } 68 | tmp = this->parse_argument(argv[i]); 69 | this->s = tmp.first; 70 | this->sdnn = tmp.second; 71 | } else if (0 == strcmp(argv[i], "-r")) { 72 | ++i; 73 | if (i >= argc) { 74 | return 1; 75 | } 76 | tmp = this->parse_argument(argv[i]); 77 | this->r = tmp.first; 78 | this->rdnn = tmp.second; 79 | } else if (0 == strcmp(argv[i], "-k")) { 80 | ++i; 81 | if (i < argc) { 82 | this->k = (int)stod(argv[i]); 83 | } else { 84 | return 1; 85 | } 86 | } else if (0 == strcmp(argv[i], "-ordered")) { 87 | this->ordered = true; 88 | } else if (0 == strcmp(argv[i], "-n")) { 89 | ++i; 90 | if (i < argc) { 91 | this->n = (int)stod(argv[i]); 92 | } else { 93 | return 1; 94 | } 95 | } else if (0 == strcmp(argv[i], "-a")) { 96 | ++i; 97 | if (i < argc) { 98 | this->a = stod(argv[i]); 99 | } else { 100 | return 1; 101 | } 102 | } else if (0 == strcmp(argv[i], "-t")) { 103 | ++i; 104 | if (i < argc) { 105 | if (strcmp(argv[i], "auto") == 0 || 106 | strcmp(argv[i], "automatic") == 0) { 107 | this->tauto = true; 108 | } else { 109 | this->t = stod(argv[i]); 110 | this->tauto = false; 111 | } 112 | } else { 113 | return 1; 114 | } 115 | } else if (0 == strcmp(argv[i], "-m")) { 116 | ++i; 117 | if (i < argc) { 118 | this->m = (int)stod(argv[i]); 119 | } else { 120 | return 1; 121 | } 122 | } else if (0 == strcmp(argv[i], "-delim")) { 123 | ++i; 124 | if (i < argc) { 125 | if (strlen(argv[i]) > 1) { 126 | std::cerr << "[Error] only a character as delimiter is allowed" 127 | << std::endl; 128 | return 1; 129 | } 130 | this->delimiter = *argv[i]; 131 | } else { 132 | return 1; 133 | } 134 | } else if (0 == strcmp(argv[i], "-dmax")) { 135 | ++i; 136 | if (i >= argc) { 137 | return 1; 138 | } 139 | if (strcmp(argv[i], "none") == 0) { 140 | this->isdmax = false; 141 | } else { 142 | tmp = this->parse_argument(argv[i]); 143 | this->dmax = tmp.first; 144 | this->dmax_dnn = tmp.second; 145 | this->isdmax = true; 146 | } 147 | } else if (0 == strcmp(argv[i], "-link")) { 148 | ++i; 149 | if (i >= argc) { 150 | return 1; 151 | } 152 | if (strcmp(argv[i], "single") == 0) { 153 | this->link = SINGLE; 154 | } else if (strcmp(argv[i], "complete") == 0) { 155 | this->link = COMPLETE; 156 | } else if (strcmp(argv[i], "average") == 0) { 157 | this->link = AVERAGE; 158 | } else { 159 | std::cerr << "[Error] " << argv[i] << " is not a valide option!" 160 | << std::endl; 161 | return 1; 162 | } 163 | } else if (0 == strcmp(argv[i], "-skip")) { 164 | ++i; 165 | if (i < argc) { 166 | int tmp = atoi(argv[i]); 167 | if (tmp < 0) { 168 | std::cerr << "[Error] skip takes only positive integers. parameter " 169 | "is ignored!" 170 | << std::endl; 171 | } else { 172 | this->skip = (size_t)tmp; 173 | } 174 | } else { 175 | return 1; 176 | } 177 | } else if (0 == strcmp(argv[i], "-oprefix")) { 178 | if (i + 1 == argc) { 179 | std::cerr << "[Error] not enough parameters" << std::endl; 180 | return 1; 181 | } else if (argv[i + 1][0] == '-') { 182 | std::cerr << "[Error] please enter outfile name" << std::endl; 183 | return 1; 184 | } 185 | this->outfile_prefix = argv[++i]; 186 | } else if (0 == strcmp(argv[i], "-gnuplot")) { 187 | this->gnuplot = true; 188 | } else if (argv[i][0] == '-') { 189 | return 1; 190 | } else { 191 | this->infile_name = argv[i]; 192 | } 193 | } 194 | } catch (const std::invalid_argument &e) { 195 | std::cerr << e.what() << std::endl; 196 | return 1; 197 | } 198 | return 0; 199 | } 200 | 201 | //------------------------------------------------------------------- 202 | // parses the argument string *str*. 203 | // the result is returned in *result*. *result_dnn* stores if *result* 204 | // depends on dnn. If *str* is not a number a invalid_argument exception 205 | // is thrown. 206 | //------------------------------------------------------------------- 207 | std::pair Opt::parse_argument(const char* str) { 208 | double result = 0.0; 209 | bool dnn = false; 210 | char buff[4]; 211 | size_t count = sscanf(str, "%lf%3s", &result, buff); 212 | if (count == 2) { 213 | if (std::strcmp("dnn", buff) && std::strcmp("dNN", buff)) 214 | throw std::invalid_argument("not a number"); 215 | dnn = true; 216 | } else if (count == 0) { 217 | throw std::invalid_argument("not a number"); 218 | } 219 | return std::pair(result, dnn); 220 | } 221 | 222 | //------------------------------------------------------------------- 223 | // compute attributes which depend on dnn. 224 | // If r,s,dmax depend on dnn their new value will be computed. 225 | //------------------------------------------------------------------- 226 | void Opt::set_dnn(double dnn) { 227 | if (this->rdnn) { 228 | this->r *= dnn; 229 | if (this->verbose > 0) { 230 | std::cout << "[Info] computed smoothed radius: " << this->r << std::endl; 231 | } 232 | } 233 | if (this->sdnn) { 234 | this->s *= dnn; 235 | if (this->verbose > 0) { 236 | std::cout << "[Info] computed distance scale: " << this->s << std::endl; 237 | } 238 | } 239 | if (this->dmax_dnn) { 240 | this->dmax *= dnn; 241 | if (this->verbose > 0) { 242 | std::cout << "[Info] computed max gap: " << this->dmax << std::endl; 243 | } 244 | } 245 | } 246 | 247 | // read access functions 248 | const char* Opt::get_ifname() { return this->infile_name; } 249 | const char* Opt::get_ofprefix() { return this->outfile_prefix; } 250 | bool Opt::needs_dnn() { return this->rdnn || this->sdnn || this->dmax_dnn; } 251 | bool Opt::is_gnuplot() { return this->gnuplot; } 252 | size_t Opt::get_skip() { return this->skip; } 253 | char Opt::get_delimiter() { return this->delimiter; } 254 | int Opt::get_verbosity() { return this->verbose; } 255 | double Opt::get_r() { return this->r; } 256 | size_t Opt::get_k() { return this->k; } 257 | size_t Opt::get_n() { return this->n; } 258 | double Opt::get_a() { return this->a; } 259 | double Opt::get_s() { return this->s; } 260 | bool Opt::is_tauto() { return this->tauto; } 261 | double Opt::get_t() { return this->t; } 262 | bool Opt::is_dmax() { return this->isdmax; } 263 | double Opt::get_dmax() { return this->dmax; } 264 | Linkage Opt::get_linkage() { return this->link; } 265 | size_t Opt::get_m() { return this->m; } 266 | bool Opt::get_ordered() {return this->ordered;} 267 | -------------------------------------------------------------------------------- /src/pointcloud.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // pointcloud.cpp 3 | // Classes and functions for 3D points and clouds thereof. 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2024-02-02 7 | // License: see ../LICENSE 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "kdtree/kdtree.hpp" 18 | #include "pointcloud.h" 19 | #include "util.h" 20 | 21 | // a single 3D point 22 | Point::Point(const std::vector &point) { 23 | if (point.size() != 3) { 24 | throw std::invalid_argument("Point::Point(): point must be of dimension 3"); 25 | } 26 | this->x = point[0]; 27 | this->y = point[1]; 28 | this->z = point[2]; 29 | } 30 | 31 | Point::Point(const std::vector &point, 32 | const std::set &cluster_ids) { 33 | if (point.size() != 3) { 34 | throw std::invalid_argument("Point::Point(): point must be of dimension 3"); 35 | } 36 | this->x = point[0]; 37 | this->y = point[1]; 38 | this->z = point[2]; 39 | this->cluster_ids = cluster_ids; 40 | } 41 | 42 | Point::Point(double x, double y, double z) { 43 | this->x = x; 44 | this->y = y; 45 | this->z = z; 46 | } 47 | 48 | Point::Point(double x, double y, double z, 49 | const std::set &cluster_ids) { 50 | this->x = x; 51 | this->y = y; 52 | this->z = z; 53 | this->cluster_ids = cluster_ids; 54 | } 55 | 56 | Point::Point(double x, double y, double z, size_t index) { 57 | this->x = x; 58 | this->y = y; 59 | this->z = z; 60 | this->index = index; // only used for chronological order 61 | } 62 | 63 | 64 | // representation of 3D point as std::vector. 65 | std::vector Point::as_vector() const { 66 | std::vector point(3); 67 | point[0] = this->x; 68 | point[1] = this->y; 69 | point[2] = this->z; 70 | return point; 71 | } 72 | 73 | // Euclidean norm of the point 74 | double Point::norm() const { return sqrt((x * x) + (y * y) + (z * z)); } 75 | 76 | // squared euclidean norm of the point 77 | double Point::squared_norm() const { return (x * x) + (y * y) + (z * z); } 78 | 79 | Point &Point::operator=(const Point &other) { 80 | x = other.x; 81 | y = other.y; 82 | z = other.z; 83 | return *this; 84 | } 85 | 86 | bool Point::operator==(const Point &p) const { 87 | return (x == p.x && y == p.y && z == p.z); 88 | } 89 | 90 | // formatted output of the point 91 | std::ostream &operator<<(std::ostream &strm, const Point &p) { 92 | return strm << p.x << " " << p.y << " " << p.z; 93 | } 94 | 95 | // vector addition 96 | Point Point::operator+(const Point &p) const { 97 | Point ret; 98 | ret.x = this->x + p.x; 99 | ret.y = this->y + p.y; 100 | ret.z = this->z + p.z; 101 | return ret; 102 | } 103 | 104 | // vector subtraction 105 | Point Point::operator-(const Point &p) const { 106 | Point ret; 107 | ret.x = this->x - p.x; 108 | ret.y = this->y - p.y; 109 | ret.z = this->z - p.z; 110 | return ret; 111 | } 112 | 113 | // scalar product (dot product) 114 | double Point::operator*(const Point &p) const { 115 | return this->x * p.x + this->y * p.y + this->z * p.z; 116 | } 117 | 118 | // scalar division 119 | Point Point::operator/(double c) const { return Point(x / c, y / c, z / c); } 120 | 121 | // scalar multiplication 122 | Point operator*(Point x, double c) { 123 | Point v(c * x.x, c * x.y, c * x.z); 124 | return v; 125 | } 126 | Point operator*(double c, Point x) { 127 | Point v(c * x.x, c * x.y, c * x.z); 128 | return v; 129 | } 130 | 131 | PointCloud::PointCloud() { this->points2d = false; } 132 | 133 | void PointCloud::set2d(bool is2d) { this->points2d = is2d; } 134 | 135 | bool PointCloud::is2d() const { return this->points2d; } 136 | 137 | 138 | void PointCloud::setOrdered(bool ordered) {this->ordered=ordered;} 139 | 140 | bool PointCloud::isOrdered() const { return this->ordered; } 141 | 142 | 143 | // Split string *input* into substrings by *delimiter*. The result is 144 | // returned in *result* 145 | void split(const std::string &input, std::vector &result, 146 | const char delimiter) { 147 | std::stringstream ss(input); 148 | std::string element; 149 | 150 | while (std::getline(ss, element, delimiter)) { 151 | result.push_back(element); 152 | } 153 | } 154 | 155 | //------------------------------------------------------------------- 156 | // Load csv file. 157 | // The csv file is split by *delimiter* and saved in *cloud*. 158 | // Lines starting with '#' are ignored. 159 | // If there are more than 3 columns all other are ignored and 160 | // if there are two columns the PointCloud is set to 2D. 161 | // Throws invalid_argument exception in case of problems. 162 | //------------------------------------------------------------------- 163 | void load_csv_file(const char *fname, PointCloud &cloud, const char delimiter, 164 | size_t skip) { 165 | std::ifstream infile(fname); 166 | std::string line; 167 | std::vector items; 168 | size_t count = 0, count2d = 0, skiped = 0, countpoints = 0; 169 | //size_t countOrdered = 0; //! 170 | if (infile.fail()) throw std::exception(); 171 | for (size_t i = 0; i < skip; ++i) { 172 | // skip the header 173 | std::getline(infile, line, '\n'); 174 | skiped++; 175 | } 176 | while (!infile.eof()) { 177 | #ifdef WEBDEMO 178 | if (countpoints > 1000) 179 | throw std::length_error("Number of points limited to 1000 in demo mode"); 180 | #endif 181 | //std::getline(infile, line, '\n'); 182 | std::getline(infile, line, '\n'); 183 | count++; 184 | 185 | // skip comments and empty lines 186 | if (line[0] == '#' || line.empty() || 187 | line.find_first_not_of("\n\r\t ") == std::string::npos) 188 | continue; 189 | 190 | countpoints++; 191 | Point point; 192 | split(line, items, delimiter); 193 | if (items.size() < 2) { 194 | std::ostringstream oss; 195 | oss << "row " << count + skiped << ": " 196 | << "To few columns!"; 197 | throw std::invalid_argument(oss.str()); 198 | } else if (items.size() == 2) { 199 | items.push_back("0"); // z=0 for 2D data (size=2) 200 | count2d++; 201 | } 202 | size_t column = 1; 203 | try { 204 | // create point 205 | point.x = stod(items[0].c_str()); 206 | column++; 207 | point.y = stod(items[1].c_str()); 208 | column++; 209 | point.z = stod(items[2].c_str()); 210 | point.index = countpoints-1; 211 | cloud.push_back(point); 212 | } catch (const std::invalid_argument &e) { 213 | std::ostringstream oss; 214 | oss << "row " << count + skiped << " column " << column << ": " 215 | << e.what(); 216 | throw std::invalid_argument(oss.str()); 217 | } 218 | 219 | items.clear(); 220 | } 221 | 222 | // check if the cloud is 2d or if a problem occurred 223 | if (count2d && count2d != cloud.size()) { 224 | throw std::invalid_argument("Mixed 2d and 3d points."); 225 | } else if (count2d) { 226 | cloud.set2d(true); 227 | } 228 | } 229 | 230 | //------------------------------------------------------------------- 231 | // Smoothing of the PointCloud *cloud*. 232 | // For every point the nearest neighbours in the radius *r* is searched 233 | // and the centroid of this neighbours is computed. The result is 234 | // returned in *result_cloud* and contains these centroids. The 235 | // centroids are duplicated in the result cloud, so it has the same 236 | // size and order as *cloud*. 237 | //------------------------------------------------------------------- 238 | void smoothen_cloud(const PointCloud &cloud, PointCloud &result_cloud, 239 | double r) { 240 | Kdtree::KdNodeVector nodes; 241 | 242 | // If the smooth-radius is zero return the unsmoothed pointcloud 243 | if (r == 0) { 244 | result_cloud = cloud; 245 | return; 246 | } 247 | 248 | // build kdtree 249 | for (size_t i = 0; i < cloud.size(); ++i) { 250 | nodes.push_back(cloud[i].as_vector()); 251 | } 252 | Kdtree::KdTree kdtree(&nodes); 253 | 254 | for (size_t i = 0; i < cloud.size(); ++i) { 255 | size_t result_size; 256 | Point new_point, point = cloud[i]; 257 | Kdtree::KdNodeVector result; 258 | 259 | kdtree.range_nearest_neighbors(point.as_vector(), r, &result); 260 | result_size = result.size(); 261 | 262 | // compute the centroid with mean 263 | std::vector x_list; 264 | std::vector y_list; 265 | std::vector z_list; 266 | 267 | for (Kdtree::KdNodeVector::iterator it = result.begin(); it != result.end(); 268 | ++it) { 269 | x_list.push_back(it->point[0]); 270 | y_list.push_back(it->point[1]); 271 | z_list.push_back(it->point[2]); 272 | } 273 | 274 | new_point.x = 275 | std::accumulate(x_list.begin(), x_list.end(), 0.0) / result_size; 276 | 277 | new_point.y = 278 | std::accumulate(y_list.begin(), y_list.end(), 0.0) / result_size; 279 | 280 | new_point.z = 281 | std::accumulate(z_list.begin(), z_list.end(), 0.0) / result_size; 282 | 283 | new_point.index = point.index; 284 | result_cloud.push_back(new_point); 285 | } 286 | result_cloud.setOrdered(cloud.isOrdered()); 287 | } 288 | -------------------------------------------------------------------------------- /src/cluster.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // cluster.cpp 3 | // Functions for triplet clustering and for propagating 4 | // the triplet cluster labels to points 5 | // 6 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 7 | // Date: 2019-04-02 8 | // License: see ../LICENSE 9 | // 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "cluster.h" 16 | #include "hclust/fastcluster.h" 17 | 18 | // compute mean of *a* with size *m* 19 | double mean(const double *a, size_t m) { 20 | double sum = 0; 21 | for (size_t i = 0; i < m; ++i) { 22 | sum += a[i]; 23 | } 24 | return sum / m; 25 | } 26 | 27 | // compute standard deviation of *a* with size *m* 28 | double sd(const double *a, size_t m) { 29 | double sum = 0, result; 30 | double mean_val = mean(a, m); 31 | for (size_t k = 0; k < m; ++k) { 32 | double tmp = mean_val - a[k]; 33 | sum += (tmp * tmp); 34 | } 35 | result = (1.0 / (m - 1.0)) * sum; 36 | return std::sqrt(result); 37 | } 38 | 39 | //------------------------------------------------------------------- 40 | // computation of condensed distance matrix. 41 | // The distance matrix is computed from the triplets in *triplets* 42 | // and saved in *result*. *triplet_metric* is used as distance metric. 43 | //------------------------------------------------------------------- 44 | void calculate_distance_matrix(const std::vector &triplets, 45 | const PointCloud &cloud, double *result, 46 | ScaleTripletMetric &triplet_metric) { 47 | size_t const triplet_size = triplets.size(); 48 | size_t k = 0; 49 | 50 | for (size_t i = 0; i < triplet_size; ++i) { 51 | for (size_t j = i + 1; j < triplet_size; j++) { 52 | result[k++] = triplet_metric(triplets[i], triplets[j]); 53 | } 54 | } 55 | } 56 | 57 | //------------------------------------------------------------------- 58 | // Computation of the clustering. 59 | // The triplets in *triplets* are clustered by the fastcluster algorithm 60 | // and the result is returned as cluster_group. *t* is the cut distance 61 | // and *triplet_metric* is the distance metric for the triplets. 62 | // *opt_verbose* is the verbosity level for debug outputs. the clustering 63 | // is returned in *result*. 64 | //------------------------------------------------------------------- 65 | void compute_hc(const PointCloud &cloud, cluster_group &result, 66 | const std::vector &triplets, double s, double t, 67 | bool tauto, double dmax, bool is_dmax, Linkage method, 68 | int opt_verbose) { 69 | const size_t triplet_size = triplets.size(); 70 | size_t k, cluster_size; 71 | hclust_fast_methods link; 72 | 73 | if (!triplet_size) { 74 | // if no triplets are generated 75 | return; 76 | } 77 | // choose linkage method 78 | switch (method) { 79 | case SINGLE: 80 | link = HCLUST_METHOD_SINGLE; 81 | break; 82 | case COMPLETE: 83 | link = HCLUST_METHOD_COMPLETE; 84 | break; 85 | case AVERAGE: 86 | link = HCLUST_METHOD_AVERAGE; 87 | break; 88 | } 89 | 90 | double *distance_matrix = new double[(triplet_size * (triplet_size - 1)) / 2]; 91 | double *cdists = new double[triplet_size - 1]; 92 | int *merge = new int[2 * (triplet_size - 1)], *labels = new int[triplet_size]; 93 | ScaleTripletMetric metric(s); 94 | calculate_distance_matrix(triplets, cloud, distance_matrix, metric); 95 | 96 | hclust_fast(triplet_size, distance_matrix, link, merge, cdists); 97 | 98 | // splitting the dendrogram into clusters 99 | if (tauto) { 100 | // automatic stopping criterion where cdist is unexpected large 101 | for (k = (triplet_size - 1) / 2; k < (triplet_size - 1); ++k) { 102 | if ((cdists[k - 1] > 0.0 || cdists[k] > 1.0e-8) && 103 | (cdists[k] > cdists[k - 1] + 2 * sd(cdists, k + 1))) { 104 | break; 105 | } 106 | } 107 | if (opt_verbose) { 108 | double automatic_t; 109 | double prev_cdist = (k > 0) ? cdists[k - 1] : 0.0; 110 | if (k < (triplet_size - 1)) { 111 | automatic_t = (prev_cdist + cdists[k]) / 2.0; 112 | } else { 113 | automatic_t = cdists[k - 1]; 114 | } 115 | std::cout << "[Info] optimal cdist threshold: " << automatic_t 116 | << std::endl; 117 | } 118 | } else { 119 | // fixed threshold t 120 | for (k = 0; k < (triplet_size - 1); ++k) { 121 | if (cdists[k] >= t) { 122 | break; 123 | } 124 | } 125 | } 126 | cluster_size = triplet_size - k; 127 | cutree_k(triplet_size, merge, cluster_size, labels); 128 | 129 | // generate clusters 130 | for (size_t i = 0; i < cluster_size; ++i) { 131 | cluster_t new_cluster; 132 | result.push_back(new_cluster); 133 | } 134 | 135 | for (size_t i = 0; i < triplet_size; ++i) { 136 | result[labels[i]].push_back(i); 137 | } 138 | 139 | if (opt_verbose > 1) { 140 | // write debug file 141 | const char *fname = "debug_cdist.csv"; 142 | std::ofstream of(fname); 143 | of << std::fixed; // set float style 144 | if (of.is_open()) { 145 | for (size_t i = 0; i < (triplet_size - 1); ++i) { 146 | of << cdists[i] << std::endl; 147 | } 148 | } else { 149 | std::cerr << "[Error] could not write file '" << fname << "'\n"; 150 | } 151 | of.close(); 152 | } 153 | 154 | // cleanup 155 | delete[] distance_matrix; 156 | delete[] cdists; 157 | delete[] merge; 158 | delete[] labels; 159 | } 160 | 161 | //------------------------------------------------------------------- 162 | // Remove all clusters in *cl_group* which contains less then *m* 163 | // triplets. *cl_group* will be modified. 164 | //------------------------------------------------------------------- 165 | void cleanup_cluster_group(cluster_group &cl_group, size_t m, int opt_verbose) { 166 | size_t old_size = cl_group.size(); 167 | cluster_group::iterator it = cl_group.begin(); 168 | while (it != cl_group.end()) { 169 | if (it->size() < m) { 170 | it = cl_group.erase(it); 171 | } else { 172 | ++it; 173 | } 174 | } 175 | if (opt_verbose > 0) { 176 | std::cout << "[Info] in pruning removed clusters: " 177 | << old_size - cl_group.size() << std::endl; 178 | } 179 | } 180 | 181 | //------------------------------------------------------------------- 182 | // Convert the triplet indices ind *cl_group* to point indices. 183 | // *triplets* contains all triplets and *cl_group* will be modified. 184 | //------------------------------------------------------------------- 185 | void cluster_triplets_to_points(const std::vector &triplets, 186 | cluster_group &cl_group) { 187 | for (size_t i = 0; i < cl_group.size(); ++i) { 188 | cluster_t point_indices, ¤t_cluster = cl_group[i]; 189 | for (std::vector::const_iterator triplet_index = 190 | current_cluster.begin(); 191 | triplet_index < current_cluster.end(); ++triplet_index) { 192 | const triplet ¤t_triplet = triplets[*triplet_index]; 193 | point_indices.push_back(current_triplet.point_index_a); 194 | point_indices.push_back(current_triplet.point_index_b); 195 | point_indices.push_back(current_triplet.point_index_c); 196 | } 197 | // sort point-indices and remove duplicates 198 | std::sort(point_indices.begin(), point_indices.end()); 199 | std::vector::iterator new_end = 200 | std::unique(point_indices.begin(), point_indices.end()); 201 | point_indices.resize(std::distance(point_indices.begin(), new_end)); 202 | // replace triplet cluster with point cluster 203 | current_cluster = point_indices; 204 | } 205 | } 206 | 207 | //------------------------------------------------------------------- 208 | // Adds the cluster ids to the points in *cloud* 209 | // *cl_group* contains the clusters with the point indices. For every 210 | // point the corresponding cluster id is saved. For gnuplot the points 211 | // which overlap between multiple clusteres are saved in seperate 212 | // clusters in *cl_group* 213 | //------------------------------------------------------------------- 214 | void add_clusters(PointCloud &cloud, cluster_group &cl_group, bool gnuplot) { 215 | for (size_t i = 0; i < cl_group.size(); ++i) { 216 | const std::vector ¤t_cluster = cl_group[i]; 217 | for (std::vector::const_iterator point_index = 218 | current_cluster.begin(); 219 | point_index != current_cluster.end(); ++point_index) { 220 | cloud[*point_index].cluster_ids.insert(i); 221 | } 222 | } 223 | 224 | // add point indices to the corresponding cluster/vertex vector. this is for 225 | // the gnuplot output 226 | if (gnuplot) { 227 | std::vector verticies; 228 | for (size_t i = 0; i < cloud.size(); ++i) { 229 | const Point &p = cloud[i]; 230 | if (p.cluster_ids.size() > 1) { 231 | // if the point is in multiple clusters add it to the corresponding 232 | // vertex or create one if none exists 233 | bool found = false; 234 | for (std::vector::iterator v = verticies.begin(); 235 | v != verticies.end(); ++v) { 236 | if (cloud[v->at(0)].cluster_ids == p.cluster_ids) { 237 | v->push_back(i); 238 | found = true; 239 | } 240 | } 241 | if (!found) { 242 | cluster_t v; 243 | v.push_back(i); 244 | verticies.push_back(v); 245 | } 246 | // remove the point from all other clusters 247 | for (std::set::iterator it = p.cluster_ids.begin(); 248 | it != p.cluster_ids.end(); ++it) { 249 | cluster_t &cluster = cl_group[*it]; 250 | cluster.erase(std::remove(cluster.begin(), cluster.end(), i), 251 | cluster.end()); 252 | } 253 | } 254 | } 255 | // extend clusters with verticies 256 | cl_group.reserve(cl_group.size() + verticies.size()); 257 | cl_group.insert(cl_group.end(), verticies.begin(), verticies.end()); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/output.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // output.cpp 3 | // Functions for writing output files. 4 | // 5 | // Author: Jens Wilberg, Lukas Aymans, Christoph Dalitz 6 | // Date: 2018-08-30 7 | // License: see ../LICENSE 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "output.h" 18 | 19 | //------------------------------------------------------------------- 20 | // Computation of cluster colour 21 | // The colour is computed from the index of the cluster and is returned 22 | // as hex value. 23 | //------------------------------------------------------------------- 24 | unsigned long compute_cluster_colour(size_t cluster_index) { 25 | const double r = (double)((cluster_index * 23) % 19) / 18.0; 26 | const double g = (double)((cluster_index * 23) % 7) / 6.0; 27 | const double b = (double)((cluster_index * 23) % 3) / 2.0; 28 | uint8_t r2 = (uint8_t)(r * 255); 29 | uint8_t g2 = (uint8_t)(g * 255); 30 | uint8_t b2 = (uint8_t)(b * 255); 31 | unsigned long rgb_hex = (r2 << 16) | (g2 << 8) | b2; 32 | 33 | return rgb_hex; 34 | } 35 | 36 | //------------------------------------------------------------------- 37 | // Finds min and max Point of *cloud* 38 | // the Points are returned in *min* and *max*. 39 | //------------------------------------------------------------------- 40 | void find_min_max_point(const PointCloud &cloud, Point &min, Point &max) { 41 | min = max = cloud[0]; 42 | for (std::vector::const_iterator p = cloud.begin(); p != cloud.end(); 43 | ++p) { 44 | if (min.x > p->x) { 45 | min.x = p->x; 46 | } else if (max.x < p->x) { 47 | max.x = p->x; 48 | } 49 | if (min.y > p->y) { 50 | min.y = p->y; 51 | } else if (max.y < p->y) { 52 | max.y = p->y; 53 | } 54 | if (min.z > p->z) { 55 | min.z = p->z; 56 | } else if (max.z < p->z) { 57 | max.z = p->z; 58 | } 59 | } 60 | } 61 | 62 | //------------------------------------------------------------------- 63 | // saves smoothen cloud as gnuplot script. 64 | // The script is saved as file 'debug_smoothed.gnuplot' in the current 65 | // working directory. It contains the original PointCloud *cloud* in 66 | // black and the smoothed PointCloud *cloud_smooth* in red. 67 | // *fname* is the name of the new file. The function returns false if 68 | // an error occurred. 69 | //------------------------------------------------------------------- 70 | bool debug_gnuplot(const PointCloud &cloud, const PointCloud &cloud_smooth, 71 | const char *fname) { 72 | // saves cloud in gnuplot file 73 | std::ofstream of(fname); 74 | of << std::fixed; // set float style 75 | bool is2d = cloud.is2d(); 76 | if (!of.is_open()) { 77 | std::cerr << "[Error] could not save under '" << fname << std::endl; 78 | return false; 79 | } 80 | // find ranges for each axis 81 | Point min, max; 82 | find_min_max_point(cloud, min, max); 83 | // Write header 84 | if (!is2d) { 85 | // when max and min are the same, the script can't be ploted, so the range 86 | // must be changed 87 | if (max.x > min.x) { 88 | of << "set xrange [" << min.x << ":" << max.x << "]\n"; 89 | } else { 90 | of << "set xrange [" << (min.x - 1.0) << ":" << (max.x + 1.0) << "]\n"; 91 | } 92 | if (max.y > min.y) { 93 | of << "set yrange [" << min.y << ":" << max.y << "]\n"; 94 | } else { 95 | of << "set yrange [" << (min.y - 1.0) << ":" << (max.y + 1.0) << "]\n"; 96 | } 97 | if (max.z > min.z) { 98 | of << "set zrange [" << min.z << ":" << max.z << "]\n "; 99 | } else { 100 | of << "set zrange [" << (min.z - 1.0) << ":" << (max.z + 1.0) << "]\n "; 101 | } 102 | of << "splot "; 103 | } else { 104 | // if we have 2D data we use plot instead of splot. For plot the header is 105 | // not needed. 106 | of << "plot "; 107 | } 108 | 109 | of << "'-' with points lc 'black' title 'original', '-' with points lc " 110 | "'red' title 'smoothed'\n"; 111 | for (PointCloud::const_iterator it = cloud.begin(); it != cloud.end(); ++it) { 112 | of << it->x << " " << it->y; 113 | if (!is2d) of << " " << it->z; 114 | of << std::endl; 115 | } 116 | of << "e\n"; 117 | 118 | for (PointCloud::const_iterator it = cloud_smooth.begin(); 119 | it != cloud_smooth.end(); ++it) { 120 | of << it->x << " " << it->y; 121 | if (!is2d) of << " " << it->z; 122 | of << std::endl; 123 | } 124 | of << "e\npause mouse keypress\n"; 125 | 126 | of.close(); 127 | return true; 128 | } 129 | 130 | //------------------------------------------------------------------- 131 | // saves a PointCloud *cloud* as csv file. 132 | // The file is saved with the name *fname* under the current working 133 | // directory. The function returns false if an error occurred. 134 | //------------------------------------------------------------------- 135 | bool cloud_to_csv(const PointCloud &cloud, const char *fname) { 136 | std::ofstream of(fname); 137 | of << std::fixed; // set float style 138 | bool is2d = cloud.is2d(); 139 | if (!of.is_open()) { 140 | std::cerr << "[Error] could not save under '" << fname << std::endl; 141 | return false; 142 | } 143 | of << "# x,y,z" << std::endl; 144 | for (PointCloud::const_iterator it = cloud.begin(); it != cloud.end(); ++it) { 145 | of << it->x << "," << it->y; 146 | if (!is2d) of << "," << it->z << std::endl; 147 | } 148 | of.close(); 149 | return true; 150 | } 151 | 152 | //------------------------------------------------------------------- 153 | // prints gnuplot script to stdout. 154 | // rgb colours are created with the function *compute_cluster_colour*. 155 | // The points of *cloud* are printed with the corresponding cluster/colour. 156 | //------------------------------------------------------------------- 157 | void clusters_to_gnuplot(const PointCloud &cloud, 158 | const std::vector &clusters) { 159 | std::vector points = cloud; 160 | std::ostringstream pointstream, header, noise, clstream; 161 | std::string noiseheader = ""; 162 | bool is2d = cloud.is2d(); 163 | // set output format for floats 164 | pointstream << std::fixed; 165 | header << std::fixed; 166 | noise << std::fixed; 167 | // find ranges for each axis 168 | Point min, max; 169 | find_min_max_point(cloud, min, max); 170 | // Write header 171 | if (!is2d) { 172 | // when max and min are the same, the script can't be ploted, so the range 173 | // must be changed 174 | if (max.x > min.x) { 175 | header << "set xrange [" << min.x << ":" << max.x << "]\n"; 176 | } else { 177 | header << "set xrange [" << (min.x - 1.0) << ":" << (max.x + 1.0) 178 | << "]\n"; 179 | } 180 | if (max.y > min.y) { 181 | header << "set yrange [" << min.y << ":" << max.y << "]\n"; 182 | } else { 183 | header << "set yrange [" << (min.y - 1.0) << ":" << (max.y + 1.0) 184 | << "]\n"; 185 | } 186 | 187 | if (max.z > min.z) { 188 | header << "set zrange [" << min.z << ":" << max.z << "]\nsplot "; 189 | } else { 190 | header << "set zrange [" << (min.z - 1.0) << ":" << (max.z + 1.0) 191 | << "]\nsplot "; 192 | } 193 | } else { 194 | // if we have 2D data we use plot instead of splot. For plot the header is 195 | // not needed. 196 | header << "plot"; 197 | } 198 | 199 | // iterate over clusters 200 | for (size_t cluster_index = 0; cluster_index < clusters.size(); 201 | ++cluster_index) { 202 | const std::vector &point_indices = clusters[cluster_index]; 203 | // if there are no points in the cluster, it is only contained in an overlap 204 | // cluster 205 | if (point_indices.size() == 0) continue; 206 | // add cluster header 207 | unsigned long rgb_hex = compute_cluster_colour(cluster_index); 208 | clstream << " '-' with points lc '#" << std::hex << rgb_hex; 209 | std::set cluster_ids = cloud[point_indices[0]].cluster_ids; 210 | if (cluster_ids.size() > 1) { 211 | clstream << "' title 'overlap "; 212 | for (std::set::const_iterator clid = cluster_ids.begin(); 213 | clid != cluster_ids.end(); ++clid) { 214 | if (clid != cluster_ids.begin()) clstream << ";"; 215 | clstream << *clid; 216 | } 217 | } else { 218 | clstream << "' title 'curve " << *cluster_ids.begin(); 219 | } 220 | clstream << "',"; 221 | 222 | // add points to script 223 | for (std::vector::const_iterator it = point_indices.begin(); 224 | it != point_indices.end(); ++it) { 225 | const Point &point = cloud[*it]; 226 | 227 | // remove current point from vector points 228 | for (std::vector::iterator p = points.begin(); p != points.end(); 229 | p++) { 230 | if (*p == point) { 231 | points.erase(p); 232 | break; 233 | } 234 | } 235 | pointstream << point.x << " " << point.y; 236 | if (!is2d) pointstream << " " << point.z; 237 | pointstream << std::endl; 238 | } 239 | pointstream << "e" << std::endl; 240 | } 241 | clstream << std::endl; 242 | 243 | // plot all points red which are not clustered 244 | if (points.size() > 0) { 245 | noiseheader = " '-' with points lc 'red' title 'noise',"; 246 | for (std::vector::iterator it = points.begin(); it != points.end(); 247 | ++it) { 248 | noise << it->x << " " << it->y; 249 | if (!is2d) noise << " " << it->z; 250 | noise << std::endl; 251 | } 252 | noise << "e" << std::endl; 253 | } 254 | 255 | std::cout << header.str() << noiseheader << clstream.str() << noise.str() 256 | << pointstream.str() << "pause mouse keypress\n"; 257 | } 258 | 259 | //------------------------------------------------------------------- 260 | // saves the PointCloud *cloud* with clusters as csv file. 261 | // The csv file has following form: 262 | // x,y,z,clusterid 263 | // or 2D: 264 | // x,y,clusterid 265 | //------------------------------------------------------------------- 266 | void clusters_to_csv(const PointCloud &cloud) { 267 | bool is2d = cloud.is2d(); 268 | std::cout << std::fixed 269 | << "# Comment: curveID -1 represents noise\n# x, y, z, curveID\n"; 270 | 271 | for (PointCloud::const_iterator it = cloud.begin(); it != cloud.end(); ++it) { 272 | std::cout << it->x << "," << it->y << ","; 273 | if (!is2d) std::cout << it->z << ","; 274 | if (it->cluster_ids.empty()) { 275 | // Noise 276 | std::cout << "-1\n"; 277 | } else { 278 | for (std::set::const_iterator it2 = it->cluster_ids.begin(); 279 | it2 != it->cluster_ids.end(); ++it2) { 280 | if (it2 != it->cluster_ids.begin()) { 281 | std::cout << ";"; 282 | } 283 | std::cout << *it2; 284 | } 285 | std::cout << std::endl; 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /data/synthetic-clean.dat: -------------------------------------------------------------------------------- 1 | # 2 | # synthetically generated point cloud with four curves 3 | # Author: Christoph Dalitz 4 | # 5 | # x y z 6 | # 7 | # curve 1 8 | 5.5807409929694 5.71106501750861 15.466616301299 9 | 1.46328751492298 1.34307542844605 3.98708374607774 10 | 4.59065481620624 4.40031338589141 13.3238194831959 11 | 1.81736059316976 1.66864189632669 5.37054466840897 12 | 2.4117820677724 2.64654819279142 8.33239900471947 13 | 5.32506298485656 5.19509764761099 14.9434931258602 14 | 5.29746128746393 5.12383085782366 14.8253550204896 15 | 6.64694863128306 6.83374368848921 17.6438206413181 16 | 1.25671306388067 1.48653317947116 2.63419768217039 17 | 3.5938197398173 3.78700797599528 11.7538081923019 18 | 2.78353988342957 2.62476077010276 8.68562758019877 19 | 5.27968350625783 5.21991338797808 14.7303832765611 20 | 1.86782152812019 1.72136331918947 6.51340021915088 21 | 5.33008384472598 5.36695430975103 14.7846979901084 22 | 3.0467117319981 3.15791923127117 9.60269309432963 23 | 5.12275513503706 4.92597870491946 14.1951628499378 24 | 5.71658728354074 5.70731209126041 15.823818018089 25 | 2.80073805001904 2.92681155389968 9.47998809946595 26 | 1.64838045654858 1.38204542618277 4.62855844325136 27 | 1.4497626383763 1.41507407837069 3.69579340693174 28 | 1.72336739790501 1.81692932971244 5.28912829876388 29 | 3.17806627737072 3.08234468886189 10.165040901961 30 | 3.38590761690792 3.590675413674 11.2848247842956 31 | 4.65312804731722 4.64241206909612 13.7625980378998 32 | 1.8984789527539 2.15943443652721 6.72028757678709 33 | 0.794041556992401 1.16311786534532 1.22216956239459 34 | 2.01800253360479 2.17931861294258 6.39395750240645 35 | 6.38340768555442 6.58850933777478 16.9797748132346 36 | 6.93519493682081 7.25084546668327 18.1284272714389 37 | 4.58244841334679 4.42144729866677 13.4641168505244 38 | 2.08156177273269 2.18447930665983 6.7976707731699 39 | 1.11479532432454 1.27104823432488 1.71486431763855 40 | 1.2964473775121 1.35035646469134 2.97100990535491 41 | 4.81273266878445 4.75065891119632 13.537512115492 42 | 6.13824323649431 6.08449271968821 16.3788410012694 43 | 5.12304615271126 5.28488826917485 14.9448605506529 44 | 2.1880310807072 2.07611426101508 6.7370990584035 45 | 6.72101034455672 6.66538074653927 17.3975382497199 46 | 1.76531457800334 1.87780848704125 5.45955918689242 47 | 1.50786817021247 1.2740094280058 3.58163724600883 48 | 1.34543383571142 1.39907845050712 3.78429489866261 49 | 3.56338716762892 3.49601629464423 10.9191353801356 50 | 1.12271635050943 1.23841771853075 3.11416523132859 51 | 4.30440593421233 4.2616617983886 12.9101658239218 52 | 4.61072554702202 4.48693370216682 13.2371624032076 53 | 2.9735871205592 2.94632140388983 9.81131284595888 54 | 3.24464602937019 3.54343169373289 10.6305382459128 55 | 5.71205906254772 5.63979451472758 15.4038694578504 56 | 2.34299641754029 2.34700506633508 7.80190390174911 57 | 1.17498621623724 1.50826355625633 3.40100829222816 58 | 3.91759448943465 4.00297540986504 11.7356779530322 59 | 4.13684170311853 4.22503842871849 12.3236834354606 60 | 4.86834966159242 4.90575733808533 14.0521502235475 61 | 4.94486213820474 4.89194262440183 14.2422707489819 62 | 1.31159988481326 1.1008951141202 2.60921632417309 63 | 3.52973477029326 3.61123398710769 11.3927650428293 64 | 2.5651454181148 2.50215460830583 7.86611906774861 65 | 4.10223529241795 4.19711083831223 12.4822242986899 66 | 3.9096649399147 3.8345508997647 12.0797647998947 67 | 5.00902297186694 5.10862837793507 14.7630951004733 68 | 3.43348545263461 3.5557591222178 10.901314413848 69 | 3.16104387567834 3.32707255450135 10.163787768288 70 | 6.68937666999408 6.83145519520107 17.4359359515532 71 | 4.61910775520146 4.73354433691231 13.542114280377 72 | 3.8716664634279 3.79466305307901 11.7144260554402 73 | 1.62219032879549 1.48021722043902 4.5915927949892 74 | 1.95047779681942 1.68433886772475 6.11900284730156 75 | 3.54608582721767 3.65148956338194 11.5763423953759 76 | 2.25804383305477 2.23987414229338 7.70760654217098 77 | 1.75288078018194 2.09900895228282 6.25442322602522 78 | 1.13807547074696 1.16804805273176 2.61056016427364 79 | 6.76792533625758 6.77521284381073 17.447916449358 80 | 3.14423635768893 3.24445543453855 10.334617448307 81 | 6.62737763057634 6.87512617129706 17.6688058569113 82 | 4.35653115983836 4.31958616954675 12.9820220846782 83 | # curve 2 84 | 1.43331841017065 1.50201902812532 6.3968081202691 85 | 1.66331657274452 1.36419133707271 7.33662053717534 86 | 1.45247442574619 1.53680665006585 6.14295510453133 87 | 2.55958642639966 2.63553569270853 16.4717516474987 88 | 1.98680707336345 2.05887660589308 11.2433965337644 89 | 1.48299136029718 1.66550997426601 7.36805631762 90 | 1.93446304230983 2.00853812324064 11.6738092413867 91 | 2.55139520416497 2.70724602869824 15.8945108221999 92 | 2.64032373052074 2.68298051223556 17.7730669030108 93 | 2.64482823608847 2.71623269429732 17.9669533899863 94 | 1.53244768436349 1.46050741367504 5.47641706151782 95 | 1.23680894756805 1.24287400126123 2.79436238274336 96 | 2.54346707397501 2.497050053821 15.3503518009003 97 | 1.92459651516737 1.95929555375578 11.5889926952483 98 | 2.55808633614462 2.6179936408201 16.2398833915278 99 | 2.43713979973384 2.24587865539107 15.0655765268654 100 | 1.94476562778706 2.04855853958431 10.687038027356 101 | 1.32666863550662 1.30215071326083 4.00866828239622 102 | 1.52836953631964 1.54042112385626 6.05221884419707 103 | 2.49185993609052 2.52663662514967 17.3098904523621 104 | 1.61478016792865 1.43326577450161 5.42373508342996 105 | 2.18575305103227 2.00685827393287 12.2464826981415 106 | 1.38533678596374 1.11477397698283 4.27861143999647 107 | 2.75196137378336 2.71830217965594 18.0495823804293 108 | 2.66003471361552 2.89278135163191 18.1649354637289 109 | 2.26409526590861 2.33466724841221 15.3679901191616 110 | 2.05901722022564 2.17389920540243 10.6857005691355 111 | 2.35191420768342 2.41033960055996 14.9225081995256 112 | 2.40592243515841 2.36363173351905 16.0850133155132 113 | 2.46873732250349 2.42934406160121 15.8786598801702 114 | 1.74997882538499 1.5504097897411 7.78298316052546 115 | 2.51131787088141 2.51342883018722 15.8497411943076 116 | 2.67798901325596 2.67466564658916 17.8107956174907 117 | 1.94565149944204 1.97150235259156 10.4822992586109 118 | 2.7515243644849 2.54838467707394 18.2726427395288 119 | 1.062381697803 1.17829798139213 2.00801673898473 120 | 2.62651041102603 2.5248639263461 16.5079730845859 121 | 1.41120507769317 1.41542257328209 6.00493204005585 122 | 1.22810875177408 1.19053125118064 3.82204185911787 123 | 2.60918334388405 2.71502973834598 17.1290929222029 124 | 2.45207755527428 2.18033157255458 13.2525522438933 125 | 1.37353346060333 1.33880331355527 4.70302816339729 126 | 2.3169441497114 2.37726404943952 14.167404423184 127 | 2.1963524970678 2.23849513323104 12.9584784193998 128 | 1.55970959342909 1.64253510679644 8.00452377027009 129 | 1.15972351110353 1.2342230577267 3.11632667268254 130 | 1.35022631797316 1.20011394011644 4.29869799125391 131 | 2.53980266761706 2.68726492863254 16.4624558609408 132 | 2.75956977006869 2.6551463683781 18.1002076276771 133 | 2.5306780393411 2.61072807414538 17.5793029506736 134 | # curve 3 135 | 18.7227873844339 12.6410575733997 5.6061291094259 136 | 18.9126521878868 10.6377926356548 5.1964606983364 137 | 19.4006761298559 4.23945371109619 2.49941823777875 138 | 19.5805430204342 3.01813549002749 2.01220697223329 139 | 19.1054302611963 14.2391937151111 6.46798207255449 140 | 19.2019666606216 7.54195016490359 3.94089218599827 141 | 19.8827939760465 1.7164050425267 1.26888203674005 142 | 19.6624092016398 1.37238419814247 0.896787475343356 143 | 19.6087239667085 2.16034501093839 1.36284110276168 144 | 19.5665868082755 6.7067580534525 3.72305856641487 145 | 19.6697654608114 3.20468861753905 2.10449284551235 146 | 19.7908564830563 0.997347283086129 0.692837783752579 147 | 19.5570842895612 3.97212032662261 2.58509738715503 148 | 18.8492517576899 12.7620725982792 4.912243355578 149 | 18.1000234464831 16.8801126335497 7.02695742318482 150 | 19.7568425387757 1.70410058076564 1.12861236804971 151 | 18.8855611353496 12.4320263779126 5.78696390196096 152 | 18.7181131134603 15.3733457137221 6.37641356730372 153 | 19.6278618193196 1.93171370651451 1.22131103523051 154 | 19.570343536051 1.57534753831485 1.01828298523185 155 | 19.0897608158946 8.69575228051101 4.85569915339152 156 | 19.6601264727389 1.80565029521267 1.52634988036209 157 | 18.8167152192436 14.6376982170381 6.1386480117682 158 | 20.159668679046 1.79926267470042 1.27173667682723 159 | 18.8804050377212 11.0471239790291 5.48970312542034 160 | 19.4977983986267 4.4625087083679 2.99144452813817 161 | 20.0984950741672 1.46515359507564 0.771176186494408 162 | 20.0117203819121 1.46710385414875 0.670158087689435 163 | 18.8741424529914 6.69717903399501 3.93743657294874 164 | 18.965957211275 10.393445185772 5.34225321076654 165 | 18.901089654066 7.63223707651701 3.94456158923668 166 | 19.9196848287956 0.98391444469272 0.344850402719243 167 | 18.4021967130154 10.2089763900205 4.85496650148573 168 | 19.6093804212322 2.88168814344438 2.09160474269612 169 | 19.728797407867 3.76416533807335 2.30280798868878 170 | 19.1516704140013 3.91916624312494 3.01151677286609 171 | 19.7380891025092 1.58729183558594 1.01365925897907 172 | 19.0122152407206 13.0705738893626 5.96783468312258 173 | 19.0548777268851 6.09476086849033 3.52794156404015 174 | 19.1020852734344 9.7499879747903 4.88968115369034 175 | 19.2381097341633 6.31865380797022 3.49980207355607 176 | 19.2101421884162 8.69200206483776 4.5517999134066 177 | 20.173895042659 1.19618480402885 0.371921659598276 178 | 18.4813969097271 10.2624568110401 5.07587313639897 179 | 19.7912671889856 1.40208237246089 1.28636493516012 180 | 19.3030201180922 4.2560134018936 3.13399299241506 181 | 19.6833807173449 2.84283872860945 2.00193068900662 182 | 18.9776154939971 8.63067036281812 4.25892984621317 183 | 19.5686202528171 2.7355763059529 2.65313220980933 184 | 19.8405016331718 2.28012717723605 1.39445551912154 185 | # curve 4 186 | 15.5870785522203 9.89047576391265 -8.93830237119249 187 | 18.7447435018701 3.28123003938421 -1.98698956978013 188 | 16.2936036392928 8.75809160306925 -7.77933443782134 189 | 19.391941915952 2.10472206924979 -1.18928809932239 190 | 20.361020126019 0.293225540828591 0.798303638486077 191 | 22.1792804773969 -2.68189401001619 3.70793475602356 192 | 17.3305230276742 5.79444844220353 -4.72137272358471 193 | 16.6661147811189 7.62626895169571 -6.58502923686813 194 | 14.7700594538684 11.3349325751513 -10.1235656772824 195 | 21.1986249438724 -1.92350199064189 2.7282373709685 196 | 19.027523888676 2.59668908336466 -1.69215562760235 197 | 19.799062444491 1.25209937494926 -0.719123473219168 198 | 18.0710358382405 5.04440206094135 -4.52290088563587 199 | 18.4684880367857 3.70590016530211 -3.02377811083513 200 | 17.3773513127167 6.45916511159841 -5.02133433909653 201 | 17.7330133390271 5.70787156002958 -4.79167593884479 202 | 18.2717709993342 4.57191697210127 -3.25324078943118 203 | 16.8926879672515 7.33850445805431 -6.85354565465729 204 | 19.2054452944718 2.68256053089612 -1.66610585056842 205 | 16.1397509318277 9.50003425402578 -8.62731638575231 206 | 15.7291708970086 8.68491521387967 -7.86010903699315 207 | 19.1144445386036 3.88683332490478 -2.5345838993608 208 | 20.1791863555576 0.164076006972747 0.937859878962328 209 | 15.4394647837694 9.38526853596501 -8.21130082855766 210 | 16.0870425923364 8.19960640733091 -7.09221828555908 211 | 17.0914152606205 6.80917906579065 -6.00587051986781 212 | 20.6652759906487 -0.132334267517823 1.57386881777658 213 | 20.9168830788351 -0.563164558571719 1.51258326029689 214 | 15.7305726655936 9.33357446641397 -8.32103183778397 215 | 20.3099906548967 0.00377317376080151 0.705299483411937 216 | 18.8408998540736 4.07221264759273 -2.65868317589848 217 | 17.0984298805244 6.43416053637814 -5.84951470556136 218 | 19.4636066314888 1.1006301425507 -0.1692402908395 219 | 16.3378372065673 8.18007105914287 -7.0508203285558 220 | 15.925005298312 9.18707143569641 -8.32844302018277 221 | 15.9509774126243 8.44943866772116 -7.63158402795672 222 | 15.7154943689168 9.51910884562259 -8.60147306172313 223 | 17.4362558455095 6.46572949839637 -5.31346110045658 224 | 21.3877113577534 -2.17216614648697 2.99588725489513 225 | 18.7369480974383 3.59418536287094 -2.5218998503643 226 | 16.880057142608 7.33083452075883 -6.27175849919511 227 | 20.8060961693512 -0.654599225893482 1.65641077149036 228 | 14.9392055372928 11.7907113384378 -10.5119333321989 229 | 16.8668038382231 7.29976580497581 -6.16499297337443 230 | 16.2133773030409 8.19405823840808 -7.35171640460981 231 | 19.7495699575763 1.36106062170831 -0.650386851394781 232 | 20.1790513055545 0.397486155124489 0.653945772068188 233 | 21.9041510604156 -3.00657406374665 3.57606123449719 234 | 17.7515594653588 5.57132652612401 -4.66052391556022 235 | 15.3828405830918 10.0669265882707 -9.02027516060421 236 | -------------------------------------------------------------------------------- /src/kdtree/kdtree.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Kd-Tree implementation. 3 | // 4 | // Copyright: Christoph Dalitz, 2018 5 | // Jens Wilberg, 2018 6 | // Version: 1.2 7 | // License: BSD style license 8 | // (see the file LICENSE for details) 9 | // 10 | 11 | #include "kdtree.hpp" 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Kdtree { 18 | 19 | //-------------------------------------------------------------- 20 | // function object for comparing only dimension d of two vecotrs 21 | //-------------------------------------------------------------- 22 | class compare_dimension { 23 | public: 24 | compare_dimension(size_t dim) { d = dim; } 25 | bool operator()(const KdNode& p, const KdNode& q) { 26 | return (p.point[d] < q.point[d]); 27 | } 28 | size_t d; 29 | }; 30 | 31 | //-------------------------------------------------------------- 32 | // internal node structure used by kdtree 33 | //-------------------------------------------------------------- 34 | class kdtree_node { 35 | public: 36 | kdtree_node() { 37 | dataindex = cutdim = 0; 38 | loson = hison = (kdtree_node*)NULL; 39 | } 40 | ~kdtree_node() { 41 | if (loson) delete loson; 42 | if (hison) delete hison; 43 | } 44 | // index of node data in kdtree array "allnodes" 45 | size_t dataindex; 46 | // cutting dimension 47 | size_t cutdim; 48 | // value of point 49 | // double cutval; // == point[cutdim] 50 | CoordPoint point; 51 | // roots of the two subtrees 52 | kdtree_node *loson, *hison; 53 | // bounding rectangle of this node's subtree 54 | CoordPoint lobound, upbound; 55 | }; 56 | 57 | //-------------------------------------------------------------- 58 | // different distance metrics 59 | //-------------------------------------------------------------- 60 | class DistanceMeasure { 61 | public: 62 | DistanceMeasure() {} 63 | virtual ~DistanceMeasure() {} 64 | virtual double distance(const CoordPoint& p, const CoordPoint& q) = 0; 65 | virtual double coordinate_distance(double x, double y, size_t dim) = 0; 66 | }; 67 | // Maximum distance (Linfinite norm) 68 | class DistanceL0 : virtual public DistanceMeasure { 69 | DoubleVector* w; 70 | 71 | public: 72 | DistanceL0(const DoubleVector* weights = NULL) { 73 | if (weights) 74 | w = new DoubleVector(*weights); 75 | else 76 | w = (DoubleVector*)NULL; 77 | } 78 | ~DistanceL0() { 79 | if (w) delete w; 80 | } 81 | double distance(const CoordPoint& p, const CoordPoint& q) { 82 | size_t i; 83 | double dist, test; 84 | if (w) { 85 | dist = (*w)[0] * fabs(p[0] - q[0]); 86 | for (i = 1; i < p.size(); i++) { 87 | test = (*w)[i] * fabs(p[i] - q[i]); 88 | if (test > dist) dist = test; 89 | } 90 | } else { 91 | dist = fabs(p[0] - q[0]); 92 | for (i = 1; i < p.size(); i++) { 93 | test = fabs(p[i] - q[i]); 94 | if (test > dist) dist = test; 95 | } 96 | } 97 | return dist; 98 | } 99 | double coordinate_distance(double x, double y, size_t dim) { 100 | if (w) 101 | return (*w)[dim] * fabs(x - y); 102 | else 103 | return fabs(x - y); 104 | } 105 | }; 106 | // Manhatten distance (L1 norm) 107 | class DistanceL1 : virtual public DistanceMeasure { 108 | DoubleVector* w; 109 | 110 | public: 111 | DistanceL1(const DoubleVector* weights = NULL) { 112 | if (weights) 113 | w = new DoubleVector(*weights); 114 | else 115 | w = (DoubleVector*)NULL; 116 | } 117 | ~DistanceL1() { 118 | if (w) delete w; 119 | } 120 | double distance(const CoordPoint& p, const CoordPoint& q) { 121 | size_t i; 122 | double dist = 0.0; 123 | if (w) { 124 | for (i = 0; i < p.size(); i++) dist += (*w)[i] * fabs(p[i] - q[i]); 125 | } else { 126 | for (i = 0; i < p.size(); i++) dist += fabs(p[i] - q[i]); 127 | } 128 | return dist; 129 | } 130 | double coordinate_distance(double x, double y, size_t dim) { 131 | if (w) 132 | return (*w)[dim] * fabs(x - y); 133 | else 134 | return fabs(x - y); 135 | } 136 | }; 137 | // Euklidean distance (L2 norm) (squared) 138 | class DistanceL2 : virtual public DistanceMeasure { 139 | DoubleVector* w; 140 | 141 | public: 142 | DistanceL2(const DoubleVector* weights = NULL) { 143 | if (weights) 144 | w = new DoubleVector(*weights); 145 | else 146 | w = (DoubleVector*)NULL; 147 | } 148 | ~DistanceL2() { 149 | if (w) delete w; 150 | } 151 | double distance(const CoordPoint& p, const CoordPoint& q) { 152 | size_t i; 153 | double dist = 0.0; 154 | if (w) { 155 | for (i = 0; i < p.size(); i++) 156 | dist += (*w)[i] * (p[i] - q[i]) * (p[i] - q[i]); 157 | } else { 158 | for (i = 0; i < p.size(); i++) dist += (p[i] - q[i]) * (p[i] - q[i]); 159 | } 160 | return dist; 161 | } 162 | double coordinate_distance(double x, double y, size_t dim) { 163 | if (w) 164 | return (*w)[dim] * (x - y) * (x - y); 165 | else 166 | return (x - y) * (x - y); 167 | } 168 | }; 169 | 170 | //-------------------------------------------------------------- 171 | // destructor and constructor of kdtree 172 | //-------------------------------------------------------------- 173 | KdTree::~KdTree() { 174 | if (root) delete root; 175 | delete distance; 176 | } 177 | // distance_type can be 0 (Maximum), 1 (Manhatten), or 2 (Euklidean [squared]) 178 | KdTree::KdTree(const KdNodeVector* nodes, int distance_type /*=2*/) { 179 | size_t i, j; 180 | double val; 181 | // copy over input data 182 | if (!nodes || nodes->empty()) 183 | throw std::invalid_argument( 184 | "kdtree::KdTree(): argument nodes must not be empty"); 185 | dimension = nodes->begin()->point.size(); 186 | allnodes = *nodes; 187 | // initialize distance values 188 | distance = NULL; 189 | this->distance_type = -1; 190 | set_distance(distance_type); 191 | // compute global bounding box 192 | lobound = nodes->begin()->point; 193 | upbound = nodes->begin()->point; 194 | for (i = 1; i < nodes->size(); i++) { 195 | for (j = 0; j < dimension; j++) { 196 | val = allnodes[i].point[j]; 197 | if (lobound[j] > val) lobound[j] = val; 198 | if (upbound[j] < val) upbound[j] = val; 199 | } 200 | } 201 | // build tree recursively 202 | root = build_tree(0, 0, allnodes.size()); 203 | } 204 | 205 | // distance_type can be 0 (Maximum), 1 (Manhatten), or 2 (Euklidean [squared]) 206 | void KdTree::set_distance(int distance_type, 207 | const DoubleVector* weights /*=NULL*/) { 208 | if (distance) delete distance; 209 | this->distance_type = distance_type; 210 | if (distance_type == 0) { 211 | distance = (DistanceMeasure*)new DistanceL0(weights); 212 | } else if (distance_type == 1) { 213 | distance = (DistanceMeasure*)new DistanceL1(weights); 214 | } else { 215 | distance = (DistanceMeasure*)new DistanceL2(weights); 216 | } 217 | } 218 | 219 | //-------------------------------------------------------------- 220 | // recursive build of tree 221 | // "a" and "b"-1 are the lower and upper indices 222 | // from "allnodes" from which the subtree is to be built 223 | //-------------------------------------------------------------- 224 | kdtree_node* KdTree::build_tree(size_t depth, size_t a, size_t b) { 225 | size_t m; 226 | double temp, cutval; 227 | kdtree_node* node = new kdtree_node(); 228 | node->lobound = lobound; 229 | node->upbound = upbound; 230 | node->cutdim = depth % dimension; 231 | if (b - a <= 1) { 232 | node->dataindex = a; 233 | node->point = allnodes[a].point; 234 | } else { 235 | m = (a + b) / 2; 236 | std::nth_element(allnodes.begin() + a, allnodes.begin() + m, 237 | allnodes.begin() + b, compare_dimension(node->cutdim)); 238 | node->point = allnodes[m].point; 239 | cutval = allnodes[m].point[node->cutdim]; 240 | node->dataindex = m; 241 | if (m - a > 0) { 242 | temp = upbound[node->cutdim]; 243 | upbound[node->cutdim] = cutval; 244 | node->loson = build_tree(depth + 1, a, m); 245 | upbound[node->cutdim] = temp; 246 | } 247 | if (b - m > 1) { 248 | temp = lobound[node->cutdim]; 249 | lobound[node->cutdim] = cutval; 250 | node->hison = build_tree(depth + 1, m + 1, b); 251 | lobound[node->cutdim] = temp; 252 | } 253 | } 254 | return node; 255 | } 256 | 257 | //-------------------------------------------------------------- 258 | // k nearest neighbor search 259 | // returns the *k* nearest neighbors of *point* in O(log(n)) 260 | // time. The result is returned in *result* and is sorted by 261 | // distance from *point*. 262 | // The optional search predicate is a callable class (aka "functor") 263 | // derived from KdNodePredicate. When Null (default, no search 264 | // predicate is applied). 265 | //-------------------------------------------------------------- 266 | void KdTree::k_nearest_neighbors(const CoordPoint& point, size_t k, 267 | KdNodeVector* result, 268 | std::vector* distances, 269 | KdNodePredicate* pred /*=NULL*/) { 270 | size_t i; 271 | double d, temp_dist; 272 | KdNode temp; 273 | searchpredicate = pred; 274 | 275 | result->clear(); 276 | if (k < 1) return; 277 | if (point.size() != dimension) 278 | throw std::invalid_argument( 279 | "kdtree::k_nearest_neighbors(): point must be of same dimension as " 280 | "kdtree"); 281 | 282 | // collect result of k values in neighborheap 283 | //std::priority_queue, compare_nn4heap>* 284 | //neighborheap = new std::priority_queue, compare_nn4heap>(); 285 | SearchQueue* neighborheap = new SearchQueue(); 286 | if (k > allnodes.size()) { 287 | // when more neighbors asked than nodes in tree, return everything 288 | k = allnodes.size(); 289 | for (i = 0; i < k; i++) { 290 | if (!(searchpredicate && !(*searchpredicate)(allnodes[i]))) 291 | neighborheap->push( 292 | nn4heap(i, distance->distance(allnodes[i].point, point))); 293 | } 294 | } else { 295 | neighbor_search(point, root, k, neighborheap); 296 | } 297 | 298 | // copy over result sorted by distance 299 | // (we must revert the vector for ascending order) 300 | while (!neighborheap->empty()) { 301 | i = neighborheap->top().dataindex; 302 | d = neighborheap->top().distance; 303 | neighborheap->pop(); 304 | result->push_back(allnodes[i]); 305 | distances->push_back(d); 306 | } 307 | // beware that less than k results might have been returned 308 | k = result->size(); 309 | for (i = 0; i < k / 2; i++) { 310 | temp = (*result)[i]; 311 | (*result)[i] = (*result)[k - 1 - i]; 312 | (*result)[k - 1 - i] = temp; 313 | temp_dist = (*distances)[i]; 314 | (*distances)[i] = (*distances)[k - 1 - i]; 315 | (*distances)[k - 1 - i] = temp_dist; 316 | } 317 | delete neighborheap; 318 | } 319 | 320 | //-------------------------------------------------------------- 321 | // range nearest neighbor search 322 | // returns the nearest neighbors of *point* in the given range 323 | // *r*. The result is returned in *result* and is sorted by 324 | // distance from *point*. 325 | //-------------------------------------------------------------- 326 | void KdTree::range_nearest_neighbors(const CoordPoint& point, double r, 327 | KdNodeVector* result) { 328 | KdNode temp; 329 | 330 | result->clear(); 331 | if (point.size() != dimension) 332 | throw std::invalid_argument( 333 | "kdtree::k_nearest_neighbors(): point must be of same dimension as " 334 | "kdtree"); 335 | if (this->distance_type == 2) { 336 | // if euclidien distance is used the range must be squared because we 337 | // get squared distances from this implementation 338 | r *= r; 339 | } 340 | 341 | // collect result in range_result 342 | std::vector range_result; 343 | range_search(point, root, r, &range_result); 344 | 345 | // copy over result 346 | for (std::vector::iterator i = range_result.begin(); 347 | i != range_result.end(); ++i) { 348 | result->push_back(allnodes[*i]); 349 | } 350 | 351 | // clear vector 352 | range_result.clear(); 353 | } 354 | 355 | //-------------------------------------------------------------- 356 | // recursive function for nearest neighbor search in subtree 357 | // under *node*. Stores result in *neighborheap*. 358 | // returns "true" when no nearer neighbor elsewhere possible 359 | //-------------------------------------------------------------- 360 | bool KdTree::neighbor_search(const CoordPoint& point, kdtree_node* node, 361 | size_t k, SearchQueue* neighborheap) { 362 | double curdist, dist; 363 | 364 | curdist = distance->distance(point, node->point); 365 | if (!(searchpredicate && !(*searchpredicate)(allnodes[node->dataindex]))) { 366 | if (neighborheap->size() < k) { 367 | neighborheap->push(nn4heap(node->dataindex, curdist)); 368 | } else if (curdist < neighborheap->top().distance) { 369 | neighborheap->pop(); 370 | neighborheap->push(nn4heap(node->dataindex, curdist)); 371 | } 372 | } 373 | // first search on side closer to point 374 | if (point[node->cutdim] < node->point[node->cutdim]) { 375 | if (node->loson) 376 | if (neighbor_search(point, node->loson, k, neighborheap)) return true; 377 | } else { 378 | if (node->hison) 379 | if (neighbor_search(point, node->hison, k, neighborheap)) return true; 380 | } 381 | // second search on farther side, if necessary 382 | if (neighborheap->size() < k) { 383 | dist = std::numeric_limits::max(); 384 | } else { 385 | dist = neighborheap->top().distance; 386 | } 387 | if (point[node->cutdim] < node->point[node->cutdim]) { 388 | if (node->hison && bounds_overlap_ball(point, dist, node->hison)) 389 | if (neighbor_search(point, node->hison, k, neighborheap)) return true; 390 | } else { 391 | if (node->loson && bounds_overlap_ball(point, dist, node->loson)) 392 | if (neighbor_search(point, node->loson, k, neighborheap)) return true; 393 | } 394 | 395 | if (neighborheap->size() == k) dist = neighborheap->top().distance; 396 | return ball_within_bounds(point, dist, node); 397 | } 398 | 399 | //-------------------------------------------------------------- 400 | // recursive function for range search in subtree under *node*. 401 | // Stores result in *range_result*. 402 | //-------------------------------------------------------------- 403 | void KdTree::range_search(const CoordPoint& point, kdtree_node* node, 404 | double r, std::vector* range_result) { 405 | double curdist = distance->distance(point, node->point); 406 | if (curdist <= r) { 407 | range_result->push_back(node->dataindex); 408 | } 409 | if (node->loson != NULL && this->bounds_overlap_ball(point, r, node->loson)) { 410 | range_search(point, node->loson, r, range_result); 411 | } 412 | if (node->hison != NULL && this->bounds_overlap_ball(point, r, node->hison)) { 413 | range_search(point, node->hison, r, range_result); 414 | } 415 | } 416 | 417 | // returns true when the bounds of *node* overlap with the 418 | // ball with radius *dist* around *point* 419 | bool KdTree::bounds_overlap_ball(const CoordPoint& point, double dist, 420 | kdtree_node* node) { 421 | double distsum = 0.0; 422 | size_t i; 423 | for (i = 0; i < dimension; i++) { 424 | if (point[i] < node->lobound[i]) { // lower than low boundary 425 | distsum += distance->coordinate_distance(point[i], node->lobound[i], i); 426 | if (distsum > dist) return false; 427 | } else if (point[i] > node->upbound[i]) { // higher than high boundary 428 | distsum += distance->coordinate_distance(point[i], node->upbound[i], i); 429 | if (distsum > dist) return false; 430 | } 431 | } 432 | return true; 433 | } 434 | 435 | // returns true when the bounds of *node* completely contain the 436 | // ball with radius *dist* around *point* 437 | bool KdTree::ball_within_bounds(const CoordPoint& point, double dist, 438 | kdtree_node* node) { 439 | size_t i; 440 | for (i = 0; i < dimension; i++) 441 | if (distance->coordinate_distance(point[i], node->lobound[i], i) <= dist || 442 | distance->coordinate_distance(point[i], node->upbound[i], i) <= dist) 443 | return false; 444 | return true; 445 | } 446 | 447 | } // namespace Kdtree 448 | -------------------------------------------------------------------------------- /data/synthetic-noise.dat: -------------------------------------------------------------------------------- 1 | # 2 | # synthetically generated point cloud with four curves 3 | # Author: Christoph Dalitz 4 | # 5 | # x y z 6 | # 7 | # line 1 8 | 5.5807409929694 5.71106501750861 15.466616301299 9 | 1.46328751492298 1.34307542844605 3.98708374607774 10 | 4.59065481620624 4.40031338589141 13.3238194831959 11 | 1.81736059316976 1.66864189632669 5.37054466840897 12 | 2.4117820677724 2.64654819279142 8.33239900471947 13 | 5.32506298485656 5.19509764761099 14.9434931258602 14 | 5.29746128746393 5.12383085782366 14.8253550204896 15 | 6.64694863128306 6.83374368848921 17.6438206413181 16 | 1.25671306388067 1.48653317947116 2.63419768217039 17 | 3.5938197398173 3.78700797599528 11.7538081923019 18 | 2.78353988342957 2.62476077010276 8.68562758019877 19 | 5.27968350625783 5.21991338797808 14.7303832765611 20 | 1.86782152812019 1.72136331918947 6.51340021915088 21 | 5.33008384472598 5.36695430975103 14.7846979901084 22 | 3.0467117319981 3.15791923127117 9.60269309432963 23 | 5.12275513503706 4.92597870491946 14.1951628499378 24 | 5.71658728354074 5.70731209126041 15.823818018089 25 | 2.80073805001904 2.92681155389968 9.47998809946595 26 | 1.64838045654858 1.38204542618277 4.62855844325136 27 | 1.4497626383763 1.41507407837069 3.69579340693174 28 | 1.72336739790501 1.81692932971244 5.28912829876388 29 | 3.17806627737072 3.08234468886189 10.165040901961 30 | 3.38590761690792 3.590675413674 11.2848247842956 31 | 4.65312804731722 4.64241206909612 13.7625980378998 32 | 1.8984789527539 2.15943443652721 6.72028757678709 33 | 0.794041556992401 1.16311786534532 1.22216956239459 34 | 2.01800253360479 2.17931861294258 6.39395750240645 35 | 6.38340768555442 6.58850933777478 16.9797748132346 36 | 6.93519493682081 7.25084546668327 18.1284272714389 37 | 4.58244841334679 4.42144729866677 13.4641168505244 38 | 2.08156177273269 2.18447930665983 6.7976707731699 39 | 1.11479532432454 1.27104823432488 1.71486431763855 40 | 1.2964473775121 1.35035646469134 2.97100990535491 41 | 4.81273266878445 4.75065891119632 13.537512115492 42 | 6.13824323649431 6.08449271968821 16.3788410012694 43 | 5.12304615271126 5.28488826917485 14.9448605506529 44 | 2.1880310807072 2.07611426101508 6.7370990584035 45 | 6.72101034455672 6.66538074653927 17.3975382497199 46 | 1.76531457800334 1.87780848704125 5.45955918689242 47 | 1.50786817021247 1.2740094280058 3.58163724600883 48 | 1.34543383571142 1.39907845050712 3.78429489866261 49 | 3.56338716762892 3.49601629464423 10.9191353801356 50 | 1.12271635050943 1.23841771853075 3.11416523132859 51 | 4.30440593421233 4.2616617983886 12.9101658239218 52 | 4.61072554702202 4.48693370216682 13.2371624032076 53 | 2.9735871205592 2.94632140388983 9.81131284595888 54 | 3.24464602937019 3.54343169373289 10.6305382459128 55 | 5.71205906254772 5.63979451472758 15.4038694578504 56 | 2.34299641754029 2.34700506633508 7.80190390174911 57 | 1.17498621623724 1.50826355625633 3.40100829222816 58 | 3.91759448943465 4.00297540986504 11.7356779530322 59 | 4.13684170311853 4.22503842871849 12.3236834354606 60 | 4.86834966159242 4.90575733808533 14.0521502235475 61 | 4.94486213820474 4.89194262440183 14.2422707489819 62 | 1.31159988481326 1.1008951141202 2.60921632417309 63 | 3.52973477029326 3.61123398710769 11.3927650428293 64 | 2.5651454181148 2.50215460830583 7.86611906774861 65 | 4.10223529241795 4.19711083831223 12.4822242986899 66 | 3.9096649399147 3.8345508997647 12.0797647998947 67 | 5.00902297186694 5.10862837793507 14.7630951004733 68 | 3.43348545263461 3.5557591222178 10.901314413848 69 | 3.16104387567834 3.32707255450135 10.163787768288 70 | 6.68937666999408 6.83145519520107 17.4359359515532 71 | 4.61910775520146 4.73354433691231 13.542114280377 72 | 3.8716664634279 3.79466305307901 11.7144260554402 73 | 1.62219032879549 1.48021722043902 4.5915927949892 74 | 1.95047779681942 1.68433886772475 6.11900284730156 75 | 3.54608582721767 3.65148956338194 11.5763423953759 76 | 2.25804383305477 2.23987414229338 7.70760654217098 77 | 1.75288078018194 2.09900895228282 6.25442322602522 78 | 1.13807547074696 1.16804805273176 2.61056016427364 79 | 6.76792533625758 6.77521284381073 17.447916449358 80 | 3.14423635768893 3.24445543453855 10.334617448307 81 | 6.62737763057634 6.87512617129706 17.6688058569113 82 | 4.35653115983836 4.31958616954675 12.9820220846782 83 | # line 2 84 | 1.43331841017065 1.50201902812532 6.3968081202691 85 | 1.66331657274452 1.36419133707271 7.33662053717534 86 | 1.45247442574619 1.53680665006585 6.14295510453133 87 | 2.55958642639966 2.63553569270853 16.4717516474987 88 | 1.98680707336345 2.05887660589308 11.2433965337644 89 | 1.48299136029718 1.66550997426601 7.36805631762 90 | 1.93446304230983 2.00853812324064 11.6738092413867 91 | 2.55139520416497 2.70724602869824 15.8945108221999 92 | 2.64032373052074 2.68298051223556 17.7730669030108 93 | 2.64482823608847 2.71623269429732 17.9669533899863 94 | 1.53244768436349 1.46050741367504 5.47641706151782 95 | 1.23680894756805 1.24287400126123 2.79436238274336 96 | 2.54346707397501 2.497050053821 15.3503518009003 97 | 1.92459651516737 1.95929555375578 11.5889926952483 98 | 2.55808633614462 2.6179936408201 16.2398833915278 99 | 2.43713979973384 2.24587865539107 15.0655765268654 100 | 1.94476562778706 2.04855853958431 10.687038027356 101 | 1.32666863550662 1.30215071326083 4.00866828239622 102 | 1.52836953631964 1.54042112385626 6.05221884419707 103 | 2.49185993609052 2.52663662514967 17.3098904523621 104 | 1.61478016792865 1.43326577450161 5.42373508342996 105 | 2.18575305103227 2.00685827393287 12.2464826981415 106 | 1.38533678596374 1.11477397698283 4.27861143999647 107 | 2.75196137378336 2.71830217965594 18.0495823804293 108 | 2.66003471361552 2.89278135163191 18.1649354637289 109 | 2.26409526590861 2.33466724841221 15.3679901191616 110 | 2.05901722022564 2.17389920540243 10.6857005691355 111 | 2.35191420768342 2.41033960055996 14.9225081995256 112 | 2.40592243515841 2.36363173351905 16.0850133155132 113 | 2.46873732250349 2.42934406160121 15.8786598801702 114 | 1.74997882538499 1.5504097897411 7.78298316052546 115 | 2.51131787088141 2.51342883018722 15.8497411943076 116 | 2.67798901325596 2.67466564658916 17.8107956174907 117 | 1.94565149944204 1.97150235259156 10.4822992586109 118 | 2.7515243644849 2.54838467707394 18.2726427395288 119 | 1.062381697803 1.17829798139213 2.00801673898473 120 | 2.62651041102603 2.5248639263461 16.5079730845859 121 | 1.41120507769317 1.41542257328209 6.00493204005585 122 | 1.22810875177408 1.19053125118064 3.82204185911787 123 | 2.60918334388405 2.71502973834598 17.1290929222029 124 | 2.45207755527428 2.18033157255458 13.2525522438933 125 | 1.37353346060333 1.33880331355527 4.70302816339729 126 | 2.3169441497114 2.37726404943952 14.167404423184 127 | 2.1963524970678 2.23849513323104 12.9584784193998 128 | 1.55970959342909 1.64253510679644 8.00452377027009 129 | 1.15972351110353 1.2342230577267 3.11632667268254 130 | 1.35022631797316 1.20011394011644 4.29869799125391 131 | 2.53980266761706 2.68726492863254 16.4624558609408 132 | 2.75956977006869 2.6551463683781 18.1002076276771 133 | 2.5306780393411 2.61072807414538 17.5793029506736 134 | # line 3 135 | 18.7227873844339 12.6410575733997 5.6061291094259 136 | 18.9126521878868 10.6377926356548 5.1964606983364 137 | 19.4006761298559 4.23945371109619 2.49941823777875 138 | 19.5805430204342 3.01813549002749 2.01220697223329 139 | 19.1054302611963 14.2391937151111 6.46798207255449 140 | 19.2019666606216 7.54195016490359 3.94089218599827 141 | 19.8827939760465 1.7164050425267 1.26888203674005 142 | 19.6624092016398 1.37238419814247 0.896787475343356 143 | 19.6087239667085 2.16034501093839 1.36284110276168 144 | 19.5665868082755 6.7067580534525 3.72305856641487 145 | 19.6697654608114 3.20468861753905 2.10449284551235 146 | 19.7908564830563 0.997347283086129 0.692837783752579 147 | 19.5570842895612 3.97212032662261 2.58509738715503 148 | 18.8492517576899 12.7620725982792 4.912243355578 149 | 18.1000234464831 16.8801126335497 7.02695742318482 150 | 19.7568425387757 1.70410058076564 1.12861236804971 151 | 18.8855611353496 12.4320263779126 5.78696390196096 152 | 18.7181131134603 15.3733457137221 6.37641356730372 153 | 19.6278618193196 1.93171370651451 1.22131103523051 154 | 19.570343536051 1.57534753831485 1.01828298523185 155 | 19.0897608158946 8.69575228051101 4.85569915339152 156 | 19.6601264727389 1.80565029521267 1.52634988036209 157 | 18.8167152192436 14.6376982170381 6.1386480117682 158 | 20.159668679046 1.79926267470042 1.27173667682723 159 | 18.8804050377212 11.0471239790291 5.48970312542034 160 | 19.4977983986267 4.4625087083679 2.99144452813817 161 | 20.0984950741672 1.46515359507564 0.771176186494408 162 | 20.0117203819121 1.46710385414875 0.670158087689435 163 | 18.8741424529914 6.69717903399501 3.93743657294874 164 | 18.965957211275 10.393445185772 5.34225321076654 165 | 18.901089654066 7.63223707651701 3.94456158923668 166 | 19.9196848287956 0.98391444469272 0.344850402719243 167 | 18.4021967130154 10.2089763900205 4.85496650148573 168 | 19.6093804212322 2.88168814344438 2.09160474269612 169 | 19.728797407867 3.76416533807335 2.30280798868878 170 | 19.1516704140013 3.91916624312494 3.01151677286609 171 | 19.7380891025092 1.58729183558594 1.01365925897907 172 | 19.0122152407206 13.0705738893626 5.96783468312258 173 | 19.0548777268851 6.09476086849033 3.52794156404015 174 | 19.1020852734344 9.7499879747903 4.88968115369034 175 | 19.2381097341633 6.31865380797022 3.49980207355607 176 | 19.2101421884162 8.69200206483776 4.5517999134066 177 | 20.173895042659 1.19618480402885 0.371921659598276 178 | 18.4813969097271 10.2624568110401 5.07587313639897 179 | 19.7912671889856 1.40208237246089 1.28636493516012 180 | 19.3030201180922 4.2560134018936 3.13399299241506 181 | 19.6833807173449 2.84283872860945 2.00193068900662 182 | 18.9776154939971 8.63067036281812 4.25892984621317 183 | 19.5686202528171 2.7355763059529 2.65313220980933 184 | 19.8405016331718 2.28012717723605 1.39445551912154 185 | # line 4 186 | 15.5870785522203 9.89047576391265 -8.93830237119249 187 | 18.7447435018701 3.28123003938421 -1.98698956978013 188 | 16.2936036392928 8.75809160306925 -7.77933443782134 189 | 19.391941915952 2.10472206924979 -1.18928809932239 190 | 20.361020126019 0.293225540828591 0.798303638486077 191 | 22.1792804773969 -2.68189401001619 3.70793475602356 192 | 17.3305230276742 5.79444844220353 -4.72137272358471 193 | 16.6661147811189 7.62626895169571 -6.58502923686813 194 | 14.7700594538684 11.3349325751513 -10.1235656772824 195 | 21.1986249438724 -1.92350199064189 2.7282373709685 196 | 19.027523888676 2.59668908336466 -1.69215562760235 197 | 19.799062444491 1.25209937494926 -0.719123473219168 198 | 18.0710358382405 5.04440206094135 -4.52290088563587 199 | 18.4684880367857 3.70590016530211 -3.02377811083513 200 | 17.3773513127167 6.45916511159841 -5.02133433909653 201 | 17.7330133390271 5.70787156002958 -4.79167593884479 202 | 18.2717709993342 4.57191697210127 -3.25324078943118 203 | 16.8926879672515 7.33850445805431 -6.85354565465729 204 | 19.2054452944718 2.68256053089612 -1.66610585056842 205 | 16.1397509318277 9.50003425402578 -8.62731638575231 206 | 15.7291708970086 8.68491521387967 -7.86010903699315 207 | 19.1144445386036 3.88683332490478 -2.5345838993608 208 | 20.1791863555576 0.164076006972747 0.937859878962328 209 | 15.4394647837694 9.38526853596501 -8.21130082855766 210 | 16.0870425923364 8.19960640733091 -7.09221828555908 211 | 17.0914152606205 6.80917906579065 -6.00587051986781 212 | 20.6652759906487 -0.132334267517823 1.57386881777658 213 | 20.9168830788351 -0.563164558571719 1.51258326029689 214 | 15.7305726655936 9.33357446641397 -8.32103183778397 215 | 20.3099906548967 0.00377317376080151 0.705299483411937 216 | 18.8408998540736 4.07221264759273 -2.65868317589848 217 | 17.0984298805244 6.43416053637814 -5.84951470556136 218 | 19.4636066314888 1.1006301425507 -0.1692402908395 219 | 16.3378372065673 8.18007105914287 -7.0508203285558 220 | 15.925005298312 9.18707143569641 -8.32844302018277 221 | 15.9509774126243 8.44943866772116 -7.63158402795672 222 | 15.7154943689168 9.51910884562259 -8.60147306172313 223 | 17.4362558455095 6.46572949839637 -5.31346110045658 224 | 21.3877113577534 -2.17216614648697 2.99588725489513 225 | 18.7369480974383 3.59418536287094 -2.5218998503643 226 | 16.880057142608 7.33083452075883 -6.27175849919511 227 | 20.8060961693512 -0.654599225893482 1.65641077149036 228 | 14.9392055372928 11.7907113384378 -10.5119333321989 229 | 16.8668038382231 7.29976580497581 -6.16499297337443 230 | 16.2133773030409 8.19405823840808 -7.35171640460981 231 | 19.7495699575763 1.36106062170831 -0.650386851394781 232 | 20.1790513055545 0.397486155124489 0.653945772068188 233 | 21.9041510604156 -3.00657406374665 3.57606123449719 234 | 17.7515594653588 5.57132652612401 -4.66052391556022 235 | 15.3828405830918 10.0669265882707 -9.02027516060421 236 | # noise 237 | 21.2373364674671 2.01250928192788 14.3945818313814 238 | 3.7007832203555 9.90175001538489 12.922280610289 239 | 12.4913943392599 12.6316287168808 5.33385244323779 240 | 21.0791300834529 1.13779186047483 9.80736698573536 241 | 2.6349415633784 12.4023640744977 2.06132465306345 242 | 19.0956741922847 6.7778512324442 5.59983031149629 243 | 7.68149762894214 4.76277703019156 1.22597530475476 244 | 17.1674346765227 14.2685765005827 15.9701795789441 245 | 0.883875794227609 4.68660619425436 8.56228153837453 246 | 19.9960194880052 9.24192738213498 0.829250556435998 247 | 19.2215560959086 5.42355320839059 13.3098801428874 248 | 12.0069087644872 13.8430343731209 -0.896606322160908 249 | 3.12066957273308 -1.94969275594233 -2.6266236659278 250 | 13.1097153398498 4.23298603219937 5.0943979107197 251 | 5.60703653651711 8.84210916917641 4.70527835327789 252 | 14.9857937196939 1.37976503049107 17.7875518398009 253 | 12.1359189216928 16.8701014881124 3.44752287784663 254 | 16.7315022589866 11.2907404965849 8.024760564958 255 | 13.6725937030018 9.12465697578893 -8.66195187256723 256 | 11.0511777777408 7.82965261761062 4.7949527082874 257 | 15.8744139448462 14.6733154349789 -4.07787756536695 258 | 19.3449974397258 -0.92450730836139 -5.45449056056222 259 | 5.68333249994672 7.34577045256681 -1.0402673682477 260 | 2.06981477543327 9.50321833963095 13.2332691321949 261 | 19.3249221589644 0.774993100864891 -3.78274431856142 262 | 4.59269117593062 9.97963884725475 0.849004405334073 263 | 4.78739786989652 5.16526028781914 15.3054078683918 264 | 18.7784893091804 16.3042585974271 -6.55601177793689 265 | 7.21857939864378 11.6058152619359 -7.53765611848749 266 | 11.3399288547288 7.76968343358062 7.89169275914938 267 | 9.12230284916267 5.40677096267523 2.16776676453379 268 | 13.845810119606 5.0906067041902 2.31035472417669 269 | 20.7508670444073 14.2097756856559 18.1572589648346 270 | 2.19069534809233 13.4663809835511 -6.38112660782846 271 | 1.99620188077251 -2.99103399300167 -5.23187457700606 272 | 16.7384545012344 -2.84269390022516 -2.02075171841027 273 | 20.8898060876408 -0.942129028678022 9.05913788638952 274 | 5.85603129024558 11.0822800705481 -2.69446647948624 275 | 8.19127852411895 9.47180388163969 -9.33920745834413 276 | 3.05597506278923 11.5536233040256 14.5846970266046 277 | 11.8573053168213 12.1808290085367 2.41378392208042 278 | 7.34813723623733 8.5788051774881 3.3206659931119 279 | 12.7269040959694 -1.25358580634079 15.5266943831193 280 | 19.4959096042713 -2.39350302550973 -0.3015259049651 281 | 19.0056246371651 0.101840174167988 -0.145347071880973 282 | 12.4139474827177 3.27353327067159 0.204118444391201 283 | 7.85815516830262 14.1711022724564 8.88838860546873 284 | 20.9885763158282 7.88667969326504 12.1132406251577 285 | 19.0011472161789 13.7073684563007 -7.89494311036592 286 | 21.5654618781331 0.847807496919471 6.72708481413327 287 | 12.2249294898476 11.7769981548606 2.15871731414765 288 | 14.3124571374339 4.05578947055131 -0.401743966277987 289 | 9.99410887064571 2.10733567595036 11.6385266206613 290 | 4.18307146625881 -2.7336629141941 15.8233264096457 291 | 21.2074564704449 4.01726505815525 11.7308578156235 292 | 20.6044842581628 12.7857537774271 1.38396505331162 293 | 12.3095171834833 8.63888272742251 9.12974075634574 294 | 7.98122077633695 3.3902824149775 -8.99056634665028 295 | 5.17161985819266 1.23267823912779 8.09425873546004 296 | 14.1509134755852 0.0564753330591268 -5.23211512942887 297 | 6.32448988811152 6.01702943248307 4.57373613491567 298 | 6.39323008288292 1.69419670781873 0.837002151003823 299 | 5.02585661624843 14.7944793415927 7.1348988126712 300 | 4.11679030887868 -1.74138825753324 4.48788127761557 301 | 6.58556320139836 3.00553161424876 15.1751087309627 302 | 7.3826533891952 0.0351126368833068 -0.217751930337334 303 | 5.02994708345163 9.94431218749655 -8.02063617790982 304 | 10.1093664070933 5.50742070444445 15.9389318306143 305 | 21.3737273268057 -2.06270664735341 12.048750566702 306 | 14.8969553998717 11.9615516751665 -9.93830932254744 307 | 3.17174701399756 1.50654661929259 -5.07209297326931 308 | 20.5913936736769 1.89300853543213 -0.98531847418111 309 | 4.24158910040837 11.8714112267662 2.46581085449109 310 | 18.342613145956 5.70018497468074 -5.85111640177777 311 | 11.7355998950259 -2.12417759094187 12.8996470084988 312 | 19.1972568622081 14.9030180832354 -1.74300970203938 313 | 14.9720365430239 12.9390656769695 7.27350798309979 314 | 13.2915673908078 4.72563394043975 -0.120411329856047 315 | 3.62831484053034 7.89916747467984 -7.22041879619156 316 | 4.72186069047804 10.48656332443 -7.59922142058189 317 | 5.5692050687066 15.6865127770526 -0.184918471534832 318 | 10.9703679012158 -2.4487568480659 -3.63192802916434 319 | 6.06504977721007 3.71250839008319 -2.0910323326983 320 | 20.0919279859603 -0.372788749276742 2.27396318319996 321 | 13.8402068655136 9.90851224877236 8.60699952538076 322 | 18.3061785357427 -2.58674526513505 0.872549563618191 323 | 18.7731486050844 1.1286149250672 -8.6588981107289 324 | 20.8316685958121 4.64336906424584 -6.53309522060407 325 | 8.87406420755746 -2.71699401188047 -6.72098322999304 326 | 14.7314933349242 3.18792858175122 7.14113384729417 327 | 17.6996515966702 6.04418583367275 -1.52820022197479 328 | 3.30249553798213 16.7319892957332 -1.34967481741735 329 | 13.5040522845961 6.84220264215539 -6.96904971081718 330 | 9.23741683205544 4.43333734648579 10.8870755812112 331 | 15.6722408627452 2.50363173309234 12.2376114887666 332 | 1.00538937975785 15.031292245219 13.043633895787 333 | 3.83619951780796 4.9105715985224 7.79039380364254 334 | 5.70106864098251 -2.6003681613407 15.9298111789093 335 | 8.25425457388128 2.06098027583325 4.90831818294443 336 | 22.1462715944423 8.80711996521549 6.9933309875124 337 | -------------------------------------------------------------------------------- /data/tennis.dat: -------------------------------------------------------------------------------- 1 | # Test data file for triplclust 2 | # Source: V. Renò et al: "Real- time tracking of a tennis ball by combining 3D data and domain knowledge." TISHW, 2016, pp. 1–7 3 | # (courtesy of Vito Renò) 4 | # x y z 5 | 0.634624 4.306905 0.001293 6 | 0.639202 4.315956 0.000728 7 | 0.642509 4.327574 0.000163 8 | 0.647126 4.331687 0.002382 9 | 0.64696 4.342307 0.001454 10 | 0.659415 4.375274 0.000168 11 | 0.692514 4.556401 0.120232 12 | 0.699356 4.549379 0.154877 13 | 0.700666 4.554675 0.184958 14 | 0.70394 4.556939 0.210308 15 | 0.704302 4.565834 0.231636 16 | 0.554193 4.67323 0.11585 17 | 0.497463 4.732638 0.305685 18 | -1.038465 8.666365 0.416978 19 | -1.091375 8.677609 0.336152 20 | -1.142027 8.685531 0.247731 21 | -1.192358 8.695481 0.160936 22 | -1.301158 8.710468 0.144646 23 | -1.328576 8.709449 0.216875 24 | -1.352903 8.713545 0.280121 25 | -1.378168 8.715601 0.342075 26 | -1.403184 8.720159 0.397704 27 | -1.431059 8.718194 0.454687 28 | -1.453819 8.719107 0.500012 29 | -1.502467 8.916718 0.468019 30 | -1.477682 8.992484 0.375272 31 | -1.453069 9.067616 0.281316 32 | -1.426798 9.136235 0.188137 33 | -1.363095 9.326077 0.144575 34 | -1.346287 9.377537 0.221406 35 | -1.328083 9.422952 0.292877 36 | -1.938158 11.045107 0.109813 37 | -1.968969 11.08385 0.022636 38 | -1.992185 11.072512 0.075685 39 | -2.14238 11.167363 0.425127 40 | -2.211699 11.209319 0.540526 41 | -2.15085 11.406709 0.405931 42 | -2.098079 11.457837 0.31764 43 | -1.380011 12.098813 0.275861 44 | -1.372603 12.080821 0.146029 45 | -1.367331 12.047287 0.039181 46 | -1.364051 12.03063 0.108006 47 | -1.36361 12.017448 0.199981 48 | -1.359091 11.986157 0.292334 49 | -3.703139 1.658536 2.948212 50 | -3.423048 1.662258 3.09926 51 | -1.231319 11.086514 0.381248 52 | -3.419565 1.618967 3.046768 53 | -3.473877 1.35956 3.021358 54 | -1.085727 11.607694 0.047558 55 | -3.467318 1.346369 3.00339 56 | -3.465495 1.352252 3.037482 57 | -3.462375 1.353672 3.05619 58 | -3.459672 1.354435 3.07252 59 | -1.067209 11.647587 0.347302 60 | -1.064757 11.648961 0.394946 61 | -1.060724 11.655225 0.439421 62 | -1.059537 11.663633 0.47575 63 | -1.05499 11.65802 0.513906 64 | -1.054814 11.673083 0.543596 65 | -1.050634 11.672246 0.570952 66 | -1.048588 11.678649 0.594779 67 | -1.043727 11.677066 0.615751 68 | -1.041379 11.685114 0.6317 69 | -1.03876 11.686617 0.642531 70 | -1.034627 11.689355 0.651487 71 | -1.032784 11.690631 0.65472 72 | -3.440746 1.347989 3.181871 73 | -1.026395 11.72394 0.486442 74 | -3.692406 1.864548 2.915935 75 | -3.694527 1.856522 2.918181 76 | -1.02681 11.738242 0.372276 77 | -1.028852 11.753878 0.253778 78 | -1.029176 11.769741 0.133225 79 | -1.025208 11.786801 0.025227 80 | -1.036651 11.79374 0.110927 81 | -1.044765 11.802551 0.198724 82 | -1.05603 11.815709 0.291369 83 | -0.872836 11.689453 0.462528 84 | -0.864148 11.693793 0.387275 85 | -0.860649 11.688418 0.308294 86 | -0.85381 11.694997 0.224273 87 | -0.83016 11.679393 0.249401 88 | -0.829229 11.680193 0.307063 89 | -0.827745 11.674227 0.360415 90 | -0.827428 11.683967 0.407626 91 | -0.825277 11.677377 0.455436 92 | -0.82442 11.67846 0.494233 93 | -0.824399 11.682743 0.530815 94 | -0.794194 11.687719 0.344634 95 | -0.789247 11.68359 0.275191 96 | -3.320372 1.323261 3.284062 97 | -0.649853 11.860387 2.217966 98 | -0.65644 11.854736 2.312857 99 | -0.666414 11.847557 2.405853 100 | -0.671443 11.835431 2.496245 101 | -0.678378 11.828912 2.580833 102 | -0.686256 11.816912 2.660977 103 | -0.692731 11.806584 2.73807 104 | -0.947896 11.482225 2.811704 105 | -0.952626 11.45818 2.7413 106 | -0.494481 7.138676 2.246427 107 | -0.407383 6.274372 2.138525 108 | 0.292727 -0.7515 1.089624 109 | 0.357556 -1.44968 0.964814 110 | 0.422351 -2.118602 0.840896 111 | 0.677554 -4.34036 3.00824 112 | 0.654518 -3.972169 3.049955 113 | 0.629194 -3.611873 3.087872 114 | 0.605379 -3.255948 3.122225 115 | 0.582775 -2.89908 3.150452 116 | 0.559597 -2.548476 3.174946 117 | 0.53908 -2.194843 3.197655 118 | 0.466683 -1.15636 3.237635 119 | 0.444865 -0.813972 3.240728 120 | 0.420723 -0.473219 3.241565 121 | 0.397406 -0.135794 3.237116 122 | 0.374696 0.198692 3.229702 123 | 0.352193 0.531316 3.220262 124 | 0.329653 0.862319 3.206491 125 | 0.306204 1.193031 3.187149 126 | 0.282092 1.522185 3.163818 127 | 0.26061 1.844523 3.139045 128 | 0.240117 2.166849 3.109302 129 | 0.217199 2.485992 3.076501 130 | 0.194552 2.80608 3.039595 131 | 0.172679 3.120075 2.999137 132 | 0.150478 3.432382 2.955866 133 | 0.126636 3.748891 2.907326 134 | 0.106882 4.055522 2.856491 135 | 0.083425 4.361685 2.802444 136 | 0.061175 4.667715 2.744071 137 | 0.039383 4.9675 2.68237 138 | 0.019256 5.273646 2.616483 139 | -0.001306 5.565846 2.550935 140 | -0.023357 5.869756 2.47775 141 | -0.042545 6.15787 2.402207 142 | -0.06233 6.449177 2.32601 143 | -0.081984 6.73432 2.243505 144 | -0.102827 7.028859 2.158818 145 | -0.33225 10.335036 0.901319 146 | -0.348256 10.603445 0.775179 147 | -0.368504 10.855163 0.649703 148 | -0.38216 11.12261 0.515711 149 | -0.400478 11.376107 0.389269 150 | -0.420724 11.639095 0.247886 151 | -0.440079 11.883871 0.110886 152 | -0.454599 12.094707 0.065476 153 | -0.468068 12.318808 0.291428 154 | -0.486878 12.661652 0.602565 155 | -0.493344 12.769406 0.696723 156 | -0.500215 12.881669 0.790704 157 | -0.506047 12.996923 0.873475 158 | -0.510642 13.10339 0.952273 159 | -0.517701 13.218971 1.028098 160 | -0.525605 13.332022 1.099898 161 | -0.530157 13.44515 1.166311 162 | -0.536373 13.545727 1.231602 163 | -0.54162 13.66513 1.288902 164 | -0.549182 13.760218 1.344822 165 | -0.55282 13.877619 1.393578 166 | -0.561144 13.982358 1.437984 167 | -0.565544 14.095172 1.479741 168 | -0.576861 14.315321 1.559064 169 | -0.580605 14.319961 1.603279 170 | -0.647587 13.960653 1.738392 171 | -0.707387 13.537781 1.865279 172 | -0.937086 11.839131 2.351712 173 | -0.996469 11.410117 2.46039 174 | -1.05176 10.997256 2.560788 175 | -1.105887 10.597476 2.654728 176 | -1.159106 10.19035 2.744986 177 | -1.213111 9.79389 2.826663 178 | -1.264502 9.397476 2.903682 179 | -1.317195 9.008567 2.974622 180 | -1.36546 8.619679 3.040217 181 | -1.41718 8.233264 3.100361 182 | -1.468061 7.858561 3.155011 183 | -1.517606 7.480788 3.203881 184 | -1.563986 7.105543 3.249751 185 | -1.612281 6.745983 3.289212 186 | -1.660714 6.375736 3.32368 187 | -1.706071 6.019271 3.352205 188 | -1.751246 5.660835 3.377289 189 | -1.796875 5.306979 3.394718 190 | -1.841 4.95455 3.409895 191 | -1.884704 4.610613 3.417169 192 | -1.928792 4.268957 3.421649 193 | -1.971872 3.929058 3.421361 194 | -2.014097 3.591988 3.417429 195 | -2.053014 3.265667 3.405859 196 | -2.094567 2.930987 3.393348 197 | -2.133751 2.607343 3.373728 198 | -2.172403 2.283597 3.353109 199 | -2.21161 1.965392 3.32627 200 | -2.249724 1.644964 3.294213 201 | -2.288763 1.332629 3.256461 202 | -2.324419 1.025195 3.217388 203 | -2.361113 0.722988 3.172705 204 | -2.639888 2.766886 2.109606 205 | -2.604933 3.202968 2.068195 206 | -2.569409 3.636046 2.020327 207 | -2.535712 4.063203 1.969079 208 | -2.500221 4.501656 1.911375 209 | -2.466199 4.917621 1.855798 210 | -2.433765 5.334539 1.791984 211 | -2.400572 5.754124 1.728344 212 | -2.367901 6.172562 1.653244 213 | -2.335912 6.57033 1.583971 214 | -2.305224 6.975081 1.508274 215 | -2.272719 7.376671 1.427388 216 | -2.24381 7.777551 1.34059 217 | -2.211813 8.166511 1.25747 218 | -2.183214 8.557711 1.166782 219 | -2.148773 8.94001 1.076762 220 | -2.120404 9.319304 0.980293 221 | -2.090829 9.702213 0.880789 222 | -2.059857 10.075688 0.780883 223 | -2.034297 10.448128 0.67533 224 | -2.00774 10.811828 0.568665 225 | -1.980849 11.183037 0.460479 226 | -1.954891 11.546572 0.347635 227 | -1.930092 11.895503 0.234612 228 | -1.904655 12.261034 0.115091 229 | -1.876929 12.600634 0.031336 230 | -1.86607 12.78607 0.129 231 | -1.850812 12.966564 0.239081 232 | -1.834494 13.158299 0.341083 233 | -1.819812 13.33598 0.435239 234 | -1.804715 13.521316 0.524568 235 | -1.790477 13.703417 0.610514 236 | -1.775818 13.878175 0.69347 237 | -1.761134 14.061591 0.771016 238 | -1.748541 14.235185 0.839718 239 | -1.733705 14.419157 0.906332 240 | -1.721223 14.595207 0.970689 241 | -1.707355 14.775968 1.027668 242 | -1.693945 14.956944 1.078092 243 | -1.678829 15.123419 1.127564 244 | -1.668152 15.289245 1.171318 245 | -1.655699 15.47077 1.204331 246 | -1.640428 15.649531 1.242516 247 | -1.624013 15.795655 1.287296 248 | -1.614288 15.79598 1.375985 249 | -1.597838 15.794582 1.468087 250 | -1.581527 15.793562 1.55581 251 | -1.565564 15.792635 1.640131 252 | -1.550311 15.787081 1.718657 253 | -1.535097 15.788147 1.792129 254 | -1.519368 15.784125 1.867203 255 | 1.698669 11.735614 0.654952 256 | 1.701846 11.741833 0.674815 257 | 1.703947 11.735347 0.692582 258 | 1.706859 11.741472 0.704711 259 | 1.721744 11.725937 0.427087 260 | 1.728452 11.725084 0.35211 261 | 1.732144 11.721224 0.279985 262 | 1.757863 11.725881 0.247177 263 | 1.759673 11.720175 0.305283 264 | 1.763978 11.725391 0.351841 265 | 1.766411 11.716944 0.398706 266 | 1.767802 11.725493 0.439112 267 | 1.93447 11.821171 2.179647 268 | 1.923421 11.811882 2.282691 269 | 1.913038 11.806467 2.379366 270 | 1.905116 11.808134 2.470694 271 | 1.895754 11.80204 2.563277 272 | 1.882298 11.778792 2.64048 273 | 1.452874 11.393924 2.752494 274 | 1.246738 10.658352 2.674719 275 | 1.017492 9.845616 2.586629 276 | -0.543967 4.480911 1.907945 277 | -1.978807 -0.116946 1.091913 278 | -2.175115 -0.724443 0.957094 279 | -2.365155 -1.305593 0.822213 280 | -2.552048 -1.870758 0.691353 281 | -2.414218 -3.261698 2.085403 282 | -2.25875 -2.885944 2.095481 283 | -2.116267 -2.537819 2.104322 284 | 3.114653 10.174375 0.708778 285 | 3.172468 10.310027 0.7651 286 | 3.229118 10.454902 0.817279 287 | 3.286794 10.598992 0.863151 288 | 3.342084 10.728004 0.902535 289 | 3.399498 10.877821 0.940109 290 | 3.453994 11.005467 0.974083 291 | 3.551564 10.394883 1.143776 292 | 3.312661 4.615793 1.501308 293 | 3.24413 3.031515 1.506994 294 | 3.222476 2.516534 1.498461 295 | 3.192993 2.0026 1.482277 296 | 3.178632 1.514445 1.468378 297 | 3.156601 1.022349 1.446996 298 | 3.133281 0.537505 1.420526 299 | 3.110962 0.061365 1.38996 300 | 3.090384 -0.415381 1.357746 301 | 3.069746 -0.886146 1.319226 302 | 3.0479 -1.337395 1.281316 303 | 3.029507 -1.797313 1.234895 304 | 3.00883 -2.239372 1.187884 305 | 0.738123 -3.657683 1.645297 306 | 0.687852 -3.416123 1.614106 307 | 0.636888 -3.15675 1.580961 308 | 0.587419 -2.900196 1.545501 309 | 0.537711 -2.647633 1.505991 310 | 0.487499 -2.393625 1.465216 311 | 0.435681 -2.137479 1.418962 312 | 0.384467 -1.88857 1.371915 313 | 0.332944 -1.635699 1.320959 314 | 0.282773 -1.388475 1.26817 315 | 0.229495 -1.138359 1.209995 316 | 0.180019 -0.891705 1.151087 317 | 0.127567 -0.647209 1.087257 318 | 0.074649 -0.404424 1.020414 319 | 0.023898 -0.157992 0.955515 320 | -0.052875 -0.372048 0.614117 321 | -0.061569 -0.404734 0.558095 322 | -0.066297 -0.435737 0.504292 323 | -0.074004 -0.467558 0.443822 324 | -0.078653 -0.497921 0.380612 325 | -0.084126 -0.528216 0.312899 326 | -0.090932 -0.558832 0.242383 327 | 2.44101 -1.081549 1.473318 328 | -0.09796 -0.58761 0.169348 329 | -0.103857 -0.618906 0.094779 330 | -0.125465 -0.698132 0.155351 331 | -0.133213 -0.722432 0.207648 332 | -0.139494 -0.749247 0.259042 333 | -0.147706 -0.773253 0.303403 334 | -0.156761 -0.797792 0.343306 335 | -0.163846 -0.822101 0.382977 336 | -0.171842 -0.847059 0.416663 337 | -0.17894 -0.870873 0.44665 338 | -0.187431 -0.896542 0.473927 339 | -0.194836 -0.919681 0.493862 340 | -0.203119 -0.944132 0.511565 341 | -0.210839 -0.966846 0.525561 342 | -0.21879 -0.990987 0.536231 343 | -0.225259 -1.016747 0.543903 344 | -0.232179 -1.041167 0.549302 345 | -0.240458 -1.064207 0.546577 346 | -0.248626 -1.088733 0.54168 347 | -0.25552 -1.112624 0.533713 348 | -0.263541 -1.136221 0.519842 349 | -0.270061 -1.159886 0.503591 350 | -0.278618 -1.184388 0.48454 351 | -0.284997 -1.207615 0.461566 352 | -0.292452 -1.231743 0.434866 353 | -0.300495 -1.256015 0.402634 354 | -0.31005 -1.276545 0.36566 355 | -0.316892 -1.300911 0.326454 356 | -0.324261 -1.324209 0.284606 357 | -0.330178 -1.348446 0.238604 358 | -0.337733 -1.363284 0.195697 359 | -0.416338 -1.596205 0.318373 360 | -0.423838 -1.61786 0.333205 361 | -0.430949 -1.638079 0.346743 362 | -0.438426 -1.656978 0.355804 363 | -0.410703 1.803019 0.056932 364 | -0.385322 1.800671 0.075988 365 | -0.371528 1.807009 0.152579 366 | -0.354296 1.81268 0.225763 367 | -0.305517 1.829201 0.431917 368 | -0.288911 1.833549 0.491202 369 | -0.272247 1.84015 0.547519 370 | -0.255581 1.845971 0.599836 371 | -0.239139 1.84991 0.648908 372 | -0.223362 1.855296 0.693816 373 | -0.207865 1.862137 0.731285 374 | -0.191859 1.868803 0.767749 375 | -0.173841 1.870941 0.801585 376 | -0.159806 1.877756 0.828013 377 | -0.143329 1.885194 0.851821 378 | -0.084216 1.835145 0.7185 379 | -0.077969 1.806992 0.634229 380 | -0.066017 1.752254 0.457042 381 | -0.060401 1.728083 0.362697 382 | -0.0553 1.700001 0.265314 383 | -0.220281 1.237293 0.264138 384 | 1.073223 2.692137 0.162755 385 | 1.124172 2.612383 0.018415 386 | 1.159462 2.611487 0.096537 387 | 1.181949 2.599145 0.15672 388 | 1.204766 2.58107 0.217007 389 | 1.227117 2.569028 0.272829 390 | 1.251336 2.550535 0.324175 391 | 1.274057 2.538672 0.370562 392 | 1.29693 2.524357 0.413047 393 | 1.319524 2.50417 0.453375 394 | 1.343273 2.488589 0.4882 395 | 1.368372 2.472079 0.520771 396 | 1.388936 2.45662 0.546863 397 | 1.411893 2.441629 0.570489 398 | 1.273834 2.419216 0.241173 399 | 1.215337 2.42067 0.118611 400 | 1.127409 2.431285 0.115541 401 | 1.095487 2.43843 0.213083 402 | -1.581529 -3.613175 1.790676 403 | -1.571949 -3.489146 1.871586 404 | -1.561153 -3.370174 1.944191 405 | -1.550399 -3.247863 2.017165 406 | -1.540883 -3.12785 2.084882 407 | -1.530477 -3.007467 2.148271 408 | -1.522174 -2.885892 2.206734 409 | -1.502498 -2.646865 2.315095 410 | -1.493178 -2.527223 2.360746 411 | -1.443748 -1.932566 2.532226 412 | -1.240388 0.487427 2.167138 413 | -1.195147 1.040304 1.835542 414 | -1.03914 2.839797 0.015269 415 | -0.972088 3.327646 0.64537 416 | -0.94612 3.538 0.851772 417 | -0.91448 3.735018 1.0341 418 | -0.972908 6.628398 1.653758 419 | -0.980787 6.671473 1.606794 420 | -0.988981 6.71901 1.55624 421 | -0.997638 6.768381 1.500814 422 | -1.007471 6.809585 1.445541 423 | -1.011988 6.855463 1.379204 424 | -1.092983 7.257472 0.645688 425 | -1.101215 7.303666 0.548434 426 | -1.10849 7.349435 0.439628 427 | -1.116214 7.394256 0.334956 428 | -1.125499 7.432508 0.223594 429 | -1.132424 7.48055 0.105479 430 | -1.148797 7.564147 0.119932 431 | -1.154522 7.610319 0.204544 432 | -1.160728 7.646396 0.291877 433 | -1.165435 7.695964 0.367865 434 | -1.200909 7.910273 0.700839 435 | -1.205593 7.950128 0.755412 436 | -1.213934 7.996483 0.805282 437 | -1.218822 8.035382 0.850548 438 | -1.226217 8.075548 0.893443 439 | -1.232424 8.117924 0.933234 440 | -1.240364 8.159176 0.965484 441 | -1.245584 8.207022 0.995822 442 | -1.152615 8.962534 0.403898 443 | -1.114186 8.970196 0.297455 444 | -4.003483 -0.881894 3.829815 445 | -3.998537 -0.891648 3.814533 446 | -3.994196 -0.898337 3.79975 447 | -3.989264 -0.909044 3.780773 448 | -4.281554 -0.613623 3.689973 449 | -1.004753 11.788784 0.29366 450 | -1.007112 11.791066 0.347756 451 | -1.007864 11.80039 0.398104 452 | -1.007007 11.802211 0.442835 453 | -1.006918 11.806854 0.487101 454 | -1.006746 11.807996 0.523725 455 | -1.006854 11.814235 0.559825 456 | -1.005463 11.816677 0.589546 457 | -1.006405 11.818759 0.616225 458 | -1.006334 11.826505 0.638321 459 | -1.008667 11.830972 0.654478 460 | -1.007617 11.828948 0.66994 461 | -1.007461 11.836562 0.682807 462 | -3.973996 -0.897378 3.83691 463 | -1.116744 11.823532 0.418302 464 | -4.060258 -0.84156 3.740057 465 | -4.052906 -0.848339 3.728744 466 | -0.890416 11.712183 0.383459 467 | -0.876819 11.720345 0.309392 468 | -0.866022 11.721745 0.235693 469 | -4.052148 -0.862905 3.736468 470 | -0.82766 11.735433 0.226522 471 | -0.82735 11.741376 0.281827 472 | -0.825684 11.744791 0.328863 473 | -0.827624 11.751064 0.373964 474 | -1.051689 12.04959 0.103629 475 | -0.677464 11.894822 2.161557 476 | -0.687409 11.887075 2.257856 477 | -0.697556 11.877001 2.3514 478 | -0.709561 11.868903 2.440272 479 | -0.718129 11.868154 2.525495 480 | -0.73039 11.850872 2.608684 481 | -0.739936 11.846353 2.685452 482 | -0.751244 11.835511 2.7566 483 | -0.593268 9.705656 2.563629 484 | 0.115865 7.130677 2.224813 485 | 0.340938 6.299689 2.10557 486 | 2.101791 -0.425282 0.973837 487 | 2.266644 -1.077583 0.836289 488 | 2.428235 -1.720055 0.700227 489 | 3.194001 -1.46981 2.251115 490 | 3.153092 -1.157396 2.270439 491 | 3.05191 -0.475201 2.29732 492 | 2.999963 -0.132339 2.30508 493 | 2.949989 0.199096 2.310839 494 | 2.89963 0.538575 2.312034 495 | 2.850959 0.862922 2.310818 496 | 2.80209 1.200007 2.307934 497 | 2.750905 1.527291 2.298415 498 | 2.702637 1.855306 2.286984 499 | 2.65442 2.179282 2.272845 500 | 2.605032 2.49899 2.254234 501 | 2.556268 2.820778 2.233796 502 | 2.51127 3.132418 2.209543 503 | 2.463185 3.451077 2.183333 504 | 2.415132 3.764763 2.153828 505 | 2.367335 4.069623 2.121163 506 | 2.3209 4.385133 2.084794 507 | 2.277661 4.691722 2.047056 508 | 2.23219 4.997119 2.004687 509 | 2.184369 5.299335 1.959071 510 | 2.140794 5.600211 1.913753 511 | 2.099117 5.903687 1.862078 512 | 2.052976 6.198042 1.81051 513 | 2.010789 6.497349 1.752984 514 | 1.968292 6.780852 1.696912 515 | 1.925347 7.07444 1.634004 516 | 1.881805 7.365993 1.569519 517 | 1.841997 7.659144 1.501035 518 | 1.799653 7.941064 1.43094 519 | 1.756954 8.222928 1.360713 520 | 1.717171 8.505942 1.282648 521 | 1.675809 8.789391 1.205179 522 | 1.63449 9.072744 1.121081 523 | 1.595314 9.340595 1.04023 524 | 1.556747 9.622123 0.949663 525 | 1.515612 9.887122 0.863597 526 | 1.47602 10.160608 0.769658 527 | 1.439896 10.432194 0.676025 528 | 1.399866 10.710249 0.575189 529 | 1.36175 10.961474 0.482404 530 | 1.322531 11.244865 0.373964 531 | 1.285483 11.493907 0.270873 532 | 1.247039 11.766486 0.161117 533 | 1.208351 12.010268 0.054256 534 | 1.190468 12.190437 0.077095 535 | 1.184419 12.309331 0.179983 536 | 1.174644 12.432196 0.26369 537 | 1.166949 12.550081 0.349255 538 | 1.160221 12.674721 0.432114 539 | 1.152665 12.79413 0.508646 540 | 1.146837 12.920958 0.581561 541 | 1.13729 13.04184 0.647825 542 | 1.131351 13.156629 0.713234 543 | 1.123813 13.279212 0.770783 544 | 1.114421 13.397875 0.826999 545 | 1.109728 13.50851 0.875912 546 | 1.099753 13.627029 0.921694 547 | 1.093713 13.749389 0.962242 548 | 1.081336 13.986824 1.050374 549 | 0.953283 13.130034 1.218022 550 | 0.874172 12.534665 1.318718 551 | 0.637712 10.808378 1.57581 552 | 0.556929 10.22757 1.652195 553 | 0.482003 9.67184 1.722406 554 | 0.408631 9.135232 1.782186 555 | 0.333717 8.583621 1.83888 556 | 0.263316 8.056852 1.886535 557 | 0.190702 7.515391 1.932807 558 | 0.122901 6.997319 1.970859 559 | 0.053215 6.482728 2.005518 560 | -0.014615 5.97177 2.033221 561 | -0.08046 5.464216 2.056381 562 | -0.148174 4.968947 2.071828 563 | -0.968839 -1.328678 1.793796 564 | -1.064678 -2.127335 1.684434 565 | -1.112658 -2.521001 1.623922 566 | -1.158909 -2.90341 1.559958 567 | -1.206522 -3.284994 1.4925 568 | -0.662955 -3.594299 1.721274 569 | -0.485401 -2.638048 1.707059 570 | -0.396832 -2.163713 1.69433 571 | -0.310197 -1.698786 1.679486 572 | -0.219843 -1.217767 1.656325 573 | 1.99261 10.532962 0.510865 574 | 2.033067 10.7611 0.561164 575 | 2.077608 10.998517 0.609177 576 | 2.119957 11.226896 0.65297 577 | 2.160974 11.454369 0.689727 578 | 2.204084 11.68275 0.724189 579 | 2.240323 11.908647 0.749548 580 | 2.284676 12.13272 0.773425 581 | 2.324561 12.348082 0.793879 582 | 2.363149 12.566315 0.806481 583 | 2.40341 12.785952 0.816335 584 | 2.443967 13.010164 0.81754 585 | 2.480995 13.218256 0.819886 586 | 2.518031 13.425847 0.813031 587 | 2.298974 12.725358 0.968852 588 | 2.135304 12.135715 1.061211 589 | 1.671095 10.440915 1.296257 590 | 1.516278 9.881414 1.369584 591 | 1.36758 9.345316 1.429879 592 | 1.215377 8.80588 1.488302 593 | 1.06773 8.27174 1.544176 594 | 0.922571 7.748602 1.590091 595 | 0.776768 7.231722 1.634046 596 | 0.634318 6.719141 1.673316 597 | -1.636272 -1.418634 1.567904 598 | -1.741996 -1.803835 1.518859 599 | -1.846779 -2.186247 1.466671 600 | -1.95183 -2.562665 1.409455 601 | -2.055548 -2.939295 1.350362 602 | -2.130769 -3.206998 1.311543 603 | 4.721797 8.433201 0.022894 604 | 4.108503 6.745748 0.823791 605 | 4.093534 6.774636 0.794053 606 | 4.031953 6.866205 0.647411 607 | 3.749571 7.36551 0.521582 608 | 2.98226 8.945489 0.021189 609 | 2.970129 8.950035 0.022878 610 | 2.88738 9.151319 0.019388 611 | 2.81421 9.312014 0.019678 612 | 2.805269 9.332843 0.018253 613 | 2.79884 9.349149 0.026834 614 | 2.761292 9.440074 0.018215 615 | 2.754326 9.451548 0.013389 616 | 2.746494 9.479034 0.016863 617 | 2.730458 9.519934 0.022559 618 | 2.721874 9.539825 0.012781 619 | 2.716306 9.55447 0.015291 620 | 2.709243 9.577341 0.013357 621 | 5.584053 2.62944 1.429297 622 | 2.687772 9.623559 0.008729 623 | 2.680429 9.626688 0.016448 624 | 2.655737 9.674418 0.015524 625 | 2.636014 9.736539 0.008938 626 | 2.626523 9.763369 0.01004 627 | 2.616946 9.767845 0.011552 628 | 2.612413 9.780125 0.012111 629 | 2.599926 9.828703 0.007827 630 | 2.585286 9.851382 0.007504 631 | 2.571729 9.888333 0.008055 632 | 2.567699 9.895081 0.010194 633 | 2.563246 9.912126 0.007497 634 | 2.545179 9.94113 0.007224 635 | 2.540112 9.954477 0.008767 636 | 2.534699 9.967969 0.008679 637 | 2.53177 9.981528 0.004965 638 | 2.523937 9.987039 0.008793 639 | 2.517727 10.001006 0.004387 640 | 2.515551 10.010093 0.005979 641 | 2.510535 10.026277 0.005688 642 | 2.507728 10.035984 0.005384 643 | 2.501363 10.04376 0.006257 644 | 2.498464 10.049158 0.00859 645 | 2.489497 10.050312 0.010257 646 | 2.493564 10.07219 0.009044 647 | 2.488117 10.074133 0.011957 648 | 2.484353 10.097112 0.00769 649 | 2.479032 10.103448 0.008111 650 | 2.475103 10.114647 0.008029 651 | 2.473592 10.125716 0.006924 652 | 2.472515 10.148801 0.00555 653 | 2.467407 10.140736 0.011731 654 | 2.464051 10.15689 0.00947 655 | 2.462541 10.163393 0.007027 656 | 2.459925 10.159533 0.008858 657 | 2.450281 10.211484 0.005144 658 | 2.442863 10.230973 0.003259 659 | 2.446736 10.22584 0.008246 660 | 2.446529 10.237094 0.006999 661 | 2.442436 10.232081 0.009117 662 | 2.438511 10.245002 0.013054 663 | 2.435481 10.265206 0.008425 664 | 2.436589 10.257708 0.010745 665 | 2.433302 10.271405 0.010455 666 | 2.434964 10.259809 0.013268 667 | 2.432469 10.29176 0.01111 668 | 2.42697 10.312475 0.008913 669 | 2.42139 10.327409 0.013226 670 | 2.424276 10.316235 0.014341 671 | 5.734653 1.40269 1.314207 672 | 2.421472 10.324775 0.012691 673 | 2.424776 10.331364 0.016791 674 | 5.72976 1.384142 1.266459 675 | 2.421734 10.336754 0.017021 676 | 2.420681 10.344206 0.014105 677 | 5.730028 1.411847 1.265479 678 | 2.417869 10.350058 0.015721 679 | 5.725977 1.374113 1.20479 680 | 2.414372 10.367612 0.015356 681 | 5.729969 1.383697 1.282831 682 | 2.41533 10.3557 0.018642 683 | 5.730847 1.332352 1.204686 684 | 2.417722 10.370333 0.016379 685 | 5.730282 1.358 1.254969 686 | 5.733606 1.288375 1.210879 687 | 5.733146 1.329438 1.27025 688 | 5.73356 1.290141 1.222178 689 | 5.734033 1.314891 1.268145 690 | 2.413699 10.393588 0.016435 691 | 2.414832 10.410925 0.010927 692 | 2.408794 10.437812 0.014683 693 | 2.411035 10.441128 0.016583 694 | 2.411189 10.443723 0.019329 695 | 2.409982 10.456257 0.018115 696 | 2.409114 10.463856 0.016329 697 | 2.409579 10.467165 0.017834 698 | 2.412065 10.459031 0.019502 699 | 2.411122 10.46564 0.016704 700 | 2.411055 10.473658 0.01818 701 | 2.410209 10.484327 0.01422 702 | 2.411591 10.477676 0.018356 703 | 2.412078 10.479467 0.019751 704 | 2.410297 10.494782 0.015272 705 | 2.408279 10.499623 0.018846 706 | -------------------------------------------------------------------------------- /data/radar.dat: -------------------------------------------------------------------------------- 1 | # Test data file for triplclust 2 | # (courtesy of Christoph Degen) 3 | # x y time 4 | 53.5982486527077 -21.3878705195847 1220 5 | 53.5625947135637 -23.1442055070057 1220 6 | 58.7203327655044 -5.63405891950282 1220 7 | 50.527295300976 -84.09154086808 1220 8 | 102.585286936224 0.983363723394449 1220 9 | 56.7167816439599 13.7044693786871 1221 10 | 58.7487485937543 5.32960023527553 1221 11 | 55.0416389465183 17.3387210047661 1222 12 | 55.8255620405815 16.9738747448303 1222 13 | 55.0829401949731 17.20706144224 1223 14 | 55.521137777223 17.9446109158884 1223 15 | 60.1415663211756 -3.97825708516174 1223 16 | 60.8792725618935 -2.05659143601185 1223 17 | 54.8301030061851 17.9964737749131 1224 18 | 55.5330268820293 17.9077839589327 1224 19 | 50.527295300976 -84.09154086808 1224 20 | 102.586501662509 -0.84721700170275 1224 21 | 54.6454439129036 18.5496286637148 1225 22 | 55.2971148400832 18.6235037348682 1225 23 | 54.7551929861677 18.2231200689545 1226 24 | 55.4270558637891 18.2331368467523 1226 25 | 102.585085743733 1.00413293494408 1226 26 | 55.2146269995159 16.7796969372015 1227 27 | 55.4909933733878 18.0376122431609 1227 28 | 55.7854133654476 17.1053633883102 1228 29 | 64.0432361726623 -3.13660653136356 1228 30 | 64.7342423288969 -1.86144865727134 1229 31 | 65.4017682162439 0.174121189716739 1230 32 | 66.0013717491887 -2.37252254476025 1231 33 | 66.6841888590453 -0.328909122986226 1231 34 | 66.6835962922738 -0.432678320514586 1232 35 | 67.3100857163275 -1.46377486678873 1232 36 | 67.9595984212784 -1.00303111545508 1233 37 | 68.600329745559 -1.02587660104797 1234 38 | 69.2313295511378 -1.6079516727066 1235 39 | 55.0715159258942 17.2435900445349 1236 40 | 55.2468587742161 18.7720642600066 1236 41 | 81.0705509961678 -16.411489456407 1236 42 | 79.3315561391882 -57.713941266689 1236 43 | 16.6017173595306 1.51829566101615 1237 44 | 17.1584212030528 2.30085328055215 1237 45 | 55.1292881587978 17.0579849661159 1237 46 | 55.4092086092705 18.2873016679429 1237 47 | 55.0467810257466 17.3223890587732 1238 48 | 55.5778586162799 17.7681578287903 1238 49 | 55.1917326873506 16.8548481739834 1239 50 | 55.3852231231165 18.359816464295 1239 51 | 73.0969251688488 0.104593784288175 1240 52 | 73.7345828719436 -0.709882173656904 1240 53 | 74.3774429985329 -0.481262921894346 1241 54 | 74.9814615918132 -2.40433307913985 1242 55 | 75.6319374091107 -2.13267150409202 1243 56 | 76.264655702999 -2.41869789411983 1243 57 | 76.2765697324775 -2.00816305276731 1244 58 | 76.8504655984223 -3.79276591760029 1244 59 | 77.5455767315336 -2.47300513016603 1245 60 | 78.2130789129125 -1.42174644804293 1246 61 | 55.0630825423465 17.2705010041614 1247 62 | 55.2628926509671 18.7248096611872 1247 63 | 78.8659282014343 0.57165805006698 1247 64 | 79.505023719883 -0.795163065563221 1247 65 | 55.1361312308076 17.0358532776952 1248 66 | 55.5277110912371 17.9242601512059 1248 67 | 80.7781175862288 -1.44270586933011 1249 68 | 81.4039238859279 -2.13817678737165 1250 69 | 81.7740683103655 -7.01022310427205 1251 70 | 82.1772978938846 -9.41609982209987 1251 71 | 83.2902130457519 -3.31106429917381 1252 72 | 83.99407520017 -0.700956680677515 1253 73 | 55.4619588036373 18.1266910014937 1254 74 | 84.5960178899985 -2.66548328733226 1254 75 | 85.9171001584171 0.818621627199781 1255 76 | 54.882013032134 17.8375421384414 1256 77 | 55.0188254524333 19.4302508175958 1256 78 | 65.3639688342617 -73.1569982587699 1256 79 | 102.556667426707 2.61496966099206 1256 80 | 87.0598172568118 -4.99514045953985 1257 81 | 87.6203923137801 -6.26379970778617 1257 82 | 87.8325210389894 -1.42006476454007 1258 83 | 88.4845289562229 -0.510226807752878 1258 84 | 89.0899330494233 -2.57020587682687 1259 85 | 89.740304012515 -2.22972189776873 1260 86 | 90.39123347957 -1.79225864256838 1261 87 | 91.0456940374721 -0.885492651026083 1261 88 | 91.668897713906 -2.0581680974699 1262 89 | 92.3105252102003 -2.03711178313286 1263 90 | 92.9151845480061 -3.30653238453254 1263 91 | 92.9648859913125 1.30178670383284 1264 92 | 93.6131949855914 -0.581334318305431 1264 93 | 94.24211501819 -1.61780496298355 1265 94 | 54.8011577108202 20.0359405707297 1266 95 | 94.7121211072865 -5.93670947231661 1266 96 | 94.9988499162777 -10.1449019504646 1266 97 | 65.4379914997572 -73.0907934317155 1266 98 | 96.0449624868166 -5.09485828124754 1267 99 | 55.1328240717535 19.1043845981251 1268 100 | 96.5754562002627 -6.89110301121265 1268 101 | 63.9838460359455 -74.3670778130244 1268 102 | 96.9339621815626 -10.1315556447861 1269 103 | 92.7105492652028 -32.0803502310121 1269 104 | 56.24251103447 12.9225855206117 1270 105 | 55.6670790562667 17.4866266141687 1270 106 | 50.527295300976 -84.09154086808 1270 107 | 96.3601287375002 -21.5708278629354 1270 108 | 56.3175452826744 12.591558971546 1271 109 | 56.3018690920118 15.3200958791375 1271 110 | 99.279243181341 -4.6053088213659 1271 111 | 99.990082911642 -2.82563609174196 1271 112 | 55.8780250560133 16.8003606163543 1272 113 | 100.558152650062 -4.74413697175274 1272 114 | 55.2046600059163 16.8124590001341 1273 115 | 54.93661061814 19.6615008275275 1273 116 | 54.0000740770427 -81.9047423271321 1273 117 | 101.308252682785 -0.595011227579486 1273 118 | 55.8539236663389 16.8803143357813 1274 119 | 59.6648619883488 -77.8748936430175 1274 120 | 56.2006679257175 15.6872791045238 1275 121 | 103.17537005405 -3.35796280650833 1275 122 | 55.1140827029915 17.1070497692564 1276 123 | 54.6503182885345 20.4437890803509 1276 124 | 58.3902749088686 -78.8350849055594 1276 125 | 102.576803434051 -1.64544743215826 1276 126 | 103.706123903781 -5.83238929202141 1276 127 | 104.463781735839 -3.42763846502117 1276 128 | 41.4696590745305 40.1307941641106 1277 129 | 58.735072396562 -78.57853451529 1277 130 | 105.159462030435 -0.336371029405884 1277 131 | 54.1714120157535 19.8914902463209 1278 132 | 53.914459105844 22.3122589695502 1278 133 | 50.527295300976 -84.09154086808 1278 134 | 102.589816732037 -0.193914638712418 1278 135 | 105.764752960948 -2.73075650865447 1278 136 | 56.36115492006 12.3948973403169 1279 137 | 57.4456687813249 10.2274600593848 1279 138 | 51.3978213282622 -83.5623047713982 1279 139 | 55.4081904669356 16.1290326176057 1280 140 | 54.7829511010104 20.0856682653154 1280 141 | 59.1552636689251 -78.2626960707331 1280 142 | 55.6344124823536 15.3305385340286 1281 143 | 54.853076623873 19.893360346998 1281 144 | 51.3890704223652 -83.5676866804699 1281 145 | 55.091942240214 17.1782177248086 1282 146 | 55.3543832268134 18.452589481671 1282 147 | 56.006154648684 -80.5462938779165 1282 148 | 55.2172620669533 16.7710236368984 1283 149 | 55.341487400654 18.4912296314566 1283 150 | 50.527295300976 -84.09154086808 1283 151 | 55.1648933808341 16.9424851270765 1284 152 | 55.2498068015818 18.7633858721683 1284 153 | 57.955976135809 -79.1549091727452 1284 154 | 55.1119922712669 17.1137830970192 1285 155 | 55.3204741580847 18.5540006447311 1285 156 | 53.6307149405708 -82.1470707387868 1285 157 | 55.4510630866269 15.9810158176171 1286 158 | 55.4060160291021 18.2969721206238 1286 159 | 62.1959683073362 -0.0627878397942635 1286 160 | 57.5656888487535 -79.4391986576432 1286 161 | 102.547163369704 2.96435234533918 1286 162 | 55.204953430273 16.8114954944939 1287 163 | 55.1464807658035 19.0649274886337 1287 164 | 64.7608292015722 0.148735082594775 1287 165 | 53.6149428468804 -82.1573655829211 1287 166 | 102.587317488453 0.741883092785868 1287 167 | 63.8643521696927 -74.4697209471438 1288 168 | 102.562361558502 2.38119540212149 1288 169 | 67.1630498625346 -71.5088774150643 1289 170 | 102.569882329787 2.03158530551937 1289 171 | 54.9424032128929 17.6506541859473 1290 172 | 55.4755471121706 18.0850621675829 1290 173 | 50.527295300976 -84.09154086808 1290 174 | 55.5766180294165 17.7720378519835 1291 175 | 66.0433516938167 0.29263124719868 1291 176 | 55.1987879874771 16.8317280370601 1292 177 | 55.3659664034447 18.4178056568265 1292 178 | 72.0783183686571 -66.5515652494103 1292 179 | 55.0485947202558 17.316624478374 1293 180 | 55.4076125301628 18.2921369639346 1293 181 | 54.9112035998558 17.7474782212906 1294 182 | 55.2146698120706 18.8665322341977 1294 183 | 72.1850874457554 -66.4357431391314 1294 184 | 55.1179627507813 17.0945443403293 1295 185 | 55.3743184377323 18.3926794827893 1295 186 | 56.1452449795324 -80.4494020001907 1295 187 | 102.589996637286 -0.0262671229250436 1295 188 | 54.9257434640107 17.7024283341493 1296 189 | 55.5217641284844 17.9426728516389 1296 190 | 58.318702417601 -78.8880457631402 1296 191 | 70.4808638369299 -2.68530385620395 1297 192 | 71.1669496610539 -0.928011282810326 1297 193 | 71.8083787456606 -0.898519849399396 1298 194 | 72.4396719017485 -1.53813873464866 1298 195 | 55.662499160496 17.5011996219625 1299 196 | 72.3913482097232 3.0601699917824 1299 197 | 73.0857060842629 -1.2849027841598 1299 198 | 55.9040543553357 16.7135426416924 1300 199 | 73.0555248618379 2.46205121014812 1300 200 | 73.7289962788979 -1.1522810882161 1300 201 | 51.5508708567938 -83.4679730789371 1300 202 | 55.5271274583671 15.7146867681877 1301 203 | 55.4206879216902 18.2524834279234 1301 204 | 61.7990982895274 40.2264104115857 1301 205 | 73.9554167451431 7.92666228952663 1301 206 | 55.6386222110171 17.5769599037977 1302 207 | 73.6921734839188 -2.59927128557387 1302 208 | 74.3704698627703 -1.12643392650247 1302 209 | 74.5751037477335 8.15808194459895 1302 210 | 55.5725842514205 17.7846473290525 1303 211 | 75.0006638071234 -1.70318187251228 1303 212 | 75.5572685847651 -3.97962410406649 1303 213 | 75.6546968705912 1.05123043094922 1304 214 | 76.2995701229059 -0.723469460148701 1304 215 | 76.9433572596162 0.314498996935732 1305 216 | 77.5849353747418 0.100139388550149 1306 217 | 76.8505450170707 -3.7911563643809 1307 218 | 77.4803805687501 -4.02776016188197 1307 219 | 78.8338523452369 2.32059225401367 1307 220 | 59.118376675447 -78.2905636412203 1307 221 | 78.8344812540022 -2.29912796781104 1308 222 | 79.4399989314835 -3.31174436904375 1308 223 | 78.6392274618487 -6.00277670779314 1309 224 | 80.134207879402 -1.59098319940326 1309 225 | 54.571979343739 -81.5248053447938 1309 226 | 55.2707312324639 18.7016595527976 1310 227 | 80.7880931063498 -0.685341695619562 1310 228 | 81.0391421914747 -12.9922634236665 1310 229 | 62.5259269359983 -75.5969792835282 1310 230 | 54.6392896186127 18.5677487588926 1311 231 | 54.9328348685384 19.6720475372009 1311 232 | 80.742284975355 -2.80519125169158 1311 233 | 81.3460629979549 -3.74014153913388 1311 234 | 70.7304874776502 -67.9822988517891 1311 235 | 78.9307007849394 -59.3339658087875 1311 236 | 55.3381441520857 16.367744676741 1312 237 | 55.3440687239087 18.4835023218889 1312 238 | 68.5847005720402 -70.1465156899727 1312 239 | 17.2648397636011 1.27696982626052 1313 240 | 55.2690978241885 16.5994002813438 1313 241 | 55.5948892141487 17.7147987362693 1313 242 | 55.2734416429283 16.5849302966821 1314 243 | 55.534276945865 17.9039069786449 1314 244 | 61.7614661235255 -76.2228057622692 1314 245 | 55.0401256480891 17.3435242278656 1315 246 | 55.1826502394396 18.9599818974511 1315 247 | 57.7014791830248 -79.3406208451317 1315 248 | 102.578080509842 -1.56380910485787 1315 249 | 55.2084732108499 16.7999330274518 1316 250 | 55.3453589809288 18.4796385319662 1316 251 | 55.1412115337761 -81.1408750666848 1316 252 | 55.3749604296267 18.3907465432287 1317 253 | 85.2467838495604 2.37996708093406 1317 254 | 85.8242629340098 -4.0760437723298 1317 255 | 55.355671325685 18.4487249717354 1318 256 | 85.2618934508056 1.75724932308369 1318 257 | 85.9161563825059 -0.912312148768938 1318 258 | 86.5410530784932 -1.90419958654911 1318 259 | 87.1295617867214 -3.57808214744945 1318 260 | 55.9864987188997 16.4352597241006 1319 261 | 85.7896942449314 -4.74832626945378 1319 262 | 86.5243563996959 -2.55256608502128 1319 263 | 87.1052594885282 4.12758748376863 1319 264 | 87.7277334191561 -4.51808865976372 1319 265 | 55.3149666403008 18.5704137428977 1320 266 | 87.193206200881 -1.30690527990552 1320 267 | 88.4846659538157 -0.485887890008245 1320 268 | 54.9887297223094 17.5057952554805 1321 269 | 55.6370881200488 17.5818152226077 1321 270 | 87.2020360587884 -0.410019757784278 1321 271 | 87.8260221077325 -1.77712597529798 1321 272 | 88.9714927284289 5.26266193813844 1321 273 | 89.7676970689972 -0.23321005287168 1321 274 | 55.7084041709177 15.0594478228677 1322 275 | 55.8053777286651 17.0401178857722 1322 276 | 87.7174210026393 -4.71406285975537 1322 277 | 88.4298532673733 3.15170542894215 1322 278 | 89.119777765447 1.1346100816315 1322 279 | 55.7126681174308 17.3408307827798 1323 280 | 88.4823117908718 0.807896121389359 1323 281 | 89.1250590483319 -0.58819948261515 1323 282 | 89.766378297199 -0.539584473603945 1323 283 | 89.6906949889755 -3.72465520478988 1324 284 | 90.3416686841233 -3.48857855982929 1324 285 | 90.37058555085 -2.63525106896951 1325 286 | 90.9880967631681 -3.35689550274718 1325 287 | 91.0042550153611 -2.88583940979382 1326 288 | 91.6557664553684 -2.57746764848194 1326 289 | 91.90759276271 -8.85309446260826 1326 290 | 92.7447724412707 -6.52471155047663 1327 291 | 93.3783072571134 -6.65281585466821 1328 292 | 55.2997146327193 18.6157826195894 1329 293 | 92.5592527071233 -8.77208152600594 1329 294 | 94.2545591114131 0.521174359629561 1329 295 | 55.401543581445 18.3105098179503 1330 296 | 93.3285275207699 -7.31807186387915 1330 297 | 93.7016687912546 -10.2073895651152 1330 298 | 94.874130823297 -2.12831025086297 1330 299 | 95.5334353202362 -1.0311434983805 1330 300 | 55.1984942099968 16.8326914350899 1331 301 | 55.2024748251535 18.9021843758412 1331 302 | 96.1447123813183 2.60512593046545 1331 303 | 95.3998704400077 -5.15414794410692 1332 304 | 95.9909057683736 -6.02813484978753 1333 305 | 55.2806729220544 16.5608111300396 1334 306 | 55.3893874076714 18.3472494833121 1334 307 | 95.3477545932154 -20.1902733520867 1334 308 | 90.8484192848468 -37.026470658769 1334 309 | 93.4635452977052 -29.8154409054069 1335 310 | 55.0676020619026 17.2560849311754 1336 311 | 55.5123617565743 17.9717415184846 1336 312 | 55.4090349051559 16.126131429429 1337 313 | 55.3967480026581 18.3250132804861 1337 314 | 51.9639645113713 -83.2114247460103 1337 315 | 99.3332009957795 3.23916284432842 1337 316 | 55.1107973695132 17.1176305982007 1338 317 | 55.4391363469171 18.1963722238234 1338 318 | 102.58974451411 -0.228955300404678 1338 319 | 55.4916055307411 15.8396647572043 1339 320 | 55.5725842514205 17.7846473290525 1339 321 | 100.209997918492 -9.61276324350972 1339 322 | 55.2855841802583 16.5444082954809 1340 323 | 55.1657517611677 19.009093445644 1340 324 | 101.009607407776 -7.79585218735039 1340 325 | 102.496506111196 4.37885087637949 1340 326 | 54.926052421305 17.7014696963855 1341 327 | 55.3003644124278 18.6138522840569 1341 328 | 59.6838886331957 -77.8603124423495 1341 329 | 101.763518117934 -6.16351200703993 1341 330 | 55.5402730764906 15.6681629615235 1342 331 | 55.6774460275408 17.453590027564 1342 332 | 62.8919872055867 -75.2927138661658 1342 333 | 102.557722354636 -2.57326357558418 1342 334 | 55.2782341063272 18.6794709530565 1343 335 | 103.701284019312 -5.91781993185911 1343 336 | 55.2419423660613 18.7865272103378 1344 337 | 104.298226451698 -6.80517149161248 1344 338 | 54.5413205758174 18.8535835810043 1345 339 | 55.1514700622514 19.0504895205501 1345 340 | 105.137816034934 -2.15991652718241 1345 341 | 54.9170858671897 17.7292680011249 1346 342 | 55.170726439637 18.9946504343391 1346 343 | 55.6189165781291 -80.8141753325189 1346 344 | 55.1272037213562 17.0647201519418 1347 345 | 55.325976805762 18.5375859131734 1347 346 | 62.4770955029286 -75.6373409997862 1347 347 | 102.495602118201 4.39995982110643 1347 348 | 55.4312171016163 16.0497175873434 1348 349 | 55.480910616177 18.0686014455956 1348 350 | 57.0709955343817 -79.7953399937276 1348 351 | 54.291227929077 19.5621019819703 1349 352 | 54.6937253600326 20.327375808041 1349 353 | 57.7845328333254 -79.2801525051153 1349 354 | 102.589576197095 0.294882519763415 1349 355 | 55.2055402285372 16.8095684678506 1350 356 | 55.2314413746914 18.8173771996532 1350 357 | 102.521591127765 3.74585806343934 1350 358 | 54.6735473377614 18.4666316773137 1351 359 | 54.9475831030397 19.6308153711085 1351 360 | 16.4908905073876 -9.90324321994338 1352 361 | 55.1755277683823 16.9078206602884 1352 362 | 55.3101030217933 18.5848945307368 1352 363 | 55.4943692285659 15.8299794037642 1353 364 | 55.5189450150629 17.9513939685593 1353 365 | 102.419051073478 5.91997273712172 1353 366 | 16.0066355279388 -10.6682386116794 1354 367 | 55.0994324070851 17.154177695741 1354 368 | 55.0577136157122 19.3197818934433 1354 369 | 55.0331590767827 17.3656173523871 1355 370 | 55.3720709347657 18.3994445947501 1355 371 | 102.484382632279 4.65396782117811 1355 372 | 54.9783318930928 17.5384231404349 1356 373 | 55.1054259964115 19.1832694490281 1356 374 | 54.6412337291189 18.5620268440653 1357 375 | 55.100401939838 19.1976953582533 1357 376 | 54.8620540719719 17.8988347945902 1358 377 | 55.2425981059155 18.7845988913338 1358 378 | 55.026790549262 17.3857868918162 1359 379 | 55.0620957179421 19.3072891973002 1359 380 | 55.3598064615338 16.2943270048665 1360 381 | 55.5023157851817 18.0027426655487 1360 382 | 54.5702132597396 18.76979192177 1361 383 | 55.2661599050525 18.7151641817316 1361 384 | 54.907795316681 17.7580200884409 1362 385 | 55.032376917628 19.3918356994928 1362 386 | 102.483886566383 4.66487880322529 1362 387 | 54.5518418962784 18.8231190221876 1363 388 | 54.9933441842237 19.5022536245304 1363 389 | 54.7815339607519 18.1437813287912 1364 390 | 55.0428608549103 19.3620574812437 1364 391 | 55.0485947202558 17.316624478374 1365 392 | 55.2294705207224 18.8231609194805 1365 393 | 54.7564650665123 18.2192973909483 1366 394 | 55.0371135940599 19.378388174318 1366 395 | 54.6606418742163 18.5047965105987 1367 396 | 55.0910135968727 19.2246202009088 1367 397 | 54.1630751355989 19.9141797685837 1368 398 | 54.4920754552372 20.8619153861945 1368 399 | 55.1069120838021 17.1301344007602 1369 400 | 55.2837739836531 18.6630687488515 1369 401 | 54.6172144948432 18.6325828811868 1370 402 | 54.9851702750113 19.5252874710724 1370 403 | 102.367668802096 6.75044324651412 1370 404 | 55.1373204233656 17.0320040080758 1371 405 | 56.0170939581451 16.3306762102597 1371 406 | 60.775923955987 -77.0109205456997 1371 407 | 102.584892145566 -1.02372041227665 1371 408 | 55.0856423942942 17.1984088219792 1372 409 | 54.8269892888832 19.9651457925226 1372 410 | 59.4062914772355 -78.07232127279 1372 411 | 102.338845344556 7.17417824862763 1372 412 | 55.1134855207266 17.1089736032486 1373 413 | 55.3233879311897 18.5453106961056 1373 414 | 54.6074510108966 18.661177832402 1374 415 | 55.3682161496838 18.4110412905379 1374 416 | 54.8382639013089 17.9715908113444 1375 417 | 55.2140112122202 18.8684595782704 1375 418 | 55.0383091366911 17.3492879212382 1376 419 | 55.4296011496589 18.2253975920892 1376 420 | 54.9620849025857 17.5892719337943 1377 421 | 55.2011550692168 18.9060381895384 1377 422 | 102.473021410271 4.89775285720194 1377 423 | 54.9217254966667 17.714890134257 1378 424 | 55.0715244640518 19.2803784767142 1378 425 | 54.3601959130031 19.3696247846964 1379 426 | 55.0225547788127 19.4196876033717 1379 427 | 54.3490307942073 19.4009307954619 1380 428 | 54.8683374519071 19.8512303463451 1380 429 | 60.891447544646 -76.9196101908845 1380 430 | 55.1310741645229 17.0522117470397 1381 431 | 55.4021827050604 18.3085759281029 1381 432 | 54.7210658061986 18.3253436811888 1382 433 | 55.2628926509671 18.7248096611872 1382 434 | 56.8018828171773 -79.9871297423758 1382 435 | 54.7605974844854 18.2068730742589 1383 436 | 55.1104462761187 19.1688422250023 1383 437 | 55.2479038348012 16.6698046137487 1384 438 | 55.4783872226791 18.0763478659402 1384 439 | 58.5538847364284 -78.7136417546098 1384 440 | 54.657734473241 18.5133823558173 1385 441 | 55.1198073585034 19.141907892409 1385 442 | 59.7382308365291 -77.8186262569673 1385 443 | 54.8388911947624 17.9696765838736 1386 444 | 55.1720523819496 18.990798744745 1386 445 | 60.9894006287792 -76.8419665738863 1386 446 | 102.539045656415 3.23298869030288 1386 447 | 54.618515161421 18.6287698348986 1387 448 | 55.1010720322543 19.1957719796917 1387 449 | 54.5228673295106 18.9068824021462 1388 450 | 54.8690303571394 19.8493150679642 1388 451 | 102.508091468723 4.09869289407054 1388 452 | 54.4671951536729 19.0666702937799 1389 453 | 54.9984479776328 19.4878557325227 1389 454 | 54.7726606953978 18.1705504690096 1390 455 | 55.2215810484734 18.8462937339648 1390 456 | 102.558632057224 2.53674802670984 1390 457 | 54.6032155183865 18.6735673895652 1391 458 | 54.7706712439418 20.1191295161606 1391 459 | 102.580358413141 1.40647354453988 1391 460 | 54.2638611691328 19.637887743265 1392 461 | 54.700817620479 20.3082828582599 1392 462 | 59.2221731727575 -78.2120772048405 1392 463 | 102.546331567957 2.99298879961395 1392 464 | 54.3303822000131 19.4530931730792 1393 465 | 54.7994090364966 20.0407227976122 1393 466 | 54.8907235458662 17.8107195983566 1394 467 | 55.1938916025806 18.9272325172643 1394 468 | 54.3205291459252 19.4805897576713 1395 469 | 54.9211515692572 19.7046418720736 1395 470 | 102.585159074903 0.99661305271238 1395 471 | 54.8682986014463 17.8796832349605 1396 472 | 55.286053695045 18.6563144224308 1396 473 | 55.1987879874771 16.8317280370601 1397 474 | 55.4742844014601 18.0889350473134 1397 475 | 54.8017665789949 18.0825784616397 1398 476 | 55.2340682378099 18.8096652522302 1398 477 | 54.8482925777931 17.9409605456564 1399 478 | 54.9646932965317 19.5828570902766 1399 479 | 54.0086508254981 -81.8990870035156 1399 480 | 102.588438237323 0.566074223820808 1399 481 | 53.4377476369491 21.7857841605425 1400 482 | 54.828382985391 19.9613180979436 1400 483 | 102.301451031551 -7.68903224334279 1400 484 | 54.2604328846718 19.6473582745373 1401 485 | 55.3617861108049 18.430367321936 1401 486 | 54.7869149176323 18.1275265220635 1402 487 | 54.8077988670442 20.0177667173346 1402 488 | 102.422944608486 5.8522233148658 1402 489 | 54.427477340254 19.1797542835182 1403 490 | 55.2307844906651 18.8193051291939 1403 491 | 54.7137051330211 18.3473086477779 1404 492 | 54.8607110820798 19.8722968116059 1404 493 | 56.4285250637251 -80.2509587178405 1404 494 | 54.862991177566 17.8959621996223 1405 495 | 55.1398224910964 19.084176079946 1405 496 | 66.3601146786722 -72.2546192006675 1405 497 | 102.589368595226 -0.359932261449461 1405 498 | 54.4478662317125 19.1217971648468 1406 499 | 54.8928946407926 19.7832231690098 1406 500 | 54.1946354965757 19.8281302041644 1407 501 | 54.7262945646274 20.2395277668632 1407 502 | 102.576402394749 -1.67026098274451 1407 503 | 54.6441487713562 18.5534435901774 1408 504 | 55.0035480015077 19.4734565048384 1408 505 | 53.9790431293903 20.4077477158901 1409 506 | 54.7015264799558 20.3063734271458 1409 507 | 54.3676293075293 19.348750540515 1410 508 | 54.7920600473434 20.060806483497 1410 509 | 102.584949871571 1.01791937165933 1410 510 | 54.7586905649405 18.2126074907768 1411 511 | 54.9667437071494 19.5771010936899 1411 512 | 54.5561113222996 18.81074111748 1412 513 | 54.7231146718947 20.2481239033808 1412 514 | 55.7429725908994 -80.7286555241089 1412 515 | 102.530328064139 3.49856071263658 1412 516 | 54.2785839553944 19.5971576458731 1413 517 | 54.8496037420571 19.9029337118756 1413 518 | 51.3978213282622 -83.5623047713982 1413 519 | 54.7774158573027 18.1562103974403 1414 520 | 55.067148467428 19.2928733128646 1414 521 | 57.7070180626626 -79.3365923286067 1414 522 | 54.5891857873468 18.7145413802887 1415 523 | 54.7843532116813 20.0818436448883 1415 524 | 54.992700245806 17.4933182579786 1416 525 | 55.3707862762735 18.4033102497145 1416 526 | 102.584597676215 1.05281508783815 1416 527 | 54.6867477830198 18.4275033826174 1417 528 | 55.1414876896755 19.0793641500278 1417 529 | 50.527295300976 -84.09154086808 1417 530 | 102.550161524843 2.85875343961794 1417 531 | 54.4087053542238 19.2329416802852 1418 532 | 54.896346628518 19.7736422502684 1418 533 | 54.2043252403946 -81.7697128601631 1418 534 | 102.589365513293 0.360809617343457 1418 535 | 54.6557954566832 18.5191059988653 1419 536 | 55.0188254524333 19.4302508175958 1419 537 | 53.8355720619833 -82.0129623703412 1419 538 | 102.374832423083 6.64091758311489 1419 539 | 54.1901352415996 19.8404260664218 1420 540 | 54.9486108948075 19.6279382954769 1420 541 | 53.9060399305523 20.5998088099308 1421 542 | 54.6310262021468 20.4952867045168 1421 543 | 51.4255288113526 -83.5452560141672 1421 544 | 53.8388825877093 20.7746958029919 1422 545 | 54.6131198907588 20.5429534584881 1422 546 | 102.571650046467 1.94028522259112 1422 547 | 54.3439497799485 19.4151586734294 1423 548 | 54.5746606668626 20.6449077037608 1423 549 | 54.1877108718587 19.8470464923082 1424 550 | 54.8756096222724 19.8311187174089 1424 551 | 52.6650217835052 -82.7695010045544 1424 552 | 54.3405603678235 19.4246431861934 1425 553 | 54.9108267148982 19.7333958174061 1425 554 | 102.587950701121 -0.648437309534427 1425 555 | 54.8046063076762 18.0739699972258 1426 556 | 54.9619584780928 19.5905314183257 1426 557 | 57.1322571219224 -79.7514890905151 1426 558 | 54.7536025104514 18.2278982915608 1427 559 | 54.626374562931 20.5076815635109 1427 560 | 57.3047220217101 -79.6276563137113 1427 561 | 102.490843869445 4.50943709669127 1427 562 | 54.968528015614 17.5691260965551 1428 563 | 54.5833035975016 20.6220457128523 1428 564 | 102.530675606766 3.48836059203589 1428 565 | 55.0531263154056 17.3022121967103 1429 566 | 55.0684952298775 19.2890288536504 1429 567 | 55.3186795529229 16.4334097655061 1430 568 | 55.4768095525001 18.0811891997081 1430 569 | 56.9469771961173 -79.883894523395 1430 570 | 102.509068577232 4.07418205636241 1430 571 | 54.8224579297796 19.9775851277761 1431 572 | 58.8378811078661 -78.5015831861792 1431 573 | 54.463533611986 19.0771268931166 1432 574 | 55.0482656151103 19.3466858600707 1432 575 | 60.5485079979996 -77.1898503380864 1432 576 | 55.0273973941863 17.3838660838807 1433 577 | 54.5037184473995 20.8314780178125 1433 578 | 50.527295300976 -84.09154086808 1433 579 | 102.589832164255 -0.185570767902829 1433 580 | 54.5787251022533 18.7450268128019 1434 581 | 55.5289623053409 17.9203835419901 1434 582 | 50.527295300976 -84.09154086808 1434 583 | 55.5076548633222 17.9862740047599 1435 584 | 55.9400633054906 -80.5922088875699 1435 585 | 47.0452286081369 33.4209474612575 1436 586 | 52.3226160293685 25.8253683970484 1436 587 | 60.7463494057524 -77.0342511476178 1436 588 | 56.744648381884 13.5886232200451 1437 589 | 50.527295300976 -84.09154086808 1437 590 | 56.1571927815692 13.2884522384542 1438 591 | 50.6706562949072 28.9325144369337 1438 592 | 57.2018316621393 -79.7016014299354 1438 593 | 55.7683687028694 17.1608523454058 1439 594 | 57.6266767935387 -79.3949679622898 1439 595 | 54.8153219673642 18.0414451088099 1440 596 | 55.1278206662465 19.1188176828529 1440 597 | 50.527295300976 -84.09154086808 1440 598 | 54.6315071849327 18.5906343813933 1441 599 | 55.2215810484734 18.8462937339648 1441 600 | 54.1800612926241 -81.7857920077045 1441 601 | 54.8341848651883 17.9840327560408 1442 602 | 55.2353812655662 18.8058091409952 1442 603 | 50.527295300976 -84.09154086808 1442 604 | 54.9241984265707 17.7072214420751 1443 605 | 55.3714286392538 18.4013774334431 1443 606 | 58.4755484776527 -78.7718544039538 1443 607 | 102.531766447863 3.45614945869666 1443 608 | 55.4546876367456 15.9684339592513 1444 609 | 55.7262703894928 17.2970688117131 1444 610 | 61.1006516580374 -76.7535353124739 1444 611 | 55.003985470485 17.4578018766017 1445 612 | 55.5295878109026 17.9184452046279 1445 613 | 50.527295300976 -84.09154086808 1445 614 | 54.8087057506707 18.0615347613761 1446 615 | 55.1968640043725 18.9185624475754 1446 616 | 53.2084921104454 -82.4211816424192 1446 617 | 54.6315071849327 18.5906343813933 1447 618 | 55.1527999029145 19.0466391751687 1447 619 | 59.4921005488853 -78.0069534610942 1447 620 | 55.0880432082053 17.1907172477154 1448 621 | 55.2648532054332 18.7190224419419 1448 622 | 55.0715159258942 17.2435900445349 1449 623 | 55.1134566309041 19.160185259894 1449 624 | 66.0398948955223 -0.73635466207617 1449 625 | 61.5150319021548 -76.4218271574089 1449 626 | 55.4000197329533 16.1570751557447 1450 627 | 55.5666835835647 17.8030749176652 1450 628 | 58.7144985717475 -78.5939086282662 1450 629 | 55.1800023886593 18.967686664102 1451 630 | 67.3259939159777 -0.0286221196838303 1451 631 | 55.2690978241885 16.5994002813438 1452 632 | 55.1790091672689 18.970575856269 1452 633 | 67.9314047730344 -2.19939413524573 1452 634 | 63.9565847812584 -74.3905241218108 1452 635 | 54.92821465388 17.6947590810748 1453 636 | 54.8186214984529 19.9881099158815 1453 637 | 68.5806489507826 -1.93707343420914 1453 638 | 69.2488378492098 0.401193885607167 1453 639 | 59.0500327680718 -78.3421243399083 1453 640 | 54.684817759528 18.433230064403 1454 641 | 54.7843532116813 20.0818436448883 1454 642 | 69.2406305117076 1.13911647393115 1454 643 | 69.8889698404761 -0.53270595737574 1454 644 | 55.2475140092152 18.7701357693964 1455 645 | 70.4014786538151 -4.28891913614866 1455 646 | 55.1855513558562 16.8750758679824 1456 647 | 55.0313614880484 19.3947171614268 1456 648 | 51.0648915426557 -83.7661725742368 1456 649 | 102.578295154356 -1.54966552060011 1456 650 | 54.8039753737167 18.0758830278648 1457 651 | 54.9766455272373 19.5492774181682 1457 652 | 50.527295300976 -84.09154086808 1457 653 | 102.363918321055 6.80707910636724 1457 654 | 72.4063881847554 -2.68083681683718 1458 655 | 73.0250992682694 -3.24134013324825 1458 656 | 73.7338646002944 -0.78093220285724 1459 657 | 74.3728524330181 -0.956275052748124 1460 658 | 54.8645252781112 19.861763945015 1461 659 | 75.019499506695 0.274032415953399 1461 660 | 75.6616176924672 -0.240524757424426 1462 661 | 76.279196302304 -1.90578605162638 1462 662 | 76.2981510678023 0.860204995807217 1463 663 | 76.9404624295255 -0.73781917213824 1463 664 | 77.5822477202848 -0.653501085209353 1464 665 | 78.2233844273384 -0.639691122983718 1465 666 | 54.5403551117396 20.7353674981983 1466 667 | 78.8679569228333 -0.0824306867322383 1466 668 | 79.4598263360904 2.79590408246603 1466 669 | 54.9756218541049 19.5521559617976 1467 670 | 80.0368602853116 4.25716991315902 1467 671 | 53.1840349118328 -82.4369652916515 1467 672 | 80.7141417692006 -3.52320868830998 1468 673 | 81.4318771499389 -0.141449062415484 1469 674 | 82.0720464425386 0.566276199199014 1470 675 | 82.7148646794657 -0.149619710177234 1470 676 | 83.3529656651014 -0.711231911842308 1471 677 | 54.7210658061986 18.3253436811888 1472 678 | 54.7022352727805 20.3044639712889 1472 679 | 83.9953078074592 0.533174765136891 1472 680 | 59.5179653627002 -77.9872208447283 1472 681 | 54.8219308203712 18.0213530326232 1473 682 | 54.8704159670346 19.8454844386473 1473 683 | 53.7883270598536 -82.0439558291909 1473 684 | 55.2628926509671 18.7248096611872 1474 685 | 85.8310474159192 3.93059034830929 1474 686 | 86.312986456076 -6.56111370372795 1475 687 | 87.1807958177182 -1.96775242073864 1475 688 | 54.968528015614 17.5691260965551 1476 689 | 55.3524505726508 18.458386077939 1476 690 | 56.67756688843 -80.0752660164644 1476 691 | 55.1385093471909 17.0281546554445 1477 692 | 55.4302373022801 18.2234627228998 1477 693 | 57.560142806187 -79.4432173072777 1477 694 | 54.7982939632096 18.0930993122146 1478 695 | 54.9698181928604 19.5684666962915 1478 696 | 62.641961767357 -75.5008572265043 1478 697 | 54.584938187502 18.7269267918305 1479 698 | 54.9653768337115 19.5809384486057 1479 699 | 50.527295300976 -84.09154086808 1479 700 | 102.505670243499 4.15880607037674 1479 701 | 54.7992412405576 18.0902300554519 1480 702 | 54.9173675374546 19.7151856130276 1480 703 | 58.7981403511788 -78.5313536572691 1480 704 | 102.502016798344 4.24789974834204 1480 705 | 55.1067651069746 19.1794223178053 1481 706 | 91.5786682490848 -4.55745386417366 1481 707 | 92.2473336220858 -3.97647188039717 1481 708 | 50.527295300976 -84.09154086808 1481 709 | 92.9739181343209 -0.123380519494065 1482 710 | 93.6110072332719 -0.864609606877831 1483 711 | 94.2453649563864 -1.41588143484568 1483 712 | 94.8860076437735 -1.50862766306313 1484 713 | 95.5376190593729 0.513678368385814 1485 714 | 96.1654104395411 -1.67518207804155 1485 715 | 96.7702279065516 -3.13512869178738 1486 716 | 54.8620540719719 17.8988347945902 1487 717 | 55.0758976255746 19.2678826480033 1487 718 | 97.083842646924 -8.57723387271846 1487 719 | 55.1826502394396 18.9599818974511 1488 720 | 86.392957557439 -46.4828108065611 1488 721 | 99.3453538651038 -2.8421226956807 1489 722 | 55.0079649727948 19.4609760945276 1490 723 | 100.000399651496 -2.43330424343444 1490 724 | 100.348990611244 -8.03299342116696 1490 725 | 101.227101880015 -4.09755353511772 1491 726 | 55.0313614880484 19.3947171614268 1492 727 | 101.929425775726 -2.04808716385713 1492 728 | 102.50872689963 -4.08276982170244 1492 729 | 103.187797230523 -2.95150854861791 1493 730 | 54.6687109113158 18.4809445455306 1494 731 | 54.8405664301882 19.9278216224482 1494 732 | 61.7574750220891 -76.2260394805873 1494 733 | 103.811021553027 -3.49981486895807 1494 734 | 104.3601096334 -5.77909312130113 1494 735 | 105.101268317282 -3.51411412715228 1495 736 | 105.723005794778 -4.03559731854223 1496 737 | 54.5156035887217 18.9278162860209 1497 738 | 54.9424418846282 19.6451999418242 1497 739 | 61.8226418027244 -76.173195919116 1497 740 | 54.92481649174 17.7053042150845 1498 741 | 55.059062256758 19.3159380928405 1498 742 | 58.5923444849923 -78.6850175303532 1498 743 | 102.370510853559 6.70720564626301 1498 744 | 54.3666161347946 19.3515971964046 1499 745 | 54.9115155069037 19.7314790559419 1499 746 | 61.6589742841208 -76.305738357152 1499 747 | 102.582600482546 -1.23214375720423 1499 748 | 54.3716805079211 19.3373633865776 1500 749 | 55.0465770886662 19.3514896538104 1500 750 | 63.5153239934065 -74.7676295866908 1500 751 | --------------------------------------------------------------------------------