Skip to content

Commit a9dbfdc

Browse files
authored
POL5792 more 3D shape (PCA) features (PolusAI#284)
1 parent b822a88 commit a9dbfdc

File tree

15 files changed

+477
-13
lines changed

15 files changed

+477
-13
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ option(RUN_GTEST "Downloads google unit test API and runs google test scripts to
132132

133133
#==== Source files
134134
set(SOURCE
135+
src/nyx/3rdparty/dsyevj3.cpp
135136
src/nyx/arrow_output_stream.cpp
136137
src/nyx/features/basic_morphology.cpp
137138
src/nyx/features/caliper_feret.cpp
@@ -190,6 +191,7 @@ set(SOURCE
190191
src/nyx/features/zernike.cpp
191192
src/nyx/features/zernike_nontriv.cpp
192193
src/nyx/helpers/diag.cpp
194+
src/nyx/helpers/helpers.cpp
193195
src/nyx/helpers/timing.cpp
194196
src/nyx/io/nifti/laynii_lib.cpp
195197
src/nyx/io/nifti/nifti2_io.cpp

src/nyx/3rdparty/dsyevj3.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// ----------------------------------------------------------------------------
2+
// Numerical diagonalization of 3x3 matrcies
3+
// Copyright (C) 2006 Joachim Kopp
4+
// ----------------------------------------------------------------------------
5+
// This library is free software; you can redistribute it and/or
6+
// modify it under the terms of the GNU Lesser General Public
7+
// License as published by the Free Software Foundation; either
8+
// version 2.1 of the License, or (at your option) any later version.
9+
//
10+
// This library is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
// Lesser General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Lesser General Public
16+
// License along with this library; if not, write to the Free Software
17+
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
//
19+
// Edited for better c++ compatibility: Andriy Kharchenko
20+
//
21+
// ----------------------------------------------------------------------------
22+
#include <stdio.h>
23+
#include <math.h>
24+
#include "dsyevj3.h"
25+
26+
// Macros
27+
#define SQR(x) ((x)*(x)) // x^2
28+
29+
namespace Nyxus
30+
{
31+
32+
int dsyevj3 (
33+
// IN
34+
const double A_[3][3],
35+
// OUT
36+
double Q[3][3], double w[3])
37+
// ----------------------------------------------------------------------------
38+
// Calculates the eigenvalues and normalized eigenvectors of a symmetric 3x3
39+
// matrix A using the Jacobi algorithm.
40+
// The upper triangular part of A is destroyed during the calculation,
41+
// the diagonal elements are read but not destroyed, and the lower
42+
// triangular elements are not referenced at all.
43+
// ----------------------------------------------------------------------------
44+
// Parameters:
45+
// A: The symmetric input matrix
46+
// Q: Storage buffer for eigenvectors
47+
// w: Storage buffer for eigenvalues
48+
// ----------------------------------------------------------------------------
49+
// Return value:
50+
// 0: Success
51+
// -1: Error (no convergence)
52+
// ----------------------------------------------------------------------------
53+
{
54+
double A[3][3] = { {A_[0][0], A_[0][1], A_[0][2]}, {A_[1][0], A_[1][1], A_[1][2]}, {A_[2][0], A_[2][1], A_[2][2]} };
55+
const int n = 3;
56+
double sd, so; // Sums of diagonal resp. off-diagonal elements
57+
double s, c, t; // sin(phi), cos(phi), tan(phi) and temporary storage
58+
double g, h, z, theta; // More temporary storage
59+
double thresh;
60+
61+
// Initialize Q to the identitity matrix
62+
#ifndef EVALS_ONLY
63+
for (int i = 0; i < n; i++)
64+
{
65+
Q[i][i] = 1.0;
66+
for (int j = 0; j < i; j++)
67+
Q[i][j] = Q[j][i] = 0.0;
68+
}
69+
#endif
70+
71+
// Initialize w to diag(A)
72+
for (int i = 0; i < n; i++)
73+
w[i] = A[i][i];
74+
75+
// Calculate SQR(tr(A))
76+
sd = 0.0;
77+
for (int i = 0; i < n; i++)
78+
sd += fabs(w[i]);
79+
sd = SQR(sd);
80+
81+
// Main iteration loop
82+
for (int nIter = 0; nIter < 50; nIter++)
83+
{
84+
// Test for convergence
85+
so = 0.0;
86+
for (int p = 0; p < n; p++)
87+
for (int q = p + 1; q < n; q++)
88+
so += fabs(A[p][q]);
89+
if (so == 0.0)
90+
return 0;
91+
92+
if (nIter < 4)
93+
thresh = 0.2 * so / SQR(n);
94+
else
95+
thresh = 0.0;
96+
97+
// Do sweep
98+
for (int p = 0; p < n; p++)
99+
for (int q = p + 1; q < n; q++)
100+
{
101+
g = 100.0 * fabs(A[p][q]);
102+
if (nIter > 4 && fabs(w[p]) + g == fabs(w[p])
103+
&& fabs(w[q]) + g == fabs(w[q]))
104+
{
105+
A[p][q] = 0.0;
106+
}
107+
else if (fabs(A[p][q]) > thresh)
108+
{
109+
// Calculate Jacobi transformation
110+
h = w[q] - w[p];
111+
if (fabs(h) + g == fabs(h))
112+
{
113+
t = A[p][q] / h;
114+
}
115+
else
116+
{
117+
theta = 0.5 * h / A[p][q];
118+
if (theta < 0.0)
119+
t = -1.0 / (sqrt(1.0 + SQR(theta)) - theta);
120+
else
121+
t = 1.0 / (sqrt(1.0 + SQR(theta)) + theta);
122+
}
123+
c = 1.0 / sqrt(1.0 + SQR(t));
124+
s = t * c;
125+
z = t * A[p][q];
126+
127+
// Apply Jacobi transformation
128+
A[p][q] = 0.0;
129+
w[p] -= z;
130+
w[q] += z;
131+
for (int r = 0; r < p; r++)
132+
{
133+
t = A[r][p];
134+
A[r][p] = c * t - s * A[r][q];
135+
A[r][q] = s * t + c * A[r][q];
136+
}
137+
for (int r = p + 1; r < q; r++)
138+
{
139+
t = A[p][r];
140+
A[p][r] = c * t - s * A[r][q];
141+
A[r][q] = s * t + c * A[r][q];
142+
}
143+
for (int r = q + 1; r < n; r++)
144+
{
145+
t = A[p][r];
146+
A[p][r] = c * t - s * A[q][r];
147+
A[q][r] = s * t + c * A[q][r];
148+
}
149+
150+
// Update eigenvectors
151+
#ifndef EVALS_ONLY
152+
for (int r = 0; r < n; r++)
153+
{
154+
t = Q[r][p];
155+
Q[r][p] = c * t - s * Q[r][q];
156+
Q[r][q] = s * t + c * Q[r][q];
157+
}
158+
#endif
159+
}
160+
}
161+
}
162+
163+
return -1;
164+
}
165+
166+
}

src/nyx/3rdparty/dsyevj3.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// ----------------------------------------------------------------------------
2+
// Numerical diagonalization of 3x3 matrcies
3+
// Copyright (C) 2006 Joachim Kopp
4+
// ----------------------------------------------------------------------------
5+
// This library is free software; you can redistribute it and/or
6+
// modify it under the terms of the GNU Lesser General Public
7+
// License as published by the Free Software Foundation; either
8+
// version 2.1 of the License, or (at your option) any later version.
9+
//
10+
// This library is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
// Lesser General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Lesser General Public
16+
// License along with this library; if not, write to the Free Software
17+
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
//
19+
// Edited for better c++ compatibility: Andriy Kharchenko
20+
//
21+
// ----------------------------------------------------------------------------
22+
#ifndef __DSYEVJ3_H
23+
#define __DSYEVJ3_H
24+
25+
namespace Nyxus
26+
{
27+
int dsyevj3 (/*IN*/ const double A[3][3], /*OUT*/ double Q[3][3], double w[3]);
28+
}
29+
30+
#endif

src/nyx/features/3d_surface.cpp

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
#include "../featureset.h"
44
#include "../environment.h"
55
#include "../parallel.h"
6+
#include "../3rdparty/quickhull.hpp"
7+
#include "../3rdparty/dsyevj3.h"
68
#include "3d_surface.h"
7-
#include "quickhull.hpp"
89

910
bool D3_SurfaceFeature::required (const FeatureSet & fs)
1011
{
@@ -422,20 +423,40 @@ void D3_SurfaceFeature::calculate (LR& r)
422423

423424
fval_VOLUME_CONVEXHULL += d / 6;
424425
}
425-
// other features
426+
427+
// volume-area ratio features
426428
fval_MESH_VOLUME = fval_VOLUME_CONVEXHULL;
427429
fval_AREA_2_VOLUME = fval_AREA / fval_VOXEL_VOLUME;
428430
fval_COMPACTNESS1 = fval_VOXEL_VOLUME / std::sqrt(M_PI * fval_AREA * fval_AREA * fval_AREA);
429431
fval_COMPACTNESS2 = 36. * M_PI * fval_VOXEL_VOLUME * fval_VOXEL_VOLUME / (fval_AREA * fval_AREA * fval_AREA);
430432
fval_SPHERICAL_DISPROPORTION = fval_AREA / std::pow(36. * M_PI * fval_VOXEL_VOLUME * fval_VOXEL_VOLUME, 1. / 3.);
431433
fval_SPHERICITY = std::pow(36. * M_PI * fval_VOXEL_VOLUME * fval_VOXEL_VOLUME, 1. / 3.) / fval_AREA;
434+
435+
// pca features
436+
double K[3][3];
437+
Pixel3::calc_cov_matrix (K, r.raw_pixels_3D);
438+
double L[3];
439+
if (Nyxus::calc_eigvals(L, K))
440+
{
441+
fval_MAJOR_AXIS_LEN = 4.0 * sqrt(L[1]);
442+
fval_MINOR_AXIS_LEN = 4.0 * sqrt(L[2]);
443+
fval_LEAST_AXIS_LEN = 4.0 * sqrt(L[0]);
444+
fval_ELONGATION = sqrt(L[2] / L[1]);
445+
fval_FLATNESS = sqrt(L[0] / L[1]);
446+
}
447+
else
448+
{
449+
fval_MAJOR_AXIS_LEN =
450+
fval_MINOR_AXIS_LEN =
451+
fval_LEAST_AXIS_LEN =
452+
fval_ELONGATION =
453+
fval_FLATNESS = 0.0;
454+
}
432455
}
433456

434457
void D3_SurfaceFeature::osized_add_online_pixel(size_t x, size_t y, uint32_t intensity) {}
435458

436-
void D3_SurfaceFeature::osized_calculate(LR& r, ImageLoader& imloader)
437-
{
438-
}
459+
void D3_SurfaceFeature::osized_calculate(LR& r, ImageLoader& imloader) {}
439460

440461
void D3_SurfaceFeature::save_value(std::vector<std::vector<double>>& fvals)
441462
{
@@ -448,6 +469,12 @@ void D3_SurfaceFeature::save_value(std::vector<std::vector<double>>& fvals)
448469
fvals[(int)Nyxus::Feature3D::SPHERICITY][0] = fval_SPHERICITY;
449470
fvals[(int)Nyxus::Feature3D::VOLUME_CONVEXHULL][0] = fval_VOLUME_CONVEXHULL;
450471
fvals[(int)Nyxus::Feature3D::VOXEL_VOLUME][0] = fval_VOXEL_VOLUME;
472+
473+
fvals[(int)Nyxus::Feature3D::MAJOR_AXIS_LEN][0] = fval_MAJOR_AXIS_LEN;
474+
fvals[(int)Nyxus::Feature3D::MINOR_AXIS_LEN][0] = fval_MINOR_AXIS_LEN;
475+
fvals[(int)Nyxus::Feature3D::LEAST_AXIS_LEN][0] = fval_LEAST_AXIS_LEN;
476+
fvals[(int)Nyxus::Feature3D::ELONGATION][0] = fval_ELONGATION;
477+
fvals[(int)Nyxus::Feature3D::FLATNESS][0] = fval_FLATNESS;
451478
}
452479

453480
void D3_SurfaceFeature::cleanup_instance()
@@ -460,7 +487,12 @@ void D3_SurfaceFeature::cleanup_instance()
460487
fval_SPHERICAL_DISPROPORTION =
461488
fval_SPHERICITY =
462489
fval_VOLUME_CONVEXHULL =
463-
fval_VOXEL_VOLUME = 0;
490+
fval_VOXEL_VOLUME =
491+
fval_MAJOR_AXIS_LEN,
492+
fval_MINOR_AXIS_LEN,
493+
fval_LEAST_AXIS_LEN,
494+
fval_ELONGATION,
495+
fval_FLATNESS = 0;
464496
}
465497

466498
void D3_SurfaceFeature::parallel_process (std::vector<int>& roi_labels, std::unordered_map <int, LR>& roiData, int n_threads)

src/nyx/features/3d_surface.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ class D3_SurfaceFeature : public FeatureMethod
3030
Nyxus::Feature3D::SPHERICAL_DISPROPORTION,
3131
Nyxus::Feature3D::SPHERICITY,
3232
Nyxus::Feature3D::VOLUME_CONVEXHULL,
33-
Nyxus::Feature3D::VOXEL_VOLUME
33+
Nyxus::Feature3D::VOXEL_VOLUME,
34+
Nyxus::Feature3D::MAJOR_AXIS_LEN,
35+
Nyxus::Feature3D::MINOR_AXIS_LEN,
36+
Nyxus::Feature3D::LEAST_AXIS_LEN,
37+
Nyxus::Feature3D::ELONGATION,
38+
Nyxus::Feature3D::FLATNESS
3439
};
3540

3641
private:
@@ -61,7 +66,12 @@ class D3_SurfaceFeature : public FeatureMethod
6166
fval_SPHERICAL_DISPROPORTION,
6267
fval_SPHERICITY,
6368
fval_VOLUME_CONVEXHULL,
64-
fval_VOXEL_VOLUME;
69+
fval_VOXEL_VOLUME,
70+
fval_MAJOR_AXIS_LEN,
71+
fval_MINOR_AXIS_LEN,
72+
fval_LEAST_AXIS_LEN,
73+
fval_ELONGATION,
74+
fval_FLATNESS;
6575

6676
};
6777

