GestureRecognitionToolkit  Version: 0.1.0
The Gesture Recognition Toolkit (GRT) is a cross-platform, open-source, c++ machine learning library for real-time gesture recognition.
UnlabelledData.cpp
1 /*
2 GRT MIT License
3 Copyright (c) <2012> <Nicholas Gillian, Media Lab, MIT>
4 
5 Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6 and associated documentation files (the "Software"), to deal in the Software without restriction,
7 including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
9 subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in all copies or substantial
12 portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15 LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
17 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 */
20 
21 #include "UnlabelledData.h"
22 
23 GRT_BEGIN_NAMESPACE
24 
25 UnlabelledData::UnlabelledData(const UINT numDimensions,const std::string datasetName,const std::string infoText):debugLog("[DEBUG ULCD]"),errorLog("[ERROR ULCD]"),warningLog("[WARNING ULCD]"){
26  this->datasetName = datasetName;
27  this->numDimensions = numDimensions;
28  this->infoText = infoText;
29  totalNumSamples = 0;
30  crossValidationSetup = false;
31  useExternalRanges = false;
32  if( numDimensions > 0 ) setNumDimensions( numDimensions );
33 }
34 
35 UnlabelledData::UnlabelledData(const UnlabelledData &rhs):debugLog("[DEBUG ULCD]"),errorLog("[ERROR ULCD]"),warningLog("[WARNING ULCD]"){
36  *this = rhs;
37 }
38 
40 
42  if( this != &rhs){
43  this->datasetName = rhs.datasetName;
44  this->infoText = rhs.infoText;
45  this->numDimensions = rhs.numDimensions;
46  this->totalNumSamples = rhs.totalNumSamples;
47  this->kFoldValue = rhs.kFoldValue;
48  this->crossValidationSetup = rhs.crossValidationSetup;
49  this->useExternalRanges = rhs.useExternalRanges;
50  this->externalRanges = rhs.externalRanges;
51  this->data = rhs.data;
52  this->crossValidationIndexs = rhs.crossValidationIndexs;
53  this->debugLog = rhs.debugLog;
54  this->errorLog = rhs.errorLog;
55  this->warningLog = rhs.warningLog;
56  }
57  return *this;
58 }
59 
61  totalNumSamples = 0;
62  data.clear();
63  crossValidationSetup = false;
64  crossValidationIndexs.clear();
65 }
66 
67 bool UnlabelledData::setNumDimensions(const UINT numDimensions){
68 
69  if( numDimensions > 0 ){
70  //Clear any previous training data
71  clear();
72 
73  //Set the dimensionality of the data
74  this->numDimensions = numDimensions;
75 
76  //Clear the external ranges
77  useExternalRanges = false;
78  externalRanges.clear();
79 
80  return true;
81  }
82  return false;
83 }
84 
85 bool UnlabelledData::setDatasetName(const std::string datasetName){
86 
87  //Make sure there are no spaces in the std::string
88  if( datasetName.find(" ") == std::string::npos ){
89  this->datasetName = datasetName;
90  return true;
91  }
92 
93  return false;
94 }
95 
96 bool UnlabelledData::setInfoText(const std::string infoText){
97  this->infoText = infoText;
98  return true;
99 }
100 
102 
103  if( sample.size() != numDimensions ) return false;
104 
105  //The dataset has changed so flag that any previous cross validation setup will now not work
106  crossValidationSetup = false;
107  crossValidationIndexs.clear();
108 
109  data.push_back( sample );
110  totalNumSamples++;
111 
112  return true;
113 }
114 
116 
117  if( totalNumSamples > 0 ){
118 
119  //The dataset has changed so flag that any previous cross validation setup will now not work
120  crossValidationSetup = false;
121  crossValidationIndexs.clear();
122 
123  //If there is only one sample then we just need to clear the buffer
124  if( totalNumSamples == 1 ){
125  data.clear();
126  return true;
127  }
128 
129  data.erase( data.begin()+data.size()-1 );
130 
131  return true;
132 
133  }else return false;
134 
135 }
136 
137 bool UnlabelledData::reserve(const UINT N){
138 
139  data.reserve( N );
140 
141  if( data.capacity() >= N ) return true;
142 
143  return false;
144 }
145 
146 bool UnlabelledData::setExternalRanges(const Vector< MinMax > &externalRanges,const bool useExternalRanges){
147 
148  if( externalRanges.size() != numDimensions ) return false;
149 
150  this->externalRanges = externalRanges;
151  this->useExternalRanges = useExternalRanges;
152 
153  return true;
154 }
155 
156 bool UnlabelledData::enableExternalRangeScaling(const bool useExternalRanges){
157  if( externalRanges.size() == numDimensions ){
158  this->useExternalRanges = useExternalRanges;
159  return true;
160  }
161  return false;
162 }
163 
164 bool UnlabelledData::scale(const Float minTarget,const Float maxTarget){
165  Vector< MinMax > ranges = getRanges();
166  return scale(ranges,minTarget,maxTarget);
167 }
168 
169 bool UnlabelledData::scale(const Vector<MinMax> &ranges,const Float minTarget,const Float maxTarget){
170  if( ranges.size() != numDimensions ) return false;
171 
172  //Scale the training data
173  for(UINT i=0; i<totalNumSamples; i++){
174  for(UINT j=0; j<numDimensions; j++){
175  data[i][j] = Util::scale(data[i][j],ranges[j].minValue,ranges[j].maxValue,minTarget,maxTarget);
176  }
177  }
178 
179  return true;
180 }
181 
182 bool UnlabelledData::save(const std::string &filename) const{
183 
184  //Check if the file should be saved as a csv file
185  if( Util::stringEndsWith( filename, ".csv" ) ){
186  return saveDatasetToCSVFile( filename );
187  }
188 
189  //Otherwise save it as a custom GRT file
190  return saveDatasetToFile( filename );
191 }
192 
193 bool UnlabelledData::load(const std::string &filename){
194 
195  //Check if the file should be loaded as a csv file
196  if( Util::stringEndsWith( filename, ".csv" ) ){
197  return loadDatasetFromCSVFile( filename );
198  }
199 
200  //Otherwise save it as a custom GRT file
201  return loadDatasetFromFile( filename );
202 }
203 
204 bool UnlabelledData::saveDatasetToFile(const std::string &filename) const{
205 
206  std::fstream file;
207  file.open(filename.c_str(), std::ios::out);
208 
209  if( !file.is_open() ){
210  errorLog << "saveDatasetToFile(const std::string &filename) - Failed to open file!" << std::endl;
211  return false;
212  }
213 
214  file << "GRT_UNLABELLED_DATA_FILE_V1.0\n";
215  file << "DatasetName: " << datasetName << std::endl;
216  file << "InfoText: " << infoText << std::endl;
217  file << "NumDimensions: " << numDimensions << std::endl;
218  file << "TotalNumTrainingExamples: " << totalNumSamples << std::endl;
219 
220  file << "UseExternalRanges: " << useExternalRanges << std::endl;
221 
222  if( useExternalRanges ){
223  for(UINT i=0; i<externalRanges.size(); i++){
224  file << externalRanges[i].minValue << "\t" << externalRanges[i].maxValue << std::endl;
225  }
226  }
227 
228  file << "UnlabelledTrainingData:\n";
229 
230  for(UINT i=0; i<totalNumSamples; i++){
231  for(UINT j=0; j<numDimensions; j++){
232  if( j != 0 ) file << "\t";
233  file << data[i][j];
234  }
235  file << std::endl;
236  }
237 
238  file.close();
239  return true;
240 }
241 
242 bool UnlabelledData::loadDatasetFromFile(const std::string &filename){
243 
244  std::fstream file;
245  file.open(filename.c_str(), std::ios::in);
246  clear();
247 
248  if( !file.is_open() ){
249  errorLog << "loadDatasetFromFile(const std::string &filename) - could not open file!" << std::endl;
250  return false;
251  }
252 
253  std::string word;
254 
255  //Check to make sure this is a file with the Training File Format
256  file >> word;
257  if( word != "GRT_UNLABELLED_DATA_FILE_V1.0" && word != "GRT_UNLABELLED_CLASSIFICATION_DATA_FILE_V1.0" ){
258  errorLog << "loadDatasetFromFile(const std::string &filename) - could not find file header!" << std::endl;
259  file.close();
260  return false;
261  }
262 
263  //Get the name of the dataset
264  file >> word;
265  if(word != "DatasetName:"){
266  errorLog << "loadDatasetFromFile(const std::string &filename) - failed to find DatasetName!" << std::endl;
267  file.close();
268  return false;
269  }
270  file >> datasetName;
271 
272  file >> word;
273  if(word != "InfoText:"){
274  errorLog << "loadDatasetFromFile(const std::string &filename) - failed to find InfoText!" << std::endl;
275  file.close();
276  return false;
277  }
278 
279  //Load the info text
280  file >> word;
281  infoText = "";
282  while( word != "NumDimensions:" ){
283  infoText += word + " ";
284  file >> word;
285  }
286 
287  //Get the number of dimensions in the training data
288  if(word != "NumDimensions:"){
289  errorLog << "loadDatasetFromFile(const std::string &filename) - failed to find DatasetName!" << std::endl;
290  file.close();
291  return false;
292  }
293  file >> numDimensions;
294 
295  //Get the total number of training examples in the training data
296  file >> word;
297  if(word != "TotalNumTrainingExamples:"){
298  errorLog << "loadDatasetFromFile(const std::string &filename) - failed to find DatasetName!" << std::endl;
299  file.close();
300  return false;
301  }
302  file >> totalNumSamples;
303 
304  //Check if the dataset should be scaled using external ranges
305  file >> word;
306  if(word != "UseExternalRanges:"){
307  errorLog << "loadDatasetFromFile(const std::string &filename) - failed to find DatasetName!" << std::endl;
308  file.close();
309  return false;
310  }
311  file >> useExternalRanges;
312 
313  //If we are using external ranges then load them
314  if( useExternalRanges ){
315  externalRanges.resize(numDimensions);
316  for(UINT i=0; i<externalRanges.size(); i++){
317  file >> externalRanges[i].minValue;
318  file >> externalRanges[i].maxValue;
319  }
320  }
321 
322  //Get the main training data
323  file >> word;
324  if(word != "UnlabelledTrainingData:"){
325  errorLog << "loadDatasetFromFile(const std::string &filename) - failed to find DatasetName!" << std::endl;
326  file.close();
327  return false;
328  }
329  data.resize( totalNumSamples, VectorFloat(numDimensions) );
330 
331  for(UINT i=0; i<totalNumSamples; i++){
332  for(UINT j=0; j<numDimensions; j++){
333  file >> data[i][j];
334  }
335  }
336 
337  file.close();
338  return true;
339 }
340 
341 
342 bool UnlabelledData::saveDatasetToCSVFile(const std::string &filename) const{
343 
344  std::fstream file;
345  file.open(filename.c_str(), std::ios::out );
346 
347  if( !file.is_open() ){
348  errorLog << "saveDatasetToCSVFile(const std::string &filename) - Failed to open file!" << std::endl;
349  return false;
350  }
351 
352  //Write the data to the CSV file
353  for(UINT i=0; i<totalNumSamples; i++){
354  for(UINT j=0; j<numDimensions; j++){
355  if( j != 0 ) file << ",";
356  file << data[i][j];
357  }
358  file << std::endl;
359  }
360 
361  file.close();
362 
363  return true;
364 }
365 
366 bool UnlabelledData::loadDatasetFromCSVFile(const std::string &filename){
367 
368  std::string value;
369  datasetName = "NOT_SET";
370  infoText = "";
371 
372  //Clear any previous data
373  clear();
374 
375  //Parse the CSV file
376  FileParser parser;
377 
378  if( !parser.parseCSVFile(filename,true) ){
379  errorLog << "loadDatasetFromCSVFile(const std::string &filename) - Failed to parse CSV file!" << std::endl;
380  return false;
381  }
382 
383  if( !parser.getConsistentColumnSize() ){
384  errorLog << "loadDatasetFromCSVFile(const std::string &filename) - The CSV file does not have a consistent number of columns!" << std::endl;
385  return false;
386  }
387 
388  const UINT rows = parser.getRowSize();
389  const UINT cols = parser.getColumnSize();
390 
391  //Setup the labelled classification data
392  numDimensions = cols;
393 
394  //Reserve the data so we do not have to continually resize the memory
395  data.reserve( rows );
396 
397  VectorFloat sample(numDimensions);
398  for(UINT i=0; i<rows; i++){
399 
400  //Get the input vector
401  for(UINT j=0; j<numDimensions; j++){
402  sample[j] = Util::stringToFloat( parser[i][j] );
403  }
404 
405  //Add the labelled sample to the dataset
406  if( !addSample(sample) ){
407  warningLog << "loadDatasetFromCSVFile(const std::string &filename) - Could not add sample " << i << " to the dataset!" << std::endl;
408  }
409  }
410 
411  return true;
412 }
413 
414 UnlabelledData UnlabelledData::partition(const UINT trainingSizePercentage){
415 
416  //Partitions the dataset into a training dataset (which is kept by this instance of the UnlabelledData) and
417  //a testing/validation dataset (which is return as a new instance of the UnlabelledData). The trainingSizePercentage
418  //therefore sets the size of the data which remains in this instance and the remaining percentage of data is then added to
419  //the testing/validation dataset
420 
421  //The dataset has changed so flag that any previous cross validation setup will now not work
422  crossValidationSetup = false;
423  crossValidationIndexs.clear();
424 
425  const UINT numTrainingExamples = (UINT) floor( Float(totalNumSamples) / 100.0 * Float(trainingSizePercentage) );
426 
427  UnlabelledData trainingSet(numDimensions);
428  UnlabelledData testSet(numDimensions);
429  Vector< UINT > indexs( totalNumSamples );
430 
431  //Create the random partion indexs
432  Random random;
433  UINT randomIndex = 0;
434  for(UINT i=0; i<totalNumSamples; i++) indexs[i] = i;
435  for(UINT x=0; x<totalNumSamples; x++){
436  //Pick a random index
437  randomIndex = random.getRandomNumberInt(0,totalNumSamples);
438 
439  //Swap the indexs
440  SWAP( indexs[ x ] , indexs[ randomIndex ] );
441  }
442 
443  trainingSet.reserve( numTrainingExamples );
444  testSet.reserve( totalNumSamples-numTrainingExamples );
445 
446  //Add the data to the training and test sets
447  for(UINT i=0; i<numTrainingExamples; i++){
448  trainingSet.addSample( data[ indexs[i] ] );
449  }
450  for(UINT i=numTrainingExamples; i<totalNumSamples; i++){
451  testSet.addSample( data[ indexs[i] ] );
452  }
453 
454  //Overwrite the training data in this instance with the training data of the trainingSet
455  *this = trainingSet;
456 
457  return testSet;
458 }
459 
460 bool UnlabelledData::merge(const UnlabelledData &unlabelledData){
461 
462  if( unlabelledData.getNumDimensions() != numDimensions ){
463  errorLog << "merge(const UnlabelledData &unlabelledData) - The number of dimensions in the unlabelledData (" << unlabelledData.getNumDimensions() << ") does not match the number of dimensions of this dataset (" << numDimensions << ")" << std::endl;
464  return false;
465  }
466 
467  //The dataset has changed so flag that any previous cross validation setup will now not work
468  crossValidationSetup = false;
469  crossValidationIndexs.clear();
470 
471  reserve( getNumSamples() + unlabelledData.getNumSamples() );
472 
473  //Add the data from the labelledData to this instance
474  for(UINT i=0; i<unlabelledData.getNumSamples(); i++){
475  addSample( unlabelledData[i] );
476  }
477 
478  return true;
479 }
480 
482 
483  crossValidationSetup = false;
484  crossValidationIndexs.clear();
485 
486  //K can not be zero
487  if( K > totalNumSamples ){
488  errorLog << "spiltDataIntoKFolds(const UINT K) - K can not be zero!" << std::endl;
489  return false;
490  }
491 
492  //K can not be larger than the number of examples
493  if( K > totalNumSamples ){
494  errorLog << "spiltDataIntoKFolds(const UINT K) - K can not be larger than the total number of samples in the dataset!" << std::endl;
495  return false;
496  }
497 
498  //Setup the dataset for k-fold cross validation
499  kFoldValue = K;
500  Vector< UINT > indexs( totalNumSamples );
501 
502  //Work out how many samples are in each fold, the last fold might have more samples than the others
503  UINT numSamplesPerFold = (UINT) floor( totalNumSamples/Float(K) );
504 
505  //Add the random indexs to each fold
506  crossValidationIndexs.resize(K);
507 
508  //Create the random partion indexs
509  Random random;
510  UINT randomIndex = 0;
511 
512  //Randomize the order of the data
513  for(UINT i=0; i<totalNumSamples; i++) indexs[i] = i;
514  for(UINT x=0; x<totalNumSamples; x++){
515  //Pick a random index
516  randomIndex = random.getRandomNumberInt(0,totalNumSamples);
517 
518  //Swap the indexs
519  grt_swap( indexs[ x ] , indexs[ randomIndex ] );
520  }
521 
522  UINT counter = 0;
523  UINT foldIndex = 0;
524  for(UINT i=0; i<totalNumSamples; i++){
525  //Add the index to the current fold
526  crossValidationIndexs[ foldIndex ].push_back( indexs[i] );
527 
528  //Move to the next fold if ready
529  if( ++counter == numSamplesPerFold && foldIndex < K-1 ){
530  foldIndex++;
531  counter = 0;
532  }
533  }
534 
535  crossValidationSetup = true;
536  return true;
537 
538 }
539 
541  UnlabelledData trainingData;
542 
543  if( !crossValidationSetup ){
544  errorLog << "getTrainingFoldData(const UINT foldIndex) - Cross Validation has not been setup! You need to call the spiltDataIntoKFolds(UINT K) function first before calling this function!" << std::endl;
545  return trainingData;
546  }
547 
548  if( foldIndex >= kFoldValue ) return trainingData;
549 
550  trainingData.setNumDimensions( numDimensions );
551 
552  //Work out how many samples will be in this fold
553  UINT numSamples = 0;
554  for(UINT k=0; k<kFoldValue; k++){
555  if( k != foldIndex ){
556  numSamples += (UINT)crossValidationIndexs[k].size();
557  }
558  }
559  trainingData.reserve( numSamples );
560 
561  //Add the data to the training set, this will consist of all the data that is NOT in the foldIndex
562  UINT index = 0;
563  for(UINT k=0; k<kFoldValue; k++){
564  if( k != foldIndex ){
565  for(UINT i=0; i<crossValidationIndexs[k].size(); i++){
566 
567  index = crossValidationIndexs[k][i];
568  trainingData.addSample( data[ index ] );
569  }
570  }
571  }
572 
573  return trainingData;
574 }
575 
577  UnlabelledData testData;
578 
579  if( !crossValidationSetup ) return testData;
580 
581  if( foldIndex >= kFoldValue ) return testData;
582 
583  //Add the data to the training
584  testData.setNumDimensions( numDimensions );
585 
586  //Work out how many samples will be in this fold
587  UINT numSamples = (UINT)crossValidationIndexs[ foldIndex ].size();
588 
589  testData.reserve( numSamples );
590 
591  UINT index = 0;
592  for(UINT i=0; i<crossValidationIndexs[ foldIndex ].size(); i++){
593 
594  index = crossValidationIndexs[ foldIndex ][i];
595  testData.addSample( data[ index ] );
596  }
597 
598  return testData;
599 }
600 
602  std::string statsText;
603  statsText += "DatasetName:\t" + datasetName + "\n";
604  statsText += "DatasetInfo:\t" + infoText + "\n";
605  statsText += "Number of Dimensions:\t" + Util::toString( numDimensions ) + "\n";
606  statsText += "Number of Samples:\t" + Util::toString( totalNumSamples ) + "\n";
607 
608  Vector< MinMax > ranges = getRanges();
609 
610  statsText += "Dataset Ranges:\n";
611  for(UINT j=0; j<ranges.size(); j++){
612  statsText += "[" + Util::toString( j+1 ) + "] Min:\t" + Util::toString( ranges[j].minValue ) + "\tMax: " + Util::toString( ranges[j].maxValue ) + "\n";
613  }
614 
615  return statsText;
616 }
617 
619 
620  //If the dataset should be scaled using the external ranges then return the external ranges
621  if( useExternalRanges ) return externalRanges;
622 
623  Vector< MinMax > ranges(numDimensions);
624 
625  //Otherwise return the min and max values for each column in the dataset
626  if( totalNumSamples > 0 ){
627  for(UINT j=0; j<numDimensions; j++){
628  ranges[j].minValue = data[0][0];
629  ranges[j].maxValue = data[0][0];
630  for(UINT i=0; i<totalNumSamples; i++){
631  if( data[i][j] < ranges[j].minValue ){ ranges[j].minValue = data[i][j]; } //Search for the min value
632  else if( data[i][j] > ranges[j].maxValue ){ ranges[j].maxValue = data[i][j]; } //Search for the max value
633  }
634  }
635  }
636  return ranges;
637 }
638 
640  return data;
641 }
642 
644  const UINT rows = getNumSamples();
645  const UINT cols = getNumDimensions();
646  MatrixDouble d(rows,cols);
647 
648  for(UINT i=0; i<rows; i++){
649  for(UINT j=0; j<cols; j++){
650  d[i][j] = data[i][j];
651  }
652  }
653 
654  return d;
655 }
656 
658  const UINT rows = getNumSamples();
659  const UINT cols = getNumDimensions();
660  MatrixFloat d(rows,cols);
661 
662  for(UINT i=0; i<rows; i++){
663  for(UINT j=0; j<cols; j++){
664  d[i][j] = data[i][j];
665  }
666  }
667 
668  return d;
669 }
670 
671 GRT_END_NAMESPACE
672 
bool loadDatasetFromCSVFile(const std::string &filename)
static std::string toString(const int &i)
Definition: Util.cpp:73
bool scale(const Float minTarget, const Float maxTarget)
bool addSample(const VectorFloat &sample)
static Float scale(const Float &x, const Float &minSource, const Float &maxSource, const Float &minTarget, const Float &maxTarget, const bool constrain=false)
Definition: Util.cpp:52
static Float stringToFloat(const std::string &s)
Definition: Util.cpp:132
Definition: Random.h:40
bool load(const std::string &filename)
MatrixFloat getDataAsMatrixFloat() const
UINT getNumDimensions() const
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
bool setExternalRanges(const Vector< MinMax > &externalRanges, const bool useExternalRanges=false)
UINT getNumSamples() const
UnlabelledData & operator=(const UnlabelledData &rhs)
bool reserve(const UINT N)
Vector< VectorFloat > getData() const
The UnlabelledData class is the main data container for supporting unsupervised learning.
UnlabelledData getTestFoldData(const UINT foldIndex) const
UnlabelledData(const UINT numDimensions=0, const std::string datasetName="NOT_SET", const std::string infoText="")
bool save(const std::string &filename) const
bool saveDatasetToFile(const std::string &filename) const
bool setNumDimensions(const UINT numDimensions)
bool enableExternalRangeScaling(const bool useExternalRanges)
UnlabelledData partition(const UINT partitionPercentage)
bool setInfoText(const std::string infoText)
bool saveDatasetToCSVFile(const std::string &filename) const
Vector< MinMax > getRanges() const
int getRandomNumberInt(int minRange, int maxRange)
Definition: Random.h:88
static bool stringEndsWith(const std::string &str, const std::string &ending)
Definition: Util.cpp:156
UnlabelledData getTrainingFoldData(const UINT foldIndex) const
MatrixDouble getDataAsMatrixDouble() const
bool merge(const UnlabelledData &unlabelledData)
bool setDatasetName(const std::string datasetName)
bool spiltDataIntoKFolds(const UINT K)
bool loadDatasetFromFile(const std::string &filename)
std::string getStatsAsString() const