sgsPy
structurally guided sampling
Loading...
Searching...
No Matches
pca.h
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Project: sgs
4 * Purpose: C++ implementation of PCA
5 * Author: Joseph Meyer
6 * Date: October, 2025
7 *
8 ******************************************************************************/
9
14
15#include <iostream>
16#include <numeric>
17
18#include "utils/helper.h"
19#include "utils/raster.h"
20
21#include "oneapi/dal.hpp"
22
23namespace sgs {
24namespace pca {
25
26typedef oneapi::dal::homogen_table DALHomogenTable;
27
38template <typename T>
39struct PCAResult {
40 std::vector<std::vector<T>> eigenvectors;
41 std::vector<T> eigenvalues;
42 std::vector<double> means;
43 std::vector<double> stdevs;
44};
45
80template <typename T>
83 std::vector<helper::RasterBandMetaData>& bands,
84 GDALDataType type,
85 size_t size,
86 int width,
87 int height,
88 int nComp)
89{
90 int bandCount = static_cast<int>(bands.size());
91 T *p_data = reinterpret_cast<T *>(VSIMalloc3(width * height, bandCount, size));
92
93 std::vector<helper::Variance> bandVariances(bandCount);
94 std::vector<T> noDataVals(bandCount);
95 for (size_t i = 0; i < bands.size(); i++) {
96 noDataVals[i] = static_cast<T>(bands[i].nan);
97 }
98
99 //read full bands into p_data
100 for (size_t i = 0; i < bands.size(); i++) {
101 bands[i].p_band->RasterIO(
102 GF_Read,
103 0,
104 0,
105 width,
106 height,
107 (void *)((size_t)p_data + i * size),
108 width,
109 height,
110 type,
111 size * bands.size(),
112 size * bands.size() * width
113 );
114 }
115
116 //remove nodata values from p_data, ensuring data pixels are consecutive
117 int nFeatures = 0;
118 for (int i = 0; i < height * width; i++) {
119 bool isNan = false;
120 for (int b = 0; b < bandCount; b++) {
121 T val = p_data[i * bandCount + b];
122 isNan = val == noDataVals[b] || std::isnan(val);
123 if (isNan) {
124 break;
125 }
126 p_data[nFeatures * bandCount + b] = val;
127 }
128 nFeatures += !isNan;
129 }
130
131 //update variance calculations
132 for (int i = 0; i < nFeatures; i++) {
133 for (int b = 0; b < bandCount; b++) {
134 T val = p_data[i * bandCount + b];
135 bandVariances[b].update(static_cast<double>(val));
136 }
137 }
138
139 //calculate pca
140 DALHomogenTable table = DALHomogenTable::wrap<T>(p_data, nFeatures, bandCount, oneapi::dal::data_layout::row_major);
141 const auto desc = oneapi::dal::pca::descriptor<T, oneapi::dal::pca::method::cov>().set_component_count(nComp).set_deterministic(true);
142 const auto result = oneapi::dal::train(desc, table);
143
144 VSIFree(p_data);
145
146 PCAResult<T> retval;
147 auto eigenvectors = result.get_eigenvectors();
148 auto eigenvalues = result.get_eigenvalues();
149 int64_t eigRows = eigenvectors.get_row_count();
150 int64_t eigCols = eigenvectors.get_column_count();
151
152 oneapi::dal::row_accessor<const float> eigVecAcc {eigenvectors};
153 auto eigVecBlock = eigVecAcc.pull({0, eigRows});
154
155 oneapi::dal::row_accessor<const float> eigValAcc {eigenvalues};
156 auto eigValBlock = eigValAcc.pull({0, 1});
157
158 retval.eigenvectors.resize(eigRows);
159 retval.eigenvalues.resize(eigRows);
160 for (int64_t i = 0; i < eigRows; i++) {
161 retval.eigenvalues[i] = static_cast<double>(eigValBlock[i]);
162
163 retval.eigenvectors[i].resize(eigCols);
164 for (int64_t j = 0; j < eigCols; j++) {
165 retval.eigenvectors[i][j] = static_cast<double>(eigVecBlock[i * eigCols + j]);
166 }
167 }
168
169 for (int b = 0; b < bandCount; b++) {
170 retval.means.push_back(bandVariances[b].getMean());
171 retval.stdevs.push_back(bandVariances[b].getStdev());
172 }
173
174 return retval;
175}
176
217template <typename T>
218PCAResult<T>
220 std::vector<helper::RasterBandMetaData>& bands,
221 GDALDataType type,
222 size_t size,
223 int xBlockSize,
224 int yBlockSize,
225 int xBlocks,
226 int yBlocks,
227 int nComp)
228{
229 int bandCount = static_cast<int>(bands.size());
230 T *p_data = reinterpret_cast<T *>(VSIMalloc3(xBlockSize * yBlockSize, bandCount, size));
231
232 std::vector<helper::Variance> bandVariances(bandCount);
233 std::vector<T> noDataVals(bandCount);
234 for (size_t i = 0; i < bands.size(); i++) {
235 noDataVals[i] = static_cast<T>(bands[i].nan);
236 }
237
238 const auto desc = oneapi::dal::pca::descriptor<T, oneapi::dal::pca::method::cov>().set_component_count(nComp).set_deterministic(true);
239 oneapi::dal::pca::partial_train_result<> partial_result;
240
241 for (int yBlock = 0; yBlock < yBlocks; yBlock++) {
242 for (int xBlock = 0; xBlock < xBlocks; xBlock++) {
243 int xValid, yValid;
244 bands[0].p_band->GetActualBlockSize(xBlock, yBlock, &xValid, &yValid);
245
246 //read bands into memory
247 for (int i = 0; i < bandCount; i++) {
248 bands[i].p_band->RasterIO(
249 GF_Read,
250 xBlock * xBlockSize,
251 yBlock * yBlockSize,
252 xValid,
253 yValid,
254 (void *)((size_t)p_data + i * size),
255 xValid,
256 yValid,
257 type,
258 size * bands.size(),
259 size * bands.size() * static_cast<size_t>(xBlockSize)
260 );
261 }
262
263 //remove nodata values
264 int nFeatures = 0;
265 for (int x = 0; x < xValid; x++) {
266 for (int y = 0; y < yValid; y++) {
267 bool isNan = false;
268 for (int b = 0; b < bandCount; b++) {
269 T val = p_data[((y * xBlockSize) + x) * bandCount + b];
270 isNan = std::isnan(val) || val == noDataVals[b];
271 if (isNan) {
272 break;
273 }
274 p_data[nFeatures * bandCount + b] = val;
275 }
276 nFeatures += !isNan;
277 }
278 }
279
280 //update variance calculations
281 for (int i = 0; i < nFeatures; i++) {
282 for (int b = 0; b < bandCount; b++) {
283 T val = p_data[i * bandCount + b];
284 bandVariances[b].update(static_cast<double>(val));
285 }
286 }
287
288 //calculate partial result
289 DALHomogenTable table = DALHomogenTable(p_data, nFeatures, bandCount, [](const T*){}, oneapi::dal::data_layout::row_major);
290 partial_result = oneapi::dal::partial_train(desc, partial_result, table);
291 }
292 }
293
294 auto result = oneapi::dal::finalize_train(desc, partial_result);
295
296 VSIFree(p_data);
297
298 PCAResult<T> retval;
299 auto eigenvectors = result.get_eigenvectors();
300 auto eigenvalues = result.get_eigenvalues();
301 int64_t eigRows = eigenvectors.get_row_count();
302 int64_t eigCols = eigenvectors.get_column_count();
303
304 oneapi::dal::row_accessor<const T> eigVecAcc {eigenvectors};
305 auto eigVecBlock = eigVecAcc.pull({0, eigRows});
306
307 oneapi::dal::row_accessor<const T> eigValAcc {eigenvalues};
308 auto eigValBlock = eigValAcc.pull({0, 1});
309
310 retval.eigenvectors.resize(eigRows);
311 retval.eigenvalues.resize(eigRows);
312 for (int64_t i = 0; i < eigRows; i++) {
313 retval.eigenvalues[i] = static_cast<T>(eigValBlock[i]);
314
315 retval.eigenvectors[i].resize(eigCols);
316 for (int64_t j = 0; j < eigCols; j++) {
317 retval.eigenvectors[i][j] = static_cast<T>(eigValBlock[i * eigCols + j]);
318 }
319 }
320
321 for (int b = 0; b < bandCount; b++) {
322 retval.means.push_back(bandVariances[b].getMean());
323 retval.stdevs.push_back(bandVariances[b].getStdev());
324 }
325
326 return retval;
327}
328
363template <typename T>
364void
366 std::vector<helper::RasterBandMetaData>& bands,
367 std::vector<helper::RasterBandMetaData>& PCABands,
368 PCAResult<T>& result,
369 GDALDataType type,
370 size_t size,
371 int height,
372 int width)
373{
374 int bandCount = static_cast<int>(bands.size());
375 int nComp = static_cast<int>(PCABands.size());
376 std::vector<T> noDataVals(bandCount);
377 T resultNan = std::nan("");
378
379 //set no data values and read input bands
380 T *p_data = reinterpret_cast<T *>(VSIMalloc3(bandCount, height * width, size));
381 for (int b = 0; b < bandCount; b++) {
382 noDataVals[b] = static_cast<T>(bands[b].nan);
383
384 bands[b].p_band->RasterIO(
385 GF_Read,
386 0,
387 0,
388 width,
389 height,
390 (void *)((size_t)p_data + b * size),
391 width,
392 height,
393 type,
394 size * bandCount,
395 size * bandCount * width
396 );
397 }
398
399 //scale and shift data pixels, set no data pixels to nan
400 for (int i = 0; i < height * width; i++) {
401 int bi = i * bandCount;
402
403 for (int b = 0; b < bandCount; b++) {
404 T val = p_data[bi + b];
405 p_data[bi + b] = (val == noDataVals[b]) ?
406 resultNan :
407 (val - result.means[b]) / result.stdevs[b];
408 }
409 }
410
411 //read result eigenvectors into matrix format
412 T *p_comp = reinterpret_cast<T *>(VSIMalloc3(nComp, bandCount, size));
413 for (int c = 0; c < nComp; c++) {
414 int ci = c * bandCount;
415 for (int b = 0; b < bandCount; b++) {
416 p_comp[ci + b] = result.eigenvectors[c][b];
417 }
418 }
419
427
428 //create DAL homogen table wrappers for input data
429 const auto dataTable = DALHomogenTable(p_data, height * width, bandCount, [](const T*){}, oneapi::dal::data_layout::row_major);
430 const auto compTable = DALHomogenTable(p_comp, nComp, bandCount, [](const T*){}, oneapi::dal::data_layout::row_major);
431
432 //create DAL descriptor and compute the result
433 const auto kernel_desc = oneapi::dal::linear_kernel::descriptor{}.set_scale(1.0).set_shift(0.0);
434 const auto compute_result = oneapi::dal::compute(kernel_desc, dataTable, compTable);
435
436 //get the raw data from the result
437 const auto values = compute_result.get_values();
438 oneapi::dal::row_accessor<const T> valAcc {values};
439 const T *p_result = valAcc.pull({0, height * width}).get_data();
440
441 //write the raw result to the output
442 for (int c = 0; c < nComp; c++) {
443 PCABands[c].p_band->RasterIO(
444 GF_Write,
445 0,
446 0,
447 width,
448 height,
449 (void *)((size_t)p_result + c * size),
450 width,
451 height,
452 type,
453 size * nComp,
454 size * nComp * width
455 );
456 }
457
458 VSIFree(p_data);
459 VSIFree(p_comp);
460}
461
499template <typename T>
500void
502 std::vector<helper::RasterBandMetaData>& bands,
503 std::vector<helper::RasterBandMetaData>& PCABands,
504 PCAResult<T>& result,
505 GDALDataType type,
506 size_t size,
507 int xBlockSize,
508 int yBlockSize,
509 int xBlocks,
510 int yBlocks)
511{
512 int bandCount = static_cast<int>(bands.size());
513 int nComp = static_cast<int>(PCABands.size());
514 std::vector<T> noDataVals(bandCount);
515 T resultNan = std::nan("");
516 for (int i = 0; i < bandCount; i++) {
517 noDataVals[i] = static_cast<T>(bands[i].nan);
518 }
519
520 T *p_data = reinterpret_cast<T *>(VSIMalloc3(xBlockSize * yBlockSize, size, bandCount));
521 T *p_comp = reinterpret_cast<T *>(VSIMalloc3(nComp, bandCount, size));
522
523 //create DAL homogen table wrappers for input data
524 const auto dataTable = DALHomogenTable(p_data, xBlockSize * yBlockSize, bandCount, [](const T*){}, oneapi::dal::data_layout::row_major);
525 const auto compTable = DALHomogenTable(p_comp, nComp, bandCount, [](const T*){}, oneapi::dal::data_layout::row_major);
526
527 for (int yBlock = 0; yBlock < yBlocks; yBlock++) {
528 for (int xBlock = 0; xBlock < xBlocks; xBlock++) {
529 int xValid, yValid;
530 bands[0].p_band->GetActualBlockSize(xBlock, yBlock, &xValid, &yValid);
531
532 //read bands into memory, specifically into the memory location of the dal data homogen table
533 for (int b = 0; b < bandCount; b++) {
534 bands[b].p_band->RasterIO(
535 GF_Read,
536 xBlock * xBlockSize,
537 yBlock * yBlockSize,
538 xValid,
539 yValid,
540 (void *)((size_t)p_data + b * size),
541 xValid,
542 yValid,
543 type,
544 size * bands.size(),
545 size * bands.size() * static_cast<size_t>(xBlockSize)
546 );
547 }
548
549 //scale and shift data pixels, set no data pixels to nan
550 for (int i = 0; i < xBlockSize * yBlockSize; i++) {
551 int bi = i * bandCount;
552
553 for (int b = 0; b < bandCount; b++) {
554 T val = p_data[bi + b];
555 p_data[bi + b] = (val == noDataVals[b]) ?
556 resultNan :
557 (val - result.means[b]) / result.stdevs[b];
558 }
559 }
560
568
569 //create DAL descriptor and compute the result
570 const auto kernel_desc = oneapi::dal::linear_kernel::descriptor{}.set_scale(1.0).set_shift(0.0);
571 const auto compute_result = oneapi::dal::compute(kernel_desc, dataTable, compTable);
572
573 //get the raw data from the result
574 const auto values = compute_result.get_values();
575 oneapi::dal::row_accessor<const T> valAcc {values};
576 const T *p_result = valAcc.pull({0, xBlockSize * yBlockSize}).get_data();
577
578 //write the raw result to the output
579 for (int c = 0; c < nComp; c++) {
580 PCABands[c].p_band->RasterIO(
581 GF_Write,
582 xBlock * xBlockSize,
583 yBlock * yBlockSize,
584 xValid,
585 yValid,
586 (void *)((size_t)p_comp + c * size),
587 xValid,
588 yValid,
589 type,
590 size * nComp,
591 size * nComp * xBlockSize
592 );
593 }
594 }
595 }
596
597 VSIFree(p_data);
598 VSIFree(p_comp);
599}
600
641std::tuple<
643 std::vector<std::vector<double>>,
644 std::vector<double>,
645 std::vector<double>,
646 std::vector<double>
647>
650 int nComp,
651 bool largeRaster,
652 std::string tempFolder,
653 std::string filename,
654 std::map<std::string, std::string> driverOptions)
655{
656 GDALAllRegister();
657
658 int bandCount = p_raster->getBandCount();
659 int height = p_raster->getHeight();
660 int width = p_raster->getWidth();
661 double *geotransform = p_raster->getGeotransform();
662 std::string projection = std::string(p_raster->getDataset()->GetProjectionRef());
663
664 bool isMEMDataset = !largeRaster && filename == "";
665 bool isVRTDataset = largeRaster && filename == "";
666 GDALDataset *p_dataset = nullptr;
667
668 std::vector<helper::RasterBandMetaData> bands(bandCount);
669 std::vector<helper::RasterBandMetaData> pcaBands(nComp);
670 std::vector<helper::VRTBandDatasetInfo> VRTBandInfo;
671
672 int xBlockSize, yBlockSize;
673 p_raster->getRasterBand(0)->GetBlockSize(&xBlockSize, &yBlockSize);
674
675 GDALDataType type = GDT_Float32;
676 size_t size = sizeof(float);
677 for (int i = 0; i < bandCount; i++) {
678 bands[i].p_band = p_raster->getRasterBand(i);
679 bands[i].nan = bands[i].p_band->GetNoDataValue();
680
681 if (p_raster->getRasterBandType(i) == GDT_Float64) {
682 type = GDT_Float64;
683 size = sizeof(double);
684 }
685 }
686
687 if (isMEMDataset) {
688 p_dataset = helper::createVirtualDataset("MEM", width, height, geotransform, projection);
689
690 for (int i = 0; i < nComp; i++) {
691 pcaBands[i].type = type == GDT_Float64 ? GDT_Float64 : GDT_Float32;
692 pcaBands[i].size = type == GDT_Float64 ? sizeof(double) : sizeof(float);
693 pcaBands[i].name = "comp_" + std::to_string(i + 1);
694 pcaBands[i].nan = std::nan("");
695 helper::addBandToMEMDataset(p_dataset, pcaBands[i]);
696 }
697 }
698 else if (isVRTDataset){
699 p_dataset = helper::createVirtualDataset("VRT", width, height, geotransform, projection);
700
701 for (int i = 0; i < nComp; i++) {
702 pcaBands[i].type = type == GDT_Float64 ? GDT_Float64 : GDT_Float32;
703 pcaBands[i].size = type == GDT_Float64 ? sizeof(double) : sizeof(float);
704 pcaBands[i].name = "comp_" + std::to_string(i + 1);
705 pcaBands[i].nan = std::nan("");
706 helper::createVRTBandDataset(p_dataset, pcaBands[i], tempFolder, pcaBands[i].name, VRTBandInfo, driverOptions);
707 }
708 }
709 else {
710 std::filesystem::path filepath = filename;
711 std::string extension = filepath.extension().string();
712 std::string driver;
713
714 if (extension == ".tif") {
715 driver = "GTiff";
716 }
717 else {
718 throw std::runtime_error("sgs only supports .tif files right now.");
719 }
720
721 bool useTiles = xBlockSize != width &&
722 yBlockSize != height;
723
724 for (int i = 0; i < nComp; i++) {
725 pcaBands[i].type = type == GDT_Float64 ? GDT_Float64 : GDT_Float32;
726 pcaBands[i].size = type == GDT_Float64 ? sizeof(double) : sizeof(float);
727 pcaBands[i].name = "comp_" + std::to_string(i + 1);
728 pcaBands[i].nan = std::nan("");
729
730 if (useTiles) {
731 pcaBands[i].xBlockSize = xBlockSize;
732 pcaBands[i].yBlockSize = yBlockSize;
733 }
734 }
735
736 p_dataset = helper::createDataset(
737 filename,
738 driver,
739 width,
740 height,
741 geotransform,
742 projection,
743 pcaBands.data(),
744 pcaBands.size(),
745 useTiles,
746 driverOptions
747 );
748 }
749
750 //get block size
751 int xBlocks = (width + xBlockSize - 1) / xBlockSize;
752 int yBlocks = (height + yBlockSize - 1) / yBlockSize;
753
754 //these vectors are not used for calculation, but set and returned to the Python side of the application for reference
755 std::vector<std::vector<double>> eigenvectors;
756 std::vector<double> eigenvalues;
757 std::vector<double> means;
758 std::vector<double> stdevs;
759
760 //calculate PCA eigenvectors (and eigenvalues), and write values to PCA bands
761 switch(type) {
762 case GDT_Float32: {
763 PCAResult<float> result;
764 if (largeRaster) {
765 result = calculatePCA<float>(bands, type, size, xBlockSize, yBlockSize, xBlocks, yBlocks, nComp);
766 writePCA<float>(bands, pcaBands, result, type, size, xBlockSize, yBlockSize, xBlocks, yBlocks);
767 }
768 else {
769 result = calculatePCA<float>(bands, type, size, width, height, nComp);
770 writePCA<float>(bands, pcaBands, result, type, size, height, width);
771 }
772
773 eigenvectors.resize(result.eigenvectors.size());
774 for (size_t i = 0; i < result.eigenvectors.size(); i++) {
775 eigenvectors[i].resize(result.eigenvectors[i].size());
776 for (size_t j = 0; j < result.eigenvectors[i].size(); j++) {
777 eigenvectors[i][j] = static_cast<double>(result.eigenvectors[i][j]);
778 }
779 }
780
781 eigenvalues.resize(result.eigenvalues.size());
782 for (size_t i = 0; i < result.eigenvalues.size(); i++) {
783 eigenvalues[i] = static_cast<double>(result.eigenvalues[i]);
784 }
785
786 means = result.means;
787 stdevs = result.stdevs;
788
789 break;
790 }
791 case GDT_Float64: {
792 PCAResult<double> result;
793 if (largeRaster) {
794 result = calculatePCA<double>(bands, type, size, xBlockSize, yBlockSize, xBlocks, yBlocks, nComp);
795 writePCA<double>(bands, pcaBands, result, type, size, xBlockSize, yBlockSize, xBlocks, yBlocks);
796 }
797 else {
798 result = calculatePCA<double>(bands, type, size, width, height, nComp);
799 writePCA<double>(bands, pcaBands, result, type, size, height, width);
800 }
801
802 eigenvectors = result.eigenvectors;
803 eigenvalues = result.eigenvalues;
804 means = result.means;
805 stdevs = result.stdevs;
806
807 break;
808 }
809 default:
810 throw std::runtime_error("should not be here! GDALDataType should be one of Float32/Float64!");
811 }
812
813 if (isVRTDataset) {
814 for (int c = 0; c < nComp; c++) {
815 GDALClose(VRTBandInfo[c].p_dataset);
816 helper::addBandToVRTDataset(p_dataset, pcaBands[c], VRTBandInfo[c]);
817 }
818 }
819
820 std::vector<void *> buffers(nComp);
821 if (isMEMDataset) {
822 for (int c = 0; c < nComp; c++) {
823 buffers[c] = pcaBands[c].p_buffer;
824 }
825 }
826
827 raster::GDALRasterWrapper *p_outrast = !isMEMDataset ?
828 new raster::GDALRasterWrapper(p_dataset) :
829 new raster::GDALRasterWrapper(p_dataset, buffers);
830
831 return {p_outrast, eigenvectors, eigenvalues, means, stdevs};
832}
833
834} //namespace pca
835} //namespace sgs
Definition raster.h:57
GDALDataset * getDataset()
Definition raster.h:429
int getWidth()
Definition raster.h:467
double * getGeotransform()
Definition raster.h:590
GDALDataType getRasterBandType(int band)
Definition raster.h:724
int getBandCount()
Definition raster.h:485
int getHeight()
Definition raster.h:476
GDALRasterBand * getRasterBand(int band)
Definition raster.h:696
void addBandToVRTDataset(GDALDataset *p_dataset, RasterBandMetaData &band, VRTBandDatasetInfo &info)
Definition helper.h:599
GDALDataset * createDataset(std::string filename, std::string driverName, int width, int height, double *geotransform, std::string projection, RasterBandMetaData *bands, size_t bandCount, bool useTiles, std::map< std::string, std::string > &driverOptions)
Definition helper.h:375
void addBandToMEMDataset(GDALDataset *p_dataset, RasterBandMetaData &band)
Definition helper.h:467
void createVRTBandDataset(GDALDataset *p_dataset, RasterBandMetaData &band, std::string tempFolder, std::string key, std::vector< VRTBandDatasetInfo > &VRTBandInfo, std::map< std::string, std::string > &driverOptions)
Definition helper.h:538
GDALDataset * createVirtualDataset(std::string driverName, int width, int height, double *geotransform, std::string projection)
Definition helper.h:294
void writePCA(std::vector< helper::RasterBandMetaData > &bands, std::vector< helper::RasterBandMetaData > &PCABands, PCAResult< T > &result, GDALDataType type, size_t size, int height, int width)
Definition pca.h:365
std::tuple< raster::GDALRasterWrapper *, std::vector< std::vector< double > >, std::vector< double >, std::vector< double >, std::vector< double > > pca(raster::GDALRasterWrapper *p_raster, int nComp, bool largeRaster, std::string tempFolder, std::string filename, std::map< std::string, std::string > driverOptions)
Definition pca.h:648
PCAResult< T > calculatePCA(std::vector< helper::RasterBandMetaData > &bands, GDALDataType type, size_t size, int width, int height, int nComp)
Definition pca.h:82
oneapi::dal::homogen_table DALHomogenTable
Definition pca.h:26
Definition pca.h:23
Definition pca.h:39
std::vector< double > stdevs
Definition pca.h:43
std::vector< std::vector< T > > eigenvectors
Definition pca.h:40
std::vector< double > means
Definition pca.h:42
std::vector< T > eigenvalues
Definition pca.h:41