src/nyx/features/ellipse_fitting.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void EllipseFittingFeature::calculate (LR& r)
5454
majorAxisLength = 2. * sqrt(2.) * sqrt(uxx + uyy + common);
5555
minorAxisLength = 2. * sqrt(2.) * sqrt(uxx + uyy - common);
5656
eccentricity = sqrt (1.0 - minorAxisLength * minorAxisLength / (majorAxisLength * majorAxisLength));
57-
elongation = sqrt(minorAxisLength/majorAxisLength);
57+
elongation = minorAxisLength / majorAxisLength;
5858
roundness = (4. * area) / (M_PI * majorAxisLength * majorAxisLength);
5959

6060
// Calculate orientation [-90,90]

src/nyx/features/pixel.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
#include <numeric>
12
#include <climits>
23
#include "pixel.h"
4+
#include "../helpers/helpers.h"
35

46
bool operator == (const Pixel2& p1, const Pixel2& p2)
57
{
@@ -120,7 +122,7 @@ double Pixel2::max_sqdist(const std::vector<Pixel2>& cloud) const
120122
return extrem_d;
121123
}
122124

123-
int Pixel2::find_center (const std::vector<Pixel2>& cloud, const std::vector<Pixel2>& contour)
125+
/*static*/ int Pixel2::find_center(const std::vector<Pixel2>& cloud, const std::vector<Pixel2>& contour)
124126
{
125127
int idxMinDif = 0;
126128
auto minmaxDist = cloud[idxMinDif].min_max_sqdist(contour);
@@ -209,8 +211,31 @@ double Pixel2::sqdist(int x, int y) const
209211
return retval;
210212
}
211213

