RDKit
Open-source cheminformatics and machine learning.
Loading...
Searching...
No Matches
Conrec.h
Go to the documentation of this file.
1//
2// Copyright (C) 2019-2023 Greg Landrum
3//
4// @@ All Rights Reserved @@
5// This file is part of the RDKit.
6// The contents are covered by the terms of the BSD license
7// which is included in the file license.txt, found at the root
8// of the RDKit source tree.
9//
10#include <vector>
11#include <list>
12#include <unordered_map>
13#include <Geometry/point.h>
14#include <cmath>
15
17#include <boost/dynamic_bitset.hpp>
18#include <boost/functional/hash.hpp>
20
21namespace conrec {
25 double isoVal;
26 ConrecSegment(double x1, double y1, double x2, double y2, double isoVal)
27 : p1(x1, y1), p2(x2, y2), isoVal(isoVal) {}
29 double isoVal)
30 : p1(p1), p2(p2), isoVal(isoVal) {}
31};
32// adapted from conrec.c by Paul Bourke:
33// http://paulbourke.net/papers/conrec/conrec.c
34/*
35 Derivation from the fortran version of CONREC by Paul Bourke
36 d ! matrix of data to contour
37 ilb,iub,jlb,jub ! index bounds of data matrix
38 x ! data matrix column coordinates
39 y ! data matrix row coordinates
40 nc ! number of contour levels
41 z ! contour levels in increasing order
42*/
43inline void Contour(const double *d, size_t ilb, size_t iub, size_t jlb,
44 size_t jub, const double *x, const double *y, size_t nc,
45 double *z, std::vector<ConrecSegment> &res) {
46 PRECONDITION(d, "no data");
47 PRECONDITION(x, "no data");
48 PRECONDITION(y, "no data");
49 PRECONDITION(z, "no data");
50 PRECONDITION(nc > 0, "no contours");
51 PRECONDITION(iub > ilb, "bad bounds");
52 PRECONDITION(jub > jlb, "bad bounds");
53
54 int m1, m2, m3, case_value;
55 double dmin, dmax, x1 = 0, x2 = 0, y1 = 0, y2 = 0;
56 int i, j, m;
57 size_t k;
58 double h[5];
59 int sh[5];
60 double xh[5], yh[5];
61 int im[4] = {0, 1, 1, 0}, jm[4] = {0, 0, 1, 1};
62 int castab[3][3][3] = {{{0, 0, 8}, {0, 2, 5}, {7, 6, 9}},
63 {{0, 3, 4}, {1, 3, 1}, {4, 3, 0}},
64 {{9, 6, 7}, {5, 2, 0}, {8, 0, 0}}};
65 double temp1, temp2;
66 size_t ny = jub - jlb + 1;
67
68 auto xsect = [&](int p1, int p2) {
69 return (h[p2] * xh[p1] - h[p1] * xh[p2]) / (h[p2] - h[p1]);
70 };
71 auto ysect = [&](int p1, int p2) {
72 return (h[p2] * yh[p1] - h[p1] * yh[p2]) / (h[p2] - h[p1]);
73 };
74
75 for (j = (jub - 1); j >= (int)jlb; j--) {
76 for (i = ilb; i <= (int)iub - 1; i++) {
77 temp1 = std::min(d[i * ny + j], d[i * ny + j + 1]);
78 temp2 = std::min(d[(i + 1) * ny + j], d[(i + 1) * ny + j + 1]);
79 dmin = std::min(temp1, temp2);
80 temp1 = std::max(d[i * ny + j], d[i * ny + j + 1]);
81 temp2 = std::max(d[(i + 1) * ny + j], d[(i + 1) * ny + j + 1]);
82 dmax = std::max(temp1, temp2);
83 if (dmax < z[0] || dmin > z[nc - 1]) {
84 continue;
85 }
86 for (k = 0; k < nc; k++) {
87 if (z[k] < dmin || z[k] > dmax) {
88 continue;
89 }
90 for (m = 4; m >= 0; m--) {
91 if (m > 0) {
92 h[m] = d[(i + im[m - 1]) * ny + j + jm[m - 1]] - z[k];
93 xh[m] = x[i + im[m - 1]];
94 yh[m] = y[j + jm[m - 1]];
95 } else {
96 h[0] = 0.25 * (h[1] + h[2] + h[3] + h[4]);
97 xh[0] = 0.50 * (x[i] + x[i + 1]);
98 yh[0] = 0.50 * (y[j] + y[j + 1]);
99 }
100 if (h[m] > 0.0) {
101 sh[m] = 1;
102 } else if (h[m] < 0.0) {
103 sh[m] = -1;
104 } else {
105 sh[m] = 0;
106 }
107 }
108
109 /*
110 Note: at this stage the relative heights of the corners and the
111 centre are in the h array, and the corresponding coordinates are
112 in the xh and yh arrays. The centre of the box is indexed by 0
113 and the 4 corners by 1 to 4 as shown below.
114 Each triangle is then indexed by the parameter m, and the 3
115 vertices of each triangle are indexed by parameters m1,m2,and m3.
116 It is assumed that the centre of the box is always vertex 2
117 though this isimportant only when all 3 vertices lie exactly on
118 the same contour level, in which case only the side of the box
119 is drawn.
120 vertex 4 +-------------------+ vertex 3
121 | \ / |
122 | \ m-3 / |
123 | \ / |
124 | \ / |
125 | m=2 X m=2 | the centre is vertex 0
126 | / \ |
127 | / \ |
128 | / m=1 \ |
129 | / \ |
130 vertex 1 +-------------------+ vertex 2
131 */
132 /* Scan each triangle in the box */
133 for (m = 1; m <= 4; m++) {
134 m1 = m;
135 m2 = 0;
136 if (m != 4) {
137 m3 = m + 1;
138 } else {
139 m3 = 1;
140 }
141 if ((case_value = castab[sh[m1] + 1][sh[m2] + 1][sh[m3] + 1]) == 0) {
142 continue;
143 }
144 switch (case_value) {
145 case 1: /* Line between vertices 1 and 2 */
146 x1 = xh[m1];
147 y1 = yh[m1];
148 x2 = xh[m2];
149 y2 = yh[m2];
150 break;
151 case 2: /* Line between vertices 2 and 3 */
152 x1 = xh[m2];
153 y1 = yh[m2];
154 x2 = xh[m3];
155 y2 = yh[m3];
156 break;
157 case 3: /* Line between vertices 3 and 1 */
158 x1 = xh[m3];
159 y1 = yh[m3];
160 x2 = xh[m1];
161 y2 = yh[m1];
162 break;
163 case 4: /* Line between vertex 1 and side 2-3 */
164 x1 = xh[m1];
165 y1 = yh[m1];
166 x2 = xsect(m2, m3);
167 y2 = ysect(m2, m3);
168 break;
169 case 5: /* Line between vertex 2 and side 3-1 */
170 x1 = xh[m2];
171 y1 = yh[m2];
172 x2 = xsect(m3, m1);
173 y2 = ysect(m3, m1);
174 break;
175 case 6: /* Line between vertex 3 and side 1-2 */
176 x1 = xh[m3];
177 y1 = yh[m3];
178 x2 = xsect(m1, m2);
179 y2 = ysect(m1, m2);
180 break;
181 case 7: /* Line between sides 1-2 and 2-3 */
182 x1 = xsect(m1, m2);
183 y1 = ysect(m1, m2);
184 x2 = xsect(m2, m3);
185 y2 = ysect(m2, m3);
186 break;
187 case 8: /* Line between sides 2-3 and 3-1 */
188 x1 = xsect(m2, m3);
189 y1 = ysect(m2, m3);
190 x2 = xsect(m3, m1);
191 y2 = ysect(m3, m1);
192 break;
193 case 9: /* Line between sides 3-1 and 1-2 */
194 x1 = xsect(m3, m1);
195 y1 = ysect(m3, m1);
196 x2 = xsect(m1, m2);
197 y2 = ysect(m1, m2);
198 break;
199 default:
200 break;
201 }
202
203 /* Finally draw the line */
204 res.emplace_back(RDGeom::Point2D(x1, y1), RDGeom::Point2D(x2, y2),
205 z[k]);
206 } /* m */
207 } /* k - contour */
208 } /* i */
209 } /* j */
210}
211
212struct tplHash {
213 template <class T1, class T2, class T3>
214 std::size_t operator()(const std::tuple<T1, T2, T3> &p) const {
215 std::size_t res = 0;
216 boost::hash_combine(res, std::get<0>(p));
217 boost::hash_combine(res, std::get<1>(p));
218 boost::hash_combine(res, std::get<2>(p));
219 return res;
220 }
221};
222
223inline std::vector<std::pair<std::vector<RDGeom::Point2D>, double>>
224connectLineSegments(const std::vector<ConrecSegment> &segments,
225 double coordMultiplier = 1000,
226 double isoValMultiplier = 1e6) {
227 std::vector<std::pair<std::vector<RDGeom::Point2D>, double>> res;
228 std::unordered_map<std::tuple<int, int, long>, std::list<size_t>, tplHash>
229 endPointHashes;
230
231 auto makePointKey = [&coordMultiplier, &isoValMultiplier](const auto &pt,
232 double isoVal) {
233 return std::make_tuple<int, int, long>(
234 std::lround(coordMultiplier * pt.x),
235 std::lround(coordMultiplier * pt.y),
236 std::lround(isoValMultiplier * isoVal));
237 };
238
239 // first hash all of the endpoints
240 for (auto i = 0u; i < segments.size(); ++i) {
241 const auto &seg = segments[i];
242 endPointHashes[makePointKey(seg.p1, seg.isoVal)].push_back(i);
243 endPointHashes[makePointKey(seg.p2, seg.isoVal)].push_back(i);
244 }
245
246 boost::dynamic_bitset<> segmentsDone(segments.size());
247 // a candidate end point hasn't been used already.
248 auto isCandidate = [&segmentsDone](const auto &pr) {
249 return !pr.second.empty() && !segmentsDone[pr.second.front()];
250 };
251 auto singlePoint =
252 std::find_if(endPointHashes.begin(), endPointHashes.end(), isCandidate);
253 while (singlePoint != endPointHashes.end()) {
254 auto segId = singlePoint->second.front();
255 auto currKey = singlePoint->first;
256 auto currVal = segments[segId].isoVal;
257 std::vector<RDGeom::Point2D> contour;
258 while (1) {
259 segmentsDone.set(segId, true);
260 // move onto the next segment
261 const auto seg = segments[segId];
262 auto k1 = makePointKey(seg.p1, seg.isoVal);
263 auto k2 = makePointKey(seg.p2, seg.isoVal);
264 auto endPtKey = k2;
265 if (k1 == currKey) {
266 contour.push_back(seg.p1);
267 } else if (k2 == currKey) {
268 contour.push_back(seg.p2);
269 endPtKey = k1;
270 }
271 // remove this segment from the two hash lists:
272 auto &segs1 = endPointHashes[currKey];
273 segs1.erase(std::find(segs1.begin(), segs1.end(), segId));
274
275 auto &segs = endPointHashes[endPtKey];
276 segs.erase(std::find(segs.begin(), segs.end(), segId));
277 if (segs.empty()) {
278 // we're at the end, push on the last point
279 if (k1 == currKey) {
280 contour.push_back(seg.p2);
281 } else if (k2 == currKey) {
282 contour.push_back(seg.p1);
283 }
284 break;
285 }
286 segId = segs.front();
287 currKey = endPtKey;
288 }
289 res.push_back(std::make_pair(contour, currVal));
290 singlePoint = std::find_if(singlePoint, endPointHashes.end(), isCandidate);
291 }
292 return res;
293}
294
295} // namespace conrec
#define PRECONDITION(expr, mess)
Definition Invariant.h:109
std::vector< std::pair< std::vector< RDGeom::Point2D >, double > > connectLineSegments(const std::vector< ConrecSegment > &segments, double coordMultiplier=1000, double isoValMultiplier=1e6)
Definition Conrec.h:224
void Contour(const double *d, size_t ilb, size_t iub, size_t jlb, size_t jub, const double *x, const double *y, size_t nc, double *z, std::vector< ConrecSegment > &res)
Definition Conrec.h:43
ConrecSegment(const RDGeom::Point2D &p1, const RDGeom::Point2D &p2, double isoVal)
Definition Conrec.h:28
RDGeom::Point2D p2
Definition Conrec.h:24
RDGeom::Point2D p1
Definition Conrec.h:23
ConrecSegment(double x1, double y1, double x2, double y2, double isoVal)
Definition Conrec.h:26
std::size_t operator()(const std::tuple< T1, T2, T3 > &p) const
Definition Conrec.h:214