├── src ├── stdafx.cpp ├── FillHole.cpp ├── Smooth.h ├── Simplify.h ├── ChainDecomposition.h ├── MyScrollArea.h ├── chooseRoot.h ├── ContractLoops.h ├── MyScrollArea.cpp ├── RemoveShortBranches.h ├── ContractLoops2.h ├── SplitEmUp.h ├── ContractDeg2.h ├── IsLoopContractible.h ├── FindRoots.h ├── bilinearInterpCoeff.h ├── ScanConvert.h ├── intersections.h ├── traceAuto.h ├── l2_regularizer.h ├── chopFakeEnds.h ├── findSingularities.h ├── greedyTrace.h ├── polynomial_energy.h ├── imagequiverviewer.ui ├── Smooth.cpp ├── Optimizer.h ├── ILSLinIneqSolver.h ├── TotalEnergy.h ├── chooseRoot.cpp ├── stdafx.h ├── Params.h ├── FindRoots.cpp ├── mainwindow.ui ├── AlmostReebGraph.h ├── intersections.cpp ├── bilinearInterpCoeff.cpp ├── CircularSegment.h ├── graph_typedefs.h ├── mainwindow.hpp ├── ChainDecomposition.cpp ├── Equations.h ├── imagequiverviewer.hpp ├── ScanConvert.cpp ├── Simplify.cpp ├── simple_svg_1.0.0.cpp ├── CMakeLists.txt ├── FillHole.h ├── ContractLoops2.cpp ├── TopoGraphEmbedding.h ├── TotalEnergy.cpp ├── typedefs.h ├── findSingularities.cpp ├── ContractDeg2.cpp ├── typedefs.cpp ├── CircularSegment.cpp ├── HermiteSpline.h ├── traceAuto.cpp ├── LBFGS │ ├── LineSearch.h │ └── Param.h ├── polynomial_energy.cpp ├── ContractLoops.cpp ├── l2_regularizer.cpp ├── LBFGS.h ├── IsLoopContractible.cpp ├── greedyTrace.cpp ├── RemoveShortBranches.cpp ├── graph_typedefs.cpp ├── mainwindow.cpp ├── Optimizer.cpp ├── imagequiverviewer.cpp ├── SplitEmUp.cpp ├── chopFakeEnds.cpp └── AlmostReebGraph.cpp ├── sample_inputs ├── hippo.png ├── kitten.png ├── puppy.png ├── elephant.png ├── penguin.png ├── banana_tree.png └── Copyrights.txt ├── Makefile ├── .gitignore ├── LICENSE ├── Dockerfile ├── CMakeLists.txt ├── cmake └── FindGUROBI.cmake └── README.md /src/stdafx.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" -------------------------------------------------------------------------------- /src/FillHole.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" -------------------------------------------------------------------------------- /sample_inputs/hippo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpix/PolyVectorization/HEAD/sample_inputs/hippo.png -------------------------------------------------------------------------------- /sample_inputs/kitten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpix/PolyVectorization/HEAD/sample_inputs/kitten.png -------------------------------------------------------------------------------- /sample_inputs/puppy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpix/PolyVectorization/HEAD/sample_inputs/puppy.png -------------------------------------------------------------------------------- /sample_inputs/elephant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpix/PolyVectorization/HEAD/sample_inputs/elephant.png -------------------------------------------------------------------------------- /sample_inputs/penguin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpix/PolyVectorization/HEAD/sample_inputs/penguin.png -------------------------------------------------------------------------------- /src/Smooth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "typedefs.h" 3 | 4 | void smooth(std::vector& curves); 5 | -------------------------------------------------------------------------------- /sample_inputs/banana_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpix/PolyVectorization/HEAD/sample_inputs/banana_tree.png -------------------------------------------------------------------------------- /src/Simplify.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "typedefs.h" 4 | 5 | MyPolyline simplify(const MyPolyline& poly, double eps); 6 | -------------------------------------------------------------------------------- /src/ChainDecomposition.h: -------------------------------------------------------------------------------- 1 | #include "graph_typedefs.h" 2 | 3 | std::vector> chainDecomposition(const G& g, std::map& myChain); 4 | -------------------------------------------------------------------------------- /src/MyScrollArea.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class MyScrollArea : public QScrollArea 6 | { 7 | protected: 8 | void wheelEvent(QWheelEvent * event); 9 | }; 10 | -------------------------------------------------------------------------------- /sample_inputs/Copyrights.txt: -------------------------------------------------------------------------------- 1 | 'Dracolion', 'Mouse', 'Muten', 'Sheriff' are from [Noris et al. 2013]. Please ask the authors for the .pngs. 2 | 'Banana-Tree', 'Elephant', 'Hippo', 'Kitten', 'Penguin', and 'Puppy': (c) Ivan Huska. https://www.easy-drawings-and-sketches.com/ -------------------------------------------------------------------------------- /src/chooseRoot.h: -------------------------------------------------------------------------------- 1 | #ifndef _CHOOSE_ROOTS_H_ 2 | #define _CHOOSE_ROOTS_H_ 3 | 4 | #include "typedefs.h" 5 | 6 | Eigen::Vector2d chooseRoot(std::complex root1, std::complex root2, Eigen::Vector2d dir, Eigen::Vector2d* otherRoot = nullptr); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /src/ContractLoops.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONTRACT_LOOPS_H_ 2 | 3 | #include "typedefs.h" 4 | #include "graph_typedefs.h" 5 | 6 | std::vector contractLoops(G& reebGraph, const cv::Mat & origMask, const std::vector& polys); //returns incontractible loops 7 | 8 | 9 | #endif -------------------------------------------------------------------------------- /src/MyScrollArea.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "MyScrollArea.h" 3 | 4 | void MyScrollArea::wheelEvent(QWheelEvent * event) 5 | { 6 | auto km = QApplication::keyboardModifiers(); 7 | if (!(km & Qt::KeyboardModifier::AltModifier)) 8 | QScrollArea::wheelEvent(event); 9 | } 10 | -------------------------------------------------------------------------------- /src/RemoveShortBranches.h: -------------------------------------------------------------------------------- 1 | #ifndef _REMOVE_SHORT_BRANCHES_H_ 2 | #define _REMOVE_SHORT_BRANCHES_H_ 3 | 4 | #include "typedefs.h" 5 | #include "graph_typedefs.h" 6 | 7 | void removeBranchesFilter1(G& g, bool onlyAtIntersections, const std::map& myChainsBeforeSharpCornersSplit); 8 | 9 | #endif -------------------------------------------------------------------------------- /src/ContractLoops2.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONTRACT_LOOPS2_H_ 2 | 3 | #include "typedefs.h" 4 | #include "graph_typedefs.h" 5 | #include 6 | 7 | std::vector contractLoops2(G& g, const cv::Mat & origMask, const std::vector& polys); //returns incontractible loops 8 | 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/SplitEmUp.h: -------------------------------------------------------------------------------- 1 | #ifndef _SPLIT_EM_UP_H_ 2 | #define _SPLIT_EM_UP_H_ 3 | 4 | #include "graph_typedefs.h" 5 | 6 | struct ChainSplitInfo 7 | { 8 | int chain; 9 | std::array splitEnd; 10 | }; 11 | 12 | void splitEmUpCorrectly(G & g); //returns one-degree verts that became part of chains 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/ContractDeg2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "graph_typedefs.h" 3 | std::vector> topoGraph(const G& g); 4 | std::vector> topoGraphHighValenceSeparated(const G & g, std::vector>& chainsSeparated, bool onlyLoops=false); 5 | void contractDeg2(G& g); 6 | 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | build: 4 | docker build -t mixaill76/poly-vectorization:latest . 5 | 6 | run: 7 | docker run -it --rm \ 8 | -e DISPLAY \ 9 | -v /tmp/.X11-unix:/tmp/.X11-unix:ro \ 10 | -v $XAUTHORITY:/root/.Xauthority:ro \ 11 | -v ./sample_inputs:/app/sample_inputs \ 12 | mixaill76/poly-vectorization:latest -------------------------------------------------------------------------------- /src/IsLoopContractible.h: -------------------------------------------------------------------------------- 1 | #ifndef _IS_LOOP_CONTRACTIBLE_H_ 2 | #define _IS_LOOP_CONTRACTIBLE_H_ 3 | 4 | #include "typedefs.h" 5 | #include "graph_typedefs.h" 6 | 7 | bool isLoopContractible(const std::vector& loop, const cv::Mat& origMask, const G& g, const std::vector& polys, std::vector& outCvLoop); 8 | 9 | #endif -------------------------------------------------------------------------------- /src/FindRoots.h: -------------------------------------------------------------------------------- 1 | #ifndef _FIND_ROOTS_H_ 2 | #define _FIND_ROOTS_H_ 3 | #include 4 | #include "Eigen/Dense" 5 | #include "opencv2/core/core.hpp" 6 | 7 | std::array, 2> findRoots(std::complex c0, std::complex c2); 8 | std::array findRoots(const Eigen::VectorXcd& X, const cv::Mat& mask); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/bilinearInterpCoeff.h: -------------------------------------------------------------------------------- 1 | #ifndef _BILINEAR_INTERP_COEFFS_H_ 2 | #define _BILINEAR_INTERP_COEFFS_H_ 3 | #include "Eigen/Dense" 4 | #include "opencv2/core/core.hpp" 5 | #include 6 | 7 | std::array, 2> bilinearInterpCoeff(const Eigen::VectorXcd &X, const Eigen::Vector2d& p, const cv::Mat& extMask, const Eigen::MatrixXi& indices); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/ScanConvert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "graph_typedefs.h" 3 | 4 | std::set> scanConvert(const std::vector& poly, const cv::Mat & origMask, const std::array& roots, 5 | int startIdx, std::map, bool>& isRootMatchingOK, std::array& hitSingularity, 6 | const std::set>& singularities, std::array segment); -------------------------------------------------------------------------------- /src/intersections.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // Returns 1 if the lines intersect, otherwise 0. In addition, if the lines 6 | // intersect the intersection point may be stored in the doubles i_x and i_y. 7 | bool get_line_intersection(double p0_x, double p0_y, double p1_x, double p1_y, 8 | double p2_x, double p2_y, double p3_x, double p3_y, double *i_x, double *i_y, double *sOut, double *tOut); -------------------------------------------------------------------------------- /src/traceAuto.h: -------------------------------------------------------------------------------- 1 | #ifndef _TRACE_AUTO_H_ 2 | #define _TRACE_AUTO_H_ 3 | 4 | #include "typedefs.h" 5 | std::vector traceAll(const cv::Mat & bwImg, const cv::Mat & origMask, const cv::Mat & extMask, const std::array& roots, const Eigen::VectorXcd & X, const Eigen::MatrixXi& indices, std::map, std::vector>& pixelInfo, std::vector>& endedWithASingularity); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /.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 | *.svg 34 | .vscode/settings.json 35 | -------------------------------------------------------------------------------- /src/l2_regularizer.h: -------------------------------------------------------------------------------- 1 | #ifndef _L2_REGULARIZER_H_ 2 | #define _L2_REGULARIZER_H_ 3 | #include "Eigen/Dense" 4 | #include "Eigen/Sparse" 5 | #include "opencv2/core/core.hpp" 6 | 7 | std::pair l2_regularizer(const Eigen::VectorXcd& X, const Eigen::MatrixXd& D, const cv::Mat & mask, Eigen::MatrixXi& indices, bool computeGrad); 8 | Eigen::SparseMatrix laplacian_matrix(const cv::Mat& mask, const Eigen::MatrixXi& indices, const Eigen::VectorXd& w); 9 | 10 | #endif -------------------------------------------------------------------------------- /src/chopFakeEnds.h: -------------------------------------------------------------------------------- 1 | #ifndef _CHOP_FAKE_ENDS_H_ 2 | #define _CHOP_FAKE_ENDS_H_ 3 | 4 | #include "typedefs.h" 5 | #include "graph_typedefs.h" 6 | 7 | std::pair,G> chopFakeEnds(const std::vector& polys, const std::vector>& radii, const std::vector>& protectedEnds, 8 | const std::vector>& isItASpecialDeg2Vertex, const std::vector>& yJunctions); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/findSingularities.h: -------------------------------------------------------------------------------- 1 | #ifndef _FIND_SINGULARITIES_H_ 2 | #define _FIND_SINGULARITIES_H_ 3 | #include "typedefs.h" 4 | #include 5 | bool isItASingularity(const Eigen::Vector2d & p, const std::array, 2> myRoots, const std::array& roots, const cv::Mat & origMask); 6 | 7 | std::set> findSingularities(std::array & roots, Eigen::VectorXcd& X, const Eigen::MatrixXi& indices, const cv::Mat & origMask); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/greedyTrace.h: -------------------------------------------------------------------------------- 1 | #ifndef _GREEDY_TRACE_H_ 2 | #define _GREEDY_TRACE_H_ 3 | 4 | #include "typedefs.h" 5 | #include 6 | std::pair> greedyTrace(const cv::Mat & origMask, 7 | const std::array& allRoots, const Eigen::Vector2d & seedCenter, const Eigen::Vector2d& initialDir, const Eigen::VectorXcd & X, 8 | const std::map, std::vector>& pixelInfo, std::map, std::vector>& outNewPixelInfo, 9 | const Eigen::MatrixXi& indices, int myCurveIdx, bool perpendicular, std::array& closestDistToSingularity); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/polynomial_energy.h: -------------------------------------------------------------------------------- 1 | #ifndef _POLYNOMIAL_ENERGY_H_ 2 | #define _POLYNOMIAL_ENERGY_H_ 3 | #include "Eigen/Dense" 4 | #include "opencv2/core/core.hpp" 5 | #include "Eigen/Sparse" 6 | 7 | std::pair polynomial_energy(const Eigen::VectorXcd& X, const Eigen::MatrixXd & weight, const Eigen::MatrixXcd& tau, const cv::Mat & mask, Eigen::MatrixXi& indices, std::vector& energiesOut, bool computeGrad); 8 | std::pair>, Eigen::VectorXcd> polynomial_energy_matrix(const Eigen::MatrixXd& weight, const Eigen::MatrixXcd& tau, const cv::Mat& mask, const Eigen::MatrixXi& indices); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/imagequiverviewer.ui: -------------------------------------------------------------------------------- 1 | 2 | ImageQuiverViewer 3 | 4 | 5 | ImageQuiverViewer 6 | 7 | 8 | 9 | 0 10 | 0 11 | 400 12 | 300 13 | 14 | 15 | 16 | ImageQuiverViewer 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Smooth.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Smooth.h" 3 | 4 | void smooth(std::vector& curves) 5 | { 6 | const int numIter = 10; 7 | const double lambda = 0.5; 8 | for (int i = 0; i < numIter; ++i) 9 | { 10 | for (int j = 0; j < curves.size(); ++j) 11 | { 12 | MyPolyline newPoly = curves[j]; 13 | for (int k = 1; k + 1 < curves[j].size(); ++k) 14 | { 15 | Eigen::Vector2d prev = curves[j][k - 1] - curves[j][k]; 16 | Eigen::Vector2d next = curves[j][k + 1] - curves[j][k]; 17 | double wPrev = 1 / prev.norm(), wNext = 1 / next.norm(); 18 | double cosAngle = prev.normalized().dot(next.normalized()); 19 | Eigen::Vector2d L = (wPrev*prev + wNext*next) / (wPrev + wNext); 20 | newPoly[k] += lambda*L; 21 | } 22 | curves[j] = newPoly; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Optimizer.h: -------------------------------------------------------------------------------- 1 | #ifndef _OPTIMIZER_H_ 2 | #define _OPTIMIZER_H_ 3 | 4 | #include "Eigen/Dense" 5 | #include "Eigen/Sparse" 6 | #include "opencv2/core/core.hpp" 7 | 8 | Eigen::VectorXcd optimize(cv::Mat& bwImg, const Eigen::MatrixXd& weight, const Eigen::MatrixXcd& tauNormalized, double beta, cv::Mat& extMask, const Eigen::MatrixXi& indices); 9 | Eigen::VectorXcd optimizeByLinearSolve(cv::Mat& bwImg, const Eigen::MatrixXd& weight, const Eigen::MatrixXcd& tauNormalized, double beta, cv::Mat& mask, const Eigen::MatrixXi& indices); 10 | Eigen::VectorXcd optimizeByLinearSolve_holdingSomeFixed(cv::Mat& bwImg, const Eigen::MatrixXd& weight, const Eigen::MatrixXcd& tauNormalized, double beta, cv::Mat& mask, cv::Mat& fixedMask, const Eigen::MatrixXi& indices, const Eigen::MatrixXi& fixedIndices, const Eigen::VectorXcd& Xfixed); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/ILSLinIneqSolver.h: -------------------------------------------------------------------------------- 1 | #ifndef _LS_LIN_INEQ_SOLVE_H_ 2 | #define _LS_LIN_INEQ_SOLVE_H_ 3 | #include 4 | #include 5 | 6 | /** 7 | * This is an interface header for solving Linear (Overdetermined) System with linear inequality constraints. 8 | * Minimizes 1/2*x'*H*x + f'*x subject to the restrictions A*x ≤ b, additional restrictions Aeq*x = beq. 9 | */ 10 | class ILSLinIneqSolver 11 | { 12 | public: 13 | typedef Eigen::Triplet Triplet; 14 | 15 | /** 16 | * Main solving interface. 17 | */ 18 | virtual std::vector solve(const Eigen::SparseMatrix& H, const std::vector& f, const Eigen::SparseMatrix& A, const std::vector b, const Eigen::SparseMatrix& Aeq, const std::vector& bEq, int nUnknowns) = 0; 19 | }; 20 | 21 | #endif // _LS_LIN_INEQ_SOLVE_H_ 22 | -------------------------------------------------------------------------------- /src/TotalEnergy.h: -------------------------------------------------------------------------------- 1 | #ifndef _TOTAL_ENERGY_H_ 2 | #define _TOTAL_ENERGY_H_ 3 | #include "Eigen/Dense" 4 | #include "opencv2/core/core.hpp" 5 | #include 6 | #include 7 | struct TotalEnergy 8 | { 9 | TotalEnergy(cv::Mat & bwImg, const Eigen::MatrixXd & weight, const Eigen::MatrixXcd & tauNormalized, double beta, cv::Mat & mask, const Eigen::MatrixXi& indices, int nnz); 10 | double operator()(const Eigen::VectorXd& x, Eigen::VectorXd& grad); 11 | double energyOnly(const Eigen::VectorXd & x); 12 | 13 | int m, n; 14 | const double beta; 15 | double alpha; 16 | cv::Mat& bwImg; 17 | const Eigen::MatrixXd& weight; 18 | const Eigen::MatrixXcd & tauNormalized; 19 | Eigen::MatrixXcd g; 20 | cv::Mat & mask; 21 | bool noisy; 22 | Eigen::MatrixXd smartWeights; 23 | Eigen::MatrixXi indices; 24 | int nnz; 25 | Eigen::MatrixXd onesMatrix; 26 | }; 27 | 28 | #endif -------------------------------------------------------------------------------- /src/chooseRoot.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "chooseRoot.h" 3 | #include 4 | 5 | Eigen::Vector2d chooseRoot(std::complex root1, std::complex root2, Eigen::Vector2d dir, Eigen::Vector2d * otherRoot) 6 | { 7 | Eigen::Vector2d eigRoot1 = _toEig(root1), eigRoot2 = _toEig(root2); 8 | eigRoot1.normalize(); eigRoot2.normalize(); 9 | std::array roots = { eigRoot1, -eigRoot1, eigRoot2, -eigRoot2 }; 10 | 11 | std::array dots; 12 | for (int i = 0; i < 4; ++i) 13 | dots[i] = roots[i].dot(dir); 14 | 15 | auto it = std::max_element(dots.begin(), dots.end()); 16 | int idx = it - dots.begin(); 17 | 18 | if (otherRoot) 19 | *otherRoot = roots[(idx + 2) % 4]; 20 | 21 | if (fabs(dots[idx] - dots[(idx + 2) % 4]) < 1e-3) 22 | return 0.5*(roots[idx] + roots[(idx + 2) % 4]); 23 | 24 | return roots[idx]; 25 | } 26 | -------------------------------------------------------------------------------- /src/stdafx.h: -------------------------------------------------------------------------------- 1 | #ifndef _STDAFX_H_ 2 | #define _STDAFX_H_ 3 | 4 | #ifdef WITH_QT 5 | #include 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include "Eigen/Dense" 12 | #include "Eigen/Core" 13 | #include "opencv2/core/core.hpp" 14 | #include "opencv2/highgui/highgui.hpp" 15 | #include "opencv2/imgproc/imgproc.hpp" 16 | #include "opencv2/core/eigen.hpp" 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "typedefs.h" 25 | #include "graph_typedefs.h" 26 | #include "intersections.h" 27 | #include "ChainDecomposition.h" 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/Params.h: -------------------------------------------------------------------------------- 1 | #ifndef _PARAMS_H_ 2 | #define _PARAMS_H_ 3 | 4 | //If you see gaps in the final vectorization, or some separate curves get connected, this is the constant to tune (0-255). However, an easier way is to adjust the contrast of your input image. 5 | const double BACKGROUND_FOREGROUND_THRESHOLD = 90.0; 6 | 7 | //The more regularization, the more orthogonal is the frame field => the sharper the junctions, but narrow junctions might become wabbly. 8 | const double FRAME_FIELD_REGULARIZER_WEIGHT = 0.1; 9 | //Adjust only if you have crazily noisy image 10 | const double FRAME_FIELD_SMOOTHNESS_WEIGHT = 50.0; 11 | //If you see if removing little branches in the vectorization, try tuning that, but just a bit 12 | const double PRUNE_SHORT_BRANCHES_RATIO = 0.75; 13 | //If you have a very low-res image and you want to preserve tiny holes (white areas), tune this. 14 | const int MAX_NUMBER_OF_WHITE_PIXELS_IN_A_CONTRACTIBLE_LOOP = 4; 15 | 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/FindRoots.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "FindRoots.h" 3 | 4 | std::array, 2> findRoots(std::complex c0, std::complex c2) 5 | { 6 | //roots of z^4 + c2*z^2 + c0 = 0 7 | return{ sqrt((-c2 + sqrt(c2*c2 - 4.0 * c0)) / 2.0), sqrt((-c2 - sqrt(c2*c2 - 4.0 * c0)) / 2.0) }; 8 | } 9 | 10 | std::array findRoots(const Eigen::VectorXcd & X, const cv::Mat & mask) 11 | { 12 | typedef std::complex cmplx; 13 | 14 | int m = mask.rows, n = mask.cols; 15 | Eigen::MatrixXcd root1(m,n), root2(m,n); 16 | root1.setZero(); root2.setZero(); 17 | 18 | 19 | int nonzeros = X.size() / 2; 20 | 21 | int idx = 0; 22 | for (int j=0; j(i, j) == 0) 26 | continue; 27 | 28 | cmplx c0 = X(idx), c2 = X(idx + nonzeros); 29 | auto roots = findRoots(c0, c2); 30 | root1(i, j) = roots[0]; 31 | root2(i, j) = roots[1]; 32 | 33 | idx++; 34 | } 35 | return{ root1,root2 }; 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mikhail Bessmeltsev and Justin Solomon // Massachusetts Institute of Technology 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 866 10 | 619 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 0 28 | 0 29 | 844 30 | 597 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/AlmostReebGraph.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "typedefs.h" 3 | #include "graph_typedefs.h" 4 | #include 5 | 6 | typedef std::map, std::vector> VectorsPerPixel; 7 | 8 | G computeAlmostReebGraph(const cv::Mat & origMask, const std::array& roots, const std::vector& polys, 9 | std::map, std::vector>& pixelInfo, const std::set>& singularities, const Eigen::MatrixXi& indices, const Eigen::VectorXcd& X, const std::vector>& endedWithASingularity); 10 | Cluster createCluster(int seedCurve, int seedPtIdx, const cv::Mat & origMask, const std::array& roots, 11 | const std::vector& polys, const std::map, std::vector>& pixelInfo, const std::set& onlyTheseCurves, const std::set>& singularities, const Eigen::MatrixXi& indices, const Eigen::VectorXcd& X); 12 | void contractSingularityBranches(G& g); 13 | void connectStuffAroundSingularities(G& g, const cv::Mat & origMask, const std::vector& polys, const std::set>& singularities, const std::array& roots, const std::vector>& endedWithASingularity); 14 | void simpleThresholds(G& g); 15 | -------------------------------------------------------------------------------- /src/intersections.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "intersections.h" 3 | 4 | bool get_line_intersection(double p0_x, double p0_y, double p1_x, double p1_y, double p2_x, double p2_y, double p3_x, double p3_y, double * i_x, double * i_y, double * sOut, double * tOut) 5 | { 6 | double s1_x, s1_y, s2_x, s2_y; 7 | s1_x = p1_x - p0_x; s1_y = p1_y - p0_y; 8 | s2_x = p3_x - p2_x; s2_y = p3_y - p2_y; 9 | 10 | double s, t; 11 | double sTop = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)); 12 | double sBottom = (-s2_x * s1_y + s1_x * s2_y); 13 | 14 | if ((sTop < 0 && sBottom > 0) || (sTop > 0 && sBottom < 0)) 15 | return false; 16 | 17 | if (sTop < 0) 18 | { 19 | sTop = -sTop; 20 | sBottom = -sBottom; 21 | } 22 | 23 | double tTop = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)); 24 | double tBottom = (-s2_x * s1_y + s1_x * s2_y); 25 | 26 | if ((tTop < 0 && tBottom > 0) || (tTop > 0 && tBottom < 0)) 27 | return false; 28 | 29 | if (tTop < 0) 30 | { 31 | tTop = -tTop; 32 | tBottom = -tBottom; 33 | } 34 | 35 | if (sTop < sBottom && tTop < tBottom) 36 | { 37 | s = sTop / sBottom; 38 | t = tTop / tBottom; 39 | if (sOut != nullptr) 40 | *sOut = s; 41 | if (tOut != nullptr) 42 | *tOut = t; 43 | 44 | // Collision detected 45 | if (i_x != nullptr) 46 | *i_x = p0_x + (t * s1_x); 47 | if (i_y != nullptr) 48 | *i_y = p0_y + (t * s1_y); 49 | return true; 50 | } 51 | 52 | return false; // No collision 53 | } 54 | -------------------------------------------------------------------------------- /src/bilinearInterpCoeff.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "bilinearInterpCoeff.h" 3 | 4 | std::array, 2> bilinearInterpCoeff(const Eigen::VectorXcd & X, const Eigen::Vector2d & p, const cv::Mat & extMask, const Eigen::MatrixXi & indices) 5 | { 6 | double i = p.y(); 7 | double j = p.x(); 8 | int i1 = std::floor(i), i2 = std::ceil(i); 9 | int j1 = std::floor(j), j2 = std::ceil(j); 10 | if (i1 == i2) 11 | i2 = i1 + 1; 12 | if (j1 == j2) 13 | j2 = j1 + 1; 14 | 15 | int nonzeros = X.size() / 2; 16 | 17 | int m = extMask.rows, n = extMask.cols; 18 | if ((i2 == m) || (j2 == n)) 19 | return{ X(indices((int)std::round(i),(int)std::round(j))),X(indices((int)std::round(i),(int)std::round(j)) + nonzeros) }; 20 | 21 | 22 | std::array, 2> result; 23 | 24 | for (int rootIdx = 0; rootIdx < 2; rootIdx++) 25 | { 26 | std::complex X_11 = extMask.at(i1, j1) != 0 ? X(indices(i1, j1) +rootIdx*nonzeros) : 0; 27 | std::complex X_12 = extMask.at(i1, j2) != 0 ? X(indices(i1, j2) +rootIdx*nonzeros) : 0; 28 | std::complex X_21 = extMask.at(i2, j1) != 0 ? X(indices(i2, j1) +rootIdx*nonzeros) : 0; 29 | std::complex X_22 = extMask.at(i2, j2) != 0 ? X(indices(i2, j2) +rootIdx*nonzeros) : 0; 30 | Eigen::Matrix2cd M; 31 | M << X_11, X_12, X_21, X_22; 32 | result[rootIdx] = (1.0 / ((i2 - i1)*(j2 - j1)))*Eigen::Vector2d(i2 - i, i - i1).dot(M*Eigen::Vector2d(j2 - j, j - j1)); 33 | } 34 | 35 | return result; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/CircularSegment.h: -------------------------------------------------------------------------------- 1 | #ifndef _CIRCULAR_SEGMENT_H_ 2 | #define _CIRCULAR_SEGMENT_H_ 3 | 4 | /* 5 | Defines operations for a segment on a (potentially) closed curve. 6 | Examples of valid segments are: [1.5, 5] (for any modulus), [9, 0.5] for modulus 10. 7 | Modulus = length of the curve or number of pieces in the polyline. 8 | */ 9 | #include 10 | 11 | class CircularSegment 12 | { 13 | public: 14 | //if modulus <0, the curve is considered to be NOT closed 15 | CircularSegment (double begin=-1, double end=-1, double modulus=-1); 16 | 17 | //if intersection is empty, returns FALSE 18 | bool intersectionWith (const CircularSegment& other,CircularSegment& outIntersection, double tolerance = 1e-10) const; 19 | 20 | //union is not always a segment. in case it's not a segment (i.e. two disjoint segments or empty set), the function returns FALSE 21 | std::vector unionWith (const CircularSegment& other, double tolerance = 1e-10) const; 22 | 23 | std::vector complement() const; 24 | 25 | void extendToContainPoint (double p); 26 | bool isInside (double p, double tolerance = 1e-10) const; 27 | double length() const; 28 | double pointAtT(double t) const; 29 | const double& begin() const {return begin_;} 30 | double& begin() {return begin_;} 31 | const double& end() const {return end_;} 32 | double& end() {return end_;} 33 | const double& modulus() const {return modulus_;} 34 | double& modulus() {return modulus_;} 35 | private: 36 | double begin_, end_, modulus_; 37 | bool empty_; 38 | }; 39 | 40 | #endif -------------------------------------------------------------------------------- /src/graph_typedefs.h: -------------------------------------------------------------------------------- 1 | #ifndef _GRAPH_TYPEDEFS_H_ 2 | #define _GRAPH_TYPEDEFS_H_ 3 | 4 | #include 5 | #include 6 | #include "simple_svg_1.0.0.hpp" 7 | #include "typedefs.h" 8 | 9 | struct Cluster 10 | { 11 | Eigen::Vector2d location; 12 | mutable Eigen::Vector2d root; 13 | std::vector clusterPoints; 14 | std::vector clusterCurve; 15 | int clusterIdx; 16 | int seedCurve; 17 | bool sharpCorner; //filled in SplitEmUp 18 | bool clusterCurveHitSingularity; 19 | bool nextToSingularity; //if one of the cluster points is right next to a curve end which is at singularity 20 | double width; 21 | bool split; 22 | }; 23 | 24 | struct Edge 25 | { 26 | int edgeCurve; 27 | double weight; 28 | }; 29 | 30 | //typedef boost::property VertexProperty; 31 | using G = boost::adjacency_list; 32 | using listG = boost::adjacency_list; 33 | using V = G::vertex_descriptor; 34 | using E = std::pair; 35 | using vertex_descriptor = boost::graph_traits::vertex_descriptor; 36 | using edge_descriptor = boost::graph_traits::edge_descriptor; 37 | using edge_iter = boost::graph_traits::edge_iterator; 38 | using oedge_iter = boost::graph_traits::out_edge_iterator; 39 | 40 | std::set contract_edges(const std::vector::edge_descriptor>>& edgeLoops, G& g); 41 | 42 | G listToVec(const listG& g); 43 | 44 | #endif -------------------------------------------------------------------------------- /src/mainwindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "ui_mainwindow.h" 4 | #include 5 | #include 6 | #include 7 | #include "imagequiverviewer.hpp" 8 | #include "typedefs.h" 9 | #include "MyScrollArea.h" 10 | class MainWindow : public QMainWindow { 11 | Q_OBJECT 12 | 13 | public: 14 | MainWindow(); 15 | ~MainWindow(); 16 | void setImage(QString filename, const cv::Mat& mask); 17 | void setRoots(const std::array& newRoots); 18 | void setExtraRoots(const std::array& newRoots); 19 | void setPolys(const std::vector& newPolys); 20 | void setGraph(std::string s, const G & g); 21 | void setSplitVertices(const std::set& vtx); 22 | void setIncontractibleLoops(const std::vector>& loops); 23 | void setVectorization(std::string s, const std::vector& vectorization); 24 | protected: 25 | void keyPressEvent(QKeyEvent * event); 26 | void redraw(); 27 | void setScale(double newScale); 28 | virtual void wheelEvent(QWheelEvent * event); 29 | private: 30 | const int numGraphs, numVectorizations; 31 | Ui::MainWindow ui; 32 | QImage image; 33 | ImageQuiverViewer *imageLabel; 34 | MyScrollArea *scrollArea; 35 | double scale; 36 | int origWidth, origHeight; 37 | std::vector checkGraphs, checkVectorizations; 38 | QCheckBox* checkPolys, *checkIncontractibleLoops; 39 | QRadioButton* maskRadio, *imageRadio; 40 | QImage actualImage, maskImage; 41 | std::map graphNameToIdx, vectorizationNameToIdx; 42 | QVBoxLayout *l; 43 | }; 44 | -------------------------------------------------------------------------------- /src/ChainDecomposition.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "ChainDecomposition.h" 3 | 4 | std::vector> chainDecomposition(const G & g, std::map& myChain) 5 | { 6 | std::vector> chains; 7 | //1. Break into chains, record their adjacencies into another graph 8 | edge_iter eit, eend; 9 | for (std::tie(eit, eend) = boost::edges(g); eit != eend; ++eit) 10 | { 11 | //find a seed edge for a new chain 12 | if (myChain.find(*eit) != myChain.end()) 13 | continue; 14 | 15 | //grow the chain into both direction until it hits a high-valence vertex or reaches an end 16 | std::vector newChain = { *eit }; 17 | myChain[*eit] = chains.size(); 18 | 19 | 20 | for (int dir : { -1, 1 }) 21 | { 22 | size_t curVtx = dir == -1 ? newChain.front().m_source : newChain.back().m_target; 23 | bool continuationFound = true; 24 | //try extending starting from this vertex 25 | while (continuationFound && (boost::degree(curVtx, g) == 2)) 26 | { 27 | continuationFound = false; 28 | oedge_iter oeit, oeend; 29 | for (std::tie(oeit, oeend) = boost::out_edges(curVtx, g); oeit != oeend; ++oeit) 30 | { 31 | if (myChain.find(*oeit) == myChain.end()) 32 | { 33 | curVtx = oeit->m_target; 34 | if (dir == -1) 35 | newChain.insert(newChain.begin(), boost::edge(oeit->m_target, oeit->m_source, g).first); 36 | else 37 | newChain.push_back(*oeit); 38 | 39 | myChain[*oeit] = chains.size(); 40 | continuationFound = true; 41 | break; 42 | } 43 | } 44 | } 45 | } 46 | 47 | chains.push_back(newChain); 48 | } 49 | return chains; 50 | } 51 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt update && \ 4 | apt install -y libopencv-dev libomp-dev libboost-all-dev wget git software-properties-common 5 | 6 | WORKDIR /tmp/cmake 7 | RUN wget https://github.com/Kitware/CMake/releases/download/v3.30.3/cmake-3.30.3-linux-x86_64.sh && \ 8 | sh cmake-3.30.3-linux-x86_64.sh --skip-license && rm -rf cmake-3.30.3-linux-x86_64.sh && \ 9 | cp -r * /usr/ && rm -rf /tmp/cmake 10 | 11 | WORKDIR /tmp 12 | RUN git clone --branch 3.3 https://gitlab.com/libeigen/eigen.git && \ 13 | cd eigen && mkdir build && cd build && cmake .. && make install && \ 14 | cd /usr/local/include && \ 15 | ln -sf eigen3/Eigen Eigen && \ 16 | ln -sf eigen3/unsupported unsupported && \ 17 | rm -rf /tmp/eigen 18 | 19 | RUN add-apt-repository -y ppa:beineri/opt-qt571-xenial && \ 20 | apt update && apt install -y qt57-meta-minimal mesa-common-dev libglu1-mesa-dev && \ 21 | apt clean && apt autoremove 22 | 23 | WORKDIR /app 24 | 25 | COPY cmake /app/cmake 26 | COPY src /app/src 27 | COPY CMakeLists.txt /app/ 28 | 29 | WORKDIR /app/build_cli 30 | RUN cmake .. -D CMAKE_BUILD_TYPE=Release && \ 31 | make -j8 && mv polyvector_thing /usr/bin/polyvector_thing_cli && \ 32 | rm -rf /app/build_cli 33 | 34 | WORKDIR /app/build_cli_qt 35 | RUN cmake .. -D CMAKE_BUILD_TYPE=Release -D WITH_QT=1 && \ 36 | make -j8 && mv polyvector_thing /usr/bin/polyvector_thing_cli_qt && \ 37 | rm -rf /app/build_cli_qt 38 | 39 | WORKDIR /app/build_gui 40 | RUN cmake .. -D CMAKE_BUILD_TYPE=Release -D WITH_GUI=1 -D WITH_QT=1 && \ 41 | make -j8 && mv polyvector_thing /usr/bin/polyvector_thing && \ 42 | rm -rf /app/build_gui 43 | 44 | WORKDIR /app/ 45 | RUN rm -rf * -------------------------------------------------------------------------------- /src/Equations.h: -------------------------------------------------------------------------------- 1 | #ifndef _EQUATIONS_H_ 2 | #define _EQUATIONS_H_ 3 | #include 4 | #include 5 | #include 6 | 7 | typedef Eigen::Triplet Triplet; 8 | struct Equations 9 | { 10 | std::vector ineq, eq, exactEq; 11 | std::vector ineqB, eqB, exactB; 12 | 13 | void add(Equations other) 14 | { 15 | auto offsetRow = [](std::vector& tripletsInGlobalIndices, int offset) 16 | { 17 | for (auto& tr : tripletsInGlobalIndices) 18 | tr = Triplet(tr.row() + offset, tr.col(), tr.value()); 19 | }; 20 | 21 | offsetRow(other.ineq, ineqB.size()); 22 | ineq.insert(ineq.end(), other.ineq.begin(), other.ineq.end()); 23 | ineqB.insert(ineqB.end(), other.ineqB.begin(), other.ineqB.end()); 24 | 25 | offsetRow(other.eq, eqB.size()); 26 | eq.insert(eq.end(), other.eq.begin(), other.eq.end()); 27 | eqB.insert(eqB.end(), other.eqB.begin(), other.eqB.end()); 28 | 29 | offsetRow(other.exactEq, exactB.size()); 30 | exactEq.insert(exactEq.end(), other.exactEq.begin(), other.exactEq.end()); 31 | exactB.insert(exactB.end(), other.exactB.begin(), other.exactB.end()); 32 | } 33 | 34 | void addEqLHS(int index, double value) 35 | { 36 | eq.push_back(Triplet(eqB.size(), index, value)); 37 | } 38 | 39 | void addExactEqLHS(int index, double value) 40 | { 41 | exactEq.push_back(Triplet(exactB.size(), index, value)); 42 | } 43 | 44 | void addEqRHSAndFinishEquation(double rhs) 45 | { 46 | eqB.push_back(rhs); 47 | } 48 | 49 | void addExactRHSAndFinishEquation(double rhs) 50 | { 51 | exactB.push_back(rhs); 52 | } 53 | 54 | void addIneqLHS(int index, double value) 55 | { 56 | ineq.push_back(Triplet(ineqB.size(), index, value)); 57 | } 58 | 59 | void addIneqRHSAndFinishEquation(double rhs) 60 | { 61 | ineqB.push_back(rhs); 62 | } 63 | }; 64 | 65 | #endif -------------------------------------------------------------------------------- /src/imagequiverviewer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "typedefs.h" 6 | #include "graph_typedefs.h" 7 | #include 8 | #include "TopoGraphEmbedding.h" 9 | class ImageQuiverViewer : public QLabel { 10 | Q_OBJECT 11 | 12 | public: 13 | ImageQuiverViewer(QWidget * parent = Q_NULLPTR); 14 | ~ImageQuiverViewer(); 15 | 16 | template 17 | QPointF modelToScreen(const T & p) 18 | { 19 | T result = (p + T(0.5, 0.5))*scale; 20 | return{ result.x(), result.y() }; 21 | } 22 | void setRoots(const std::array& newRoots); 23 | void setExtraRoots(const std::array& newRoots); 24 | void setPolys(const std::vector& newPolys); 25 | void drawRoots(const std::array& thoseOnes, QPainter & painter); 26 | void drawGraphs(QPainter& painter); 27 | void drawPolys(QPainter & painter, const std::vector& curves, double width, QColor color); 28 | public: 29 | double scale; 30 | std::vector graphHidden; 31 | std::vector graphs; 32 | std::vector graphTags; 33 | std::vector vectorizationsHidden; 34 | bool polysHidden; 35 | bool showCircles; 36 | bool showIncontractibleLoops; 37 | 38 | std::set splitVtx; 39 | std::vector> incontractibleLoops; 40 | std::unique_ptr tmpDistances; 41 | std::vector> vectorizations; 42 | protected: 43 | bool areGraphsVisible(); 44 | void paintEvent(QPaintEvent * event); 45 | void drawClusters(QPainter & painter); 46 | virtual void mousePressEvent(QMouseEvent * event); 47 | private: 48 | std::array roots, extraRoots; 49 | std::vector polys; 50 | std::set> clustersToDraw; 51 | std::vector colors; 52 | }; 53 | -------------------------------------------------------------------------------- /src/ScanConvert.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "ScanConvert.h" 3 | 4 | std::set> scanConvert(const std::vector& poly, const cv::Mat & origMask, const std::array& roots, int startIdx, std::map, 5 | bool>& isRootMatchingOK, std::array& hitSingularity, const std::set>& singularities, std::array segment) 6 | { 7 | std::set> pixels; 8 | 9 | int m = origMask.rows, n = origMask.cols; 10 | Eigen::Vector2d p0 = poly[startIdx]; 11 | std::array pi0 = { (int)std::round(p0.y()), (int)std::round(p0.x()) }; 12 | isRootMatchingOK[pi0] = true; 13 | hitSingularity = { false,false }; 14 | Eigen::Vector2d r0 = _toEig(roots[0](pi0[0], pi0[1])); 15 | 16 | int dirIdx = 0; 17 | for (int dir : {-1, 1}) 18 | { 19 | std::array prevPixel = pi0; 20 | std::array pi = pi0; 21 | for (int i = startIdx; (i >= segment[0]) && (i <= segment[1]); i += dir) 22 | { 23 | Eigen::Vector2d p = poly[i]; 24 | pi = { (int)std::round(p.y()), (int)std::round(p.x()) }; 25 | 26 | if (singularities.find(pi) != singularities.end()) 27 | { 28 | hitSingularity[dirIdx] = true; 29 | break; 30 | } 31 | 32 | if (i == startIdx || (prevPixel != pi)) 33 | { 34 | for (int i = -1; i <= 1; ++i) 35 | { 36 | for (int j = -1; j <= 1; ++j) 37 | { 38 | std::array newPixel = { pi[0] + i,pi[1] + j }; 39 | if (!inBounds(newPixel, origMask) || singularities.find(newPixel) != singularities.end()) 40 | continue; 41 | 42 | isRootMatchingOK[newPixel] = !(isRootMatchingOK[prevPixel] ^ rootMatching(roots, newPixel, prevPixel)); 43 | 44 | pixels.insert(newPixel); 45 | } 46 | } 47 | } 48 | prevPixel = pi; 49 | } 50 | ++dirIdx; 51 | } 52 | return pixels; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | # specify the C++ standard 4 | set(CMAKE_CXX_STANDARD 11) 5 | set(CMAKE_CXX_STANDARD_REQUIRED True) 6 | 7 | set(CMAKE_VERBOSE_MAKEFILE ON) 8 | project(polyvector_thing) 9 | # Find includes in corresponding build directories 10 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 11 | 12 | # Instruct CMake to run moc automatically when needed. 13 | set(CMAKE_AUTOMOC ON) 14 | set(CMAKE_AUTOUIC ON) 15 | add_executable(polyvector_thing) 16 | ##### QT ##### 17 | find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui) 18 | if (WITH_QT) 19 | target_compile_definitions(polyvector_thing PRIVATE WITH_QT=${WITH_QT}) 20 | endif() 21 | 22 | if (WITH_GUI) 23 | target_compile_definitions(polyvector_thing PRIVATE WITH_GUI=${WITH_GUI}) 24 | endif() 25 | 26 | if ((Qt5_FOUND) AND (WITH_QT OR WITH_GUI)) 27 | message(STATUS "Qt found") 28 | target_link_libraries(polyvector_thing PUBLIC Qt5::Core Qt5::Widgets Qt5::Gui) 29 | endif() 30 | 31 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 32 | 33 | target_include_directories(polyvector_thing PUBLIC src) 34 | #target_compile_options() 35 | 36 | ##### OpenMP ##### 37 | find_package(OpenMP) 38 | if(OpenMP_CXX_FOUND) 39 | target_link_libraries(polyvector_thing PUBLIC OpenMP::OpenMP_CXX) 40 | endif() 41 | 42 | 43 | ##### OpenCV ##### 44 | find_package(OpenCV REQUIRED) 45 | target_link_libraries(polyvector_thing PUBLIC ${OpenCV_LIBS}) 46 | 47 | ##### Eigen ##### 48 | find_package (Eigen3 REQUIRED NO_MODULE) 49 | add_definitions ( ${EIGEN3_DEFINITIONS} ) 50 | include_directories ( ${EIGEN3_INCLUDE_DIRS} ) 51 | 52 | ##### Boost ##### 53 | find_package(Boost 1.48 REQUIRED) 54 | message(STATUS "Boost_INCLUDE_DIRS = ${Boost_INCLUDE_DIRS}") 55 | target_include_directories(polyvector_thing PUBLIC ${Boost_INCLUDE_DIR}) 56 | target_link_libraries(polyvector_thing PUBLIC ${Boost_LIBRARIES}) 57 | 58 | ##### Sources ##### 59 | add_subdirectory(src) 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/Simplify.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Simplify.h" 3 | #include 4 | 5 | typedef std::pair Segment; 6 | 7 | double perpendicularDistance(const Eigen::Vector2d& point, Segment segment) 8 | { 9 | Eigen::Vector2d v = segment.first - segment.second; 10 | Eigen::Vector2d w = point - segment.second; 11 | 12 | Eigen::Vector2d closestPoint; 13 | 14 | double c1 = w.dot(v); 15 | 16 | if (c1<0) 17 | { 18 | closestPoint = segment.second; 19 | return (closestPoint-point).norm(); 20 | } 21 | double c2 = v.squaredNorm(); 22 | if (c2 <= c1) 23 | { 24 | closestPoint = segment.first; 25 | return (closestPoint - point).norm(); 26 | } 27 | double b = c1 / c2; 28 | auto prb = segment.second + v*b; 29 | closestPoint = prb; 30 | return (prb-point).norm(); 31 | } 32 | 33 | MyPolyline simplify(const MyPolyline & poly, double eps) 34 | { 35 | if (poly.size() <= 3) 36 | return poly; 37 | 38 | std::vector mask(poly.size()); 39 | std::queue> queue; 40 | // Find the point with the maximum distance 41 | queue.push({ 0,poly.size()-1}); 42 | 43 | while (!queue.empty()) 44 | { 45 | auto testPair = queue.front(); 46 | queue.pop(); 47 | 48 | double dmax = 0; 49 | int index = -1; 50 | 51 | for (int i = testPair.first+1; i < testPair.second; ++i) 52 | { 53 | double d = perpendicularDistance(poly[i], { poly[testPair.first], poly[testPair.second] }); 54 | if (d > dmax) { 55 | index = i; 56 | dmax = d; 57 | } 58 | } 59 | 60 | // If max distance is greater than epsilon, simplify 61 | if (dmax > eps) { 62 | queue.push({ testPair.first, index }); 63 | queue.push({ index,testPair.second }); 64 | } 65 | else { 66 | mask[testPair.first] = true; 67 | mask[testPair.second] = true; 68 | } 69 | 70 | } 71 | 72 | MyPolyline result; 73 | for (int i = 0; i < mask.size(); ++i) 74 | { 75 | if (mask[i]) 76 | result.push_back(poly[i]); 77 | } 78 | return result; 79 | } 80 | -------------------------------------------------------------------------------- /src/simple_svg_1.0.0.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "simple_svg_1.0.0.hpp" 3 | namespace svg 4 | { 5 | std::string elemStart(std::string const & element_name) 6 | { 7 | return "\t<" + element_name + " "; 8 | } 9 | std::string elemEnd(std::string const & element_name) 10 | { 11 | return "\n"; 12 | } 13 | std::string emptyElemEnd() 14 | { 15 | return "/>\n"; 16 | } 17 | 18 | 19 | optional getMinPoint(std::vector const & points) 20 | { 21 | if (points.empty()) 22 | return optional(); 23 | 24 | Point min = points[0]; 25 | for (unsigned i = 0; i < points.size(); ++i) { 26 | if (points[i].x < min.x) 27 | min.x = points[i].x; 28 | if (points[i].y < min.y) 29 | min.y = points[i].y; 30 | } 31 | return optional(min); 32 | } 33 | 34 | optional getMaxPoint(std::vector const & points) 35 | { 36 | if (points.empty()) 37 | return optional(); 38 | 39 | Point max = points[0]; 40 | for (unsigned i = 0; i < points.size(); ++i) { 41 | if (points[i].x > max.x) 42 | max.x = points[i].x; 43 | if (points[i].y > max.y) 44 | max.y = points[i].y; 45 | } 46 | return optional(max); 47 | } 48 | 49 | // Convert coordinates in user space to SVG native space. 50 | double translateX(double x, Layout const & layout) 51 | { 52 | if (layout.origin == Layout::BottomRight || layout.origin == Layout::TopRight) 53 | return layout.dimensions.width - ((x + layout.origin_offset.x) * layout.scale); 54 | else 55 | return (layout.origin_offset.x + x) * layout.scale; 56 | } 57 | 58 | double translateY(double y, Layout const & layout) 59 | { 60 | if (layout.origin == Layout::BottomLeft || layout.origin == Layout::BottomRight) 61 | return layout.dimensions.height - ((y + layout.origin_offset.y) * layout.scale); 62 | else 63 | return (layout.origin_offset.y + y) * layout.scale; 64 | } 65 | double translateScale(double dimension, Layout const & layout) 66 | { 67 | return dimension * layout.scale; 68 | } 69 | } -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | SET(TARGET_SOURCES 2 | src/AlmostReebGraph.cpp 3 | src/AlmostReebGraph.h 4 | src/bilinearInterpCoeff.cpp 5 | src/bilinearInterpCoeff.h 6 | src/ChainDecomposition.cpp 7 | src/ChainDecomposition.h 8 | src/chooseRoot.cpp 9 | src/chooseRoot.h 10 | src/chopFakeEnds.cpp 11 | src/chopFakeEnds.h 12 | src/CircularSegment.cpp 13 | src/CircularSegment.h 14 | src/ContractDeg2.cpp 15 | src/ContractDeg2.h 16 | src/ContractLoops.cpp 17 | src/ContractLoops.h 18 | src/ContractLoops2.cpp 19 | src/ContractLoops2.h 20 | src/Equations.h 21 | src/FillHole.cpp 22 | src/FillHole.h 23 | src/FindRoots.cpp 24 | src/FindRoots.h 25 | src/findSingularities.cpp 26 | src/findSingularities.h 27 | src/graph_typedefs.cpp 28 | src/graph_typedefs.h 29 | src/greedyTrace.cpp 30 | src/greedyTrace.h 31 | src/HermiteSpline.h 32 | src/intersections.cpp 33 | src/intersections.h 34 | src/IsLoopContractible.cpp 35 | src/IsLoopContractible.h 36 | src/l2_regularizer.cpp 37 | src/l2_regularizer.h 38 | src/LBFGS.h 39 | src/main.cpp 40 | src/Optimizer.cpp 41 | src/Optimizer.h 42 | src/Params.h 43 | src/polynomial_energy.cpp 44 | src/polynomial_energy.h 45 | src/RemoveShortBranches.cpp 46 | src/RemoveShortBranches.h 47 | src/ScanConvert.cpp 48 | src/ScanConvert.h 49 | src/simple_svg_1.0.0.cpp 50 | src/simple_svg_1.0.0.hpp 51 | src/Simplify.cpp 52 | src/Simplify.h 53 | src/Smooth.cpp 54 | src/Smooth.h 55 | src/SplitEmUp.cpp 56 | src/SplitEmUp.h 57 | src/stdafx.cpp 58 | src/stdafx.h 59 | src/TopoGraphEmbedding.cpp 60 | src/TopoGraphEmbedding.h 61 | src/TotalEnergy.cpp 62 | src/TotalEnergy.h 63 | src/traceAuto.cpp 64 | src/traceAuto.h 65 | src/typedefs.cpp 66 | src/typedefs.h 67 | ) 68 | 69 | if (WITH_GUI) 70 | list(APPEND TARGET_SOURCES src/imagequiverviewer.cpp src/imagequiverviewer.hpp src/mainwindow.cpp src/mainwindow.h src/MyScrollArea.cpp src/MyScrollArea.h) 71 | endif() 72 | 73 | target_sources(polyvector_thing PRIVATE ${TARGET_SOURCES}) -------------------------------------------------------------------------------- /src/FillHole.h: -------------------------------------------------------------------------------- 1 | #ifndef _FILL_HOLE_H_ 2 | #define _FILL_HOLE_H_ 3 | #include "stdafx.h" 4 | #include "graph_typedefs.h" 5 | 6 | template 7 | std::vector> fillHole(TIterator holeBegin, TIterator holeEnd, G& g, const std::vector>& prohibitedEdges) 8 | { 9 | std::vector> result; 10 | std::map> myCurves; 11 | for (TIterator it = holeBegin; it != holeEnd; ++it) 12 | { 13 | for (const auto& p : g[*it].clusterPoints) 14 | myCurves[*it].insert(p.curve); 15 | } 16 | 17 | typedef std::pair, size_t> UglyType; 18 | std::vector nSharedCurves; 19 | 20 | for (TIterator it = holeBegin; it != holeEnd; ++it) 21 | { 22 | for (TIterator kIt = holeBegin; kIt != it; ++kIt) 23 | { 24 | std::pair p = { (int)*it,(int)*kIt }, p1 = {(int)*kIt, (int)*it}; 25 | if ((std::find(prohibitedEdges.begin(), prohibitedEdges.end(), p) != prohibitedEdges.end()) 26 | || (std::find(prohibitedEdges.begin(), prohibitedEdges.end(), p1) != prohibitedEdges.end())) 27 | continue; 28 | 29 | std::vector intersection; 30 | std::set_intersection(myCurves[*it].begin(), myCurves[*it].end(), myCurves[*kIt].begin(), myCurves[*kIt].end(), std::back_inserter(intersection)); 31 | nSharedCurves.push_back({ { *it, *kIt }, intersection.size() }); 32 | } 33 | } 34 | 35 | std::map connected; 36 | std::sort(nSharedCurves.begin(), nSharedCurves.end(), [](const UglyType& one, const UglyType& two) {return one.second > two.second; }); 37 | int nEdgesAdded = 0; 38 | for (auto it : nSharedCurves) 39 | { 40 | if ((std::distance(holeBegin,holeEnd) == 4) && (connected[it.first.first] || connected[it.first.second])) 41 | continue; 42 | connected[it.first.first] = true; 43 | connected[it.first.second] = true; 44 | 45 | std::cout << "CONNECTING vertex " << it.first.first << " to " << it.first.second << "(" << it.second << " shared curves)" << std::endl; 46 | 47 | result.push_back(it.first); 48 | ++nEdgesAdded; 49 | if (nEdgesAdded == 2) 50 | break; 51 | } 52 | return result; 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/ContractLoops2.cpp: -------------------------------------------------------------------------------- 1 | #include "ContractLoops2.h" 2 | #include 3 | #include "IsLoopContractible.h" 4 | struct Visitor 5 | { 6 | Visitor(std::vector>& loops) 7 | :loops(loops){ 8 | } 9 | 10 | template 11 | void cycle(const std::vector& p, const Graph & g) 12 | { 13 | //20 can be changed to anything large, 14 | if ((p.size() > 2) && (p.size() < 20)) 15 | { 16 | loops.push_back(p); 17 | } 18 | } 19 | 20 | std::vector>& loops; 21 | }; 22 | 23 | std::vector contractLoops2(G& g, const cv::Mat & origMask, const std::vector& polys) 24 | { 25 | using dirG = boost::adjacency_list; 26 | dirG gCopy(boost::num_vertices(g)); 27 | edge_iter eit, eend; 28 | for (std::tie(eit, eend) = boost::edges(g); eit != eend; ++eit) 29 | { 30 | boost::add_edge(eit->m_source, eit->m_target, gCopy); 31 | boost::add_edge(eit->m_target, eit->m_source, gCopy); 32 | } 33 | std::vector> loops; 34 | Visitor visitor(loops); 35 | boost::hawick_circuits(gCopy, visitor); 36 | 37 | std::vector> realLoops; 38 | std::vector> contractibleLoops; 39 | std::cout << "TOTAL # loops: " << loops.size() << std::endl; 40 | for (int i = 0; i < loops.size(); ++i) 41 | { 42 | const auto& loop = loops[i]; 43 | std::vector edge_loop; 44 | for (int j = 0; j < loop.size(); ++j) 45 | { 46 | edge_loop.push_back(boost::edge(loop[j], loop[(j + 1)% loop.size()],g).first); 47 | } 48 | 49 | std::vector realLoop; 50 | if (isLoopContractible(edge_loop, origMask, g, polys, realLoop)) 51 | { 52 | contractibleLoops.push_back(edge_loop); 53 | 54 | std::cout << "FOUND A CONTRACTIBLE LOOP: "; 55 | for (size_t j : loop) 56 | std::cout << j << " "; 57 | std::cout << std::endl; 58 | } 59 | } 60 | 61 | auto removedEdges = contract_edges(contractibleLoops, g); 62 | std::vector result; 63 | result.insert(result.end(), removedEdges.begin(), removedEdges.end()); 64 | return result; 65 | } 66 | -------------------------------------------------------------------------------- /src/TopoGraphEmbedding.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "graph_typedefs.h" 3 | 4 | struct Distances 5 | { 6 | enum SeedType 7 | { 8 | ST_REGULAR, ST_VAL2, ST_LOOP, ST_ADJ_TO_ANOTHER_SEED 9 | }; 10 | 11 | Distances(const G& g, const std::vector>& topoGraph, const std::vector>& chainsSeparated, const cv::Mat& bwImg); 12 | bool reconstructPath(int fixedVertex, int fixedVertexSample, int embeddedVertex, int embeddedSample, int seedIdx, int chainIdx, std::vector>& path); 13 | std::vector, int>> clusterAndPtToIndex; 14 | std::vector>> indexToClusterAndPt; 15 | std::vector>> D; 16 | std::vector>> parent; 17 | typedef boost::adjacency_list>> MyWeirdGraph; 19 | const G& g; 20 | std::vector>> graphs; 21 | std::vector> vertexSets; // 22 | const std::vector>& topoGraph; 23 | const std::vector>& chainsSeparated; 24 | std::vector seeds; 25 | std::vector seedType; 26 | std::map> adjChains; //specifies indices of chains adjacent to that vertex 27 | std::map orient(const G& g, const std::vector& chains, const std::set& vertSet, int root, const std::set& cutThoseEdges); 28 | std::pair> distanceBetweenSamples(int v1, int s1, int v2, int s2); 29 | MyPolyline convertToActualPolyline(const G& g, const std::vector>& vec, const std::vector& polys, std::vector& radii); 30 | void fillInSeedTypes(); 31 | 32 | std::map, std::vector> vertexAndCurveToSamples; 33 | std::map, std::vector> pairOfVerticesToSharedCurves; 34 | }; 35 | 36 | std::tuple, std::vector>, std::vector>, std::vector>, std::vector> > topoGraphEmbedding(G& g, const std::vector& polys, const cv::Mat& bwImg); 37 | -------------------------------------------------------------------------------- /src/TotalEnergy.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "TotalEnergy.h" 3 | #include "polynomial_energy.h" 4 | #include "l2_regularizer.h" 5 | #include 6 | #include 7 | #include "Params.h" 8 | TotalEnergy::TotalEnergy(cv::Mat & bwImg, const Eigen::MatrixXd & weight, const Eigen::MatrixXcd & tauNormalized, double beta, cv::Mat & mask, const Eigen::MatrixXi& indices, int nnz) 9 | :bwImg(bwImg),weight(weight),tauNormalized(tauNormalized),beta(beta),mask(mask),noisy(noisy),indices(indices),nnz(nnz) 10 | { 11 | m = bwImg.rows; 12 | n = bwImg.cols; 13 | 14 | alpha = FRAME_FIELD_REGULARIZER_WEIGHT; 15 | smartWeights = Eigen::MatrixXd(m,n); 16 | smartWeights.setOnes(); 17 | for (int i=1; i(0, 1); 27 | } 28 | 29 | double TotalEnergy::operator()(const Eigen::VectorXd & x, Eigen::VectorXd & grad) 30 | { 31 | Eigen::VectorXcd g1, g2, g3; 32 | double e1, e2, e3; 33 | Eigen::VectorXcd x_complex = x.head(x.size() / 2) + std::complex(0, 1)*x.tail(x.size() / 2); 34 | std::vector energiesOut; 35 | std::tie(e1,g1) = polynomial_energy(x_complex, weight, tauNormalized, mask, indices, energiesOut,true); 36 | 37 | std::tie(e2, g2) = polynomial_energy(x_complex, onesMatrix, g, mask, indices, energiesOut, true); 38 | int c = cv::countNonZero(mask); 39 | std::tie(e3, g3) = l2_regularizer(x_complex, smartWeights, mask, indices,true); 40 | 41 | double totalEnergy = e1 + alpha*e2 + beta*e3; 42 | Eigen::VectorXcd grad_complex = g1 + alpha*g2 + beta*g3; 43 | grad.setZero(); 44 | grad << grad_complex.real() , grad_complex.imag(); 45 | 46 | return totalEnergy; 47 | } 48 | 49 | double TotalEnergy::energyOnly(const Eigen::VectorXd & x) 50 | { 51 | Eigen::VectorXcd g1, g2, g3; 52 | double e1, e2, e3; 53 | Eigen::VectorXcd x_complex = x.head(x.size() / 2) + std::complex(0, 1)*x.tail(x.size() / 2); 54 | std::vector energiesOut; 55 | std::tie(e1, g1) = polynomial_energy(x_complex, weight, tauNormalized, mask, indices, energiesOut, false); 56 | 57 | std::tie(e2, g2) = polynomial_energy(x_complex, onesMatrix, g, mask, indices, energiesOut, false); 58 | std::tie(e3, g3) = l2_regularizer(x_complex, smartWeights, mask, indices, false); 59 | 60 | double totalEnergy = e1 + alpha*e2 + beta*e3; 61 | return totalEnergy; 62 | } -------------------------------------------------------------------------------- /src/typedefs.h: -------------------------------------------------------------------------------- 1 | #ifndef _TYPEDEFS_H_ 2 | #define _TYPEDEFS_H_ 3 | 4 | #define QT_NO_DEBUG_OUTPUT 1 5 | 6 | #include "Eigen/Dense" 7 | #include "opencv2/core/core.hpp" 8 | #include 9 | #include 10 | 11 | typedef std::vector MyPolyline; 12 | typedef Eigen::Matrix BoolMatrix; 13 | struct PixelInfo 14 | { 15 | int curve; 16 | Eigen::Vector2d p, tangent; 17 | double segmentIdx; 18 | }; 19 | 20 | struct CenterFit 21 | { 22 | Eigen::Vector2d center, axis; 23 | double res; 24 | }; 25 | 26 | struct PointOnCurve 27 | { 28 | int curve; 29 | double segmentIdx; 30 | Eigen::Vector2d p; 31 | Eigen::Vector2d root; 32 | }; 33 | 34 | struct Box 35 | { 36 | double xMin, xMax, yMin, yMax; 37 | Box() :xMin(std::numeric_limits::max()), xMax(std::numeric_limits::min()), yMin(std::numeric_limits::max()), yMax(std::numeric_limits::min()) 38 | {} 39 | 40 | bool inside(const Eigen::Vector2d& p) const 41 | { 42 | return (p.x() > xMin) && (p.x() < xMax) && (p.y() < yMax) && (p.y() > yMin); 43 | } 44 | bool completelyOutside(const Eigen::Vector2d& p1, const Eigen::Vector2d& p2) 45 | { 46 | if ((p1.x() < xMin) && (p2.x() < xMin)) 47 | return true; 48 | if ((p1.x() > xMax) && (p2.x() > xMax)) 49 | return true; 50 | if ((p1.y() < yMin) && (p2.y() < yMin)) 51 | return true; 52 | if ((p1.y() > yMax) && (p2.y() > yMax)) 53 | return true; 54 | return false; 55 | } 56 | }; 57 | 58 | Box findBoundingBox(const MyPolyline& poly); 59 | 60 | Eigen::Vector2d _toEig(std::complex c); 61 | void bitwiseOr(BoolMatrix& lhs, const BoolMatrix& rhs); 62 | bool doesNeighborExist(int i, int j, int m, int n, bool vertical, bool left); 63 | bool useNeighbor(int i, int j, int m, int n, bool vertical, bool left, const cv::Mat & mask, std::pair& outNeighbor); 64 | Eigen::Vector2d tangent(const MyPolyline& poly, int i); 65 | 66 | template 67 | T avg(const std::vector& vec, T zero) 68 | { 69 | T sum = zero; 70 | for (int i = 0; i < vec.size(); ++i) 71 | sum += vec[i]; 72 | return sum / vec.size(); 73 | } 74 | 75 | bool rootMatching(const std::array& roots, const std::array& p0, const std::array& p1); 76 | bool rootMatching(const std::array, 2>& r0, const std::array, 2>& r1); 77 | 78 | bool inBounds(const Eigen::Vector2d& p, const cv::Mat& mask); 79 | bool inBounds(const std::array& p, const cv::Mat& mask); 80 | bool inBounds(int i, int j, const cv::Mat& mask); 81 | #endif 82 | -------------------------------------------------------------------------------- /src/findSingularities.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "findSingularities.h" 3 | #include 4 | #include 5 | 6 | bool isItASingularity(const Eigen::Vector2d& p, const std::array,2> myRoots, const std::array& roots, const cv::Mat& origMask) 7 | { 8 | //return false; 9 | std::vector> shifts = { { -1,-1 },{ -1,0 },{ 0,-1 },{ 0,0 } }; 10 | int i = std::round(p.y()), j = std::round(p.x()); 11 | for (auto shift : shifts) 12 | { 13 | std::vector, 2>> pixelRoots; 14 | std::vector> pixelOrder = { { 0,0 },{ 0,1 },{ 1,1 },{ 1,0 } }; 15 | for (auto v : pixelOrder) 16 | { 17 | int i1 = i + shift[0] + v[0], j1 = j + shift[1] + v[1]; 18 | if (inBounds(i1, j1, origMask)) 19 | { 20 | if ((i == i1) && (j == j1)) 21 | pixelRoots.push_back(myRoots); 22 | else 23 | pixelRoots.push_back({ roots[0](i1,j1),roots[1](i1,j1) }); 24 | } 25 | } 26 | if (pixelRoots.size() == 4) 27 | { 28 | bool overallMatch = true; 29 | for (int k = 0; k < 4; ++k) 30 | { 31 | bool match = rootMatching(pixelRoots[k], pixelRoots[(k + 1) % 4]); 32 | overallMatch = !(match ^ overallMatch); 33 | } 34 | if (overallMatch == false) 35 | { 36 | return true; 37 | 38 | } 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | std::set> findSingularities(std::array& roots, Eigen::VectorXcd& X, const Eigen::MatrixXi& indices, const cv::Mat& origMask) 45 | { 46 | std::cout << "Singularities: "; 47 | std::set> singularities; 48 | int m = origMask.rows, n = origMask.cols; 49 | 50 | int maxThreads = omp_get_max_threads(); 51 | std::vector>> localSets(maxThreads); 52 | 53 | #pragma omp parallel for 54 | for (int i=0; i(i, j) == 0) 59 | continue; 60 | 61 | if (isItASingularity({ j,i }, { roots[0](i,j),roots[1](i,j) }, roots, origMask)) 62 | { 63 | /*if (singularities.find({i,j}) == singularities.end()) 64 | std::cout << i << ", " << j << "; "; 65 | singularities.insert({ i,j });*/ 66 | localSets[tid].insert({ i,j }); 67 | } 68 | } 69 | 70 | for (const auto& ls : localSets) 71 | { 72 | for (const auto& p : ls) 73 | { 74 | if (singularities.find(p) == singularities.end()) 75 | std::cout << p[0] << ", " << p[1] << "; "; 76 | singularities.insert(p); 77 | } 78 | } 79 | std::cout << std::endl; 80 | 81 | return singularities; 82 | } -------------------------------------------------------------------------------- /cmake/FindGUROBI.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find GUROBI 2 | 3 | # GUROBI_BASE - The libraries needed to use Gurobi 4 | 5 | # Once done this will define 6 | # GUROBI_FOUND - System has Gurobi 7 | # GUROBI_INCLUDE_DIRS - The Gurobi include directories 8 | # GUROBI_LIBRARIES - The libraries needed to use Gurobi 9 | 10 | 11 | set (GUROBI_BASE "c:" CACHE PATH "Base path of your gurobi installation") 12 | 13 | if (GUROBI_INCLUDE_DIR) 14 | # in cache already 15 | set(GUROBI_FOUND TRUE) 16 | set(GUROBI_INCLUDE_DIRS "${GUROBI_INCLUDE_DIR}" ) 17 | set(GUROBI_LIBRARIES "${GUROBI_CXX_LIBRARY};${GUROBI_LIBRARY}" ) 18 | else (GUROBI_INCLUDE_DIR) 19 | 20 | 21 | 22 | find_path(GUROBI_INCLUDE_DIR 23 | NAMES gurobi_c++.h 24 | PATHS "$ENV{GUROBI_HOME}/include" 25 | "C:\\gurobi752\\include" 26 | "${GUROBI_BASE}/include" 27 | ) 28 | 29 | find_library( GUROBI_LIBRARY 30 | NAMES gurobi 31 | gurobi75 32 | gurobi60 33 | gurobi56 34 | gurobi55 35 | gurobi51 36 | gurobi50 37 | gurobi46 38 | gurobi45 39 | 40 | PATHS "$ENV{GUROBI_HOME}/lib" 41 | "C:\\gurobi752\\lib" 42 | "${GUROBI_BASE}/lib" 43 | ) 44 | 45 | if ( CMAKE_GENERATOR MATCHES "^Visual Studio 14.*") 46 | SET(GUROBI_LIB_NAME "gurobi_c++md2015") 47 | endif() 48 | 49 | find_library( GUROBI_CXX_LIBRARY 50 | NAMES gurobi_c++ 51 | ${GUROBI_LIB_NAME} 52 | PATHS "$ENV{GUROBI_HOME}/lib" 53 | "C:\\gurobi752\\lib" 54 | "${GUROBI_BASE}/lib" 55 | ) 56 | 57 | # Binary dir for DLLs 58 | find_path(GUROBI_BIN_DIR 59 | NAMES "gurobi75.dll" 60 | PATHS "${GUROBI_INCLUDE_DIR}/../bin" 61 | "${GUROBI_BASE}/bin" 62 | DOC "Directory containing the GUROBI DLLs" 63 | ) 64 | 65 | set(GUROBI_INCLUDE_DIRS "${GUROBI_INCLUDE_DIR}" ) 66 | set(GUROBI_LIBRARIES "${GUROBI_CXX_LIBRARY};${GUROBI_LIBRARY}" ) 67 | 68 | # use c++ headers as default 69 | # set(GUROBI_COMPILER_FLAGS "-DIL_STD" CACHE STRING "Gurobi Compiler Flags") 70 | 71 | include(FindPackageHandleStandardArgs) 72 | # handle the QUIETLY and REQUIRED arguments and set LIBCPLEX_FOUND to TRUE 73 | # if all listed variables are TRUE 74 | find_package_handle_standard_args(GUROBI DEFAULT_MSG 75 | GUROBI_CXX_LIBRARY GUROBI_LIBRARY GUROBI_INCLUDE_DIR) 76 | 77 | mark_as_advanced(GUROBI_INCLUDE_DIR GUROBI_LIBRARY GUROBI_CXX_LIBRARY GUROBI_BIN_DIR ) 78 | 79 | endif(GUROBI_INCLUDE_DIR) -------------------------------------------------------------------------------- /src/ContractDeg2.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "ContractDeg2.h" 3 | #include "ChainDecomposition.h" 4 | std::vector> topoGraph(const G & g) 5 | { 6 | std::map myChain; 7 | auto chains = chainDecomposition(g, myChain); 8 | std::vector> result(chains.size()); 9 | for (int i = 0; i < chains.size(); ++i) 10 | result[i] = { chains[i].front().m_source,chains[i].back().m_target }; 11 | return result; 12 | } 13 | 14 | std::vector> topoGraphHighValenceSeparated(const G & g, std::vector>& chainsSeparated, bool onlyLoops) 15 | { 16 | std::map myChain; 17 | auto chains = chainDecomposition(g, myChain); 18 | std::vector> result; 19 | for (int i = 0; i < chains.size(); ++i) 20 | { 21 | if (((boost::degree(chains[i].front().m_source, g) > 2 && boost::degree(chains[i].back().m_target, g) > 2) && !onlyLoops) || 22 | (chains[i].front().m_source==chains[i].back().m_target)) 23 | { 24 | if (chains[i].size() == 1) 25 | { 26 | //two high valence vertices connected by a single edge 27 | result.push_back({ chains[i].front().m_source,chains[i].back().m_target }); 28 | chainsSeparated.push_back(chains[i]); 29 | } 30 | else 31 | { 32 | size_t sepVertex = chains[i][chains[i].size() / 2].m_source; 33 | if (g[sepVertex].split) 34 | { 35 | //find another one! 36 | double d = std::numeric_limits::max(); 37 | int bestSepVertex = -1; 38 | for (size_t vv = 1; vv < chains[i].size(); ++vv) 39 | { 40 | size_t vtx = chains[i][vv].m_source; 41 | if (!g[vtx].split) 42 | { 43 | double dist = fabs(chains[i].size() / 2 - vv); 44 | if (dist < d) 45 | { 46 | d = dist; 47 | bestSepVertex = vtx; 48 | } 49 | } 50 | } 51 | if (bestSepVertex >= 0) 52 | sepVertex = bestSepVertex; 53 | } 54 | result.push_back({ chains[i].front().m_source, sepVertex}); 55 | result.push_back({ sepVertex, chains[i].back().m_target }); 56 | 57 | for (int j = 0; j < chains[i].size(); ++j) 58 | { 59 | if (chains[i][j].m_source == sepVertex) 60 | { 61 | std::vector chain1, chain2; 62 | chain1.insert(chain1.begin(), chains[i].begin(), chains[i].begin() + j); 63 | chain2.insert(chain2.begin(), chains[i].begin() + j, chains[i].end()); 64 | chainsSeparated.push_back(chain1); 65 | chainsSeparated.push_back(chain2); 66 | break; 67 | } 68 | } 69 | } 70 | 71 | } 72 | else 73 | { 74 | result.push_back({ chains[i].front().m_source,chains[i].back().m_target }); 75 | chainsSeparated.push_back(chains[i]); 76 | } 77 | } 78 | return result; 79 | } 80 | 81 | void contractDeg2(G & g) 82 | { 83 | auto edges = topoGraph(g); 84 | for (size_t v = 0; v < boost::num_vertices(g); ++v) 85 | { 86 | boost::clear_vertex(v, g); 87 | } 88 | for (auto e : edges) 89 | boost::add_edge(e.first, e.second, g); 90 | } 91 | -------------------------------------------------------------------------------- /src/typedefs.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "typedefs.h" 3 | 4 | Eigen::Vector2d _toEig(std::complex c) 5 | { 6 | return Eigen::Vector2d(c.real(), c.imag()); 7 | } 8 | 9 | Box findBoundingBox(const MyPolyline& poly) 10 | { 11 | Box result; 12 | for (int i = 0; i < poly.size(); ++i) 13 | { 14 | result.xMin = std::min(result.xMin, poly[i].x()); 15 | result.xMax = std::max(result.xMax, poly[i].x()); 16 | result.yMin = std::min(result.yMin, poly[i].y()); 17 | result.yMax = std::max(result.yMax, poly[i].y()); 18 | } 19 | return result; 20 | } 21 | 22 | void bitwiseOr(BoolMatrix& lhs, const BoolMatrix& rhs) 23 | { 24 | #pragma omp parallel for 25 | for (int i = 0; i < lhs.rows(); ++i) 26 | for (int j = 0; j < lhs.cols(); ++j) 27 | lhs(i, j) = lhs(i, j) || rhs(i, j); 28 | } 29 | 30 | bool doesNeighborExist(int i, int j, int m, int n, bool vertical, bool left) 31 | { 32 | int coord = vertical ? i : j; 33 | int bounds = vertical ? m : n; 34 | if (left) 35 | return coord > 0; 36 | else 37 | return coord < bounds - 1; 38 | } 39 | 40 | bool useNeighbor(int i, int j, int m, int n, bool vertical, bool left, const cv::Mat & mask, std::pair& outNeighbor) 41 | { 42 | int shift = left ? -1 : 1; 43 | int iNeigh = vertical ? i + shift : i; 44 | int jNeigh = !vertical ? j + shift : j; 45 | 46 | bool result = doesNeighborExist(i, j, m, n, vertical, left) && (mask.at(iNeigh, jNeigh) != 0); 47 | if (result) 48 | { 49 | outNeighbor.first = iNeigh; 50 | outNeighbor.second = jNeigh; 51 | //outNeighbor = std::make_pair(iNeigh, jNeigh); 52 | } 53 | return result; 54 | } 55 | 56 | Eigen::Vector2d tangent(const MyPolyline& poly, int i) 57 | { 58 | Eigen::Vector2d result = (i == poly.size() - 1 ? poly[i] - poly[i - 1] : poly[i + 1] - poly[i]); 59 | return result; 60 | } 61 | 62 | bool rootMatching(const std::array, 2>& r0, const std::array, 2>& r1) 63 | { 64 | Eigen::Vector2d root00 = _toEig(r0[0]), root01 = _toEig(r0[1]), root10 = _toEig(r1[0]), root11 = _toEig(r1[1]); 65 | double dist0011 = std::max(fabs(root00.normalized().dot(root10.normalized())), fabs(root01.normalized().dot(root11.normalized()))); 66 | double dist0110 = std::max(fabs(root00.normalized().dot(root11.normalized())), fabs(root01.normalized().dot(root10.normalized()))); 67 | return dist0011 > dist0110; 68 | } 69 | 70 | bool rootMatching(const std::array& roots, const std::array& p0, const std::array& p1) 71 | { 72 | return rootMatching({ roots[0](p0[0], p0[1]), roots[1](p0[0], p0[1]) }, 73 | { roots[0](p1[0], p1[1]), roots[1](p1[0], p1[1]) }); 74 | } 75 | 76 | bool inBounds(const Eigen::Vector2d & p, const cv::Mat & mask) 77 | { 78 | int m = mask.rows, n = mask.cols; 79 | cv::Point cvPoint(std::round(p.x()), std::round(p.y())); 80 | return !((p.x() < 0) || (p.y() < 0) || (p.x() > n - 1) || (p.y() > m - 1) || (mask.at(cvPoint) == 0)); 81 | } 82 | 83 | bool inBounds(const std::array& p, const cv::Mat & mask) 84 | { 85 | int m = mask.rows, n = mask.cols; 86 | return !((p[0] < 0) || (p[1] < 0) || (p[0] > m - 1) || (p[1] > n - 1) || (mask.at(p[0],p[1]) == 0)); 87 | } 88 | 89 | bool inBounds(int i, int j, const cv::Mat & mask) 90 | { 91 | std::array p = { i,j }; 92 | return inBounds(p, mask); 93 | } 94 | -------------------------------------------------------------------------------- /src/CircularSegment.cpp: -------------------------------------------------------------------------------- 1 | #include "CircularSegment.h" 2 | #include 3 | #include 4 | CircularSegment::CircularSegment (double begin, double end, double modulus) 5 | :begin_(begin),end_(end),modulus_(modulus) 6 | { 7 | 8 | } 9 | 10 | std::vector CircularSegment::unionWith (const CircularSegment& other, double tolerance) const 11 | { 12 | CircularSegment intersection; 13 | if (!intersectionWith(other,intersection, tolerance)) 14 | { 15 | std::vector result; 16 | result.push_back(*this); 17 | result.push_back(other); 18 | return result; 19 | } 20 | 21 | std::vector result; 22 | result.push_back(*this); 23 | result[0].extendToContainPoint(other.begin()); 24 | result[0].extendToContainPoint(other.end()); 25 | return result; 26 | } 27 | 28 | double CircularSegment::pointAtT(double t) const 29 | { 30 | if ((modulus_ < 0) || (end_+1e-10 > begin_)) 31 | return begin_*(1-t)+end_*t; 32 | else 33 | { 34 | double result = begin_*(1-t) + t*(end_ + modulus_); 35 | if (result > modulus_) 36 | result -= modulus_; 37 | return result; 38 | } 39 | } 40 | 41 | std::vector CircularSegment::complement() const 42 | { 43 | std::vector result; 44 | CircularSegment s1 (0,begin_,modulus_), s2(end_,modulus_,modulus_); 45 | result.push_back(s1); 46 | result.push_back(s2); 47 | return result; 48 | } 49 | 50 | bool CircularSegment::intersectionWith (const CircularSegment& other, CircularSegment& intersection, double tolerance) const 51 | { 52 | assert(fabs(other.modulus()- modulus())<1e-10); 53 | 54 | if (isInside(other.begin(),tolerance)) 55 | { 56 | if (isInside(other.end(),tolerance)) 57 | { 58 | intersection = other; 59 | return true; 60 | } 61 | 62 | intersection = CircularSegment(other.begin(),end(),modulus()); 63 | return true; 64 | } 65 | 66 | if (isInside(other.end(),tolerance)) 67 | { 68 | intersection = CircularSegment(begin(),other.end(),modulus()); 69 | return true; 70 | } 71 | 72 | if (other.isInside(end(),tolerance)) 73 | { 74 | if (other.isInside(begin(),tolerance)) 75 | { 76 | intersection = *this; 77 | return true; 78 | } 79 | intersection = CircularSegment(other.begin(),end(),modulus()); 80 | return true; 81 | } 82 | 83 | if (other.isInside(begin(),tolerance)) 84 | { 85 | intersection = CircularSegment(begin(),other.end(),modulus()); 86 | return true; 87 | } 88 | return false; 89 | } 90 | 91 | void CircularSegment::extendToContainPoint (double p) 92 | { 93 | if (isInside (p)) 94 | return; 95 | 96 | if (modulus_ > 0) 97 | { 98 | //update left or right 99 | CircularSegment leftExtension (p,end(),modulus()); 100 | CircularSegment rightExtension (begin(),p,modulus()); 101 | if (leftExtension.length() end_) 115 | *this = CircularSegment(begin_, p); 116 | } 117 | } 118 | 119 | bool CircularSegment::isInside (double p, double tolerance) const 120 | { 121 | if (modulus_>0) 122 | { 123 | double diff = fabs(CircularSegment(begin_,p,modulus_).length() + CircularSegment(p,end_,modulus_).length() - length()); 124 | return (diff < tolerance); 125 | } 126 | else 127 | { 128 | return ((begin_ - tolerance < p) && (end_ + tolerance > p)); 129 | } 130 | } 131 | 132 | double CircularSegment::length() const 133 | { 134 | double s = end_ - begin_; 135 | if (s < 0) 136 | { 137 | if (modulus_ > 0) 138 | s+=modulus_; 139 | else 140 | return 1e100; 141 | } 142 | return s; 143 | } -------------------------------------------------------------------------------- /src/HermiteSpline.h: -------------------------------------------------------------------------------- 1 | #ifndef _HERMITE_SPLINE_H_ 2 | #define _HERMITE_SPLINE_H_ 3 | 4 | #include "typedefs.h" 5 | #include 6 | 7 | class HermiteSpline 8 | { 9 | public: 10 | // 11 | HermiteSpline(const Eigen::Vector2d& P0, const Eigen::Vector2d& N0, const Eigen::Vector2d& P1, const Eigen::Vector2d& N1) 12 | :P0_(P0), P1_(P1), N0_(N0), N1_(N1) 13 | { 14 | } 15 | 16 | /* Centripetal Catmull-Rom: interpolating spline for four points. Draws from P1 to P2 only. 17 | Reference: http://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline 18 | */ 19 | static HermiteSpline fromCatmullRom(const std::array& P) 20 | { 21 | /*Taken from http://stackoverflow.com/a/23980479/2929337 */ 22 | 23 | std::array t; 24 | std::array dt; 25 | 26 | for (int i = 0; i < 3; i++) 27 | { 28 | dt[i] = std::pow((P[i + 1] - P[i]).squaredNorm(), 0.25); 29 | } 30 | 31 | // safety check for repeated points 32 | if (dt[1] < 1e-7) dt[1] = 1.0; 33 | if (dt[0] < 1e-7) dt[0] = dt[1]; 34 | if (dt[2] < 1e-7) dt[2] = dt[1]; 35 | 36 | t[0] = 0; 37 | for (int i = 1; i < 4; i++) 38 | t[i] = t[i - 1] + dt[i - 1]; 39 | 40 | Eigen::Vector2d N1 = (P[1] - P[0]) / dt[0] - (P[2] - P[0]) / (dt[0] + dt[1]) + (P[2] - P[1]) / dt[1]; 41 | Eigen::Vector2d N2 = (P[2] - P[1]) / dt[1] - (P[3] - P[1]) / (dt[1] + dt[2]) + (P[3] - P[2]) / dt[2]; 42 | N1 *= dt[1]; 43 | N2 *= dt[2]; 44 | return HermiteSpline(P[1], N1, P[2], N2); 45 | } 46 | 47 | double getLength() const 48 | { 49 | //The length of Hermite Curve is can't be calculated analytically. 50 | //Instead of this, one may use Runge-Cutta method, but this works as well. 51 | Eigen::Vector2d prevPoint = getPoint(0); 52 | const int samples = 50; 53 | double length = 0; 54 | for (int i = 1; i <= samples; i++) 55 | { 56 | double t = (double)i / samples; 57 | Eigen::Vector2d p = getPoint(t); 58 | length += (p - prevPoint).norm(); 59 | prevPoint = p; 60 | } 61 | return length; 62 | } 63 | 64 | 65 | Eigen::Vector2d getPoint(double t) const 66 | { 67 | return r(t); 68 | } 69 | 70 | 71 | Eigen::Vector2d getTangent(double t) const 72 | { 73 | return dr(t); 74 | } 75 | 76 | double getAbsCurvature(double t) 77 | { 78 | Eigen::Vector2d drt = dr(t); 79 | Eigen::Vector2d ddrt = ddr(t); 80 | double drAbs = drt.norm(); 81 | Eigen::Vector3d dr3(drt.x(), drt.y(), 0); 82 | Eigen::Vector3d ddr3(ddrt.x(), ddrt.y(), 0); 83 | return dr3.cross(ddr3).norm() / (drAbs*drAbs*drAbs); 84 | } 85 | 86 | double integrateAbsCurvature() 87 | { 88 | const int nSteps = 100; 89 | double sum = 0; 90 | double max = 0; 91 | double arcLength = 0; 92 | Eigen::Vector2d p = r(0); 93 | for (int i = 0; i < nSteps; i++) 94 | { 95 | double t = (double)i / nSteps; 96 | double k = getAbsCurvature(t); 97 | Eigen::Vector2d newP = r(t); 98 | sum += k*(newP - p).norm(); 99 | p = newP; 100 | } 101 | 102 | return sum; 103 | } 104 | 105 | 106 | 107 | HermiteSpline& operator =(const HermiteSpline &p) 108 | { 109 | P0_ = p.P0_; 110 | N0_ = p.N0_; 111 | P1_ = p.P1_; 112 | N1_ = p.N1_; 113 | return *this; 114 | } 115 | 116 | private: 117 | //radius-vector and its derivatives 118 | Eigen::Vector2d r(double t) const 119 | { 120 | double t3 = t*t*t; 121 | double t2 = t*t; 122 | return P0_*(2 * t3 - 3 * t2 + 1) + N0_*(t3 - 2 * t2 + t) + P1_*(-2 * t3 + 3 * t2) + N1_*(t3 - t2); 123 | } 124 | 125 | Eigen::Vector2d dr(double t) const 126 | { 127 | double t2 = t*t; 128 | return P0_*(6 * t2 - 6 * t) + N0_*(3 * t2 - 4 * t + 1) + P1_*(-6 * t2 + 6 * t) + N1_*(3 * t2 - 2 * t); 129 | } 130 | 131 | Eigen::Vector2d ddr(double t) const 132 | { 133 | return P0_*(12 * t - 6) + N0_*(6 * t - 4) + P1_*(-12 * t + 6) + N1_*(6 * t - 2); 134 | } 135 | 136 | private: 137 | Eigen::Vector2d P0_, P1_; 138 | Eigen::Vector2d N0_, N1_; 139 | }; 140 | 141 | #endif -------------------------------------------------------------------------------- /src/traceAuto.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "traceAuto.h" 3 | #include "greedyTrace.h" 4 | #include 5 | #include "opencv2/core/eigen.hpp" 6 | #include "opencv2/highgui/highgui.hpp" 7 | #include "opencv2/imgproc/imgproc.hpp" 8 | #include 9 | 10 | std::vector traceAll(const cv::Mat & bwImg, const cv::Mat & origMask, const cv::Mat & extMask, const std::array& roots, const Eigen::VectorXcd & X, const Eigen::MatrixXi& indices, std::map, std::vector>& pixelInfo, std::vector>& endedWithASingularity) 11 | { 12 | std::vector result; 13 | std::vector> protectedEnds; 14 | 15 | int m = bwImg.rows, n = bwImg.cols; 16 | std::map, MyPolyline> resultPerPixel; 17 | std::map, int> chosenRootIdx; 18 | int curveIdx = 0; 19 | //before enabling the pragma, look again at curveIdx 20 | //#pragma omp parallel for 21 | for (int i = 0; i < m; ++i) 22 | { 23 | if (i%10 == 0) 24 | printf("%d/%d\r", i, m); 25 | 26 | for (int j = 0; j < n; ++j) 27 | { 28 | if ((origMask.at(i, j) == 0) || ((abs(roots[0](i,j))<1e-10) && (abs(roots[1](i, j))<1e-10))) 29 | continue; 30 | 31 | std::map, std::vector> outNewPixelInfo; 32 | std::pair> tracingResult; 33 | 34 | std::map, CenterFit> emptyFits; 35 | auto root = std::abs(roots[0](i, j)) > std::abs(roots[1](i, j)) ? roots[0](i, j) : roots[1](i, j); 36 | //next center found, start tracing 37 | Eigen::Vector2d seedCenter((double)j, (double)i); 38 | std::array distToSingularity = { std::numeric_limits::max(), std::numeric_limits::max() }; 39 | tracingResult = greedyTrace(origMask, roots, seedCenter, _toEig(root), X, pixelInfo, outNewPixelInfo, indices, curveIdx, false, distToSingularity); 40 | 41 | if (tracingResult.first.size() > 2) //otherwise it's just junk, man 42 | { 43 | //check it's not a duplicate 44 | std::set candidateCurves; 45 | for (auto &it : outNewPixelInfo) 46 | { 47 | for (auto &record : it.second) 48 | { 49 | for (auto &pi : pixelInfo[it.first]) 50 | candidateCurves.insert(pi.curve); 51 | } 52 | //candidates only for one pixel are more than enough 53 | break; 54 | } 55 | 56 | for (auto &it : outNewPixelInfo) 57 | { 58 | for (auto &record : it.second) 59 | { 60 | std::map bestDist; 61 | 62 | for (auto &pi : pixelInfo[it.first]) 63 | { 64 | if (candidateCurves.find(pi.curve) == candidateCurves.end()) 65 | continue; 66 | 67 | double dist = (pi.p - record.p).squaredNorm(); 68 | if (bestDist.find(pi.curve) != bestDist.end()) 69 | bestDist[pi.curve] = std::min(bestDist[pi.curve], dist); 70 | else 71 | bestDist[pi.curve] = dist; 72 | } 73 | 74 | for (auto &ii : bestDist) 75 | { 76 | if (ii.second > 0.01) 77 | candidateCurves.erase(ii.first); 78 | } 79 | 80 | if (candidateCurves.empty()) 81 | break; 82 | } 83 | 84 | if (candidateCurves.empty()) 85 | break; 86 | } 87 | 88 | if (!candidateCurves.empty()) 89 | continue; 90 | 91 | resultPerPixel[{i, j}] = tracingResult.first; 92 | endedWithASingularity.push_back({ distToSingularity[0] < 1e10, distToSingularity[1] < 1e10 }); 93 | curveIdx++; 94 | 95 | for (auto& it : outNewPixelInfo) 96 | { 97 | pixelInfo[it.first].insert(pixelInfo[it.first].end(), it.second.begin(), it.second.end()); 98 | } 99 | 100 | } 101 | } 102 | } 103 | 104 | for (int i = 0; i < m; ++i) 105 | for (int j = 0; j < n; ++j) 106 | { 107 | if (resultPerPixel.find({ i,j }) == resultPerPixel.end()) 108 | continue; 109 | 110 | result.insert(result.end(), resultPerPixel[{i, j}]); 111 | } 112 | 113 | std::cout << "Done. " << result.size() << " curves" << std::endl; 114 | return result; 115 | } -------------------------------------------------------------------------------- /src/LBFGS/LineSearch.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 Yixuan Qiu 2 | // Under MIT license 3 | 4 | #ifndef LINE_SEARCH_H 5 | #define LINE_SEARCH_H 6 | 7 | #include 8 | #include // std::runtime_error 9 | 10 | 11 | namespace LBFGSpp { 12 | 13 | 14 | /// 15 | /// Line search algorithms for LBFGS. Mainly for internal use. 16 | /// 17 | template 18 | class LineSearch 19 | { 20 | private: 21 | typedef Eigen::Matrix Vector; 22 | 23 | public: 24 | /// 25 | /// Line search by backtracking. 26 | /// 27 | /// \param f A function object such that `f(x, grad)` returns the 28 | /// objective function value at `x`, and overwrites `grad` with 29 | /// the gradient. 30 | /// \param fx In: The objective function value at the current point. 31 | /// Out: The function value at the new point. 32 | /// \param x Out: The new point moved to. 33 | /// \param grad In: The current gradient vector. Out: The gradient at the 34 | /// new point. 35 | /// \param step In: The initial step length. Out: The calculated step length. 36 | /// \param drt The current moving direction. 37 | /// \param xp The current point. 38 | /// \param param Parameters for the LBFGS algorithm 39 | /// 40 | template 41 | static void Backtracking(Foo& f, Scalar& fx, Vector& x, Vector& grad, 42 | Scalar& step, 43 | const Vector& drt, const Vector& xp, 44 | const LBFGSParam& param) 45 | { 46 | // Decreasing and increasing factors 47 | const Scalar dec = 0.5; 48 | const Scalar inc = 2.1; 49 | 50 | // Check the value of step 51 | if(step <= Scalar(0)) 52 | std::invalid_argument("'step' must be positive"); 53 | 54 | // Save the function value at the current x 55 | const Scalar fx_init = fx; 56 | // Projection of gradient on the search direction 57 | const Scalar dg_init = grad.dot(drt); 58 | // Make sure d points to a descent direction 59 | if(dg_init > 0) 60 | std::logic_error("the moving direction increases the objective function value"); 61 | 62 | const Scalar dg_test = param.ftol * dg_init; 63 | Scalar width; 64 | 65 | int iter; 66 | for(iter = 0; iter < param.max_linesearch; iter++) 67 | { 68 | // x_{k+1} = x_k + step * d_k 69 | x.noalias() = xp + step * drt; 70 | // Evaluate this candidate 71 | fx = f(x, grad); 72 | 73 | if(fx > fx_init + step * dg_test) 74 | { 75 | width = dec; 76 | } else { 77 | // Armijo condition is met 78 | if(param.linesearch == LBFGS_LINESEARCH_BACKTRACKING_ARMIJO) 79 | break; 80 | 81 | const Scalar dg = grad.dot(drt); 82 | if(dg < param.wolfe * dg_init) 83 | { 84 | width = inc; 85 | } else { 86 | // Regular Wolfe condition is met 87 | if(param.linesearch == LBFGS_LINESEARCH_BACKTRACKING_WOLFE) 88 | break; 89 | 90 | if(dg > -param.wolfe * dg_init) 91 | { 92 | width = dec; 93 | } else { 94 | // Strong Wolfe condition is met 95 | break; 96 | } 97 | } 98 | } 99 | 100 | if(iter >= param.max_linesearch) 101 | throw std::runtime_error("the line search routine reached the maximum number of iterations"); 102 | 103 | if(step < param.min_step) 104 | throw std::runtime_error("the line search step became smaller than the minimum value allowed"); 105 | 106 | if(step > param.max_step) 107 | throw std::runtime_error("the line search step became larger than the maximum value allowed"); 108 | 109 | step *= width; 110 | } 111 | } 112 | }; 113 | 114 | 115 | } // namespace LBFGSpp 116 | 117 | #endif // LINE_SEARCH_H 118 | -------------------------------------------------------------------------------- /src/polynomial_energy.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "polynomial_energy.h" 3 | #include 4 | #include 5 | #include 6 | std::pair polynomial_energy(const Eigen::VectorXcd & X, const Eigen::MatrixXd & weight, const Eigen::MatrixXcd & tau, const cv::Mat & mask, Eigen::MatrixXi& indices,std::vector& energiesOut, bool computeGrad) 7 | { 8 | int nonzeros = countNonZero(mask); 9 | Eigen::VectorXcd grad; 10 | if (computeGrad) 11 | { 12 | grad = Eigen::VectorXcd(nonzeros * 2); 13 | grad.setZero(); 14 | } 15 | 16 | std::vector energies(X.size()/2,0); 17 | int m = mask.rows; 18 | int n = mask.cols; 19 | 20 | //#pragma omp parallel for 21 | for (int j=0; j(i, j) == 0) || (std::abs(tau(i, j)) < 1e-6)) 26 | continue; 27 | 28 | auto myTau = tau(i, j); 29 | int idx = indices(i, j); 30 | 31 | auto x0 = X(idx); 32 | auto x2 = X(idx + nonzeros); 33 | 34 | auto res = x0 + x2*std::pow(myTau,2) + std::pow(myTau,4); 35 | energies[idx] += (res.real()*res.real() + res.imag()*res.imag())*weight(i,j); 36 | auto tmp2 = res.real() * res.real() + res.imag() * res.imag(); 37 | auto tmp1 = (res.real() * res.real() + res.imag() * res.imag()) - std::conj(std::pow(myTau, 4)) * std::pow(myTau, 4); 38 | if (computeGrad) 39 | { 40 | double a1 = x0.real(), b1 = x0.imag(), a2 = x2.real(), b2 = x2.imag(); 41 | double r = myTau.real(), m = myTau.imag(); 42 | 43 | grad(idx) = { 2 * a1 + (-2)*a2*pow(m,2) + 2 * pow(m,4) + (-4)*b2*m*r + 2 * a2*pow(r,2) + (-12)*pow(m,2)*pow(r,2) + 2 * pow(r,4), 44 | 2 * b1 + (-2)*b2*pow(m,2) + 4 * a2*m*r + (-8)*pow(m,3)*r + 2 * b2*pow(r,2) + 8 * m*pow(r,3) }; 45 | grad(idx) *= weight(i, j); 46 | 47 | grad(idx + nonzeros) = { (-2)*a1*pow(m,2) + 2 * a2*pow(m,4) + (-2)*pow(m,6) + 4 * b1*m*r + 2 * a1*pow(r,2) + 4 * a2*pow(m,2)*pow(r,2) + (-2)*pow(m,4)*pow(r,2) + 2 * a2*pow(r,4) + 2 * pow(m,2)*pow(r,4) + 2 * pow(r,6), 48 | (-2)*b1*pow(m,2) + 2 * b2*pow(m,4) + (-4)*a1*m*r + 4 * pow(m,5)*r + 2 * b1*pow(r,2) + 49 | 4 * b2*pow(m,2)*pow(r,2) + 8 * pow(m,3)*pow(r,3) + 2 * b2*pow(r,4) + 4 * m*pow(r,5) }; 50 | grad(idx+nonzeros) *= weight(i, j); 51 | } 52 | } 53 | 54 | double energy = std::accumulate(energies.begin(), energies.end(), 0.0); 55 | energiesOut = energies; //todo: get rid of the redundant copy 56 | return std::make_pair(energy, grad); 57 | } 58 | 59 | std::pair>, Eigen::VectorXcd> polynomial_energy_matrix(const Eigen::MatrixXd& weight, const Eigen::MatrixXcd& tau, const cv::Mat& mask, const Eigen::MatrixXi& indices) 60 | { 61 | typedef std::complex complex; 62 | 63 | int nonzeros = countNonZero(mask); 64 | 65 | std::vector energies(nonzeros, 0); 66 | int m = mask.rows; 67 | int n = mask.cols; 68 | 69 | Eigen::SparseMatrix A(nonzeros*2, nonzeros*2); 70 | Eigen::VectorXcd b(nonzeros*2); 71 | b.setZero(); 72 | 73 | double c = 0.0; 74 | 75 | //representing the final energy as 0.5 x* A x + (b*x + bx*) + c 76 | std::vector> trs; 77 | 78 | for (int j = 0; j < n; ++j) 79 | for (int i = 0; i < m; ++i) 80 | { 81 | double tmp = std::abs(tau(i, j)); 82 | if ((mask.at(i, j) == 0) || (std::abs(tau(i, j)) < 1e-6)) 83 | continue; 84 | 85 | auto myTau = tau(i, j); 86 | int idx = indices(i, j); 87 | 88 | trs.push_back({ idx, idx, weight(i, j) }); 89 | trs.push_back({ idx, idx + nonzeros, weight(i, j) * std::pow(myTau, 2) }); 90 | trs.push_back({ idx+nonzeros, idx, weight(i, j) * std::conj(std::pow(myTau, 2)) }); 91 | trs.push_back({ idx + nonzeros, idx + nonzeros, weight(i, j) * std::pow(myTau, 2) * std::conj(std::pow(myTau, 2)) }); 92 | 93 | b[idx] = std::conj(std::pow(myTau, 4)) * weight(i, j); 94 | b[idx+nonzeros] = std::conj(std::pow(myTau, 4))*std::pow(myTau, 2) * weight(i, j); 95 | 96 | c += weight(i, j) * std::pow(std::abs(std::pow(myTau, 4)),2); 97 | } 98 | 99 | A.setFromTriplets(trs.begin(), trs.end()); 100 | 101 | //complex aa = X.adjoint() * A * X; 102 | //complex bb = b.transpose() * X; 103 | //bb = 2.0 * bb; 104 | 105 | //double energy = (aa+bb.real()).real()+c; 106 | 107 | return { A,b }; 108 | 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is an implementation of "Vectorization of Line Drawings via PolyVector Fields" by Mikhail Bessmeltsev and Justin Solomon, Massachusetts Institute of Technology, 2018. 2 | Actual coding done by Mikhail Bessmeltsev, http://www-labs.iro.umontreal.ca/~bmpix/. 3 | 4 | USAGE: 5 | ./polyvector(.exe) filename 6 | 7 | OUTPUT: 8 | Creates a filename.svg in the same folder. 9 | 10 | ## Docker usage 11 | 12 | 1. Run docker ```make run``` 13 | 2. To launch the GUI, ```polyvector_thing $filename``` 14 | 3. To launch the CLI, ```polyvector_thing_cli $filename``` 15 | 4. The data must be placed in the sample_inputs folder, or another one must be connected 16 | 17 | ## Requirements 18 | 19 | (Other versions might also work, but these are the ones I used) 20 | - QT 5.7 (for the version with GUI) 21 | - OpenCV 2.4 22 | - Boost 1.65.1 23 | - Eigen 3.3.1 24 | 25 | ## Citation 26 | 27 | When using the code in your research work, please cite the following paper: 28 | 29 | @article{Bessm2019, 30 | author = {Bessmeltsev, Mikhail and Solomon, Justin}, 31 | title = {Vectorization of Line Drawings via Polyvector Fields}, 32 | journal = {ACM Trans. Graph.}, 33 | issue_date = {February 2019}, 34 | volume = {38}, 35 | number = {1}, 36 | month = jan, 37 | year = {2019}, 38 | pages = {9:1--9:12}, 39 | articleno = {9}, 40 | numpages = {12}, 41 | publisher = {ACM}, 42 | address = {New York, NY, USA}, 43 | keywords = {Vectorization, frame field, line drawing, polyvector field}, 44 | } 45 | 46 | 47 | ## Building 48 | 49 | 1. Create folder 'build' in the same folder as this README. 50 | 2. In cmd (windows) or sh/bash (linux/mac): 51 | 52 | > build GUI app 53 | 54 | ```bash 55 | > cd build 56 | > cmake .. -D WITH_GUI=1 -D WITH_QT=1 57 | > make 58 | ``` 59 | 60 | > build CLI qtapp 61 | 62 | ```bash 63 | > cd build 64 | > cmake .. -D WITH_QT=1 65 | > make 66 | ``` 67 | 68 | > build CLI app 69 | 70 | ```bash 71 | > cd build 72 | > cmake .. 73 | > make 74 | ``` 75 | 76 | (If you are on Windows, the last instruction will depend on the compiler you're using, e.g. 'nmake' for Visual Studio). If you want fast optimized code, replace "cmake .." by "cmake .. -D CMAKE_BUILD_TYPE=Release". 77 | 78 | ## Building docker with all requirements 79 | 80 | ```bash 81 | make build 82 | ``` 83 | 84 | After this simple command you will build a container that satisfies all env and is able to run the application: 85 | 86 | ```bash 87 | polyvector_thing $filename # GUI APP 88 | polyvector_thing_cli $filename # CLI APP 89 | polyvector_thing_cli_qt $filename # CLI QT APP 90 | ``` 91 | 92 | ## Parameters 93 | 94 | 1. All the tunable parameters are in Params.h 95 | 2. In a couple of places in the code there are seemingly random constants like 10 - those are implementation-dependent constants and have nothing to do with algorithm parameters. Proper engineering would fix those, but before that happens - please do not change. 96 | 97 | ## Notes 98 | 99 | 1. This is not the optimized version we tested for performance. The optimized version is available upon request. 100 | 2. There are a few minor changes from the paper description: 101 | - Section 5.2 has been reimplemented since the submission for robustness. This might have introduced minor (~2 pixel)-differences to some of the results, mostly making them better. 102 | - Instead of outputting a raw non-smooth vectorization with many segments, which takes a while to output, we use Douglas-Peucker algorithm and then Laplacian smoothing on the result. Douglas-Peucker does not change anything significant in the result, except for the density of the control points. Laplacian smoothing was not used for the paper results, instead we used, as we noted, Adobe Illustrator's 'Simplify' feature. Those two steps were added to immediately output a decent .svg if Illustrator is not available. 103 | 104 | ## Known Issues 105 | 106 | 1. Output might contain 'nan's. Your importer/viewer should ignore those points. 107 | 108 | ## Comparisons 109 | 110 | The whole algorithm depends on the background/foreground separation, which is done via a simple intensity threshold. If you see that some lines are missing in the vectorization, that's just it. Increase the contrast of your image in some bitmap editor (Photoshop?) and rerun. Alternatively, you can adjust the threshold in the code (BACKGROUND_FOREGROUND_THRESHOLD in Params.h). So if you're comparing to our paper, please try a few contrast settings. -------------------------------------------------------------------------------- /src/ContractLoops.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "ContractLoops.h" 3 | #include "IsLoopContractible.h" 4 | std::vector contractLoops(G & g, const cv::Mat & origMask, const std::vector& polys) 5 | { 6 | using namespace boost; 7 | int n = boost::num_vertices(g); 8 | std::vector result; //removed edges 9 | 10 | std::vector> realIncontractibleLoops; 11 | 12 | std::cout << "Reeb graph: " << num_vertices(g) << " vertices, " << num_edges(g) << " edges." << std::endl; 13 | 14 | int c = 0; 15 | //do 16 | //{ 17 | std::map::vertex_descriptor >> p; 18 | 19 | std::cout << "Computing min spaning trees..."; 20 | size_t treeRoot = 0; 21 | //create a boolean map if an edge is in the sp tree 22 | 23 | std::map::edge_descriptor, bool> isInSpTree; 24 | std::vector vertexCovered(num_vertices(g)); 25 | while (treeRoot < num_vertices(g)) 26 | { 27 | p[treeRoot] = std::vector< graph_traits < G >::vertex_descriptor>(num_vertices(g)); 28 | prim_minimum_spanning_tree(g, &p[treeRoot][0], boost::root_vertex(treeRoot).weight_map(boost::get(&Edge::weight, g))); 29 | 30 | vertexCovered[treeRoot] = true; 31 | 32 | for (size_t i = 0; i < num_vertices(g); ++i) 33 | { 34 | auto ee = edge(i, p[treeRoot][i], g); 35 | if (ee.second) 36 | { 37 | isInSpTree[ee.first] = true; 38 | vertexCovered[i] = true; 39 | vertexCovered[p[treeRoot][i]] = true; 40 | } 41 | } 42 | 43 | for (; treeRoot < num_vertices(g); ++treeRoot) 44 | if (!vertexCovered[treeRoot]) 45 | break; 46 | } 47 | std::cout << "done. " << std::endl; 48 | 49 | //now for every edge not in the tree, find the smallest loop containing it 50 | auto eii = edges(g); 51 | 52 | for (auto it = eii.first; it != eii.second; ++it) 53 | { 54 | if (!isInSpTree[*it]) 55 | g[*it].weight = 1e10; 56 | } 57 | 58 | 59 | std::vector < std::vector> loops, incontractibleLoops /*for debug only*/; 60 | std::vector> realLoops; 61 | std::vector origEdge, origEdgeForIncontractibleLoops; 62 | 63 | 64 | std::cout << "Computing loops..."; 65 | c = 0; 66 | int tmpLoopIdx = 0; 67 | for (auto it = eii.first; it != eii.second; ++it) 68 | { 69 | if (!isInSpTree[*it]) 70 | { 71 | std::vector pDij(num_vertices(g)); 72 | std::vector dDij(num_vertices(g)); 73 | auto predMap = make_iterator_property_map(pDij.begin(), get(&Cluster::clusterIdx, g)); 74 | auto distMap = make_iterator_property_map(dDij.begin(), get(&Cluster::clusterIdx, g)); 75 | size_t source = it->m_source; 76 | dijkstra_shortest_paths(g, it->m_source, 77 | predecessor_map(predMap). 78 | distance_map(distMap).weight_map(get(&Edge::weight,g))); 79 | 80 | //now record the path 81 | std::vector loop; 82 | auto cur = it->m_target; 83 | while (cur != it->m_source) 84 | { 85 | edge_descriptor ed = edge(cur, pDij[cur], g).first; 86 | loop.push_back(ed); 87 | cur = pDij[cur]; 88 | } 89 | loop.push_back(*it); 90 | realLoops.push_back({}); 91 | 92 | /*if (loop.size() > 100) 93 | { 94 | std::cout << "BIG LOOP: (size = " << loop.size() << "), "; 95 | std::cout << "non-tree edge: " << it->m_source << "-" << it->m_target << std::endl; 96 | std::cout << "loop: "; 97 | for (auto tt : loop) 98 | { 99 | std::cout << tt.m_source << " "; 100 | } 101 | }*/ 102 | 103 | if (isLoopContractible(loop, origMask, g, polys,realLoops.back())) 104 | { 105 | loops.push_back(loop); 106 | c += loop.size(); 107 | origEdge.push_back(*it); 108 | } 109 | else 110 | { 111 | std::cout << "Incontractible loop: "; 112 | for (const auto& e : loop) 113 | { 114 | std::cout << e.m_source << " "; 115 | } 116 | std::cout << std::endl; 117 | incontractibleLoops.push_back(loop); 118 | origEdgeForIncontractibleLoops.push_back(*it); 119 | realIncontractibleLoops.push_back(realLoops.back()); 120 | } 121 | ++tmpLoopIdx; 122 | } 123 | } 124 | std::cout << "done, found " << c << " edges to remove" << std::endl; 125 | 126 | std::cout << "Contracting loops..."; 127 | /*for (int i = 0; i < loops.size(); ++i) 128 | { 129 | std::cout << "Loop " << i << std::endl; 130 | for (const auto& e : loops[i]) 131 | { 132 | std::cout << e.m_source << " "; 133 | } 134 | std::cout << loops[i].back().m_target << std::endl; 135 | }*/ 136 | auto removedEdges = contract_edges(loops, g); 137 | if (!removedEdges.empty()) 138 | result.insert(result.end(), removedEdges.begin(), removedEdges.end()); 139 | std::cout << "done." << std::endl; 140 | //} while (c != 0); 141 | 142 | 143 | std::cout << "all done." << std::endl; 144 | return result; 145 | } 146 | -------------------------------------------------------------------------------- /src/l2_regularizer.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "l2_regularizer.h" 3 | #include 4 | #include 5 | #include 6 | #include "typedefs.h" 7 | //#define doesNeighborExist(i,j,m,n,vertical,left) ((left)? ((vertical)?((i)>0):((j)>0)) : (((vertical)?(i):(j))<((vertical)?(m):(n))-1) ) 8 | 9 | std::pair l2_regularizer(const Eigen::VectorXcd & X, const Eigen::MatrixXd & D, const cv::Mat & mask, Eigen::MatrixXi& indices, bool computeGrad) 10 | { 11 | int nonzeros = X.size()/2; 12 | assert(nonzeros == countNonZero(mask)); 13 | 14 | double energy = 0; 15 | Eigen::VectorXcd grad; 16 | if (computeGrad) 17 | { 18 | grad = Eigen::VectorXcd(nonzeros * 2); 19 | grad.setZero(); 20 | } 21 | 22 | Eigen::VectorXcd dx(nonzeros); 23 | 24 | int m = mask.rows, n = mask.cols; 25 | for (int k = 0; k < 2; ++k) //x0 or x2 26 | { 27 | for (int dir = 0; dir < 2; dir++) //horizontal or vertical 28 | { 29 | std::vector energies(nonzeros,0); 30 | #pragma omp parallel for 31 | for (int j = 0; j < n; ++j) 32 | { 33 | for (int i = 0; i < m; ++i) 34 | { 35 | if (mask.at(i, j) == 0) 36 | continue; 37 | int idx = indices(i, j); 38 | std::pair leftNeighbor, rightNeighbor; 39 | bool useLeft = useNeighbor(i, j, m, n, (dir == 1), true, mask, leftNeighbor); 40 | bool useRight = useNeighbor(i, j, m, n, (dir == 1), false, mask, rightNeighbor); 41 | 42 | if (useLeft & useRight) 43 | dx(idx) = 0.5*(X(indices(rightNeighbor.first, rightNeighbor.second) + k*nonzeros) - X(indices(leftNeighbor.first, leftNeighbor.second) + k*nonzeros)); 44 | else if (useLeft) 45 | dx(idx) = X(idx + k*nonzeros) - X(indices(leftNeighbor.first, leftNeighbor.second) + k*nonzeros); 46 | else if (useRight) 47 | dx(idx) = X(indices(rightNeighbor.first, rightNeighbor.second) + k*nonzeros) - X(idx + k*nonzeros); 48 | else 49 | dx(idx) = 0; 50 | 51 | auto dx1 = dx(idx); 52 | 53 | //energy += D(i, j) * (dx1.real()*dx1.real() + dx1.imag()*dx1.imag()); 54 | energies[idx] += D(i, j) * (dx1.real()*dx1.real() + dx1.imag()*dx1.imag()); 55 | } 56 | } 57 | 58 | double curEnergy = std::accumulate(energies.begin(), energies.end(), 0.0); 59 | energy += curEnergy; 60 | 61 | if (computeGrad) 62 | { 63 | #pragma omp parallel for 64 | for (int i = 0; i < m; ++i) 65 | { 66 | for (int j = 0; j < n; ++j) 67 | { 68 | if (mask.at(i, j) == 0) 69 | continue; 70 | 71 | int idx = indices(i, j); 72 | for (int sign : {-1, 1}) //leftRight 73 | { 74 | std::pair neighbor; 75 | bool shouldIUseNeighbor = useNeighbor(i, j, m, n, (dir == 1), sign == 1, mask, neighbor); 76 | bool shouldIUseNeighborsNeighbor = false; 77 | std::pair tmpNeighbor; 78 | if (doesNeighborExist(i, j, m, n, (dir == 1), sign == 1)) 79 | shouldIUseNeighborsNeighbor = useNeighbor(neighbor.first, neighbor.second, m, n, (dir == 1), sign == 1, mask, tmpNeighbor); 80 | 81 | if (shouldIUseNeighbor) 82 | { 83 | int myNeighbor = indices(neighbor.first, neighbor.second); 84 | if (shouldIUseNeighborsNeighbor) 85 | grad(idx + k*nonzeros) += double(sign)*dx(myNeighbor)*D(neighbor.first, neighbor.second); 86 | else 87 | grad(idx + k*nonzeros) += 2 * double(sign)*dx(myNeighbor)*D(neighbor.first, neighbor.second); 88 | } 89 | else 90 | { 91 | grad(idx + k*nonzeros) -= 2 * double(sign)*dx(idx)*D(i, j); 92 | } 93 | } 94 | 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | return std::make_pair(energy, grad); 102 | } 103 | 104 | Eigen::SparseMatrix laplacian_matrix(const cv::Mat& mask, const Eigen::MatrixXi& indices, const Eigen::VectorXd& w) 105 | { 106 | int nonzeros = countNonZero(mask); 107 | Eigen::SparseMatrix L(2*nonzeros, 2*nonzeros); //L = diff^T*diff 108 | 109 | int m = mask.rows, n = mask.cols; 110 | Eigen::VectorXd wTwice(w.size() * 2); 111 | wTwice << w, w; 112 | 113 | for (int dir = 0; dir < 2; dir++) //horizontal or vertical 114 | { 115 | std::vector> trs; 116 | for (int j = 0; j < n; ++j) 117 | { 118 | for (int i = 0; i < m; ++i) 119 | { 120 | if (mask.at(i, j) == 0) 121 | continue; 122 | int idx = indices(i, j); 123 | std::pair leftNeighbor, rightNeighbor; 124 | bool useLeft = useNeighbor(i, j, m, n, (dir == 1), true, mask, leftNeighbor); 125 | bool useRight = useNeighbor(i, j, m, n, (dir == 1), false, mask, rightNeighbor); 126 | 127 | if (useLeft & useRight) 128 | { 129 | trs.push_back({ idx, indices(rightNeighbor.first, rightNeighbor.second),0.5 }); 130 | trs.push_back({ idx, indices(leftNeighbor.first, leftNeighbor.second), -0.5 }); 131 | } 132 | else if (useLeft) 133 | { 134 | trs.push_back({ idx, idx, 1.0 }); 135 | trs.push_back({ idx, indices(leftNeighbor.first, leftNeighbor.second), -1.0 }); 136 | } 137 | else if (useRight) 138 | { 139 | trs.push_back({ idx, indices(rightNeighbor.first, rightNeighbor.second), 1.0 }); 140 | trs.push_back({ idx, idx, -1.0 }); 141 | } 142 | } 143 | } 144 | 145 | Eigen::SparseMatrix diffMatrix(2*nonzeros, 2*nonzeros); 146 | //copy trs 147 | std::vector> newTrs; 148 | for (auto tr : trs) 149 | { 150 | newTrs.push_back({ tr.row() + nonzeros, tr.col() + nonzeros, tr.value() }); 151 | } 152 | trs.insert(trs.end(), newTrs.begin(), newTrs.end()); 153 | diffMatrix.setFromTriplets(trs.begin(), trs.end()); 154 | L += diffMatrix.transpose() * wTwice.asDiagonal() * diffMatrix; 155 | } 156 | return L; 157 | } 158 | -------------------------------------------------------------------------------- /src/LBFGS.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 Yixuan Qiu 2 | // Under MIT license 3 | 4 | #ifndef LBFGS_H 5 | #define LBFGS_H 6 | 7 | #include 8 | #include "LBFGS/Param.h" 9 | #include "LBFGS/LineSearch.h" 10 | #include 11 | 12 | namespace LBFGSpp { 13 | 14 | 15 | /// 16 | /// LBFGS solver for unconstrained numerical optimization 17 | /// 18 | template 19 | class LBFGSSolver 20 | { 21 | private: 22 | typedef Eigen::Matrix Vector; 23 | typedef Eigen::Matrix Matrix; 24 | typedef Eigen::Map MapVec; 25 | 26 | const LBFGSParam& m_param; // Parameters to control the LBFGS algorithm 27 | Matrix m_s; // History of the s vectors 28 | Matrix m_y; // History of the y vectors 29 | Vector m_ys; // History of the s'y values 30 | Vector m_alpha; // History of the step lengths 31 | Vector m_fx; // History of the objective function values 32 | Vector m_xp; // Old x 33 | Vector m_grad; // New gradient 34 | Vector m_gradp; // Old gradient 35 | Vector m_drt; // Moving direction 36 | 37 | inline void reset(int n) 38 | { 39 | const int m = m_param.m; 40 | m_s.resize(n, m); 41 | m_y.resize(n, m); 42 | m_ys.resize(m); 43 | m_alpha.resize(m); 44 | m_xp.resize(n); 45 | m_grad.resize(n); 46 | m_gradp.resize(n); 47 | m_drt.resize(n); 48 | if(m_param.past > 0) 49 | m_fx.resize(m_param.past); 50 | } 51 | 52 | public: 53 | /// 54 | /// Constructor for LBFGS solver. 55 | /// 56 | /// \param param An object of \ref LBFGSParam to store parameters for the 57 | /// algorithm 58 | /// 59 | LBFGSSolver(const LBFGSParam& param) : 60 | m_param(param) 61 | { 62 | m_param.check_param(); 63 | } 64 | 65 | /// 66 | /// Minimizing a multivariate function using LBFGS algorithm. 67 | /// Exceptions will be thrown if error occurs. 68 | /// 69 | /// \param f A function object such that `f(x, grad)` returns the 70 | /// objective function value at `x`, and overwrites `grad` with 71 | /// the gradient. 72 | /// \param x In: An initial guess of the optimal point. Out: The best point 73 | /// found. 74 | /// \param fx Out: The objective function value at `x`. 75 | /// 76 | /// \return Number of iterations used. 77 | /// 78 | template 79 | inline int minimize(Foo& f, Vector& x, Scalar& fx) 80 | { 81 | const int n = x.size(); 82 | const int fpast = m_param.past; 83 | reset(n); 84 | 85 | // Evaluate function and compute gradient 86 | fx = f(x, m_grad); 87 | Scalar xnorm = x.norm(); 88 | Scalar gnorm = m_grad.norm(); 89 | if(fpast > 0) 90 | m_fx[0] = fx; 91 | 92 | // Early exit if the initial x is already a minimizer 93 | if(gnorm <= m_param.epsilon * std::max(xnorm, Scalar(1.0))) 94 | { 95 | return 1; 96 | } 97 | 98 | // Initial direction 99 | m_drt.noalias() = -m_grad; 100 | // Initial step 101 | Scalar step = Scalar(1.0) / m_drt.norm(); 102 | 103 | int k = 1; 104 | int end = 0; 105 | for( ; ; ) 106 | { 107 | //std::cout << fx << std::endl; 108 | // Save the curent x and gradient 109 | m_xp.noalias() = x; 110 | m_gradp.noalias() = m_grad; 111 | 112 | // Line search to update x, fx and gradient 113 | LineSearch::Backtracking(f, fx, x, m_grad, step, m_drt, m_xp, m_param); 114 | 115 | // New x norm and gradient norm 116 | xnorm = x.norm(); 117 | gnorm = m_grad.norm(); 118 | 119 | // Convergence test -- gradient 120 | if(gnorm <= m_param.epsilon * std::max(xnorm, Scalar(1.0))) 121 | { 122 | return k; 123 | } 124 | // Convergence test -- objective function value 125 | if(fpast > 0) 126 | { 127 | if(k >= fpast && (m_fx[k % fpast] - fx) / fx < m_param.delta) 128 | return k; 129 | 130 | m_fx[k % fpast] = fx; 131 | } 132 | // Maximum number of iterations 133 | if(m_param.max_iterations != 0 && k >= m_param.max_iterations) 134 | { 135 | return k; 136 | } 137 | 138 | // Update s and y 139 | // s_{k+1} = x_{k+1} - x_k 140 | // y_{k+1} = g_{k+1} - g_k 141 | MapVec svec(&m_s(0, end), n); 142 | MapVec yvec(&m_y(0, end), n); 143 | svec.noalias() = x - m_xp; 144 | yvec.noalias() = m_grad - m_gradp; 145 | 146 | // ys = y's = 1/rho 147 | // yy = y'y 148 | Scalar ys = yvec.dot(svec); 149 | Scalar yy = yvec.squaredNorm(); 150 | m_ys[end] = ys; 151 | 152 | // Recursive formula to compute d = -H * g 153 | m_drt.noalias() = -m_grad; 154 | int bound = std::min(m_param.m, k); 155 | end = (end + 1) % m_param.m; 156 | int j = end; 157 | for(int i = 0; i < bound; i++) 158 | { 159 | j = (j + m_param.m - 1) % m_param.m; 160 | MapVec sj(&m_s(0, j), n); 161 | MapVec yj(&m_y(0, j), n); 162 | m_alpha[j] = sj.dot(m_drt) / m_ys[j]; 163 | m_drt.noalias() -= m_alpha[j] * yj; 164 | } 165 | 166 | m_drt *= (ys / yy); 167 | 168 | for(int i = 0; i < bound; i++) 169 | { 170 | MapVec sj(&m_s(0, j), n); 171 | MapVec yj(&m_y(0, j), n); 172 | Scalar beta = yj.dot(m_drt) / m_ys[j]; 173 | m_drt.noalias() += (m_alpha[j] - beta) * sj; 174 | j = (j + 1) % m_param.m; 175 | } 176 | 177 | // step = 1.0 as initial guess 178 | step = Scalar(1.0); 179 | k++; 180 | } 181 | 182 | return k; 183 | } 184 | }; 185 | 186 | 187 | } // namespace LBFGSpp 188 | 189 | #endif // LBFGS_H 190 | -------------------------------------------------------------------------------- /src/IsLoopContractible.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "IsLoopContractible.h" 3 | #include "CircularSegment.h" 4 | #include "Params.h" 5 | std::vector subcurve(const MyPolyline& poly, const CircularSegment& s) 6 | { 7 | std::vector real; 8 | Eigen::Vector2d start = poly[s.begin()]; 9 | Eigen::Vector2d end = poly[s.end()]; 10 | real.push_back({ (float)start.x(),(float)start.y() }); 11 | for (int j = std::floor(s.begin()); j != std::floor(s.end()); j = (j + 1) % poly.size()) 12 | { 13 | real.push_back({ (float)poly[j].x(),(float)poly[j].y() }); 14 | } 15 | real.push_back({ (float)end.x(),(float)end.y() }); 16 | return real; 17 | } 18 | 19 | const std::vector convertToPolyline(const std::vector& loop, const G& g, const std::vector& polys) 20 | { 21 | std::vector cvLoop; 22 | 23 | for (const auto& e : loop) 24 | { 25 | //now there's an edge bewteen two clusters 26 | //add the segment of the curve that connects them 27 | int myCurve = g[e].edgeCurve; 28 | 29 | /*std::cout << "Edge: " << e.m_source << " " << e.m_target << ", curve = " << myCurve << " " << "clPS: " << g[e.m_source].clusterPoints[0].curve << " @" << g[e.m_source].clusterPoints[0].segmentIdx << ", clPT: " << 30 | g[e.m_target].clusterPoints[0].curve << " @" << g[e.m_target].clusterPoints[0].segmentIdx; 31 | if (myCurve != -1) 32 | std::cout << " (length = " << polys[myCurve].size() << ")"; 33 | 34 | std::cout << std::endl;*/ 35 | 36 | std::map pt; 37 | std::vector curve; 38 | if (myCurve != -1) 39 | { 40 | const auto& sourcePts = g[e.m_source].clusterPoints; 41 | const auto& targetPts = g[e.m_target].clusterPoints; 42 | CircularSegment bestS; 43 | double bestLength = std::numeric_limits::max(); 44 | bool curveClosed = (polys[myCurve].front() - polys[myCurve].back()).squaredNorm() < 1e-6; 45 | double modulus = curveClosed ? polys[myCurve].size() : -1.0; 46 | bool inverted; 47 | for (const auto& pt1 : sourcePts) 48 | { 49 | if (pt1.curve == myCurve) 50 | { 51 | for (const auto& pt2 : targetPts) 52 | { 53 | if (pt2.curve == myCurve) 54 | { 55 | CircularSegment s1 = CircularSegment(pt1.segmentIdx, pt2.segmentIdx, modulus); 56 | CircularSegment s2 = CircularSegment(pt2.segmentIdx, pt1.segmentIdx, modulus); 57 | double minLen = std::min(s1.length(), s2.length()); 58 | if (minLen < bestLength) 59 | { 60 | if (s1.length() < s2.length()) 61 | { 62 | bestS = s1; 63 | inverted = false; 64 | } 65 | else 66 | { 67 | bestS = s2; 68 | inverted = true; 69 | } 70 | bestLength = minLen; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | curve = subcurve(polys[myCurve], bestS); 78 | 79 | if (inverted) 80 | std::reverse(curve.begin(), curve.end()); 81 | 82 | /* 83 | int startPt = (pt[e.m_source].segmentIdx <= pt[e.m_target].segmentIdx) ? e.m_source : e.m_target; 84 | int endPt = (pt[e.m_source].segmentIdx > pt[e.m_target].segmentIdx) ? e.m_source : e.m_target; 85 | 86 | curve.push_back({ (float)pt[startPt].p.x(), (float)pt[startPt].p.y() }); 87 | 88 | for (int i = std::floor(pt[startPt].segmentIdx + 1); i < pt[endPt].segmentIdx; ++i) 89 | { 90 | curve.push_back({ (float)polys[myCurve][i].x(), (float)polys[myCurve][i].y() }); 91 | } 92 | 93 | cv::Point2f newPoint((float)pt[endPt].p.x(), (float)pt[endPt].p.y()); 94 | if (!curve.empty() && (cv::norm(curve.back() - newPoint) > 1e-6)) 95 | curve.push_back(newPoint); 96 | 97 | if (startPt != e.m_source) 98 | std::reverse(curve.begin(), curve.end());*/ 99 | 100 | } 101 | else 102 | { 103 | curve = { { (float)g[e.m_source].location.x(),(float)g[e.m_source].location.y() },{ (float)g[e.m_target].location.x(),(float)g[e.m_target].location.y() } }; //for the cases around singularities 104 | } 105 | 106 | cvLoop.insert(cvLoop.end(), curve.begin(), curve.end()); 107 | } 108 | return cvLoop; 109 | } 110 | 111 | bool isLoopContractible(const std::vector& loop, const cv::Mat & origMask, const G& g, const std::vector& polys, std::vector& cvLoop) 112 | { 113 | //test if the loop contains any white pixels 114 | //essentially a scan conversion 115 | 116 | cvLoop = convertToPolyline(loop, g, polys); 117 | int minI = std::numeric_limits::max(), maxI = std::numeric_limits::min(), 118 | minJ = minI, maxJ = maxI; 119 | 120 | std::vector cvLoopFiltered; 121 | for (int i = 0; i < cvLoop.size(); ++i) 122 | { 123 | if (cvLoopFiltered.empty() || (cv::norm(cvLoop[i] - cvLoopFiltered.back()) > 1e-6)) 124 | cvLoopFiltered.push_back(cvLoop[i]); 125 | 126 | minI = std::min(minI, (int)cvLoop[i].y); 127 | minJ = std::min(minJ, (int)cvLoop[i].x); 128 | maxI = std::max(maxI, (int)cvLoop[i].y + 1); 129 | maxJ = std::max(maxJ, (int)cvLoop[i].x + 1); 130 | } 131 | cvLoop = cvLoopFiltered; 132 | if (!(minI >= 0 && minJ >= 0 && maxI <= origMask.rows && maxJ <= origMask.cols)) 133 | { 134 | std::cout << "[isLoopContractible]: FATAL ERROR: " << minI << " " << minJ << " " << maxI << " " << maxJ << std::endl; 135 | } 136 | 137 | //if ((loop.size() > 10) && (loop.size() < 100)) 138 | //{ 139 | 140 | // Box box; 141 | // for (int i = 0; i < cvLoop.size(); ++i) 142 | // { 143 | // box.xMin = std::min(box.xMin, (double)cvLoop[i].x); 144 | // box.xMax = std::max(box.xMax, (double)cvLoop[i].x); 145 | // box.yMin = std::min(box.yMin, (double)cvLoop[i].y); 146 | // box.yMax = std::max(box.yMax, (double)cvLoop[i].y); 147 | // } 148 | // 149 | // svg::Document docRSPT("aloop.svg", svg::Layout(svg::Dimensions(box.xMax- box.xMin, box.yMax- box.yMin), svg::Layout::TopLeft)); 150 | // svg::Polyline poly(svg::Fill(), svg::Stroke(0.5, svg::Color::Black)); 151 | // for (size_t c = 0; c < cvLoop.size(); ++c) 152 | // { 153 | // poly << svg::Point(cvLoop[c].x, cvLoop[c].y); 154 | // } 155 | // docRSPT << poly; 156 | // docRSPT.save(); 157 | //} 158 | 159 | int numWhitePixels = 0; 160 | const int threshold = MAX_NUMBER_OF_WHITE_PIXELS_IN_A_CONTRACTIBLE_LOOP; 161 | for (int i = minI; i < maxI; ++i) 162 | for (int j = minJ; j < maxJ; ++j) 163 | { 164 | if (origMask.at(i, j) == 0) 165 | { 166 | if (cv::pointPolygonTest(cvLoop, { (float)j,(float)i }, false) > 0) 167 | { 168 | ++numWhitePixels; 169 | if (numWhitePixels > threshold) 170 | return false; 171 | } 172 | } 173 | } 174 | return (numWhitePixels <= threshold); 175 | } 176 | -------------------------------------------------------------------------------- /src/greedyTrace.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "greedyTrace.h" 3 | #include "FindRoots.h" 4 | #include "chooseRoot.h" 5 | #include "bilinearInterpCoeff.h" 6 | #include 7 | #include 8 | #include "findSingularities.h" 9 | 10 | std::pair> greedyTrace(const cv::Mat & origMask, const std::array& allRoots, 11 | const Eigen::Vector2d & seedCenter, const Eigen::Vector2d& initialDir, const Eigen::VectorXcd & X, const std::map, 12 | std::vector>& pixelInfo, std::map, std::vector>& outNewPixelInfo, const Eigen::MatrixXi& indices, 13 | int myCurveIdx, bool perpendicular, std::array& closestDistToSingularity) 14 | { 15 | //a word of advice to those modifying this code: unfortunately, this h is not a global costant, although it should be. That means that in *many* places in the code there are numbers like 10 or 0.1 16 | //those should actually be just h or 1/h, but I didn't fix it - sorry! so unless you really need to modify this h, don't, but if you do, just search for any constant that looks like h or 1/h and fix that too. 17 | const double h = 0.1; 18 | using namespace cv; 19 | Rect bigBounds(Point(), origMask.size()); 20 | int m = origMask.rows, n = origMask.cols; 21 | std::array protectedEnds; 22 | MyPolyline result; 23 | 24 | int firstDirectionPolySize; 25 | std::set> singularities; 26 | for (int idx = 0; idx < 2; ++idx) 27 | { 28 | 29 | bool finishedWithAnotherCurve = false; 30 | Eigen::Vector2d p = seedCenter; 31 | //std::cout << p.x() << " " << p.y() << std::endl; 32 | std::array p0i = { static_cast(std::round(p.y())), static_cast(std::round(p.x())) }; 33 | //std::cout << allRoots[0](p0i[0], p0i[1]) << " --- " << allRoots[1](p0i[0], p0i[1]) << std::endl; 34 | MyPolyline polyline; 35 | 36 | Eigen::Vector2d dir = (2*idx-1)*chooseRoot(allRoots[0](p0i[0],p0i[1]), allRoots[1](p0i[0], p0i[1]), initialDir); //sign changes depending on idx 37 | Eigen::Vector2d perp(dir.y(), -dir.x()); 38 | std::vector pixelInfoQueue; 39 | 40 | auto emptyQueues = [&](int kk) 41 | { 42 | for (int k = 0; k < kk; ++k) 43 | { 44 | int i = std::round(pixelInfoQueue[k].p.y()), j = std::round(pixelInfoQueue[k].p.x()); 45 | if ((i != p0i[0]) || (j != p0i[1]) || (idx == 1)) 46 | { 47 | if (idx == 0) 48 | pixelInfoQueue[k].segmentIdx = -pixelInfoQueue[k].segmentIdx; //so that after we trace both directions, we adjust the segmentIdx correctly 49 | 50 | outNewPixelInfo[{i, j}].push_back(pixelInfoQueue[k]); 51 | } 52 | } 53 | pixelInfoQueue.erase(pixelInfoQueue.begin(), pixelInfoQueue.begin()+kk); 54 | //std::cout << "Emptying queues" << std::endl; 55 | }; 56 | 57 | while (polyline.size() < 10000) 58 | { 59 | std::array pi = { static_cast(std::round(p.y())),static_cast(std::round(p.x()))}; 60 | if (!inBounds(p,origMask) || (abs(X[indices(pi[0],pi[1])])<1e-10)) 61 | break; 62 | 63 | if ((idx == 0) || result.empty() || (polyline.size() > 1) || ((result.back() - p).norm() > 1e-6)) 64 | { 65 | polyline.push_back(p); 66 | 67 | PixelInfo pInfo; 68 | pInfo.p = p; 69 | pInfo.tangent = perpendicular ? perp : dir; 70 | pInfo.curve = myCurveIdx; 71 | pInfo.segmentIdx = polyline.size() - 1; 72 | pixelInfoQueue.push_back(pInfo); 73 | } 74 | 75 | 76 | if (pixelInfoQueue.size() > 3 / h) //if we've travelled at least two-pixel distance and accumulated some coverage. 77 | emptyQueues(pixelInfoQueue.size()/2); 78 | 79 | //check if this pixel is already covered by some curve with a similar tangent 80 | auto it = pixelInfo.find(pi); 81 | auto it2 = outNewPixelInfo.find(pi); 82 | 83 | if (it != pixelInfo.end() || it2 != outNewPixelInfo.end()) 84 | { 85 | const auto& myCov = it != pixelInfo.end() ? it->second : it2->second; 86 | std::vector distances(myCov.size()), dots(myCov.size()); 87 | for (int i = 0; i < myCov.size(); i++) 88 | { 89 | distances[i] = (p - myCov[i].p).squaredNorm(); 90 | dots[i] = perpendicular ? fabs(myCov[i].tangent.dot(perp)) : fabs(myCov[i].tangent.dot(dir)); 91 | } 92 | 93 | int closestVertex = -1; 94 | double bestDist = 1e-2; 95 | 96 | for (int i = 0; i < myCov.size(); ++i) 97 | { 98 | //this is a very lazy way of saying this is the same root. does not affect anything, but should be rewritten properly (via matching) 99 | if ((dots[i]>0.98)&&(distances[i] < bestDist)) 100 | { 101 | bestDist = distances[i]; 102 | closestVertex = i; 103 | } 104 | } 105 | 106 | if (closestVertex != -1) 107 | { 108 | finishedWithAnotherCurve = true; 109 | break; 110 | } 111 | } 112 | 113 | auto coeffs = bilinearInterpCoeff(X, p, origMask,indices); 114 | auto roots = findRoots(coeffs[0], coeffs[1]); 115 | 116 | Eigen::Vector2d shift = chooseRoot(roots[0], roots[1], dir); 117 | 118 | if ((shift.norm() < 1e-6) || isItASingularity(p, roots, allRoots, origMask)) 119 | { 120 | closestDistToSingularity[idx] = std::min(closestDistToSingularity[idx],h*polyline.size()); 121 | if (singularities.find(p0i) == singularities.end()) 122 | singularities.insert(p0i); 123 | break; 124 | } 125 | 126 | shift.normalize(); 127 | 128 | dir = shift; 129 | Eigen::Vector2d shiftPerp(shift.y(), -shift.x()); 130 | if (!perpendicular) 131 | p += h*shift; 132 | else 133 | p += h*shiftPerp; 134 | } 135 | 136 | protectedEnds[idx] = finishedWithAnotherCurve; 137 | 138 | if (idx == 0) 139 | { 140 | std::reverse(polyline.begin(), polyline.end()); 141 | firstDirectionPolySize = static_cast(polyline.size())-1; 142 | } 143 | 144 | result.insert(result.end(), polyline.begin(), polyline.end()); 145 | emptyQueues(pixelInfoQueue.size()); 146 | } 147 | 148 | for (auto& ppi: outNewPixelInfo) 149 | for (auto& pinfo : ppi.second) 150 | pinfo.segmentIdx += firstDirectionPolySize; 151 | 152 | //and complete the PixelInfo by pushing the first few elements from the first curve 153 | //they are somewhere in the middle though 154 | if (!result.empty()) 155 | { 156 | for (int i = 0; i <= firstDirectionPolySize; ++i) 157 | { 158 | auto p = result[i]; 159 | std::array p0i = { static_cast(std::round(p.y())), static_cast(std::round(p.x())) }; 160 | PixelInfo pInfo; 161 | pInfo.p = p; 162 | if (i + 1 < result.size()) 163 | pInfo.tangent = (result[i + 1] - result[i]).normalized(); 164 | else if (i > 0) 165 | pInfo.tangent = (result[i] - result[i - 1]).normalized(); 166 | 167 | pInfo.curve = myCurveIdx; 168 | pInfo.segmentIdx = i; 169 | 170 | outNewPixelInfo[p0i].push_back(pInfo); 171 | } 172 | } 173 | 174 | return std::make_pair(result, protectedEnds); 175 | } 176 | -------------------------------------------------------------------------------- /src/RemoveShortBranches.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "RemoveShortBranches.h" 3 | #include "boost/graph/depth_first_search.hpp" 4 | #include "ChainDecomposition.h" 5 | #include 6 | #include "Params.h" 7 | 8 | double clusterWidth(const Cluster& cl) 9 | { 10 | if (cl.clusterPoints.empty()) 11 | return 0; 12 | return (cl.clusterPoints.back().p - cl.clusterPoints.front().p).norm(); 13 | } 14 | 15 | double chainLength(const G& g, const std::vector& chain, bool includeLast, bool projectOntoRoot) 16 | { 17 | double length = 0; 18 | int limit = includeLast ? chain.size() : (int)chain.size() - 1; 19 | for (int j = 0; j < limit; ++j) 20 | { 21 | auto v_source = g[chain[j].m_source].location; 22 | auto v_target = g[chain[j].m_target].location; 23 | if (projectOntoRoot) 24 | { 25 | if (g[chain[j].m_source].root.norm() < 100) 26 | length += fabs((v_source - v_target).dot(g[chain[j].m_source].root)); 27 | } 28 | else 29 | length += (v_source - v_target).norm(); 30 | } 31 | return length; 32 | } 33 | 34 | void removeBranchesFilter1(G& g, bool onlyAtIntersections, const std::map& myChainsBeforeSharpCornersSplit) 35 | { 36 | std::cout << "Removing short branches..."; 37 | int n = boost::num_vertices(g); 38 | bool somethingDeleted = true; 39 | 40 | while (somethingDeleted) 41 | { 42 | somethingDeleted = false; 43 | //let's see what that does 44 | std::map myChain; 45 | auto chains = chainDecomposition(g, myChain); 46 | std::vector chainLengths(chains.size()); 47 | for (int i = 0; i < chains.size(); ++i) 48 | chainLengths[i] = chainLength(g, chains[i], true, false); 49 | 50 | auto orient = [&chains, &g](int chainIdx, int root) 51 | { 52 | if (chains[chainIdx].front().m_source == root) 53 | return chains[chainIdx]; 54 | else 55 | { 56 | std::vector reverseChain(chains[chainIdx].size()); 57 | for (int i = 0; i < chains[chainIdx].size(); ++i) 58 | { 59 | reverseChain[chains[chainIdx].size() - 1 - i] = boost::edge(chains[chainIdx][i].m_target, chains[chainIdx][i].m_source, g).first; 60 | } 61 | return reverseChain; 62 | } 63 | }; 64 | 65 | 66 | std::set branchesToDelete; 67 | for (size_t v = 0; v < boost::num_vertices(g); ++v) 68 | { 69 | if (boost::degree(v, g) > 2) 70 | { 71 | if (onlyAtIntersections && g[v].root.norm() < 100) 72 | continue; 73 | 74 | std::map> adjChains; 75 | std::set fixedChains; 76 | std::map> intersectionsToRemove; //if we remove some chains, invalidate their intersections 77 | 78 | //we have a junction. see which adjacent chains are actually branches 79 | oedge_iter eit, eend; 80 | for (std::tie(eit, eend) = boost::out_edges(v, g); eit != eend; ++eit) 81 | { 82 | int chainIdx = myChain[*eit]; 83 | size_t valEnd1 = boost::degree(chains[chainIdx].front().m_source, g), valEnd2 = boost::degree(chains[chainIdx].back().m_target, g); 84 | auto orientedChain = orient(chainIdx, v); 85 | adjChains[chainIdx] = orientedChain; 86 | if (valEnd1 != 1 && valEnd2 != 1) 87 | fixedChains.insert(chainIdx); 88 | } 89 | 90 | while (fixedChains.size() < adjChains.size() - 1) 91 | { 92 | //choose the longest one and mark as fixed 93 | int bestBranch = -1; 94 | double maxLength = 0; 95 | for (const auto& br : adjChains) 96 | { 97 | if (fixedChains.find(br.first) == fixedChains.end()) 98 | { 99 | double len = chainLengths[br.first]; 100 | if (maxLength < len) 101 | { 102 | maxLength = len; 103 | bestBranch = br.first; 104 | } 105 | } 106 | } 107 | 108 | if (bestBranch != -1) 109 | fixedChains.insert(bestBranch); 110 | } 111 | 112 | auto distTest = [&fixedChains, &adjChains, &g, &myChainsBeforeSharpCornersSplit](const Eigen::Vector2d& myPt, double myWidth) 113 | { 114 | 115 | Eigen::Vector2d myLoc = myPt;//.cast(); 116 | 117 | double myR = std::max(myWidth / 2,0.5+1e-6); 118 | for (size_t fc : fixedChains) 119 | { 120 | for (const auto& e : adjChains[fc]) 121 | { 122 | double r = std::max(g[e.m_source].width / 2, 0.5 + 1e-6); 123 | Eigen::Vector2d hisLoc = g[e.m_source].location;//.cast(); 124 | double dist = (hisLoc - myLoc).squaredNorm(); 125 | if (dist < std::pow(r + myR, 2)) 126 | return true; 127 | } 128 | double r = g[adjChains[fc].back().m_target].width / 2; 129 | size_t lastVertex = adjChains[fc].back().m_target; 130 | Eigen::Vector2d hisLoc = g[lastVertex].location;//.cast(); 131 | double dist = (hisLoc - myLoc).squaredNorm(); 132 | if (dist < std::pow(r + myR, 2)) 133 | return true; 134 | } 135 | return false; 136 | }; 137 | 138 | 139 | bool shouldDelete = true; 140 | for (const auto& adjChain : adjChains) 141 | { 142 | if (fixedChains.find(adjChain.first) != fixedChains.end()) 143 | continue; 144 | 145 | double coveredLength = 0; 146 | double totalLength = 0; 147 | for (const auto& e : adjChain.second) 148 | { 149 | double edgeLength = (g[e.m_source].location - g[e.m_target].location).norm(); 150 | int nSamples = 10;//std::max((int)edgeLength,3); 151 | std::vector> samples; 152 | for (int k = 0; k < nSamples; ++k) 153 | { 154 | double alpha = k / (nSamples - 1); 155 | Eigen::Vector2d pt = (1 - alpha)*g[e.m_source].location + alpha*g[e.m_target].location; 156 | double w = g[e.m_source].width*(1 - alpha) + g[e.m_target].width*alpha; 157 | samples.push_back(std::make_pair(pt, w)); 158 | } 159 | 160 | int nCovered = 0; 161 | for (int k = 0; k < nSamples; ++k) 162 | { 163 | if (distTest(samples[k].first, samples[k].second)) 164 | nCovered++; 165 | } 166 | coveredLength += (nCovered / nSamples)*edgeLength; 167 | totalLength += edgeLength; 168 | } 169 | 170 | /* the last condition is not necessary - I'm just lazy. fix the stupid bug*/ 171 | if (((totalLength - coveredLength > 1) && (adjChain.second.size()>2) && (coveredLength / totalLength < PRUNE_SHORT_BRANCHES_RATIO)) || (totalLength > 10)) 172 | shouldDelete = false; 173 | } 174 | 175 | if (shouldDelete) 176 | { 177 | //std::cout << "Deleting. " << std::endl; 178 | for (const auto& br : adjChains) 179 | { 180 | if (fixedChains.find(br.first) == fixedChains.end()) 181 | { 182 | branchesToDelete.insert(br.first); 183 | } 184 | } 185 | } 186 | /*else 187 | std::cout << "Keeping. " << std::endl;*/ 188 | } 189 | } 190 | 191 | for (size_t branch : branchesToDelete) 192 | { 193 | somethingDeleted = true; 194 | for (const auto& e : chains[branch]) 195 | { 196 | boost::remove_edge(e, g); 197 | } 198 | } 199 | } 200 | std::cout << "done." << std::endl; 201 | } -------------------------------------------------------------------------------- /src/graph_typedefs.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "graph_typedefs.h" 3 | #include 4 | #include "boost/graph/filtered_graph.hpp" 5 | #include "boost/graph/dijkstra_shortest_paths.hpp" 6 | #include "FillHole.h" 7 | 8 | std::set contract_edges(const std::vector::edge_descriptor>>& edgeLoops, G& g) 9 | { 10 | typedef boost::graph_traits::edge_descriptor ed; 11 | typedef boost::graph_traits::out_edge_iterator oei; 12 | std::map willBeRemoved; 13 | std::set anchors; 14 | //std::cout << "Contracting loop: "; 15 | for (int i = 0; i < edgeLoops.size(); ++i) 16 | { 17 | for (const auto& e : edgeLoops[i]) 18 | { 19 | willBeRemoved[e] = true; 20 | } 21 | } 22 | 23 | for (size_t i = 0; i < boost::num_vertices(g); ++i) 24 | { 25 | if (g[i].split) 26 | { 27 | anchors.insert(i); 28 | continue; 29 | } 30 | 31 | oei eit, e_end; 32 | for (std::tie(eit, e_end) = boost::out_edges(i, g); eit != e_end; ++eit) 33 | { 34 | if (!willBeRemoved[*eit]) 35 | { 36 | anchors.insert(i); 37 | break; 38 | } 39 | } 40 | } 41 | 42 | //split loops into connected components 43 | std::vector> loopIdx(boost::num_vertices(g)); 44 | for (int i = 0; i < edgeLoops.size(); ++i) 45 | { 46 | for (const auto& e : edgeLoops[i]) 47 | loopIdx[e.m_source].insert(i); 48 | } 49 | 50 | std::vector> loopIdxOptimized(boost::num_vertices(g)); 51 | for (size_t i = 0; i < boost::num_vertices(g); ++i) 52 | { 53 | if (loopIdx[i].size() != 1) 54 | loopIdxOptimized[i] = loopIdx[i]; 55 | } 56 | 57 | std::vector processed(edgeLoops.size()); 58 | bool finished = false; 59 | std::vector componentByLoop(edgeLoops.size()); 60 | std::vector> components; 61 | 62 | std::vector> adjacentLoops(edgeLoops.size()); 63 | for (int i = 0; i < edgeLoops.size(); ++i) 64 | { 65 | adjacentLoops[i].insert(i); 66 | for (const auto& e : edgeLoops[i]) 67 | { 68 | for (int j : loopIdxOptimized[e.m_source]) 69 | adjacentLoops[i].insert(j); 70 | } 71 | } 72 | 73 | for (int i = 0; i < edgeLoops.size(); ++i) 74 | { 75 | if (processed[i]) 76 | continue; 77 | 78 | std::set newComponent = { i }; 79 | 80 | bool foundContinuation = true; 81 | while (foundContinuation) 82 | { 83 | foundContinuation = false; 84 | for (int loop : newComponent) 85 | { 86 | if (processed[loop]) 87 | continue; 88 | 89 | foundContinuation = true; 90 | processed[loop] = true; 91 | componentByLoop[loop] = components.size(); 92 | for (int j : adjacentLoops[loop]) 93 | { 94 | bool notInSet = newComponent.find(j) == newComponent.end(); 95 | if (!processed[j] && notInSet) 96 | newComponent.insert(j); 97 | } 98 | break; 99 | } 100 | } 101 | 102 | components.push_back(newComponent); 103 | /*std::cout << "COMPONENT " << components.size() - 1 << ": "; 104 | for (int jj : newComponent) 105 | { 106 | std::cout << jj << " "; 107 | } 108 | std::cout << std::endl;*/ 109 | } 110 | 111 | std::vector> myAdjacentVerts(components.size()); 112 | std::map keepThisEdge; 113 | for (int i = 0; i < edgeLoops.size(); ++i) 114 | { 115 | for (const auto& e : edgeLoops[i]) 116 | { 117 | if (anchors.find(e.m_source) != anchors.end()) 118 | myAdjacentVerts[componentByLoop[i]].insert(e.m_source); 119 | } 120 | } 121 | 122 | for (int i = 0; i < components.size(); ++i) 123 | { 124 | if (myAdjacentVerts[i].empty()) 125 | continue; 126 | 127 | Eigen::Vector2d avg(0, 0); 128 | Eigen::Vector2d avgRoot(0, 0); 129 | int cnt = 0; 130 | for (int j : components[i]) 131 | { 132 | for (const auto& e : edgeLoops[j]) 133 | { 134 | avg += g[e.m_source].location; 135 | avgRoot += g[e.m_source].root; 136 | ++cnt; 137 | } 138 | 139 | } 140 | avg /= cnt; 141 | avgRoot /= cnt; 142 | 143 | 144 | G sg; //subgraph 145 | std::map localToGlobal, globalToLocal; 146 | 147 | std::set componentVertices; 148 | for (int j : components[i]) 149 | for (const auto& e : edgeLoops[j]) 150 | { 151 | componentVertices.insert(e.m_source); 152 | } 153 | 154 | size_t chosenVertex = *myAdjacentVerts[i].begin(); 155 | 156 | double distToAvg = std::numeric_limits::max(); 157 | for (size_t v : componentVertices) 158 | { 159 | double d = (g[v].location - avg).norm(); 160 | if (distToAvg > d) 161 | { 162 | distToAvg = d; 163 | chosenVertex = v; 164 | } 165 | } 166 | 167 | for (size_t v : componentVertices) 168 | { 169 | size_t local = boost::add_vertex(sg); 170 | sg[local].clusterIdx = local; 171 | localToGlobal[local] = v; 172 | globalToLocal[v] = local; 173 | } 174 | 175 | std::set componentEdges; 176 | for (int j : components[i]) 177 | for (const auto& e : edgeLoops[j]) 178 | { 179 | auto ee = boost::add_edge(globalToLocal[e.m_source], globalToLocal[e.m_target], sg); 180 | sg[ee.first].weight = (g[e.m_source].location-g[e.m_target].location).norm(); 181 | } 182 | 183 | std::vector pDij(num_vertices(sg)); 184 | std::vector dDij(num_vertices(sg)); 185 | auto predMap = make_iterator_property_map(pDij.begin(), get(&Cluster::clusterIdx, sg)); 186 | auto distMap = make_iterator_property_map(dDij.begin(), get(&Cluster::clusterIdx, sg)); 187 | boost::dijkstra_shortest_paths(sg, globalToLocal[chosenVertex], 188 | predecessor_map(predMap). 189 | distance_map(distMap).weight_map(get(&Edge::weight, sg))); 190 | 191 | if (componentVertices.find(41) != componentVertices.end()) 192 | { 193 | std::cout << "!!!!!*** "; 194 | for (size_t k : myAdjacentVerts[i]) 195 | std::cout << k << " "; 196 | std::cout << std::endl << " $$$ "; 197 | for (size_t k : componentVertices) 198 | std::cout << k << " "; 199 | std::cout << std::endl; 200 | } 201 | 202 | 203 | for (size_t k : myAdjacentVerts[i]) 204 | { 205 | if (k == chosenVertex) 206 | continue; 207 | 208 | size_t cur = globalToLocal[k]; //local 209 | while (cur != globalToLocal[chosenVertex]) 210 | { 211 | size_t prev = predMap[cur]; 212 | keepThisEdge[boost::edge(localToGlobal[predMap[cur]],localToGlobal[cur],g).first] = true; 213 | cur = prev; 214 | } 215 | } 216 | } 217 | 218 | std::map deleted; //I shouldn't need this, but somehow.... 219 | std::set result; 220 | for (int i = 0; i < edgeLoops.size(); ++i) 221 | { 222 | for (const auto& e : edgeLoops[i]) 223 | { 224 | if (!deleted[e] && (boost::edge(e.m_source, e.m_target, g).second) && !keepThisEdge[e]) 225 | { 226 | deleted[e] = true; 227 | result.insert(e); 228 | boost::remove_edge(e, g); 229 | } 230 | } 231 | 232 | } 233 | return result; 234 | } 235 | 236 | G listToVec(const listG& g) 237 | { 238 | G result; 239 | listG::vertex_iterator vit, vend; 240 | std::map index; 241 | for (std::tie(vit, vend) = boost::vertices(g); vit != vend; ++vit) 242 | { 243 | size_t v = boost::add_vertex(result); 244 | index[*vit] = v; 245 | result[v] = g[*vit]; 246 | result[v].clusterIdx = v; 247 | } 248 | 249 | listG::edge_iterator eit, eend; 250 | for (std::tie(eit, eend) = boost::edges(g); eit != eend; ++eit) 251 | { 252 | auto e = boost::add_edge(index[eit->m_source], index[eit->m_target], result); 253 | result[e.first] = g[*eit]; 254 | } 255 | return result; 256 | } 257 | -------------------------------------------------------------------------------- /src/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "mainwindow.h" 3 | #include 4 | #include "ContractDeg2.h" 5 | MainWindow::MainWindow() :scale(1.0),numGraphs(8),numVectorizations(2) { 6 | //ui.setupUi(this); 7 | 8 | 9 | imageLabel = new ImageQuiverViewer; 10 | 11 | scrollArea = new MyScrollArea; 12 | imageLabel->setBackgroundRole(QPalette::Base); 13 | imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); 14 | imageLabel->setScaledContents(true); 15 | 16 | scrollArea->setBackgroundRole(QPalette::Dark); 17 | scrollArea->setWidget(imageLabel); 18 | scrollArea->setVisible(false); 19 | setCentralWidget(scrollArea); 20 | 21 | l = new QVBoxLayout; 22 | for (int i = 0; i < numGraphs; ++i) 23 | { 24 | checkGraphs.push_back(new QCheckBox); 25 | checkGraphs.back()->setChecked(false); 26 | connect(checkGraphs[i], &QCheckBox::stateChanged, [&](int ignore) { 27 | for (int j = 0; j < numGraphs; ++j) 28 | { 29 | imageLabel->graphHidden[j] = !checkGraphs[j]->isChecked(); 30 | } 31 | imageLabel->update(); 32 | }); 33 | } 34 | 35 | checkPolys = new QCheckBox; 36 | checkPolys->setChecked(true); 37 | checkPolys->setText("Curves"); 38 | connect(checkPolys, &QCheckBox::stateChanged, [&]() { 39 | imageLabel->polysHidden = !checkPolys->isChecked(); 40 | imageLabel->update(); 41 | }); 42 | 43 | checkIncontractibleLoops = new QCheckBox; 44 | checkIncontractibleLoops->setChecked(false); 45 | checkIncontractibleLoops->setText("Incontractible"); 46 | connect(checkIncontractibleLoops, &QCheckBox::stateChanged, [&]() { 47 | imageLabel->showIncontractibleLoops = checkIncontractibleLoops->isChecked(); 48 | imageLabel->update(); 49 | }); 50 | 51 | maskRadio = new QRadioButton; 52 | maskRadio->setText("Mask"); 53 | maskRadio->setChecked(false); 54 | connect(maskRadio, &QRadioButton::toggled, [&]() { 55 | redraw(); 56 | imageLabel->update(); 57 | }); 58 | 59 | imageRadio = new QRadioButton; 60 | imageRadio->setText("Image"); 61 | imageRadio->setChecked(true); 62 | 63 | l->addWidget(maskRadio); 64 | l->addWidget(imageRadio); 65 | 66 | l->addWidget(checkPolys); 67 | l->addWidget(checkIncontractibleLoops); 68 | 69 | for (int i = 0; i < numGraphs; ++i) 70 | l->addWidget(checkGraphs[i]); 71 | 72 | 73 | for (int i = 0; i < numVectorizations; ++i) 74 | { 75 | checkVectorizations.push_back(new QCheckBox); 76 | checkVectorizations.back()->setChecked(false); 77 | connect(checkVectorizations[i], &QCheckBox::stateChanged, [&](int ignore) { 78 | for (int j = 0; j < numVectorizations; ++j) 79 | { 80 | imageLabel->vectorizationsHidden[j] = !checkVectorizations[j]->isChecked(); 81 | } 82 | imageLabel->update(); 83 | }); 84 | l->addWidget(checkVectorizations[i]); 85 | } 86 | 87 | 88 | QDockWidget* dockWidget = new QDockWidget(tr(""), this); 89 | dockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); 90 | QWidget* garbage = new QWidget(); 91 | garbage->setLayout(l); 92 | dockWidget->setWidget(garbage); 93 | addDockWidget(Qt::RightDockWidgetArea, dockWidget); 94 | 95 | imageLabel->graphHidden.resize(numGraphs,true); 96 | imageLabel->graphs.resize(numGraphs); 97 | imageLabel->graphTags.resize(numGraphs); 98 | imageLabel->polysHidden = false; 99 | imageLabel->showCircles = true; 100 | imageLabel->showIncontractibleLoops = false; 101 | imageLabel->vectorizationsHidden.resize(numVectorizations, true); 102 | imageLabel->vectorizations.resize(numVectorizations); 103 | } 104 | 105 | MainWindow::~MainWindow() { 106 | 107 | } 108 | 109 | QImage Mat2QImage(const cv::Mat_ &src) 110 | { 111 | QImage dest(src.cols, src.rows, QImage::Format_ARGB32); 112 | for (int y = 0; y < src.rows; ++y) { 113 | const uchar *srcrow = src[y]; 114 | QRgb *destrow = (QRgb*)dest.scanLine(y); 115 | for (int x = 0; x < src.cols; ++x) { 116 | unsigned int color = srcrow[x]; 117 | destrow[x] = qRgba(color, color, color, 255); 118 | } 119 | } 120 | return dest; 121 | } 122 | 123 | void MainWindow::setImage(QString filename, const cv::Mat& mask) 124 | { 125 | actualImage = QImage (filename); 126 | imageLabel->setPixmap(QPixmap::fromImage(actualImage)); 127 | origWidth = imageLabel->pixmap()->width(); 128 | origHeight = imageLabel->pixmap()->height(); 129 | scale = 1.0; 130 | scrollArea->setVisible(true); 131 | imageLabel->adjustSize(); 132 | 133 | maskImage = Mat2QImage(mask); 134 | } 135 | 136 | void MainWindow::setRoots(const std::array& newRoots) 137 | { 138 | imageLabel->setRoots(newRoots); 139 | } 140 | 141 | void MainWindow::setExtraRoots(const std::array& newRoots) 142 | { 143 | imageLabel->setExtraRoots(newRoots); 144 | } 145 | 146 | void MainWindow::setPolys(const std::vector& newPolys) 147 | { 148 | imageLabel->setPolys(newPolys); 149 | } 150 | 151 | 152 | 153 | void MainWindow::setGraph(std::string s, const G& g) 154 | { 155 | int idx = graphNameToIdx.size(); 156 | graphNameToIdx.insert(std::make_pair(s, idx)); 157 | checkGraphs[idx]->setText(QString::fromStdString(s)); 158 | imageLabel->graphs[idx] = g; 159 | imageLabel->graphTags[idx] = s; 160 | } 161 | 162 | void MainWindow::setSplitVertices(const std::set& vtx) 163 | { 164 | imageLabel->splitVtx = vtx; 165 | } 166 | 167 | void MainWindow::setIncontractibleLoops(const std::vector>& loops) 168 | { 169 | imageLabel->incontractibleLoops.resize(loops.size()); 170 | for (int i = 0; i < loops.size(); ++i) 171 | { 172 | imageLabel->incontractibleLoops[i].resize(loops[i].size() + 1); 173 | for (int j = 0; j < loops[i].size(); ++j) 174 | imageLabel->incontractibleLoops[i][j] = { loops[i][j].x,loops[i][j].y }; 175 | imageLabel->incontractibleLoops[i][loops[i].size()] = { loops[i].front().x,loops[i].front().y }; 176 | } 177 | } 178 | 179 | void MainWindow::setVectorization(std::string s, const std::vector& vectorization) 180 | { 181 | int idx = vectorizationNameToIdx.size(); 182 | vectorizationNameToIdx.insert(std::make_pair(s, idx)); 183 | checkVectorizations[idx]->setText(QString::fromStdString(s)); 184 | imageLabel->vectorizations[idx] = vectorization; 185 | } 186 | 187 | void MainWindow::keyPressEvent(QKeyEvent *event) 188 | { 189 | if (event->key() == Qt::Key_C) 190 | { 191 | imageLabel->showCircles = !imageLabel->showCircles; 192 | imageLabel->update(); 193 | } 194 | if (event->key() == Qt::Key_A) 195 | { 196 | //autoscale 197 | double w = centralWidget()->width(); 198 | double h = centralWidget()->height(); 199 | setScale(std::min(w / origWidth, h / origHeight)); 200 | } 201 | if (event->key() == Qt::Key_Plus) 202 | { 203 | setScale(scale + (10 / 300.0)*scale); 204 | } 205 | else if (event->key() == Qt::Key_Minus) 206 | { 207 | setScale(scale - (10 / 300.0)*scale); 208 | } 209 | } 210 | 211 | void MainWindow::redraw() 212 | { 213 | if (scale < 10000.0 / std::max(origHeight, origHeight)) 214 | { 215 | imageLabel->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); 216 | if (maskRadio->isChecked()) 217 | imageLabel->setPixmap(QPixmap::fromImage(maskImage).scaled(origWidth*scale, origHeight*scale, Qt::KeepAspectRatio, Qt::FastTransformation)); 218 | else 219 | imageLabel->setPixmap(QPixmap::fromImage(actualImage).scaled(origWidth*scale, origHeight*scale, Qt::KeepAspectRatio, Qt::FastTransformation)); 220 | } 221 | else 222 | { 223 | imageLabel->setPixmap(QPixmap()); 224 | imageLabel->setFixedSize(origWidth*scale, origHeight*scale); 225 | } 226 | imageLabel->resize(imageLabel->pixmap()->size()); 227 | } 228 | 229 | void MainWindow::setScale(double newScale) 230 | { 231 | scale = newScale; 232 | scale = std::max(0.5, scale); 233 | imageLabel->scale = scale; 234 | redraw(); 235 | } 236 | 237 | void MainWindow::wheelEvent(QWheelEvent * event) 238 | { 239 | auto km = QApplication::keyboardModifiers(); 240 | if (km & Qt::KeyboardModifier::AltModifier && imageLabel->underMouse()) 241 | { 242 | double oldScale = scale; 243 | 244 | auto origPos = event->pos(); 245 | auto eventPos = imageLabel->mapFromParent(event->pos()); 246 | Eigen::Vector2d mousePos(eventPos.x(), eventPos.y()); 247 | mousePos = mousePos / scale - Eigen::Vector2d(0.5, 0.5); 248 | 249 | setScale(scale + (event->delta() / 300.0)*scale); 250 | 251 | scrollArea->horizontalScrollBar()->setValue(scrollArea->horizontalScrollBar()->maximum()*mousePos.x() / actualImage.width()); 252 | scrollArea->verticalScrollBar()->setValue(scrollArea->verticalScrollBar()->maximum()*mousePos.y() / actualImage.height()); 253 | 254 | event->accept(); 255 | } 256 | } -------------------------------------------------------------------------------- /src/LBFGS/Param.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 Yixuan Qiu 2 | // Under MIT license 3 | 4 | #ifndef PARAM_H 5 | #define PARAM_H 6 | 7 | #include 8 | #include // std::invalid_argument 9 | 10 | 11 | namespace LBFGSpp { 12 | 13 | 14 | /// 15 | /// \defgroup Enumerations 16 | /// 17 | /// Enumeration types for line search. 18 | /// 19 | 20 | /// 21 | /// \ingroup Enumerations 22 | /// 23 | /// The enumeration of line search algorithms. 24 | /// 25 | enum LINE_SEARCH_ALGORITHM 26 | { 27 | /// 28 | /// Backtracking method with the Armijo condition. 29 | /// The backtracking method finds the step length such that it satisfies 30 | /// the sufficient decrease (Armijo) condition, 31 | /// \f$f(x + a \cdot d) \le f(x) + \beta' \cdot a \cdot g(x)^T d\f$, 32 | /// where \f$x\f$ is the current point, \f$d\f$ is the current search direction, 33 | /// \f$a\f$ is the step length, and \f$\beta'\f$ is the value specified by 34 | /// \ref LBFGSParam::ftol. \f$f\f$ and \f$g\f$ are the function 35 | /// and gradient values respectively. 36 | /// 37 | LBFGS_LINESEARCH_BACKTRACKING_ARMIJO = 1, 38 | 39 | /// 40 | /// The backtracking method with the defualt (regular Wolfe) condition. 41 | /// An alias of `LBFGS_LINESEARCH_BACKTRACKING_WOLFE`. 42 | /// 43 | LBFGS_LINESEARCH_BACKTRACKING = 2, 44 | 45 | /// 46 | /// Backtracking method with regular Wolfe condition. 47 | /// The backtracking method finds the step length such that it satisfies 48 | /// both the Armijo condition (`LBFGS_LINESEARCH_BACKTRACKING_ARMIJO`) 49 | /// and the curvature condition, 50 | /// \f$g(x + a \cdot d)^T d \ge \beta \cdot g(x)^T d\f$, where \f$\beta\f$ 51 | /// is the value specified by \ref LBFGSParam::wolfe. 52 | /// 53 | LBFGS_LINESEARCH_BACKTRACKING_WOLFE = 2, 54 | 55 | /// 56 | /// Backtracking method with strong Wolfe condition. 57 | /// The backtracking method finds the step length such that it satisfies 58 | /// both the Armijo condition (`LBFGS_LINESEARCH_BACKTRACKING_ARMIJO`) 59 | /// and the following condition, 60 | /// \f$\vert g(x + a \cdot d)^T d\vert \le \beta \cdot \vert g(x)^T d\vert\f$, 61 | /// where \f$\beta\f$ is the value specified by \ref LBFGSParam::wolfe. 62 | /// 63 | LBFGS_LINESEARCH_BACKTRACKING_STRONG_WOLFE = 3 64 | }; 65 | 66 | 67 | /// 68 | /// Parameters to control the LBFGS algorithm. 69 | /// 70 | template 71 | class LBFGSParam 72 | { 73 | public: 74 | /// 75 | /// The number of corrections to approximate the inverse hessian matrix. 76 | /// The L-BFGS routine stores the computation results of previous \ref m 77 | /// iterations to approximate the inverse hessian matrix of the current 78 | /// iteration. This parameter controls the size of the limited memories 79 | /// (corrections). The default value is \c 6. Values less than \c 3 are 80 | /// not recommended. Large values will result in excessive computing time. 81 | /// 82 | int m; 83 | /// 84 | /// Tolerance for convergence test. 85 | /// This parameter determines the accuracy with which the solution is to 86 | /// be found. A minimization terminates when 87 | /// \f$||g|| < \epsilon * \max(1, ||x||)\f$, 88 | /// where ||.|| denotes the Euclidean (L2) norm. The default value is 89 | /// \c 1e-5. 90 | /// 91 | Scalar epsilon; 92 | /// 93 | /// Distance for delta-based conergence test. 94 | /// This parameter determines the distance \f$d\f$ to compute the 95 | /// rate of decrease of the objective function, 96 | /// \f$(f_{k-d}(x)-f_k(x))/f_k(x)\f$, where \f$k\f$ is the current iteration 97 | /// step. If the value of this parameter is zero, the delta-based convergence 98 | /// test will not be performed. The default value is \c 0. 99 | /// 100 | int past; 101 | /// 102 | /// Delta for convergence test. 103 | /// The algorithm stops when the following condition is met, 104 | /// \f$(f_{k-d}(x)-f_k(x))/f_k(x)<\delta\f$, where \f$f_k(x)\f$ is 105 | /// the current function value, \f$f_{k-d}(x)\f$ is the function value 106 | /// \f$d\f$ iterations ago (specified by the \ref past parameter). 107 | /// The default value is \c 0. 108 | /// 109 | Scalar delta; 110 | /// 111 | /// The maximum number of iterations. 112 | /// The optimization process is terminated when the iteration count 113 | /// exceedes this parameter. Setting this parameter to zero continues an 114 | /// optimization process until a convergence or error. The default value 115 | /// is \c 0. 116 | /// 117 | int max_iterations; 118 | /// 119 | /// The line search algorithm. 120 | /// This parameter specifies the line search algorithm that will be used 121 | /// by the LBFGS routine. The default value is `LBFGS_LINESEARCH_BACKTRACKING_ARMIJO`. 122 | /// 123 | int linesearch; 124 | /// 125 | /// The maximum number of trials for the line search. 126 | /// This parameter controls the number of function and gradients evaluations 127 | /// per iteration for the line search routine. The default value is \c 20. 128 | /// 129 | int max_linesearch; 130 | /// 131 | /// The minimum step length allowed in the line search. 132 | /// The default value is \c 1e-20. Usually this value does not need to be 133 | /// modified. 134 | /// 135 | Scalar min_step; 136 | /// 137 | /// The maximum step length allowed in the line search. 138 | /// The default value is \c 1e+20. Usually this value does not need to be 139 | /// modified. 140 | /// 141 | Scalar max_step; 142 | /// 143 | /// A parameter to control the accuracy of the line search routine. 144 | /// The default value is \c 1e-4. This parameter should be greater 145 | /// than zero and smaller than \c 0.5. 146 | /// 147 | Scalar ftol; 148 | /// 149 | /// A coefficient for the Wolfe condition. 150 | /// This parameter is valid only when the backtracking line-search 151 | /// algorithm is used with the Wolfe condition. 152 | /// The default value is \c 0.9. This parameter should be greater 153 | /// the \ref ftol parameter and smaller than \c 1.0. 154 | /// 155 | Scalar wolfe; 156 | 157 | public: 158 | /// 159 | /// Constructor for LBFGS parameters. 160 | /// Default values for parameters will be set when the object is created. 161 | /// 162 | LBFGSParam() 163 | { 164 | m = 6; 165 | epsilon = Scalar(1e-5); 166 | past = 0; 167 | delta = Scalar(0); 168 | max_iterations = 0; 169 | linesearch = LBFGS_LINESEARCH_BACKTRACKING_ARMIJO; 170 | max_linesearch = 20; 171 | min_step = Scalar(1e-20); 172 | max_step = Scalar(1e+20); 173 | ftol = Scalar(1e-4); 174 | wolfe = Scalar(0.9); 175 | } 176 | 177 | /// 178 | /// Checking the validity of LBFGS parameters. 179 | /// An `std::invalid_argument` exception will be thrown if some parameter 180 | /// is invalid. 181 | /// 182 | inline void check_param() const 183 | { 184 | if(m <= 0) 185 | throw std::invalid_argument("'m' must be positive"); 186 | if(epsilon <= 0) 187 | throw std::invalid_argument("'epsilon' must be positive"); 188 | if(past < 0) 189 | throw std::invalid_argument("'past' must be non-negative"); 190 | if(delta < 0) 191 | throw std::invalid_argument("'delta' must be non-negative"); 192 | if(max_iterations < 0) 193 | throw std::invalid_argument("'max_iterations' must be non-negative"); 194 | if(linesearch < LBFGS_LINESEARCH_BACKTRACKING_ARMIJO || 195 | linesearch > LBFGS_LINESEARCH_BACKTRACKING_STRONG_WOLFE) 196 | throw std::invalid_argument("unsupported line search algorithm"); 197 | if(max_linesearch <= 0) 198 | throw std::invalid_argument("'max_linesearch' must be positive"); 199 | if(min_step < 0) 200 | throw std::invalid_argument("'min_step' must be positive"); 201 | if(max_step < min_step ) 202 | throw std::invalid_argument("'max_step' must be greater than 'min_step'"); 203 | if(ftol <= 0 || ftol >= 0.5) 204 | throw std::invalid_argument("'ftol' must satisfy 0 < ftol < 0.5"); 205 | if(wolfe <= ftol || wolfe >= 1) 206 | throw std::invalid_argument("'wolfe' must satisfy ftol < wolfe < 1"); 207 | } 208 | }; 209 | 210 | 211 | } // namespace LBFGSpp 212 | 213 | #endif // PARAM_H 214 | -------------------------------------------------------------------------------- /src/Optimizer.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Optimizer.h" 3 | #include "TotalEnergy.h" 4 | #include "LBFGS.h" 5 | #include "Params.h" 6 | #include 7 | #include 8 | #include 9 | #include "polynomial_energy.h" 10 | #include "l2_regularizer.h" 11 | 12 | //#define _USE_IPOPT_ 1 13 | 14 | #ifdef _USE_IPOPT_ 15 | #include "IpIpoptApplication.hpp" 16 | #include "MyNLP.hpp" 17 | #endif 18 | 19 | 20 | Eigen::VectorXcd optimize(cv::Mat & bwImg, const Eigen::MatrixXd & weight, const Eigen::MatrixXcd & tauNormalized, double beta, cv::Mat & mask, const Eigen::MatrixXi& indices) 21 | { 22 | using namespace cv; 23 | int m = bwImg.rows; 24 | int n = bwImg.cols; 25 | int nnz = countNonZero(mask); 26 | std::cout << "nnz = " << nnz << std::endl; 27 | TotalEnergy fun(bwImg, weight, tauNormalized, beta, mask, indices,nnz); 28 | Eigen::VectorXd X(nnz * 4); //intial guess 29 | X.setZero(); 30 | 31 | #ifndef _USE_IPOPT_ 32 | 33 | 34 | LBFGSpp::LBFGSParam param; 35 | param.epsilon = 1e-4; 36 | param.max_iterations = 2000; 37 | 38 | LBFGSpp::LBFGSSolver solver(param); 39 | 40 | double fx; 41 | int niter = solver.minimize(fun, X, fx); 42 | 43 | std::cout << "Done in " << niter << " iterations" << std::endl; 44 | std::cout << "f(x) = " << fx << std::endl; 45 | 46 | #else 47 | using namespace Ipopt; 48 | SmartPtr mynlp = new MyNLP(fun,X); 49 | 50 | // Create a new instance of IpoptApplication 51 | // (use a SmartPtr, not raw) 52 | // We are using the factory, since this allows us to compile this 53 | // example with an Ipopt Windows DLL 54 | SmartPtr app = IpoptApplicationFactory(); 55 | app->RethrowNonIpoptException(true); 56 | 57 | // Change some options 58 | // Note: The following choices are only examples, they might not be 59 | // suitable for your optimization problem. 60 | app->Options()->SetNumericValue("tol", 1e-2); 61 | app->Options()->SetStringValue("output_file", "ipopt.out"); 62 | app->Options()->SetStringValue("hessian_approximation", "limited-memory"); 63 | app->Options()->SetStringValue("linear_solver", "pardiso"); 64 | app->Options()->SetNumericValue("print_level", 0); 65 | app->Options()->SetNumericValue("print_frequency_iter", 100); 66 | 67 | // The following overwrites the default name (ipopt.opt) of the 68 | // options file 69 | // app->Options()->SetStringValue("option_file_name", "hs071.opt"); 70 | 71 | // Initialize the IpoptApplication and process the options 72 | ApplicationReturnStatus status; 73 | status = app->Initialize(); 74 | if (status != Solve_Succeeded) { 75 | std::cout << std::endl << std::endl << "*** Error during initialization!" << std::endl; 76 | return Eigen::VectorXcd(); 77 | } 78 | 79 | // Ask Ipopt to solve the problem 80 | status = app->OptimizeTNLP(mynlp); 81 | 82 | if (status == Solve_Succeeded) { 83 | std::cout << std::endl << std::endl << "*** The problem solved!" << std::endl; 84 | } 85 | else { 86 | std::cout << std::endl << std::endl << "*** The problem FAILED!" << std::endl; 87 | } 88 | #endif 89 | Eigen::VectorXcd x_complex = X.head(X.size() / 2) + std::complex(0, 1)*X.tail(X.size() / 2); 90 | return x_complex; 91 | } 92 | 93 | std::pair computeSmartWeights(const Eigen::MatrixXd& weight, cv::Mat& mask, const Eigen::MatrixXi& indices) 94 | { 95 | int nnz = countNonZero(mask); 96 | int m = weight.rows(); 97 | int n = weight.cols(); 98 | Eigen::MatrixXd smartWeights(m, n); 99 | smartWeights.setOnes(); 100 | for (int i = 1; i < m - 1; ++i) 101 | for (int j = 1; j < n - 1; ++j) 102 | { 103 | smartWeights(i, j) = 0.25 * ((1 - weight(i - 1, j)) + (1 - weight(i + 1, j)) + (1 - weight(i, j - 1)) + (1 - weight(i, j + 1))); 104 | } 105 | 106 | Eigen::VectorXd smartWeightsVector(nnz); 107 | 108 | for (int j = 0; j < n; ++j) 109 | { 110 | for (int i = 0; i < m; ++i) 111 | { 112 | if (mask.at(i, j) == 0) 113 | continue; 114 | int idx = indices(i, j); 115 | smartWeightsVector(idx) = smartWeights(i, j); 116 | } 117 | } 118 | return std::make_pair(smartWeights, smartWeightsVector); 119 | } 120 | 121 | Eigen::VectorXcd optimizeByLinearSolve(cv::Mat& bwImg, const Eigen::MatrixXd& weight, const Eigen::MatrixXcd& tauNormalized, double beta, cv::Mat& mask, const Eigen::MatrixXi& indices) 122 | { 123 | using namespace cv; 124 | int m = bwImg.rows; 125 | int n = bwImg.cols; 126 | int nnz = countNonZero(mask); 127 | std::cout << "nnz = " << nnz << std::endl; 128 | 129 | auto [A, b] = polynomial_energy_matrix(weight, tauNormalized, mask, indices); 130 | 131 | Eigen::MatrixXd onesMatrix; 132 | onesMatrix = Eigen::MatrixXd(m, n); 133 | onesMatrix.setOnes(); 134 | Eigen::MatrixXcd g = tauNormalized * std::complex(0, 1); 135 | auto [A2, b2] = polynomial_energy_matrix(onesMatrix, g, mask, indices); 136 | 137 | Eigen::SparseMatrix L = laplacian_matrix(mask, indices, computeSmartWeights(weight,mask,indices).second); 138 | 139 | const double alpha = FRAME_FIELD_REGULARIZER_WEIGHT; 140 | Eigen::SparseMatrix> totalMatrix = 2 * (A + alpha*A2) + beta * 2.0 * L.cast>(); 141 | Eigen::VectorXcd totalRhs = -2 * b.conjugate() - 2 * alpha * b2.conjugate(); 142 | 143 | Eigen::ConjugateGradient>, Eigen::Lower | Eigen::Upper> cg; 144 | cg.compute(totalMatrix); 145 | 146 | Eigen::VectorXcd result = cg.solve(totalRhs); 147 | if (cg.info() != Eigen::Success) { 148 | // solving failed 149 | return Eigen::VectorXcd(); 150 | } 151 | else 152 | return result; 153 | } 154 | 155 | Eigen::VectorXcd optimizeByLinearSolve_holdingSomeFixed(cv::Mat& bwImg, const Eigen::MatrixXd& weight, const Eigen::MatrixXcd& tauNormalized, double beta, cv::Mat& mask, cv::Mat& fixedMask, const Eigen::MatrixXi& indices, const Eigen::MatrixXi& fixedIndices, const Eigen::VectorXcd& Xfixed) 156 | { 157 | using namespace cv; 158 | int m = bwImg.rows; 159 | int n = bwImg.cols; 160 | int nnz = countNonZero(mask); 161 | int oldNnz = countNonZero(fixedMask); 162 | std::cout << "nnz = " << nnz << std::endl; 163 | 164 | Eigen::SparseMatrix L = laplacian_matrix(mask, indices, computeSmartWeights(weight, mask, indices).second); 165 | //convert it to triplets 166 | std::vector> newTrs; 167 | Eigen::VectorXcd rhs(nnz*2); 168 | rhs.setZero(); 169 | 170 | std::map newIdxToOldIdx; 171 | for (int j = 0; j < n; ++j) 172 | { 173 | for (int i = 0; i < m; ++i) 174 | { 175 | if ((mask.at(i, j) == 0) || (fixedMask.at(i,j)==0)) 176 | continue; 177 | int idx = indices(i, j); 178 | int oldIdx = fixedIndices(i,j); 179 | newIdxToOldIdx.insert({ idx,oldIdx }); 180 | } 181 | } 182 | 183 | auto oldIdx = [&newIdxToOldIdx](int idx) 184 | { 185 | auto it = newIdxToOldIdx.find(idx); 186 | if (it != newIdxToOldIdx.end()) 187 | return it->second; 188 | else 189 | return -1; 190 | }; 191 | 192 | std::vector fixEqnAdded(nnz); //says if we have already fixed that (new) index 193 | for (int k = 0; k < L.outerSize(); ++k) 194 | for (Eigen::SparseMatrix::InnerIterator it(L, k); it; ++it) 195 | { 196 | int newI = it.row(), newJ = it.col(); 197 | if ((newI >= nnz) || (newJ >= nnz)) //L is (2n x 2n), for all variables 198 | continue; 199 | 200 | int oldI = oldIdx(newI), oldJ = oldIdx(newJ); 201 | if (oldI != -1) 202 | { 203 | if (!fixEqnAdded[newI]) 204 | { 205 | fixEqnAdded[newI] = true; 206 | //fix x0 207 | newTrs.push_back({ newI, newI, 1.0 }); 208 | rhs[newI] = Xfixed[oldI]; 209 | 210 | //fix x2 211 | newTrs.push_back({ newI+nnz, newI+nnz, 1.0 }); 212 | rhs[newI+nnz] = Xfixed[oldI+oldNnz]; 213 | } 214 | } 215 | else if (oldJ != -1) 216 | { 217 | rhs(newI) -= it.value() * Xfixed(oldJ); 218 | rhs(newI+nnz) -= it.value() * Xfixed(oldJ+oldNnz); 219 | } 220 | else 221 | { 222 | newTrs.push_back({ (int)it.row(),(int)it.col(),it.value() }); //for x0 223 | newTrs.push_back({ (int)it.row()+nnz,(int)it.col()+nnz,it.value() }); //copy for x2 224 | } 225 | } 226 | 227 | 228 | 229 | //now let's fix the known part of the frame field 230 | 231 | 232 | Eigen::SparseMatrix> totalMatrix(nnz*2,nnz*2); 233 | totalMatrix.setFromTriplets(newTrs.begin(),newTrs.end()); 234 | 235 | Eigen::ConjugateGradient>, Eigen::Lower | Eigen::Upper> cg; 236 | cg.compute(totalMatrix); 237 | 238 | //std::ofstream fff("matdebug.m"); 239 | //fff << "M = [" << Eigen::MatrixXd(totalMatrix.real()) << "];" << std::endl; 240 | //fff.close(); 241 | 242 | Eigen::VectorXcd result = cg.solve(rhs); 243 | if (cg.info() != Eigen::Success) { 244 | // solving failed 245 | return Eigen::VectorXcd(); 246 | } 247 | else 248 | { 249 | /*double error = 0; 250 | std::cout << "Fixed indices: "; 251 | for (auto it : newIdxToOldIdx) 252 | { 253 | std::complex newVal, oldVal; 254 | std::cout << it.first << " "; 255 | newVal = result[it.first]; 256 | oldVal = Xfixed[it.second]; 257 | error += std::abs(std::pow(newVal - oldVal, 2)); 258 | }*/ 259 | return result; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/imagequiverviewer.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "imagequiverviewer.hpp" 3 | #include 4 | #include "IsLoopContractible.h" 5 | #include "HermiteSpline.h" 6 | ImageQuiverViewer::ImageQuiverViewer(QWidget * parent) : QLabel(parent), scale(1.0), 7 | colors({{ 93,151,205 },{ 255, 146, 0 },{ 255, 69, 0 },{ 0, 191, 89 },{ 209,63,240 },{194,42,221}, {107,182,230}, {84,183,44}}) { 8 | 9 | } 10 | 11 | ImageQuiverViewer::~ImageQuiverViewer() { 12 | 13 | } 14 | 15 | 16 | 17 | void ImageQuiverViewer::setRoots(const std::array& newRoots) 18 | { 19 | roots = newRoots; 20 | } 21 | 22 | void ImageQuiverViewer::setExtraRoots(const std::array& newRoots) 23 | { 24 | extraRoots = newRoots; 25 | } 26 | 27 | void ImageQuiverViewer::setPolys(const std::vector& newPolys) 28 | { 29 | polys = newPolys; 30 | } 31 | 32 | void ImageQuiverViewer::drawRoots(const std::array& thoseOnes, QPainter& painter) 33 | { 34 | for (int i = 0; i < thoseOnes[0].rows(); ++i) 35 | for (int j = 0; j < thoseOnes[0].cols(); ++j) 36 | { 37 | for (int rootIdx = 0; rootIdx < 2; ++rootIdx) 38 | { 39 | std::complex vec = thoseOnes[rootIdx](i, j)*0.5; 40 | QPoint origPoint((j + 0.5)*scale - vec.real()*scale, (i + 0.5)*scale - vec.imag()*scale); 41 | QPoint newPoint((j + 0.5)*scale + vec.real()*scale, (i + 0.5)*scale + vec.imag()*scale); 42 | painter.drawLine(origPoint, newPoint); 43 | } 44 | } 45 | } 46 | 47 | void ImageQuiverViewer::drawGraphs(QPainter& painter) 48 | { 49 | 50 | for (int i = 0; i < graphs.size(); ++i) 51 | { 52 | if (!graphHidden[i]) 53 | { 54 | std::map shouldDraw; 55 | QPen polyPen(colors[i % colors.size()]); 56 | polyPen.setWidth(2.0); 57 | painter.setPen(polyPen); 58 | QBrush brush(QColor(255,255,255)); 59 | brush.setStyle(Qt::BrushStyle::SolidPattern); 60 | 61 | painter.setBrush(brush); 62 | edge_iter ei, eend; 63 | 64 | for (std::tie(ei, eend) = boost::edges(graphs[i]); ei != eend; ++ei) 65 | { 66 | Eigen::Vector2d edge = graphs[i][ei->m_target].location - graphs[i][ei->m_source].location; 67 | double len = edge.norm(); 68 | if (false) 69 | { 70 | if (graphTags[i] != "Topo") 71 | //spline! 72 | { 73 | Eigen::Vector2d root1 = graphs[i][ei->m_source].root, root2 = graphs[i][ei->m_target].root; 74 | if (root1.norm() > 100) 75 | root1 = edge.normalized(); 76 | if (root2.norm() > 100) 77 | root2 = edge.normalized(); 78 | 79 | std::vector> possibleSigns = { { 1.0,1.0 },{ -1.0,1.0 },{ 1.0,-1.0 },{ -1.0,-1.0 } }; 80 | double minCurv = 1e10; 81 | std::array bestSigns; 82 | for (auto signs : possibleSigns) 83 | { 84 | HermiteSpline hs(graphs[i][ei->m_source].location, root1*signs[0] * len / 3, graphs[i][ei->m_target].location, root2*signs[1] * len / 3); 85 | double curv = hs.integrateAbsCurvature(); 86 | if (curv < minCurv) 87 | { 88 | bestSigns = signs; 89 | minCurv = curv; 90 | } 91 | } 92 | 93 | HermiteSpline hs(graphs[i][ei->m_source].location, root1*bestSigns[0] * len / 3, graphs[i][ei->m_target].location, root2*bestSigns[1] * len / 3); 94 | double splineLen = hs.getLength(); 95 | std::vector pts; 96 | for (int i = 0; i < len * 10; ++i) 97 | { 98 | double t = i / (len * 10); 99 | pts.push_back(hs.getPoint(t)); 100 | } 101 | for (int i = 0; i + 1 < pts.size(); ++i) 102 | { 103 | painter.drawLine(modelToScreen(pts[i]), modelToScreen(pts[i + 1])); 104 | } 105 | } 106 | } 107 | else 108 | { 109 | Eigen::Vector2d p1 = graphs[i][ei->m_source].location; 110 | Eigen::Vector2d p2 = graphs[i][ei->m_target].location; 111 | painter.drawLine(modelToScreen(p1), modelToScreen(p2)); 112 | } 113 | shouldDraw[ei->m_source] = true; 114 | shouldDraw[ei->m_target] = true; 115 | } 116 | 117 | if (showCircles) 118 | { 119 | for (size_t v = 0; v < boost::num_vertices(graphs[i]); ++v) 120 | { 121 | if (shouldDraw[v]) 122 | { 123 | Eigen::Vector2d p = graphs[i][v].location ; 124 | painter.drawEllipse(modelToScreen(p), 3, 3); 125 | } 126 | } 127 | 128 | QBrush selectBrush(QColor(233, 29, 235)); 129 | painter.setBrush(selectBrush); 130 | for (size_t v = 0; v < boost::num_vertices(graphs[i]); ++v) 131 | { 132 | if (graphs[i][v].clusterCurveHitSingularity && !graphs[i][v].nextToSingularity && shouldDraw[v]) 133 | { 134 | Eigen::Vector2d p = graphs[i][v].location ; 135 | painter.drawEllipse(modelToScreen(p), 5, 5); 136 | } 137 | } 138 | 139 | QBrush selectBrush1(QColor(55, 224, 59)); 140 | painter.setBrush(selectBrush1); 141 | for (size_t v = 0; v < boost::num_vertices(graphs[i]); ++v) 142 | { 143 | if (graphs[i][v].nextToSingularity && !graphs[i][v].clusterCurveHitSingularity && shouldDraw[v]) 144 | { 145 | Eigen::Vector2d p = graphs[i][v].location; 146 | painter.drawEllipse(modelToScreen(p), 5, 5); 147 | } 148 | } 149 | 150 | QBrush selectBrush2(QColor(255, 255, 59)); 151 | painter.setBrush(selectBrush2); 152 | for (size_t v = 0; v < boost::num_vertices(graphs[i]); ++v) 153 | { 154 | if (graphs[i][v].nextToSingularity && graphs[i][v].clusterCurveHitSingularity && shouldDraw[v]) 155 | { 156 | Eigen::Vector2d p = graphs[i][v].location; 157 | painter.drawEllipse(modelToScreen(p), 5, 5); 158 | } 159 | } 160 | painter.setBrush(brush); 161 | } 162 | 163 | if ((i==1) && showIncontractibleLoops) 164 | { 165 | QPen polyPen(colors[(graphs.size()+1) % colors.size()]); 166 | polyPen.setWidth(2.0); 167 | painter.setPen(polyPen); 168 | for (int j = 0; j < incontractibleLoops.size(); ++j) 169 | { 170 | for (int k = 0; k+1 < incontractibleLoops[j].size(); ++k) 171 | { 172 | painter.drawLine(modelToScreen(incontractibleLoops[j][k]), modelToScreen(incontractibleLoops[j][k + 1])); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | void ImageQuiverViewer::drawPolys(QPainter& painter, const std::vector& curves, double width, QColor color) 180 | { 181 | int alpha = areGraphsVisible() ? 80 : 255; 182 | //QPen polyPen(QColor(0, 255, 0, alpha)) 183 | QPen polyPen(QColor(color.red(), color.green(), color.blue(), alpha)); 184 | polyPen.setWidth(width); 185 | painter.setPen(polyPen); 186 | for (int i = 0; i < curves.size(); ++i) 187 | { 188 | if (curves[i].empty()) 189 | continue; 190 | 191 | for (int j = 0; j < curves[i].size() - 1; ++j) 192 | { 193 | painter.drawLine(modelToScreen(curves[i][j]), modelToScreen(curves[i][j + 1])); 194 | 195 | //if (curves.size() < 20) 196 | //{ 197 | // painter.drawEllipse(modelToScreen(curves[i][j]), 3, 3); 198 | //} 199 | } 200 | } 201 | 202 | if (curves.size() < 1000) 203 | { 204 | auto font = painter.font(); 205 | font.setPointSize(std::min(10 * scale, 10.0)); 206 | painter.setFont(font); 207 | for (int i = 0; i < curves.size(); ++i) 208 | { 209 | if (curves[i].empty()) 210 | continue; 211 | 212 | painter.drawText(modelToScreen(curves[i][curves[i].size() / 2]), QString::number(i)); 213 | } 214 | } 215 | } 216 | 217 | bool ImageQuiverViewer::areGraphsVisible() 218 | { 219 | bool result = false; 220 | for (int i = 0; i < graphHidden.size(); ++i) 221 | result |= !graphHidden[i]; 222 | return result; 223 | } 224 | 225 | void ImageQuiverViewer::paintEvent(QPaintEvent * event) 226 | { 227 | QLabel::paintEvent(event); 228 | QPainter painter(this); 229 | int alpha = areGraphsVisible() || !vectorizationsHidden[0] || !vectorizationsHidden[1] ? 20+scale : 255; 230 | 231 | painter.setPen(QColor(255, 0, 0, alpha)); 232 | drawRoots(roots, painter); 233 | painter.setPen(QColor(255, 255, 0, alpha)); 234 | drawRoots(extraRoots, painter); 235 | 236 | if (!polysHidden) 237 | drawPolys(painter,polys,1.0,colors.back()); 238 | 239 | drawGraphs(painter); 240 | drawClusters(painter); 241 | 242 | for (int i=0; i adjVerts = { cl.second }; 280 | boost::graph_traits::out_edge_iterator eit, eend; 281 | //for (std::tie(eit, eend) = boost::out_edges(cl.second, graphs[cl.first]); eit != eend; ++eit) 282 | // adjVerts.insert(eit->m_target); 283 | 284 | painter.setPen(colors[cl.first%colors.size()]); 285 | for (int v : adjVerts) 286 | { 287 | painter.drawText(modelToScreen(graphs[cl.first][v].location)+QPointF(3, 0), QString::number(v)); 288 | } 289 | 290 | QPen polyPen(QColor(255, 0, 255)); 291 | polyPen.setWidth(1.0); 292 | painter.setPen(polyPen); 293 | for (int j = 0; j+1 < graphs[cl.first][cl.second].clusterCurve.size(); ++j) 294 | painter.drawLine(modelToScreen(graphs[cl.first][cl.second].clusterCurve[j]), modelToScreen(graphs[cl.first][cl.second].clusterCurve[j+1])); 295 | } 296 | 297 | QPen pp(colors[1]); 298 | pp.setWidth(3.0); 299 | painter.setPen(pp); 300 | for (auto cl : clustersToDraw) 301 | { 302 | for (const auto& pt : graphs[cl.first][cl.second].clusterPoints) 303 | { 304 | if (pt.curve == graphs[cl.first][cl.second].seedCurve) 305 | { 306 | //draw root 307 | Eigen::Vector2d p0 = pt.p, vec = graphs[cl.first][cl.second].root; 308 | Eigen::Vector2d p1 = p0 + vec * 6; 309 | painter.drawLine(modelToScreen(p0), modelToScreen(p1)); 310 | } 311 | } 312 | } 313 | } 314 | } 315 | 316 | void ImageQuiverViewer::mousePressEvent(QMouseEvent * event) 317 | { 318 | if (event->buttons() & Qt::RightButton) 319 | { 320 | Eigen::Vector2d mousePos(event->pos().x() / scale - 0.5, event->pos().y() / scale - 0.5); 321 | if (event->modifiers() & Qt::ControlModifier) 322 | { 323 | std::cout << "You clicked at pixel " << (int)std::round(mousePos.y()) << " " << (int)std::round(mousePos.x()) << std::endl; 324 | } 325 | else 326 | { 327 | if (event->modifiers() & Qt::AltModifier) 328 | std::cout << "Selected vertices: "; 329 | for (int i = 0; i < graphs.size(); ++i) 330 | { 331 | if (!graphHidden[i]) 332 | { 333 | for (size_t v = 0; v < boost::num_vertices(graphs[i]); ++v) 334 | { 335 | if (boost::degree(v, graphs[i]) == 0) 336 | continue; 337 | 338 | if ((graphs[i][v].location - mousePos).norm() < 3 / scale) 339 | { 340 | clustersToDraw.insert({ i,v }); 341 | if (event->modifiers() & Qt::AltModifier) 342 | { 343 | std::cout << v << " " << " (valence " << boost::degree(v, graphs[i]) << "); "; 344 | oedge_iter eit, eend; 345 | std::cout << "Adjacent verts: "; 346 | for (std::tie(eit, eend) = boost::out_edges(v, graphs[i]); eit != eend; ++eit) 347 | { 348 | std::cout << eit->m_target << " "; 349 | } 350 | std::cout << std::endl; 351 | } 352 | } 353 | } 354 | } 355 | } 356 | if (event->modifiers() & Qt::AltModifier) 357 | std::cout << std::endl; 358 | } 359 | } 360 | else if (event->buttons() & Qt::LeftButton) 361 | { 362 | clustersToDraw.clear(); 363 | } 364 | update(); 365 | event->accept(); 366 | } 367 | -------------------------------------------------------------------------------- /src/SplitEmUp.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "SplitEmUp.h" 3 | #include "FillHole.h" 4 | #include "ChainDecomposition.h" 5 | #include "ContractDeg2.h" 6 | void splitEmUpCorrectly(G& g) 7 | { 8 | std::cout << "Splitting stuff... "; 9 | //1. Break into chains, record their adjacencies into another graph 10 | //2. For each chain in that little graph, if its degree is 3, 'open up' the chain 11 | //3. If degree is 4, split the chain into two 12 | //4. PROFIT 13 | 14 | int n = boost::num_vertices(g); 15 | 16 | //auto chains = chainDecomposition(g, myChain); 17 | std::vector> chains; 18 | auto tGraph = topoGraphHighValenceSeparated(g, chains, true); 19 | std::map myChain; 20 | for (int i = 0; i < chains.size(); ++i) 21 | { 22 | for (const auto& e : chains[i]) 23 | myChain[e] = i; 24 | } 25 | 26 | std::cout << "chains done... "; 27 | //2. For each chain in that little graph, if its degree is 3, 'open up' the chain 28 | 29 | auto avgTangent = [&](int chainIdx, bool end) 30 | { 31 | Eigen::Vector2d avg(0, 0); 32 | int count = 0; 33 | for (int i = 0; i < std::min(chains[chainIdx].size(), (size_t)20); ++i) 34 | { 35 | auto e = end ? chains[chainIdx][chains[chainIdx].size() - i - 1] : chains[chainIdx][i]; 36 | Eigen::Vector2d edgeVec = g[e.m_target].location - g[e.m_source].location; 37 | avg += edgeVec.normalized(); 38 | ++count; 39 | } 40 | 41 | if (end) 42 | avg = -avg; 43 | Eigen::Vector2d result = avg / count; 44 | return result; 45 | }; 46 | 47 | std::vector chainsToSplit; 48 | std::map, bool> shouldSplit; //for convenience, has the same information as chainsToSplit. {chainIdx, endIdx} 49 | std::map willBeSplit; //for convenience, per vertex 50 | for (int i = 0; i < chains.size(); ++i) 51 | { 52 | ChainSplitInfo c = { i,{false,false} }; 53 | 54 | for (int end = 0; end < 2; ++end) 55 | { 56 | size_t vtx = (end == 0) ? chains[i].front().m_source : chains[i].back().m_target; 57 | if (boost::degree(vtx, g) == 3) 58 | { 59 | //we have a candidate 60 | //should have a different sign of dot product with the root than the other 2 branches 61 | edge_descriptor myEdge = (end == 0) ? chains[i].front() : boost::edge(chains[i].back().m_target, chains[i].back().m_source, g).first; 62 | Eigen::Vector2d myEdgeVec = avgTangent(i, (end == 1)); 63 | double myDot = myEdgeVec.dot(g[vtx].root); 64 | 65 | oedge_iter oeit, oend; 66 | bool shouldSplit = true; 67 | for (std::tie(oeit, oend) = boost::out_edges(vtx, g); oeit != oend; ++oeit) 68 | { 69 | if (*oeit == myEdge) 70 | continue; 71 | 72 | bool neighEnd = (chains[myChain[*oeit]].back().m_target == vtx); 73 | Eigen::Vector2d edgeVec = avgTangent(myChain[*oeit], neighEnd); 74 | if (edgeVec.dot(g[vtx].root)*myDot > 0) 75 | shouldSplit = false; 76 | } 77 | 78 | c.splitEnd[end] = shouldSplit; 79 | } 80 | } 81 | 82 | //the last condition is only to fight a bug in the sheriff's chin. I don't have time to debug it :( 83 | if (c.splitEnd[0] && c.splitEnd[1] && chains[i].size()<100) 84 | { 85 | willBeSplit[chains[i].front().m_source] = true; 86 | willBeSplit[chains[i].back().m_target] = true; 87 | shouldSplit[{i, 0}] = c.splitEnd[0]; 88 | shouldSplit[{i, 1}] = c.splitEnd[1]; 89 | chainsToSplit.push_back(c); 90 | } 91 | } 92 | 93 | //3. Do the actual splitting 94 | std::vector> chainVertices(chains.size()); 95 | for (int i = 0; i < chains.size(); ++i) 96 | { 97 | std::vector myChainVerts; 98 | myChainVerts.reserve(chains[i].size() + 1); 99 | for (const auto& e : chains[i]) 100 | myChainVerts.push_back(e.m_source); 101 | myChainVerts.push_back(chains[i].back().m_target); 102 | chainVertices[i] = myChainVerts; 103 | } 104 | 105 | auto duplicateVtx = [&](size_t v) 106 | { 107 | size_t newV = boost::add_vertex(g); 108 | g[v].split = true; 109 | g[newV] = g[v]; 110 | g[newV].clusterIdx = boost::num_vertices(g)-1; 111 | g[newV].sharpCorner = false; 112 | return newV; 113 | }; 114 | 115 | auto createEdge = [&](size_t u, size_t v) 116 | { 117 | auto e = boost::add_edge(u, v, g); 118 | g[e.first].edgeCurve = -1; //not used in final optimization 119 | g[e.first].weight = 1; //default weight, HOPEFULLY not used in the optimization 120 | //std::cout << "Creating edge between " << u << " and " << v << std::endl; 121 | return e.first; 122 | }; 123 | 124 | std::map> newAdjacencies; //[vertex][incomingChainIdx] 125 | std::map,std::vector> newVertices; //per {chain,dir} 126 | for (const auto& c : chainsToSplit) 127 | { 128 | const auto& myChainVertices = chainVertices[c.chain]; 129 | 130 | for (int dir = 0; dir < 2; ++dir) 131 | { 132 | size_t v = (dir == 0) ? myChainVertices.front() : myChainVertices.back(); 133 | std::vector myNewVerts = { v }; 134 | //todo: fill in info for the new vertex 135 | if (c.splitEnd[dir]) 136 | { 137 | size_t newVertex = duplicateVtx(v); 138 | myNewVerts.push_back(newVertex); 139 | newVertices[{c.chain, dir}] = myNewVerts; 140 | 141 | oedge_iter eit, eend; 142 | for (std::tie(eit, eend) = boost::out_edges(v, g); eit != eend; ++eit) 143 | { 144 | int otherChain = myChain[*eit]; 145 | if (otherChain != c.chain) 146 | { 147 | newAdjacencies[v][otherChain] = myNewVerts[newAdjacencies[v].size() % myNewVerts.size()]; //if we're not splitting at this end, then all the adjacent chains should connect to that vertex 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | auto adjustYJunction = [&](size_t& v1, size_t& v2, size_t yJunctionVtx) 155 | { 156 | std::map traversed; 157 | traversed[yJunctionVtx] = true; 158 | size_t initialV1 = v1, initialV2 = v2; 159 | bool haveSharedCurves; 160 | bool fixed; 161 | 162 | do 163 | { 164 | fixed = false; 165 | haveSharedCurves = false; 166 | traversed[v1] = true; 167 | traversed[v2] = true; 168 | for (const auto& c1 : g[v1].clusterPoints) 169 | { 170 | for (const auto& c2 : g[v2].clusterPoints) 171 | { 172 | if (c1.curve == c2.curve) 173 | { 174 | haveSharedCurves = true; 175 | break; 176 | } 177 | } 178 | } 179 | 180 | if (haveSharedCurves) 181 | { 182 | oedge_iter eit, eend; 183 | if (boost::degree(v1, g) == 2) 184 | { 185 | for (std::tie(eit, eend) = boost::out_edges(v1, g); eit != eend; ++eit) 186 | { 187 | if (!traversed[eit->m_target] && (boost::degree(eit->m_target,g)==2)) 188 | { 189 | v1 = eit->m_target; 190 | fixed = true; 191 | } 192 | } 193 | } 194 | if (boost::degree(v2, g) == 2) 195 | { 196 | for (std::tie(eit, eend) = boost::out_edges(v2, g); eit != eend; ++eit) 197 | { 198 | if (!traversed[eit->m_target] && (boost::degree(eit->m_target, g) == 2)) 199 | { 200 | v2 = eit->m_target; 201 | fixed = true; 202 | } 203 | } 204 | } 205 | } 206 | 207 | if ((boost::degree(v1, g) != 2) && (boost::degree(v2, g) != 2)) 208 | break; 209 | } while (haveSharedCurves && fixed); 210 | 211 | if (haveSharedCurves) 212 | { 213 | //failed, return everything 214 | std::cout << "FAILED to adjust" << std::endl; 215 | v1 = initialV1; 216 | v2 = initialV2; 217 | } 218 | }; 219 | 220 | //now we know which vertex to connect to 221 | //for every chain, now connect beginning vertices to the ending vertices 222 | 223 | for (size_t i = 0; i < chains.size(); ++i) 224 | { 225 | const auto& myChainVertices = chainVertices[i]; 226 | if (!willBeSplit[myChainVertices.front()] && !willBeSplit[myChainVertices.back()]) 227 | continue; 228 | 229 | std::array,2> activeVerts; 230 | //std::vector edgesToRemove; 231 | for (int dir = 0; dir < 2; ++dir) 232 | { 233 | size_t vtx = dir == 0 ? myChainVertices.front() : myChainVertices.back(); 234 | size_t adjVtx = (dir == 0) ? myChainVertices[1] : myChainVertices[myChainVertices.size() - 2]; 235 | 236 | boost::remove_edge(vtx, adjVtx, g); 237 | //edgesToRemove.push_back(boost::edge(vtx, adjVtx, g).first); 238 | 239 | if (shouldSplit[{ i, dir }]) 240 | activeVerts[dir] = newVertices[{i, dir}]; 241 | else 242 | { 243 | if (willBeSplit[vtx]) 244 | { 245 | auto it = newAdjacencies.find(vtx); 246 | assert(it != newAdjacencies.end()); 247 | if (it == newAdjacencies.end()) 248 | std::cout << "ERROR 1" << std::endl; 249 | 250 | auto it2 = it->second.find(i); 251 | assert(it2 != it->second.end()); 252 | 253 | if (it2 == it->second.end()) 254 | { 255 | std::cout << "ERROR 2, size: " << it->second.size() << ", vtx = " << vtx << std::endl; 256 | } 257 | 258 | activeVerts[dir] = { it2->second }; 259 | } 260 | else 261 | activeVerts[dir] = { vtx }; 262 | } 263 | } 264 | 265 | std::vector> finalEdges; 266 | if ((activeVerts[0].size() == 2) && (activeVerts[1].size() == 2)) 267 | { 268 | std::vector pseudoHole; 269 | //look at all the adjacent clusters 270 | std::map connectedTo; //for convenience to replace the vertices in the edges returned by fillHole to finalEdges 271 | for (int dir = 0; dir < 2; ++dir) 272 | { 273 | size_t vtx = dir == 0 ? myChainVertices.front() : myChainVertices.back(); 274 | for (auto it : newAdjacencies[vtx]) 275 | { 276 | int chainIdx = it.first; 277 | if (chainIdx != i) 278 | { 279 | size_t theirVertex = (chainVertices[chainIdx].back() == vtx) ? chainVertices[chainIdx][chainVertices[chainIdx].size() - 2] : chainVertices[chainIdx][1]; 280 | pseudoHole.push_back(theirVertex); 281 | connectedTo[theirVertex] = it.second; 282 | } 283 | } 284 | } 285 | 286 | std::cout << "Hole: "; 287 | for (size_t v : pseudoHole) 288 | std::cout << v << " "; 289 | std::cout << std::endl; 290 | 291 | if (pseudoHole.size() != 4) 292 | { 293 | std::cout << "Skipping this chain: "; 294 | for (size_t v : myChainVertices) 295 | std::cout << v << " "; 296 | return; 297 | continue; 298 | } 299 | 300 | auto origHole = pseudoHole; 301 | 302 | //make sure vertices (0,1) and (2,3) don't share any curves in common 303 | adjustYJunction(pseudoHole[0], pseudoHole[1], myChainVertices.front()); 304 | adjustYJunction(pseudoHole[2], pseudoHole[3], myChainVertices.back()); 305 | std::vector> prohibitedEdges = { {pseudoHole[0], pseudoHole[1]},{ pseudoHole[2], pseudoHole[3] } }; 306 | 307 | std::cout << "Adjusted to: "; 308 | for (size_t v : pseudoHole) 309 | std::cout << v << " "; 310 | std::cout << std::endl; 311 | 312 | for (int i = 0; i < 4; ++i) 313 | connectedTo[pseudoHole[i]] = connectedTo[origHole[i]]; 314 | 315 | auto holeEdges = fillHole(pseudoHole.begin(), pseudoHole.end(), g, prohibitedEdges); 316 | 317 | 318 | 319 | //now we have connections,but those are connecting adjacent chains 320 | //convert the vertex indices into my chain indices 321 | for (auto it : holeEdges) 322 | { 323 | int a = connectedTo[it.first], b = connectedTo[it.second]; 324 | if ((activeVerts[1][0] == a) || (activeVerts[1][1] == a)) 325 | std::swap(a, b); 326 | finalEdges.push_back({ a , b }); 327 | } 328 | } 329 | else 330 | { 331 | for (size_t v1 : activeVerts[0]) 332 | for (size_t v2 : activeVerts[1]) 333 | finalEdges.push_back({ (int)v1, (int)v2 }); 334 | } 335 | 336 | assert(!finalEdges.empty()); 337 | std::vector> midChain; 338 | midChain.resize(finalEdges.size()); 339 | for (int j = 1; j + 1 < myChainVertices.size(); ++j) 340 | midChain[0].push_back(myChainVertices[j]); 341 | 342 | for (int k = 1; k < finalEdges.size(); ++k) 343 | { 344 | //for each new edge, add a midChain 345 | std::vector newMidChain; 346 | for (size_t v : midChain[0]) 347 | { 348 | size_t newV = duplicateVtx(v); 349 | newMidChain.push_back(newV); 350 | } 351 | for (int j = 1; j < newMidChain.size(); ++j) 352 | createEdge(newMidChain[j - 1], newMidChain[j]); 353 | 354 | midChain[k] = newMidChain; 355 | } 356 | for (int j=0; j 2) 359 | { 360 | createEdge(finalEdges[j].first, midChain[j].front()); 361 | createEdge(finalEdges[j].second, midChain[j].back()); 362 | } 363 | else 364 | createEdge(finalEdges[j].first, finalEdges[j].second); 365 | } 366 | } 367 | 368 | std::cout << "Processing deg 4 verts: "; 369 | //similar processing for valence for vertices 370 | //I could have not duplicated the code, but I don't want to mess with the logic above 371 | for (size_t v = 0; v < boost::num_vertices(g); ++v) 372 | { 373 | if (boost::degree(v, g) == 4) 374 | { 375 | std::vector pseudoHole; 376 | oedge_iter eit, eend; 377 | for (std::tie(eit, eend) = boost::out_edges(v, g); eit != eend; ++eit) 378 | pseudoHole.push_back(eit->m_target); 379 | 380 | auto holeEdges = fillHole(pseudoHole.begin(), pseudoHole.end(), g, {}); 381 | boost::clear_vertex(v, g); 382 | for (auto e : holeEdges) 383 | { 384 | createEdge(e.first, e.second); 385 | } 386 | 387 | std::cout << "Deg 4 vertex: " << v << ", filling the hole" << std::endl; 388 | } 389 | } 390 | 391 | std::cout << "done." << std::endl; 392 | } 393 | 394 | 395 | -------------------------------------------------------------------------------- /src/chopFakeEnds.cpp: -------------------------------------------------------------------------------- 1 | #include "chopFakeEnds.h" 2 | #include "greedyTrace.h" 3 | #include "intersections.h" 4 | #include 5 | #include "Params.h" 6 | #include "graph_typedefs.h" 7 | #include "ContractLoops.h" 8 | std::pair, G> chopFakeEnds(const std::vector& polys, const std::vector>& radii, const std::vector>& protectedEnds, 9 | const std::vector>& isItASpecialDeg2Vertex, const std::vector>& yJunctions) 10 | { 11 | std::vector result(polys.size()); 12 | 13 | auto covered = [&polys, &radii](int i, double startSegment, double endSegment, int j) 14 | { 15 | double start = std::min(startSegment, endSegment); 16 | double end = std::max(startSegment, endSegment); 17 | 18 | double coveredLength = 0, totalLength = 0; 19 | for (int k = start; k < end; ++k) 20 | { 21 | bool vertexCovered = false; 22 | for (int k1 = 0; k1 < polys[j].size(); ++k1) 23 | { 24 | if ((polys[j][k1] - polys[i][k]).squaredNorm() < std::pow(radii[i][k] + radii[j][k1], 2)) 25 | { 26 | vertexCovered = true; 27 | break; 28 | } 29 | } 30 | 31 | double length = (polys[i][k] - polys[i][k + 1]).norm(); 32 | if (vertexCovered) 33 | coveredLength += length; 34 | 35 | totalLength += length; 36 | } 37 | 38 | if (((totalLength - coveredLength > 1) && (coveredLength / totalLength < PRUNE_SHORT_BRANCHES_RATIO)) || (totalLength > 10)) 39 | return false; 40 | return true; 41 | }; 42 | 43 | std::vector bboxes; 44 | for (int i = 0; i < polys.size(); ++i) 45 | bboxes.push_back(findBoundingBox(polys[i])); 46 | 47 | int maxThreads = omp_get_max_threads(); 48 | std::vector, std::pair>>> localExtraEdges(maxThreads); 49 | std::vector, 2>> limits(polys.size()); 50 | 51 | std::vector> isItASpecialDef2VertexUpdated = isItASpecialDeg2Vertex; //if we cut this endpoint, it's no longer special 52 | 53 | #pragma omp parallel for schedule(dynamic) 54 | for (int i = 0; i < polys.size(); ++i) 55 | { 56 | int tid = omp_get_thread_num(); 57 | 58 | limits[i][0].first = 0; 59 | limits[i][0].second.curve = -1; 60 | limits[i][1].first = polys[i].size(); 61 | limits[i][1].second.curve = -1; 62 | 63 | if (polys[i].size() <= 3) //ignore the short guys 64 | continue; 65 | 66 | for (int endIdx = 0; endIdx < 2; ++endIdx) 67 | { 68 | int startSegment = endIdx == 0 ? 0 : polys[i].size() - 1; 69 | int endSegment = polys[i].size() / 2; 70 | int incSegment = endIdx == 0 ? 1 : -1; 71 | 72 | bool foundLimit = false; 73 | for (int segmentIdx = startSegment; segmentIdx != endSegment; segmentIdx += incSegment) 74 | { 75 | Eigen::Vector2d s0 = polys[i][segmentIdx], s1 = polys[i][segmentIdx + incSegment]; 76 | //now let's intersect 77 | typedef std::pair A; 78 | std::vector intersections; 79 | 80 | for (int j = 0; j < polys.size(); ++j) 81 | { 82 | //if (i == j) //todo: remove this 83 | // continue; 84 | 85 | if (bboxes[j].completelyOutside(s0, s1)) 86 | continue; 87 | 88 | for (int jSegmentIdx = 0; jSegmentIdx + 1 < polys[j].size(); ++jSegmentIdx) 89 | { 90 | if ((i == j) && (fabs(jSegmentIdx - segmentIdx) < 20)) //skip imaginary self-intersections, 20 is a random number that is big enough. can be resolved by careful engineering 91 | continue; 92 | 93 | Eigen::Vector2d sj0 = polys[j][jSegmentIdx], sj1 = polys[j][jSegmentIdx + 1]; 94 | if (bboxes[i].completelyOutside(sj0, sj1)) 95 | continue; 96 | 97 | double s, t; 98 | if (get_line_intersection(s0.x(), s0.y(), s1.x(), s1.y(), sj0.x(), sj0.y(), sj1.x(), sj1.y(), nullptr, nullptr, &s, &t)) 99 | { 100 | if (endIdx == 1) 101 | t = 1 - t; 102 | 103 | if ((jSegmentIdx != 0 || s > 1e-6) && (jSegmentIdx != polys[j].size() - 1 || s < 1 - 1e-6) && (t > 1e-6) && (t < 1 - 1e-6) && (std::min(segmentIdx, segmentIdx + incSegment) + t > 1e-6)) 104 | { 105 | PointOnCurve pt; 106 | pt.curve = j; 107 | pt.segmentIdx = jSegmentIdx + s; 108 | intersections.push_back({ std::min(segmentIdx,segmentIdx + incSegment) + t,pt }); 109 | } 110 | } 111 | } 112 | } 113 | 114 | std::sort(intersections.begin(), intersections.end(), [](const A& a, const A& b) {return a.first < b.first; }); 115 | if (endIdx == 1) 116 | std::reverse(intersections.begin(), intersections.end()); 117 | 118 | /*if (!intersections.empty()) 119 | { 120 | std::cout << "Curve " << i << " intersections (endIdx = " << endIdx << "): " << std::endl; 121 | for (auto ii : intersections) 122 | std::cout << "c" << ii.second.curve << " at " << ii.first << "(@" << ii.second.segmentIdx << ")" << std::endl; 123 | }*/ 124 | 125 | for (int k = 0; k < intersections.size(); ++k) 126 | { 127 | if (!protectedEnds[i][endIdx] && !foundLimit && covered(i, startSegment, intersections[k].first, intersections[k].second.curve)) 128 | limits[i][endIdx] = intersections[k]; 129 | 130 | foundLimit = true; 131 | 132 | if (i != intersections[k].second.curve) //remove self-loops 133 | localExtraEdges[tid].push_back(std::make_pair(std::make_pair(i, intersections[k].first), std::make_pair(intersections[k].second.curve, std::floor(intersections[k].second.segmentIdx)))); 134 | } 135 | } 136 | } 137 | 138 | //std::cout << "Curve " << i << " limits: " << limits[i][0].first << " and " << limits[i][1].first << "(length: " << polys[i].size() << ")" << std::endl; 139 | MyPolyline newPoly; 140 | int start = limits[i][0].first > 1e-5 ? std::ceil(limits[i][0].first) : 0; 141 | int end = limits[i][1].first < polys[i].size() - 1e-5 ? std::floor(limits[i][1].first) + 1 : polys[i].size(); 142 | newPoly.insert(newPoly.end(), polys[i].begin() + start, polys[i].begin() + end); 143 | 144 | if (newPoly.size() >= 2) 145 | { 146 | if (limits[i][0].first > 1e-5) 147 | { 148 | double lAlpha = limits[i][0].first - std::floor(limits[i][0].first); 149 | Eigen::Vector2d pCeil = polys[i][std::ceil(limits[i][0].first)]; 150 | Eigen::Vector2d pFloor = polys[i][std::floor(limits[i][0].first)]; 151 | newPoly.front() = pFloor + lAlpha*(pCeil - pFloor); 152 | isItASpecialDef2VertexUpdated[i][0] = false; 153 | } 154 | if (limits[i][1].first < polys[i].size() - 1e-5) 155 | { 156 | double lAlpha = limits[i][1].first - std::floor(limits[i][1].first); 157 | Eigen::Vector2d pCeil = polys[i][std::ceil(limits[i][1].first)]; 158 | Eigen::Vector2d pFloor = polys[i][std::floor(limits[i][1].first)]; 159 | newPoly.back() = pFloor + lAlpha*(pCeil - pFloor); 160 | isItASpecialDef2VertexUpdated[i][1] = false; 161 | } 162 | } 163 | result[i] = newPoly; 164 | } 165 | 166 | std::vector, std::pair>> extraEdges; 167 | 168 | // merge thread-local extraEdges into the single vector (serial) 169 | for (int t = 0; t < maxThreads; ++t) 170 | { 171 | if (!localExtraEdges[t].empty()) 172 | { 173 | extraEdges.insert(extraEdges.end(), localExtraEdges[t].begin(), localExtraEdges[t].end()); 174 | localExtraEdges[t].clear(); 175 | } 176 | } 177 | 178 | //std::cout << "Before adding edges from Y-junctions: " << extraEdges.size() << std::endl; 179 | //add extraEdges from yJunctions 180 | for (int i = 0; i < yJunctions.size(); ++i) 181 | { 182 | int curve1 = yJunctions[i].first.curve, curve2 = yJunctions[i].second.curve; 183 | if ((limits[curve1][1].first <= yJunctions[i].first.segmentIdx) || (yJunctions[i].first.segmentIdx < limits[curve1][0].first)) 184 | continue; 185 | 186 | if ((limits[curve2][1].first <= yJunctions[i].second.segmentIdx) || (yJunctions[i].second.segmentIdx < limits[curve2][0].first)) 187 | continue; 188 | 189 | extraEdges.push_back(std::make_pair(std::make_pair(yJunctions[i].first.curve, yJunctions[i].first.segmentIdx), std::make_pair(yJunctions[i].second.curve, yJunctions[i].second.segmentIdx))); 190 | } 191 | 192 | //adjust the extra edges segment indices because of the chopped beginning of the curve 193 | for (int i = 0; i < extraEdges.size(); ++i) 194 | { 195 | int curve1 = extraEdges[i].first.first; 196 | //std::cout << "Curve " << curve1 << " adjusted int pt from " << extraEdges[i].first.second << " to "; 197 | extraEdges[i].first.second -= limits[curve1][0].first; 198 | extraEdges[i].first.second = std::max(extraEdges[i].first.second, 0); 199 | //std::cout << extraEdges[i].first.second << std::endl; 200 | 201 | int curve2 = extraEdges[i].second.first; 202 | //std::cout << "Curve " << curve2 << " adjusted int pt from " << extraEdges[i].second.second << " to "; 203 | extraEdges[i].second.second -= limits[curve2][0].first; 204 | extraEdges[i].second.second = std::max(extraEdges[i].second.second, 0); 205 | //std::cout << extraEdges[i].second.second << std::endl; 206 | } 207 | 208 | /*std::cout << "Extra edges: " << std::endl; 209 | for (int i = 0; i < extraEdges.size(); ++i) 210 | std::cout << extraEdges[i].first.first << " " << extraEdges[i].first.second << " " << extraEdges[i].second.first << " " << extraEdges[i].second.second << std::endl;*/ 211 | 212 | std::map, int> curveAndPtToIdx; 213 | 214 | G curveGraph; 215 | std::vector>> curveVertices(result.size()); //for each curve, vertices in the new graph will be endpoints and all the intersections needed 216 | for (int i = 0; i < result.size(); ++i) 217 | { 218 | if (result[i].empty()) 219 | continue; 220 | 221 | size_t v1 = boost::add_vertex(curveGraph); 222 | PointOnCurve pt1, pt2; 223 | pt1.curve = i; pt2.curve = i; 224 | pt1.segmentIdx = 0; pt2.segmentIdx = result[i].size() - 1; 225 | curveGraph[v1].clusterPoints = { pt1 }; 226 | curveGraph[v1].location = result[i].front(); 227 | curveVertices[i].push_back(std::make_pair(0.0, v1)); 228 | curveAndPtToIdx[std::make_pair(i, 0)] = v1; 229 | curveGraph[v1].split = isItASpecialDef2VertexUpdated[i][0]; 230 | 231 | size_t v2 = boost::add_vertex(curveGraph); 232 | curveGraph[v2].clusterPoints = { pt2 }; 233 | curveGraph[v2].location = result[i].back(); 234 | curveVertices[i].push_back(std::make_pair(result[i].size() - 1, v2)); 235 | curveAndPtToIdx[std::make_pair(i, (int)(result[i].size() - 1))] = v2; 236 | curveGraph[v2].split = isItASpecialDef2VertexUpdated[i][1]; 237 | } 238 | 239 | //std::cout << "Orig vertices: " << boost::num_vertices(curveGraph) << std::endl; 240 | 241 | for (int jj = 0; jj < extraEdges.size(); ++jj) 242 | { 243 | for (const auto& intersectionPt : { extraEdges[jj].first, extraEdges[jj].second }) 244 | { 245 | int curve = intersectionPt.first; 246 | int roundedOff = std::floor(intersectionPt.second); 247 | if (intersectionPt.second == 0 || intersectionPt.second == result[curve].size() - 1 || curveAndPtToIdx.find(std::make_pair(curve, roundedOff)) != curveAndPtToIdx.end()) 248 | continue; //nothing to add 249 | 250 | if (result[curve].size() <= intersectionPt.second) 251 | { 252 | //std::cout << "Skipping an extra edge connecting to curve " << curve << " at " << intersectionPt.second << "(length = " << result[curve].size() << ") " << std::endl; 253 | continue; 254 | } 255 | 256 | size_t v = boost::add_vertex(curveGraph); 257 | curveGraph[v].split = false; 258 | PointOnCurve pt; 259 | pt.curve = curve; 260 | pt.segmentIdx = intersectionPt.second; 261 | pt.p = result[curve][roundedOff]; 262 | 263 | curveGraph[v].clusterPoints = { pt }; 264 | curveGraph[v].location = result[curve][roundedOff]; 265 | curveVertices[curve].push_back(std::make_pair(pt.segmentIdx, v)); 266 | curveAndPtToIdx[std::make_pair(curve, roundedOff)] = v; 267 | } 268 | } 269 | 270 | for (int i = 0; i < boost::num_vertices(curveGraph); ++i) 271 | { 272 | curveGraph[i].clusterIdx = i; 273 | curveGraph[i].clusterCurveHitSingularity = false; 274 | curveGraph[i].nextToSingularity = false; 275 | curveGraph[i].root = Eigen::Vector2d(0, 1); 276 | curveGraph[i].seedCurve = 0; 277 | curveGraph[i].sharpCorner = false; 278 | curveGraph[i].width = 0; 279 | } 280 | 281 | /*for (int i = 0; i < boost::num_vertices(curveGraph); ++i) 282 | { 283 | std::cout << "Vertex " << i << ": " << curveGraph[i].clusterPoints[0].curve << " @" << curveGraph[i].clusterPoints[0].segmentIdx << "(length = " << result[curveGraph[i].clusterPoints[0].curve].size() << ")" << std::endl; 284 | }*/ 285 | 286 | for (int i = 0; i < result.size(); ++i) 287 | { 288 | //std::cout << "Curve " << i << " "; 289 | std::sort(curveVertices[i].begin(), curveVertices[i].end(), [](const std::pair& a, const std::pair& b) {return a.first < b.first; }); 290 | /*for (auto& jj : curveVertices[i]) 291 | { 292 | std::cout << jj.second << " (@" << jj.first << ") "; 293 | } 294 | std::cout << std::endl;*/ 295 | 296 | for (int j = 0; j + 1 < curveVertices[i].size(); ++j) 297 | { 298 | if (curveVertices[i][j].second == curveVertices[i][j + 1].second) 299 | continue; 300 | 301 | auto e = boost::add_edge(curveVertices[i][j].second, curveVertices[i][j + 1].second, curveGraph); 302 | curveGraph[e.first].edgeCurve = i; 303 | //std::cout << "E " << curveVertices[i][j].second << " " << curveVertices[i][j + 1].second << std::endl; 304 | } 305 | } 306 | 307 | std::map, bool> added; 308 | 309 | for (int i = 0; i < extraEdges.size(); ++i) 310 | { 311 | extraEdges[i].first.second = std::floor(extraEdges[i].first.second); 312 | extraEdges[i].second.second = std::floor(extraEdges[i].second.second); 313 | if (curveAndPtToIdx.find(extraEdges[i].first) != curveAndPtToIdx.end() && curveAndPtToIdx.find(extraEdges[i].second) != curveAndPtToIdx.end()) 314 | { 315 | if (!added[std::minmax(curveAndPtToIdx[extraEdges[i].first], curveAndPtToIdx[extraEdges[i].second])]) 316 | { 317 | added[std::minmax(curveAndPtToIdx[extraEdges[i].first], curveAndPtToIdx[extraEdges[i].second])] = true; 318 | auto e = boost::add_edge(curveAndPtToIdx[extraEdges[i].first], curveAndPtToIdx[extraEdges[i].second], curveGraph); 319 | curveGraph[e.first].edgeCurve = -1; 320 | //std::cout << "Adding extra edge: " << curveAndPtToIdx[extraEdges[i].first] << " " << curveAndPtToIdx[extraEdges[i].second] << std::endl; 321 | } 322 | } 323 | } 324 | 325 | return std::make_pair(result, curveGraph); 326 | 327 | } 328 | -------------------------------------------------------------------------------- /src/AlmostReebGraph.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "AlmostReebGraph.h" 3 | #include "intersections.h" 4 | #include "simple_svg_1.0.0.hpp" 5 | #include "chooseRoot.h" 6 | #include "greedyTrace.h" 7 | #include "ScanConvert.h" 8 | #include "ChainDecomposition.h" 9 | #include 10 | 11 | Cluster createCluster(int seedCurve, int seedPtIdx, const cv::Mat & origMask, const std::array& roots, const std::vector& polys, 12 | const std::map, std::vector>& pixelInfo, const std::set& onlyTheseCurves, const std::set>& singularities, const Eigen::MatrixXi& indices, const Eigen::VectorXcd& X) 13 | { 14 | int m = origMask.rows, n = origMask.cols; 15 | Eigen::Vector2d myTangent = tangent(polys[seedCurve], seedPtIdx).normalized(); 16 | Eigen::Vector2d pt = polys[seedCurve][seedPtIdx]; 17 | int i = std::round(pt.y()), j = std::round(pt.x()); 18 | 19 | int myRootIdx; 20 | double dot0 = fabs(myTangent.dot(_toEig(roots[0](i, j)).normalized())), dot1 = fabs(myTangent.dot(_toEig(roots[1](i, j)).normalized())); 21 | if (dot0 < dot1) 22 | myRootIdx = 1; 23 | else 24 | myRootIdx = 0; 25 | 26 | if ((fabs(dot0 - dot1) < 1e-1) || (singularities.find({ i,j }) != singularities.end())) 27 | return Cluster(); 28 | 29 | Eigen::Vector2d traceDir(myTangent.y(), -myTangent.x()); 30 | //shoot a perpendicular line 31 | std::map < std::array, bool > isMatchingOK; 32 | std::array closestSingularity = { std::numeric_limits::max(), std::numeric_limits::max() }; 33 | //std::set> pixels = scanline(origMask, roots, pt, traceDir, isMatchingOK, bounds, singularities); 34 | std::map, std::vector> tmp1, tmp2; 35 | auto clusterCurve = greedyTrace(origMask, roots, pt, myTangent, X, tmp1, tmp2, indices, -1, true, closestSingularity).first; 36 | 37 | //find the initial point in the poly. totally unnecessary, but whatever 38 | int startIdx = -1; 39 | for (int i = 0; i < clusterCurve.size(); ++i) 40 | { 41 | if ((clusterCurve[i] - pt).squaredNorm() < 1e-10) 42 | { 43 | startIdx = i; 44 | break; 45 | } 46 | } 47 | assert(startIdx != -1); 48 | std::array ignoreMe; 49 | std::set> pixels = scanConvert(clusterCurve, origMask, roots, startIdx, isMatchingOK, ignoreMe, singularities, { 0.0,(double)clusterCurve.size() - 1 }); 50 | 51 | Eigen::ParametrizedLine pline(pt, traceDir); 52 | 53 | std::set potentialCurves; 54 | for (const auto& p : pixels) 55 | { 56 | auto it = pixelInfo.find(p); 57 | if (it != pixelInfo.end()) 58 | { 59 | for (int piIndex = 0; piIndex < it->second.size(); ++piIndex) 60 | potentialCurves.insert(it->second[piIndex].curve); 61 | } 62 | } 63 | 64 | Cluster result; 65 | 66 | //now make the intersections precise 67 | std::vector> curvesSortedByIntersectionPt; 68 | 69 | PointOnCurve seedPoint; 70 | seedPoint.curve = seedCurve; 71 | seedPoint.segmentIdx = seedPtIdx; 72 | seedPoint.p = pt; 73 | seedPoint.root = _toEig(roots[myRootIdx](i, j)).normalized(); 74 | 75 | curvesSortedByIntersectionPt = { {0,seedPoint} }; 76 | 77 | auto bbox = findBoundingBox(clusterCurve); 78 | 79 | for (size_t k : potentialCurves) 80 | { 81 | for (int segmentIdx = 0; segmentIdx + 1 < polys[k].size(); ++segmentIdx) 82 | { 83 | if ((k == seedCurve) && (fabs(segmentIdx - seedPtIdx) < 2)) 84 | continue; 85 | 86 | if (bbox.completelyOutside(polys[k][segmentIdx],polys[k][segmentIdx+1])) 87 | continue; 88 | 89 | Box miniBox; 90 | std::tie(miniBox.xMin, miniBox.xMax) = std::minmax(polys[k][segmentIdx].x(), polys[k][segmentIdx + 1].x()); 91 | std::tie(miniBox.yMin, miniBox.yMax) = std::minmax(polys[k][segmentIdx].y(), polys[k][segmentIdx + 1].y()); 92 | 93 | for (int i = 0; i + 1 < clusterCurve.size(); ++i) 94 | { 95 | if (miniBox.completelyOutside(clusterCurve[i], clusterCurve[i + 1])) 96 | continue; 97 | 98 | double t, s; 99 | if (get_line_intersection(polys[k][segmentIdx].x(), polys[k][segmentIdx].y(), polys[k][segmentIdx + 1].x(), polys[k][segmentIdx + 1].y(), 100 | clusterCurve[i].x(), clusterCurve[i].y(), clusterCurve[i + 1].x(), clusterCurve[i + 1].y(), nullptr, nullptr, &s, &t)) 101 | { 102 | PointOnCurve pc; 103 | Eigen::Vector2d edge = polys[k][segmentIdx + 1] - polys[k][segmentIdx]; 104 | pc.p = polys[k][segmentIdx] + t*edge; 105 | pc.segmentIdx = segmentIdx + t; 106 | pc.curve = k; 107 | std::array pi = { std::round(pc.p.y()), std::round(pc.p.x()) }; 108 | if (singularities.find(pi) != singularities.end()) //tough luck, skip it 109 | continue; 110 | 111 | if (origMask.at(pi[0], pi[1]) == 0) 112 | { 113 | //there is that weird border case where the curve slightly goes outside the mask, and the intersection is right outside the mask 114 | bool fixed = false; 115 | const int width = 3; 116 | for (int i = -width / 2; i <= width / 2; ++i) 117 | { 118 | for (int j = -width / 2; j <= width / 2; ++j) 119 | { 120 | std::array newPixel = { pi[0] + i,pi[1] + j }; 121 | if (!inBounds(newPixel, origMask) || (isMatchingOK.find(newPixel) == isMatchingOK.end())) 122 | continue; 123 | 124 | pi = newPixel; 125 | fixed = true; 126 | break; 127 | } 128 | if (fixed) 129 | break; 130 | } 131 | assert(fixed); 132 | } 133 | 134 | Eigen::Vector2d root0 = _toEig(roots[0](pi[0], pi[1])).normalized(), root1 = _toEig(roots[1](pi[0], pi[1])).normalized(); 135 | int theirRootIdx = (fabs(edge.dot(root0)) < fabs(edge.dot(root1))) ? 1 : 0; 136 | pc.root = _toEig(roots[theirRootIdx](pi[0], pi[1])).normalized(); 137 | 138 | assert(isMatchingOK.find(pi) != isMatchingOK.end()); 139 | Eigen::Vector2d theirTangent = tangent(polys[pc.curve], pc.segmentIdx).normalized(); 140 | if (onlyTheseCurves.empty() && ((isMatchingOK[pi] ^ (theirRootIdx == myRootIdx)) /*|| (fabs(theirTangent.normalized().dot(myTangent)) < 0.99)*/)) 141 | continue; 142 | curvesSortedByIntersectionPt.push_back({ (i - startIdx + s)*0.1,pc }); 143 | } 144 | 145 | } 146 | } 147 | } 148 | 149 | //now we have tons of intersection of AB with various curves 150 | //let's only keep my connected component of that 151 | std::sort(curvesSortedByIntersectionPt.begin(), curvesSortedByIntersectionPt.end(), [](const std::pair& a, const std::pair& b) {return a.first < b.first; }); 152 | auto itK = std::find_if(curvesSortedByIntersectionPt.begin(), curvesSortedByIntersectionPt.end(), [&seedCurve](const std::pair& a) {return (a.second.curve == seedCurve) && (fabs(a.first) < 1e-5); }); 153 | assert(itK != curvesSortedByIntersectionPt.end()); 154 | int kIdx = itK - curvesSortedByIntersectionPt.begin(); 155 | 156 | std::array hitASingularity = { fabs(closestSingularity[0] - curvesSortedByIntersectionPt[kIdx].first) < 1, fabs(closestSingularity[1] - curvesSortedByIntersectionPt[kIdx].first) < 1 }; 157 | std::vector myConnectedComponent = { seedPoint }; 158 | int dirIdx = 0; 159 | for (int increment : {-1, 1}) 160 | { 161 | for (int kk = kIdx + increment * 1; (kk < curvesSortedByIntersectionPt.size()) && (kk >= 0); kk += increment) 162 | { 163 | //if (fabs(curvesSortedByIntersectionPt[kk].first - curvesSortedByIntersectionPt[kk - increment].first) > 0.5*diam / (bounds[0] + bounds[1])) 164 | double diff = fabs(curvesSortedByIntersectionPt[kk].first - curvesSortedByIntersectionPt[kk - increment].first); 165 | if (fabs(closestSingularity[dirIdx] - curvesSortedByIntersectionPt[kk].first) < 1) 166 | hitASingularity[dirIdx] = true; 167 | 168 | if (diff > 1) 169 | break; 170 | else 171 | { 172 | if (increment == 1) 173 | myConnectedComponent.push_back(curvesSortedByIntersectionPt[kk].second); 174 | else 175 | myConnectedComponent.insert(myConnectedComponent.begin(), curvesSortedByIntersectionPt[kk].second); 176 | } 177 | } 178 | ++dirIdx; 179 | } 180 | result.clusterPoints = myConnectedComponent; 181 | 182 | //select into a set all curves that this perpendicular intersects and that have the same root as me 183 | if (!result.clusterPoints.empty()) 184 | { 185 | //orient all vectors 186 | Eigen::Vector2d vv = result.clusterPoints.begin()->root; 187 | for (auto& cp : result.clusterPoints) 188 | { 189 | if (vv.dot(cp.root) < 0) 190 | cp.root = -cp.root; 191 | } 192 | 193 | result.location = Eigen::Vector2d(0, 0); 194 | result.root = Eigen::Vector2d(0, 0); 195 | 196 | for (auto& cp : result.clusterPoints) 197 | { 198 | result.location += cp.p; 199 | result.root += cp.root; 200 | } 201 | 202 | result.location /= result.clusterPoints.size(); 203 | //result.root /= result.clusterPoints.size(); 204 | result.root = _toEig(roots[myRootIdx](i, j)).normalized(); 205 | result.seedCurve = seedCurve; 206 | result.sharpCorner = false; 207 | result.nextToSingularity = false; 208 | result.clusterCurveHitSingularity = hitASingularity[0] | hitASingularity[1]; 209 | result.width = (result.clusterPoints.back().p - result.clusterPoints.front().p).norm(); 210 | result.clusterCurve = clusterCurve; 211 | result.split = false; 212 | } 213 | return result; 214 | } 215 | 216 | void contractSingularityBranches(G& g) 217 | { 218 | std::map myChain; 219 | auto chains = chainDecomposition(g, myChain); 220 | std::set newSingularVertices; 221 | std::set edgesToRemove; 222 | for (const auto& ch : chains) 223 | { 224 | if ((boost::degree(ch.front().m_source, g) == 1) ^ (boost::degree(ch.back().m_target, g) == 1)) 225 | { 226 | auto chain = ch; 227 | if (boost::degree(ch.back().m_target, g) == 1) 228 | std::reverse(chain.begin(), chain.end()); //warning, directions of edges are screwed up 229 | 230 | for (const auto& e : chain) 231 | { 232 | if (g[e.m_source].clusterCurveHitSingularity || g[e.m_target].clusterCurveHitSingularity) 233 | { 234 | edgesToRemove.insert(e); 235 | newSingularVertices.insert(e.m_source); 236 | newSingularVertices.insert(e.m_target); 237 | } 238 | else 239 | break; 240 | } 241 | 242 | } 243 | } 244 | 245 | for (auto e : edgesToRemove) 246 | boost::remove_edge(e, g); 247 | 248 | for (size_t v : newSingularVertices) 249 | g[v].nextToSingularity = true; 250 | } 251 | 252 | void connectStuffAroundSingularities(G& g, const cv::Mat & origMask, const std::vector& polys, const std::set>& singularities, const std::array& roots, const std::vector>& endedWithASingularity) 253 | { 254 | 255 | //add the extra singularities 256 | auto extendedSingularities = singularities; 257 | for (int i = 0; i < polys.size(); ++i) 258 | { 259 | if (endedWithASingularity[i][0]) 260 | extendedSingularities.insert({ (int)polys[i].front().y(),(int)polys[i].front().x() }); 261 | if (endedWithASingularity[i][1]) 262 | extendedSingularities.insert({ (int)polys[i].back().y(),(int)polys[i].back().x() }); 263 | } 264 | 265 | std::vector> newEdges; 266 | 267 | edge_iter eit, eend; 268 | for (std::tie(eit, eend) = boost::edges(g); eit != eend; ++eit) 269 | g[*eit].weight = 1.0; 270 | 271 | for (size_t v = 0; v < boost::num_vertices(g); ++v) 272 | { 273 | 274 | 275 | if (g[v].nextToSingularity && (boost::degree(v, g) == 1)) 276 | { 277 | //connect it to the closest singular vertex 278 | std::vector pDij(num_vertices(g)); 279 | std::vector dDij(num_vertices(g)); 280 | auto predMap = make_iterator_property_map(pDij.begin(), get(&Cluster::clusterIdx, g)); 281 | auto distMap = make_iterator_property_map(dDij.begin(), get(&Cluster::clusterIdx, g)); 282 | dijkstra_shortest_paths(g, v, 283 | predecessor_map(predMap). 284 | distance_map(distMap).weight_map(get(&Edge::weight, g))); 285 | 286 | auto e = boost::out_edges(v, g).first; 287 | Eigen::Vector2d eVec = g[e->m_source].location - g[e->m_target].location; 288 | 289 | double bestDist = 10.0; //should be something reasonable 290 | int bestV = -1; 291 | for (size_t v1 = 0; v1 < boost::num_vertices(g); ++v1) 292 | { 293 | if (g[v1].nextToSingularity && !boost::edge(v, v1, g).second && (v1!=v) && boost::degree(v1,g)>=1) 294 | { 295 | //see if the straight line is within the narrow band and there is a singularity along the way 296 | std::vector poly; 297 | Eigen::Vector2d vec = g[v1].location - g[v].location; 298 | double dist = (g[v1].location - g[v].location).norm(); 299 | //std::cout << "dist: " << dist << std::endl; 300 | int nSamples = std::max((int)(dist * 10),3); 301 | for (int j = 0; j < nSamples; ++j) 302 | poly.push_back(g[v].location+j*(vec / (nSamples - 1))); 303 | 304 | bool shouldAdd = true; 305 | for (int j = 0; j < poly.size(); ++j) 306 | { 307 | if (!inBounds(poly[j], origMask)) 308 | shouldAdd = false; 309 | } 310 | 311 | if (shouldAdd && dDij[v1]>20 && vec.dot(eVec)>0) 312 | { 313 | std::map, bool> isRootMatchingOK; 314 | std::array hitSingularity; 315 | scanConvert(poly, origMask, roots, 0, isRootMatchingOK, hitSingularity, extendedSingularities, { 0.0,(double)poly.size() - 1 }); 316 | 317 | if (hitSingularity[0] | hitSingularity[1]) 318 | { 319 | if (dist < bestDist) 320 | { 321 | bestV = v1; 322 | bestDist = dist; 323 | } 324 | } 325 | } 326 | } 327 | } 328 | 329 | 330 | if (bestV != -1) 331 | { 332 | newEdges.push_back({ v,(size_t)bestV }); 333 | std::cout << "[connectStuffAroundSingularities]: Connecting vertex " << v << " to " << bestV << std::endl; 334 | } 335 | } 336 | } 337 | 338 | for (auto pair : newEdges) 339 | { 340 | auto e = boost::add_edge(pair.first, pair.second, g); 341 | g[e.first].edgeCurve = -1; 342 | } 343 | } 344 | 345 | G computeAlmostReebGraph(const cv::Mat & origMask, const std::array& roots, const std::vector& polys, std::map, std::vector>& pixelInfo, const std::set>& singularities, const Eigen::MatrixXi& indices, const Eigen::VectorXcd& X, const std::vector>& endedWithASingularity) 346 | { 347 | std::clock_t begin = -std::clock(); 348 | std::cout << "Computing Reeb graph..."; 349 | //int clusterIdx = 0; 350 | G resultList; 351 | typedef std::pair ClusterAndItsIntersection; 352 | std::map> intersectionsPerCurve; 353 | 354 | 355 | auto areClustersEquivalent = [](const Cluster& a, const Cluster& b) 356 | { 357 | if (a.clusterPoints.size() != b.clusterPoints.size()) 358 | return false; 359 | for (int i = 0; i < a.clusterPoints.size(); ++i) 360 | if (a.clusterPoints[i].curve != b.clusterPoints[i].curve) 361 | return false; 362 | return true; 363 | }; 364 | 365 | std::map < std::array, std::set> clustersPerPixel; 366 | std::cout << std::endl; 367 | 368 | std::vector seedPts; 369 | for (int k = 0; k < polys.size(); ++k) 370 | { 371 | PointOnCurve pc; 372 | pc.curve = k; 373 | pc.segmentIdx = 0; 374 | seedPts.push_back(pc); 375 | pc.segmentIdx = polys[k].size() - 1; 376 | seedPts.push_back(pc); 377 | } 378 | 379 | std::vector clusters(seedPts.size()); 380 | //for (int k = 0; k < polys.size(); ++k) 381 | #pragma omp parallel for 382 | for (int clusterIdx = 0; clusterIdx < seedPts.size(); ++clusterIdx) 383 | { 384 | Cluster cl = createCluster(seedPts[clusterIdx].curve, seedPts[clusterIdx].segmentIdx, origMask, roots, polys, pixelInfo, {}, singularities, indices, X); 385 | 386 | std::array p0i = { (int)std::round(cl.location.y()),(int)std::round(cl.location.x()) }; 387 | cl.clusterIdx = clusterIdx; 388 | clusters[clusterIdx] = cl; 389 | } 390 | 391 | for (int i = 0; i < clusters.size(); ++i) 392 | { 393 | auto v = boost::add_vertex(resultList); 394 | resultList[v] = clusters[i]; 395 | 396 | for (int j = 0; j < clusters[i].clusterPoints.size(); ++j) 397 | intersectionsPerCurve[clusters[i].clusterPoints[j].curve].push_back(ClusterAndItsIntersection(v, clusters[i].clusterPoints[j].segmentIdx)); 398 | } 399 | //now all clusters are ready 400 | //let's order them 401 | for (int i = 0; i < polys.size(); ++i) 402 | { 403 | std::sort(intersectionsPerCurve[i].begin(), intersectionsPerCurve[i].end(), [](const ClusterAndItsIntersection& a, const ClusterAndItsIntersection& b) {return a.second < b.second; }); 404 | for (int j = 0; j < static_cast(intersectionsPerCurve[i].size()) - 1; ++j) 405 | { 406 | auto pair = std::make_pair(intersectionsPerCurve[i][j].first, intersectionsPerCurve[i][j + 1].first); 407 | if (!boost::edge(pair.first, pair.second, resultList).second) //avoid duplicate edges 408 | { 409 | auto edge = boost::add_edge(pair.first, pair.second, resultList); 410 | resultList[edge.first].edgeCurve = i; 411 | } 412 | } 413 | } 414 | 415 | for (int i = 0; i < polys.size(); ++i) 416 | { 417 | if (!intersectionsPerCurve[i].empty() && (endedWithASingularity[i][0] | endedWithASingularity[i][1])) 418 | { 419 | if (endedWithASingularity[i][0]) 420 | resultList[intersectionsPerCurve[i].front().first].nextToSingularity = true; 421 | if (endedWithASingularity[i][1]) 422 | resultList[intersectionsPerCurve[i].back().first].nextToSingularity = true; 423 | } 424 | } 425 | 426 | std::cout << "done in " << double(begin + clock()) / CLOCKS_PER_SEC << " seconds. " << std::endl; 427 | return resultList; 428 | } 429 | 430 | void simpleThresholds(G& g) 431 | { 432 | //simple thresholding: if it's a single curve => delete 433 | std::vector components(boost::num_vertices(g)); 434 | int nComponents = boost::connected_components(g, &components[0]); 435 | std::vector bboxes(nComponents); 436 | std::vector shouldntRemove(nComponents); 437 | for (size_t v = 0; v < boost::num_vertices(g); ++v) 438 | { 439 | if (g[v].clusterPoints.size() > 1) 440 | shouldntRemove[components[v]] = true; 441 | 442 | bboxes[components[v]].xMax = std::max(bboxes[components[v]].xMax, g[v].location.x()); 443 | bboxes[components[v]].xMin = std::min(bboxes[components[v]].xMin, g[v].location.x()); 444 | bboxes[components[v]].yMax = std::max(bboxes[components[v]].yMax, g[v].location.y()); 445 | bboxes[components[v]].yMin = std::min(bboxes[components[v]].yMin, g[v].location.y()); 446 | } 447 | 448 | for (size_t v = 0; v < boost::num_vertices(g); ++v) 449 | { 450 | const auto& bbox = bboxes[components[v]]; 451 | if ((!shouldntRemove[components[v]]) || ((bbox.xMax-bbox.xMin)*(bbox.yMax-bbox.yMin)<10)) 452 | boost::clear_vertex(v, g); 453 | } 454 | } 455 | --------------------------------------------------------------------------------