214+
/*static*/ std::tuple<double, double, double> Pixel3::centroid(const std::vector<Pixel3>& A)
215+
{
216+
double n = A.size(),
217+
cx = std::accumulate (A.begin(), A.end(), 0.0, [](double sum, const Pixel3& p) {return sum + p.x;}),
218+
cy = std::accumulate (A.begin(), A.end(), 0.0, [](double sum, const Pixel3& p) {return sum + p.y;}),
219+
cz = std::accumulate (A.begin(), A.end(), 0.0, [](double sum, const Pixel3& p) {return sum + p.z;});
220+
cx /= n;
221+
cy /= n;
222+
cz /= n;
223+
return { cx, cy, cz };
224+
}
212225

226+
/*static*/ void Pixel3::calc_cov_matrix (double Sigma[3][3], const std::vector<Pixel3>& cloud)
227+
{
228+
auto [ccx, ccy, ccz] = Pixel3::centroid (cloud);
229+
double n = cloud.size();
230+
std::vector<std::vector<double>> table;
231+
for (auto vox : cloud)
232+
{
233+
std::vector<double> voxRow = { (double)vox.x - ccx, (double)vox.y - ccy, (double)vox.z - ccz };
234+
table.push_back({ voxRow });
235+
}
213236

237+
Nyxus::calc_cov_matrix (Sigma, table);
238+
}
214239

215240

216241

src/nyx/features/pixel.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,5 +236,11 @@ struct Pixel3 : public Point3i
236236
/// @brief Returns the angle in radians between this pixel and 'other' relative to the origin
237237
double angle(const Pixel3& other) const;
238238

239+
/// @brief Returns Cartesian coordinate of cloud's center
240+
static std::tuple<double, double, double> centroid (const std::vector<Pixel3>& cloud);
241+
242+
/// @brief Calculates a covariance matrix of a voxel cloud
243+
static void calc_cov_matrix (double Sigma[3][3], const std::vector<Pixel3>& cloud);
244+
239245
};
240246

0 commit comments

Comments
 (0)