ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
classifier.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "classifier.h"
9 
10 // Qt
11 #include <QFile>
12 
13 // system
14 #include <assert.h>
15 #include <limits.h>
16 
18  : class1(0),
19  class2(0),
20  absMaxXY(0),
21  axisScaleRatio(1.0f),
22  descriptorID(DESC_INVALID),
23  dimPerScale(0) {}
24 
26  // dot product with (+1,+1) vector gives the classification sign
27  if (refPointPos.x + refPointPos.y < 0) {
29  }
30 
31  return (refPointPos.x + refPointPos.y >= 0);
32 }
33 
35  const Point2D& R,
36  float& condnumber) const {
37  condnumber = 0;
38  if (path.size() < 2) {
39  assert(false);
40  return 0;
41  }
42 
43  // consider each path segment as a mini-classifier
44  // the segment PR[Pt-->Refpt] and each path's segment cross
45  // iff each one classifies the end point of the other in different classes
46  Point2D PR = R - P;
47  Point2D u = PR;
48  u.normalize();
49 
50  unsigned numcross = 0;
51 
52  // we'll also look for the distance between P and the nearest segment
53  float closestSquareDist = -1.0f;
54 
55  size_t segCount = path.size() - 1;
56  for (size_t i = 0; i < segCount; ++i) {
57  // current path segment (or half-line!)
58  Point2D AP = P - path[i];
59  Point2D AB = path[i + 1] - path[i];
60  Point2D v = AB;
61  v.normalize();
62 
63  condnumber = std::max<float>(condnumber, fabs(v.dot(u)));
64 
65  // Compute whether PR[Pt-->Refpt] and that segment cross
66  float denom = (u.x * v.y - v.x * u.y);
67  if (denom != 0) {
68  // 1. check whether the given pt and the refpt are on different
69  // sides of the classifier line
70  // we search for alpha and beta so that
71  // P + alpha * PR = A + beta * AB
72  float alpha = (AP.y * v.x - AP.x * v.y) / denom;
73  bool pathIntersects = (alpha >= 0 && alpha * alpha <= PR.norm2());
74  if (pathIntersects) {
75  float beta = (AP.y * u.x - AP.x * u.y) / denom;
76 
77  // first and last lines are projected to infinity
78  bool refSegIntersects =
79  ((i == 0 || beta >= 0) &&
80  (i + 1 == segCount ||
81  beta * beta <
82  AB.norm2())); // not "beta*beta <=
83  // AB.norm2()" because the
84  // equality case will be
85  // managed by the next segment!
86 
87  // crossing iif each segment/line separates the other
88  if (refSegIntersects) numcross++;
89  }
90  }
91 
92  // closest distance from the point to that segment
93  // 1. projection of the point of the line
94  float squareDistToSeg = 0;
95  float distAH = v.dot(AP);
96  if ((i == 0 || distAH >= 0.0) &&
97  (i + 1 == segCount || distAH <= AB.norm())) {
98  // 2. Is the projection within the segment limit? yes => closest
99  Point2D PH = (path[i] + v * distAH) - P;
100  squareDistToSeg = PH.norm2();
101  } else {
102  // 3. otherwise closest is the minimum of the distance to the
103  // segment ends
104  Point2D BP = P - path[i + 1];
105  squareDistToSeg = std::min(AP.norm2(), BP.norm2());
106  }
107 
108  if (closestSquareDist < 0 || squareDistToSeg < closestSquareDist) {
109  closestSquareDist = squareDistToSeg;
110  }
111  }
112 
113  assert(closestSquareDist >= 0);
114  float deltaNorm = sqrt(closestSquareDist);
115 
116  return ((numcross & 1) == 0 ? deltaNorm : -deltaNorm);
117 }
118 
119 float Classifier::classify2D(const Point2D& P) const {
120  float condpos, condneg;
121  float predpos = classify2D_checkcondnum(P, refPointPos, condpos);
122  float predneg = classify2D_checkcondnum(P, refPointNeg, condneg);
123 
124  // normal nearly aligned = bad conditionning, the lower the dot prod the
125  // better
126  return condpos < condneg ? predpos : -predneg;
127 }
128 
130  assert(weightsAxis1.size() == weightsAxis2.size());
131  assert(weightsAxis1.size() > 1);
132 
133  // There may be less weights than parameters in the descriptor
134  // if we use a descriptor computed with more (bigger) scales.
135  // In this case we assume the matching scales are all at the end!
136  //(i.e. the smallest)
137  size_t weightCount = weightsAxis1.size() - 1;
138  size_t paramCount = mscdata.params.size();
139  assert(weightCount <= paramCount);
140  unsigned shift = static_cast<unsigned>(paramCount - weightCount);
141 
142  Point2D P(weightsAxis1.back(), weightsAxis2.back());
143 
144  for (size_t i = 0; i < weightCount; ++i) {
145  P.x += weightsAxis1[i] * mscdata.params[shift + i];
146  P.y += weightsAxis2[i] * mscdata.params[shift + i];
147  }
148 
149  return P;
150 }
151 
152 float Classifier::classify(const CorePointDesc& mscdata) const {
153  Point2D P = project(mscdata);
154  return classify2D(P);
155 }
156 
158  std::vector<Classifier>& classifiers,
159  std::vector<float>& scales,
160  QString& error,
161  FileHeader* header /*=0*/,
162  bool headerOnly /*=false*/) {
163  QFile file(filename);
164  if (!file.open(QIODevice::ReadOnly)) {
165  error = QString("Failed to open input classifier file!");
166  return false;
167  }
168 
169  scales.clear();
170  classifiers.clear();
171 
172  // DGM: sadly we can't use a stream as data in prm files are saved in a
173  // strange way QDataStream stream(&file);
174  //--> changing the stream 'byte order' doesn't help :(
175  // stream.setByteOrder(QDataStream::LittleEndian);
176 
177  // scales count
178  unsigned nscales;
179  file.read(reinterpret_cast<char*>(&nscales), sizeof(unsigned));
180 
181  unsigned dimPerScale = 2;
182  unsigned descriptorID = 1;
183 
184  if (nscales == 9999) {
185  // we are loading a new 'prm' file (generated by qCanupo)
186  // descriptor ID
187  file.read(reinterpret_cast<char*>(&descriptorID), sizeof(unsigned));
188  // dimension per scale
189  file.read(reinterpret_cast<char*>(&dimPerScale), sizeof(unsigned));
190  // and now the real number of scales
191  file.read(reinterpret_cast<char*>(&nscales), sizeof(unsigned));
192  }
193 
194  // values count
195  try {
196  scales.resize(nscales);
197  } catch (const std::bad_alloc&) {
198  error = QString("Not enough memory!");
199  return false;
200  }
201 
202  // read scale values
203  {
204  for (unsigned s = 0; s < nscales; ++s)
205  file.read(reinterpret_cast<char*>(&scales[s]), sizeof(float));
206  }
207 
208  // number of classifiers
209  unsigned nclassifiers;
210  file.read(reinterpret_cast<char*>(&nclassifiers), sizeof(unsigned));
211 
212  if (header) {
213  header->classifierCount = nclassifiers;
214  header->dimPerScale = dimPerScale;
215  header->descID = descriptorID;
216  }
217 
218  if (headerOnly) {
219  // we can stop here
220  return true;
221  }
222 
223  // reserve classifiers array
224  try {
225  classifiers.resize(nclassifiers);
226  } catch (const std::bad_alloc&) {
227  error = QString("Not enough memory!");
228  return false;
229  }
230 
231  // read classifiers
232  try {
233  const unsigned fdim = nscales * dimPerScale;
234  for (unsigned ci = 0; ci < nclassifiers; ++ci) {
235  Classifier& classifier = classifiers[ci];
236 
237  classifier.dimPerScale = dimPerScale;
238  classifier.descriptorID = descriptorID;
239 
240  classifier.scales = scales; // all classifiers inside a file have
241  // the same scales!
242  file.read(reinterpret_cast<char*>(&classifier.class1), sizeof(int));
243  file.read(reinterpret_cast<char*>(&classifier.class2), sizeof(int));
244 
245  classifier.weightsAxis1.resize(fdim + 1);
246  {
247  for (unsigned i = 0; i <= fdim; ++i)
248  file.read(reinterpret_cast<char*>(
249  &classifier.weightsAxis1[i]),
250  sizeof(float));
251  }
252  classifier.weightsAxis2.resize(fdim + 1);
253  {
254  for (unsigned i = 0; i <= fdim; ++i)
255  file.read(reinterpret_cast<char*>(
256  &classifier.weightsAxis2[i]),
257  sizeof(float));
258  }
259 
260  unsigned pathsize;
261  file.read(reinterpret_cast<char*>(&pathsize), sizeof(unsigned));
262  classifier.path.resize(pathsize);
263  {
264  for (unsigned i = 0; i < pathsize; ++i) {
265  file.read(reinterpret_cast<char*>(&classifier.path[i].x),
266  sizeof(float));
267  file.read(reinterpret_cast<char*>(&classifier.path[i].y),
268  sizeof(float));
269  }
270  }
271 
272  file.read(reinterpret_cast<char*>(&classifier.refPointPos.x),
273  sizeof(float));
274  file.read(reinterpret_cast<char*>(&classifier.refPointPos.y),
275  sizeof(float));
276  file.read(reinterpret_cast<char*>(&classifier.refPointNeg.x),
277  sizeof(float));
278  file.read(reinterpret_cast<char*>(&classifier.refPointNeg.y),
279  sizeof(float));
280  file.read(reinterpret_cast<char*>(&classifier.absMaxXY),
281  sizeof(float));
282  file.read(reinterpret_cast<char*>(&classifier.axisScaleRatio),
283  sizeof(float));
284 
285  if (!classifier.checkRefPoints()) {
286  // DGM: strange test, that fails for valid classifiers!
287  // error = QString("Invalid reference points in the
288  // classifier"); return false;
289  }
290  }
291  } catch (const std::bad_alloc&) {
292  error = QString("Not enough memory!");
293  return false;
294  }
295 
296  return true;
297 }
298 
299 bool Classifier::save(QString filename, QString& error) {
300  QFile file(filename);
301  if (!file.open(QIODevice::WriteOnly)) {
302  error = QString("Failed to open output file!");
303  return false;
304  }
305 
306  // DGM: sadly we can't use a stream as data in prm files are saved in a
307  // strange way see Classifier::Load
308 
310  // we code the 'new' PRM files with 9999 in place of the number of
311  // scales!
312  const unsigned headerCode = 9999;
313  file.write(reinterpret_cast<const char*>(&headerCode),
314  sizeof(unsigned));
315 
316  // descriptor ID
317  file.write(reinterpret_cast<const char*>(&descriptorID),
318  sizeof(unsigned));
319  // dimension per scale
320  file.write(reinterpret_cast<const char*>(&dimPerScale),
321  sizeof(unsigned));
322  }
323 
324  // number of scales
325  unsigned nscales = static_cast<unsigned>(scales.size());
326  file.write(reinterpret_cast<const char*>(&nscales), sizeof(unsigned));
327 
328  // write scale values
329  {
330  for (unsigned s = 0; s < nscales; ++s)
331  file.write(reinterpret_cast<const char*>(&scales[s]),
332  sizeof(float));
333  }
334 
335  // number of 2-class classifiers = 1!
336  unsigned nclassifiers = 1;
337  file.write(reinterpret_cast<const char*>(&nclassifiers), sizeof(unsigned));
338 
339  // write classifier
340  const unsigned fdim = nscales * dimPerScale;
341  file.write(reinterpret_cast<const char*>(&class1), sizeof(int));
342  file.write(reinterpret_cast<const char*>(&class2), sizeof(int));
343 
344  // weightsAxis1
345  {
346  for (unsigned i = 0; i <= fdim; ++i)
347  file.write(reinterpret_cast<const char*>(&weightsAxis1[i]),
348  sizeof(float));
349  }
350  // weightsAxis2
351  {
352  for (unsigned i = 0; i <= fdim; ++i)
353  file.write(reinterpret_cast<const char*>(&weightsAxis2[i]),
354  sizeof(float));
355  }
356 
357  unsigned pathsize = static_cast<unsigned>(path.size());
358  file.write(reinterpret_cast<const char*>(&pathsize), sizeof(unsigned));
359  {
360  for (unsigned i = 0; i < pathsize; ++i) {
361  file.write(reinterpret_cast<const char*>(&path[i].x),
362  sizeof(float));
363  file.write(reinterpret_cast<const char*>(&path[i].y),
364  sizeof(float));
365  }
366  }
367 
368  file.write(reinterpret_cast<const char*>(&refPointPos.x), sizeof(float));
369  file.write(reinterpret_cast<const char*>(&refPointPos.y), sizeof(float));
370  file.write(reinterpret_cast<const char*>(&refPointNeg.x), sizeof(float));
371  file.write(reinterpret_cast<const char*>(&refPointNeg.y), sizeof(float));
372  file.write(reinterpret_cast<const char*>(&absMaxXY), sizeof(float));
373  file.write(reinterpret_cast<const char*>(&axisScaleRatio), sizeof(float));
374 
375  file.close();
376 
377  return true;
378 }
std::string filename
static const unsigned DESC_DIMENSIONALITY
static const unsigned DESC_INVALID
Classifier.
Definition: classifier.h:23
float absMaxXY
Definition: classifier.h:73
std::vector< float > weightsAxis2
Definition: classifier.h:71
static bool Load(QString filename, std::vector< Classifier > &classifiers, std::vector< float > &scales, QString &error, FileHeader *header=0, bool headerOnly=false)
Loads a CANUPO's classifier file (.prm)
Definition: classifier.cpp:157
float classify2D(const Point2D &P) const
Classification in the 2D space.
Definition: classifier.cpp:119
float classify2D_checkcondnum(const Point2D &P, const Point2D &R, float &condnumber) const
Checks numerical condition.
Definition: classifier.cpp:34
bool save(QString filename, QString &error)
Saves classifier as a CANUPO's classifier file (.prm)
Definition: classifier.cpp:299
float axisScaleRatio
Definition: classifier.h:73
unsigned dimPerScale
Dimension (per-scale)
Definition: classifier.h:80
int class1
Definition: classifier.h:70
Point2D project(const CorePointDesc &mscdata) const
Projects a parameter vector in (2D) MSC space.
Definition: classifier.cpp:129
unsigned descriptorID
Associated descriptor ID (see ccPointDescriptor.h)
Definition: classifier.h:77
std::vector< float > weightsAxis1
Definition: classifier.h:71
std::vector< Point2D > path
Definition: classifier.h:72
std::vector< float > scales
Associated scales.
Definition: classifier.h:83
float classify(const CorePointDesc &mscdata) const
Classification in MSC space.
Definition: classifier.cpp:152
bool checkRefPoints()
Checks the ref. points.
Definition: classifier.cpp:25
Point2D refPointPos
Definition: classifier.h:74
int class2
Definition: classifier.h:70
Classifier()
Default constructor.
Definition: classifier.cpp:17
Point2D refPointNeg
Definition: classifier.h:74
Type dot(const Vector2Tpl &v) const
Dot product.
Definition: CVGeom.h:71
Type x
Definition: CVGeom.h:36
Type norm() const
Returns vector norm.
Definition: CVGeom.h:63
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:65
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:61
Type y
Definition: CVGeom.h:36
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
int min(int a, int b)
Definition: cutil_math.h:53
static void error(char *msg)
Definition: lsd.c:159
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370
Classifier's file header info.
Definition: classifier.h:52
unsigned classifierCount
Definition: classifier.h:53
Set of descriptors.
std::vector< float > params