otbPatchesSelection.cxx 19.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*=========================================================================

  Copyright (c) Remi Cresson (IRSTEA). All rights reserved.


     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#include "itkFixedArray.h"
#include "itkObjectFactory.h"
#include "otbWrapperApplicationFactory.h"

// Application engine
#include "otbStandardFilterWatcher.h"
#include "itkFixedArray.h"

// Image
#include "itkImageRegionConstIterator.h"
#include "itkUnaryFunctorImageFilter.h"
#include "itkFlatStructuringElement.h"
#include "itkBinaryErodeImageFilter.h"
#include "otbStreamingResampleImageFilter.h"
25
#include "itkMaskImageFilter.h"
26
27
28

// image utils
#include "otbTensorflowCommon.h"
29
#include "otbTensorflowSamplingUtils.h"
30
31
32
33
34
35
36
37
38
39
40
41

// Functor to retrieve nodata
template<class TPixel, class OutputPixel>
class IsNoData
{
public:
  IsNoData(){}
  ~IsNoData(){}

  inline OutputPixel operator()( const TPixel & A ) const
  {
    for (unsigned int band = 0 ; band < A.Size() ; band++)
42
    {
43
44
      if (A[band] != m_NoDataValue)
        return 1;
45
    }
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    return 0;
  }

  void SetNoDataValue(typename TPixel::ValueType value)
  {
    m_NoDataValue = value;
  }

private:
  typename TPixel::ValueType m_NoDataValue;
};

namespace otb
{

namespace Wrapper
{

class PatchesSelection : public Application
{
public:
  /** Standard class typedefs. */
  typedef PatchesSelection          Self;
  typedef Application                         Superclass;
  typedef itk::SmartPointer<Self>             Pointer;
  typedef itk::SmartPointer<const Self>       ConstPointer;

  /** Standard macro */
  itkNewMacro(Self);
  itkTypeMacro(PatchesSelection, Application);

  /** Vector data typedefs */
  typedef VectorDataType::DataTreeType                 DataTreeType;
  typedef itk::PreOrderTreeIterator<DataTreeType>      TreeIteratorType;
  typedef VectorDataType::DataNodeType                 DataNodeType;
  typedef DataNodeType::Pointer                        DataNodePointer;
82
  typedef DataNodeType::PointType                      DataNodePointType;
83
84
85
86
87
88
89
90
91
92
93
94

  /** typedefs */
  typedef IsNoData<FloatVectorImageType::PixelType, UInt8ImageType::PixelType > IsNoDataFunctorType;
  typedef itk::UnaryFunctorImageFilter<FloatVectorImageType, UInt8ImageType, IsNoDataFunctorType> IsNoDataFilterType;

  typedef itk::FlatStructuringElement<2>                                         StructuringType;
  typedef StructuringType::RadiusType                                            RadiusType;

  typedef itk::BinaryErodeImageFilter<UInt8ImageType, UInt8ImageType, StructuringType> MorphoFilterType;

  typedef otb::StreamingResampleImageFilter<UInt8ImageType,UInt8ImageType> PadFilterType;

95
96
  typedef tf::Distribution<UInt8ImageType> DistributionType;

97
98
  typedef itk::MaskImageFilter<UInt8ImageType, UInt8ImageType, UInt8ImageType> MaskImageFilterType;

99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  void DoUpdateParameters()
  {
  }


  void DoInit()
  {

    // Documentation
    SetName("PatchesSelection");
    SetDescription("This application generate points sampled at regular interval over "
        "the input image region. The grid size and spacing can be configured.");
    SetDocLongDescription("This application produces a vector data containing "
        "a set of points centered on the patches lying in the valid regions of the input image. ");

    SetDocAuthors("Remi Cresson");

    // Input image
    AddParameter(ParameterType_InputImage, "in", "input image");
118
119
    AddParameter(ParameterType_InputImage, "mask", "input mask");
    MandatoryOff("mask");
120
121
122
123
124
125
126
127
128

    // Input no-data value
    AddParameter(ParameterType_Float, "nodata", "nodata value");
    MandatoryOn                      ("nodata");
    SetDefaultParameterFloat         ("nodata", 0);

    // Grid
    AddParameter(ParameterType_Group, "grid", "grid settings");
    AddParameter(ParameterType_Int, "grid.step", "step between patches");
Cresson Remi's avatar
Cresson Remi committed
129
    SetMinimumParameterIntValue    ("grid.step", 1);
130
    AddParameter(ParameterType_Int, "grid.psize", "patches size");
Cresson Remi's avatar
Cresson Remi committed
131
    SetMinimumParameterIntValue    ("grid.psize", 1);
132
133
134
135

    // Strategy
    AddParameter(ParameterType_Choice, "strategy", "Selection strategy for validation/training patches");
    AddChoice("strategy.chessboard", "fifty fifty, like a chess board");
136
137
138
139
140
141
142
143
144
145
146
    AddChoice("strategy.balanced", "you can chose the degree of spatial randomness vs class balance");
    AddParameter(ParameterType_Float, "strategy.balanced.sp", "Spatial proportion: between 0 and 1, "
        "indicating the amount of randomly sampled data in space");
    SetMinimumParameterFloatValue    ("strategy.balanced.sp", 0);
    SetMaximumParameterFloatValue    ("strategy.balanced.sp", 1);
    SetDefaultParameterFloat         ("strategy.balanced.sp", 0.25);
    AddParameter(ParameterType_Int,   "strategy.balanced.nclasses", "Number of classes");
    SetMinimumParameterIntValue      ("strategy.balanced.nclasses", 2);
    MandatoryOn                      ("strategy.balanced.nclasses");
    AddParameter(ParameterType_InputImage, "strategy.balanced.labelimage", "input label image");
    MandatoryOn                           ("strategy.balanced.labelimage");
147
148

    // Output points
Cresson Remi's avatar
Cresson Remi committed
149
150
    AddParameter(ParameterType_OutputVectorData, "outtrain", "output set of points (training)");
    AddParameter(ParameterType_OutputVectorData, "outvalid", "output set of points (validation)");
151
152
153
154
155

    AddRAMParameter();

  }

156
  class SampleBundle
157
  {
158
159
160
161
162
  public:
    SampleBundle(){}
    SampleBundle(unsigned int nClasses){
      dist = DistributionType(nClasses);
      id = 0;
163
      (void) point;
164
      black = true;
165
      (void) index;
166
167
    }
    ~SampleBundle(){}
168

169
170
171
172
173
    SampleBundle(const SampleBundle & other){
      dist = other.GetDistribution();
      id = other.GetSampleID();
      point = other.GetPosition();
      black = other.GetBlack();
174
      index = other.GetIndex();
175
    }
176

177
178
179
180
    DistributionType GetDistribution() const
    {
      return dist;
    }
181

182
183
184
185
    DistributionType& GetModifiableDistribution()
    {
      return dist;
    }
Cresson Remi's avatar
Cresson Remi committed
186

187
188
189
190
    unsigned int GetSampleID() const
    {
      return id;
    }
Cresson Remi's avatar
Cresson Remi committed
191

192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
    unsigned int& GetModifiableSampleID()
    {
      return id;
    }

    DataNodePointType GetPosition() const
    {
      return point;
    }

    DataNodePointType& GetModifiablePosition()
    {
      return point;
    }

    bool& GetModifiableBlack()
    {
      return black;
    }

    bool GetBlack() const
    {
      return black;
    }

217
218
219
220
221
222
223
224
225
226
    UInt8ImageType::IndexType& GetModifiableIndex()
    {
      return index;
    }

    UInt8ImageType::IndexType GetIndex() const
    {
      return index;
    }

227
228
229
230
231
232
  private:

    DistributionType dist;
    unsigned int id;
    DataNodePointType point;
    bool black;
233
    UInt8ImageType::IndexType index;
234
235
236
237
238
239
240
241
  };

  /*
   * Apply the given function at each sampling location
   */
  template<typename TLambda>
  void Apply(TLambda lambda)
  {
242
243
244
245
246
247
248
249

    // Explicit streaming over the morphed mask, based on the RAM parameter
    typedef otb::RAMDrivenStrippedStreamingManager<UInt8ImageType> StreamingManagerType;
    StreamingManagerType::Pointer m_StreamingManager = StreamingManagerType::New();
    m_StreamingManager->SetAvailableRAMInMB(GetParameterInt("ram"));

    UInt8ImageType::Pointer inputImage = m_MorphoFilter->GetOutput();
    UInt8ImageType::RegionType entireRegion = inputImage->GetLargestPossibleRegion();
250
    entireRegion.ShrinkByRadius(m_Radius);
251
    m_StreamingManager->PrepareStreaming(inputImage, entireRegion );
Cresson Remi's avatar
Cresson Remi committed
252
    UInt8ImageType::IndexType start;
253
254
    start[0] = m_Radius[0] + 1;
    start[1] = m_Radius[1] + 1;
255
256

    int m_NumberOfDivisions = m_StreamingManager->GetNumberOfSplits();
Cresson Remi's avatar
Cresson Remi committed
257
258
259
    UInt8ImageType::IndexType pos;
    UInt8ImageType::IndexValueType step = GetParameterInt("grid.step");
    pos.Fill(0);
260
261
    for (int m_CurrentDivision = 0; m_CurrentDivision < m_NumberOfDivisions; m_CurrentDivision++)
    {
262
      otbAppLogINFO("Processing split " << (m_CurrentDivision + 1) << "/" << m_NumberOfDivisions);
Cresson Remi's avatar
Cresson Remi committed
263

264
265
266
      UInt8ImageType::RegionType streamRegion = m_StreamingManager->GetSplit(m_CurrentDivision);
      tf::PropagateRequestedRegion<UInt8ImageType>(inputImage, streamRegion);
      itk::ImageRegionConstIterator<UInt8ImageType> inIt (inputImage, streamRegion);
Cresson Remi's avatar
Cresson Remi committed
267

268
269
270
      for (inIt.GoToBegin(); !inIt.IsAtEnd(); ++inIt)
      {
        UInt8ImageType::IndexType idx = inIt.GetIndex();
Cresson Remi's avatar
Cresson Remi committed
271
272
273
274
        idx[0] -= start[0];
        idx[1] -= start[1];

        if (idx[0] % step == 0 && idx[1] % step == 0)
275
        {
Cresson Remi's avatar
Cresson Remi committed
276
277
278
279
280
281
282
283
284
285
          UInt8ImageType::InternalPixelType pixVal = inIt.Get();

          if (pixVal == 1)
          {
            // Update grid position
            pos[0] = idx[0] / step;
            pos[1] = idx[1] / step;

            // Compute coordinates
            UInt8ImageType::PointType geo;
286
            m_MorphoFilter->GetOutput()->TransformIndexToPhysicalPoint(inIt.GetIndex(), geo);
Cresson Remi's avatar
Cresson Remi committed
287
288
289
290
            DataNodeType::PointType point;
            point[0] = geo[0];
            point[1] = geo[1];

291
292
            // Lambda call
            lambda(pos, geo);
Cresson Remi's avatar
Cresson Remi committed
293
          }
294
295
296
297
        }
      }

    }
298
  }
Cresson Remi's avatar
Cresson Remi committed
299

300
301
302
303
  /*
   * Allocate a std::vector of sample bundle
   */
  std::vector<SampleBundle>
304
  AllocateSamples(unsigned int nbOfClasses = 2)
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
  {
    // Nb of samples (maximum)
    const UInt8ImageType::RegionType entireRegion = m_MorphoFilter->GetOutput()->GetLargestPossibleRegion();
    const unsigned int maxNbOfCols = std::ceil(entireRegion.GetSize(0)/GetParameterInt("grid.step")) + 1;
    const unsigned int maxNbOfRows = std::ceil(entireRegion.GetSize(1)/GetParameterInt("grid.step")) + 1;
    unsigned int maxNbOfSamples = 1;
    maxNbOfSamples *= maxNbOfCols;
    maxNbOfSamples *= maxNbOfRows;

    // Nb of classes
    SampleBundle initSB(nbOfClasses);
    std::vector<SampleBundle> bundles(maxNbOfSamples, initSB);

    return bundles;
  }
Cresson Remi's avatar
Cresson Remi committed
320

Cresson Remi's avatar
Cresson Remi committed
321
322
323
324
325
326
327
328
329
330
331
332
333
334
  void SetBlackOrWhiteBundle(SampleBundle & bundle, unsigned int & count,
      const UInt8ImageType::IndexType & pos, const UInt8ImageType::PointType & geo)
  {
    // Black or white
    bool black = ((pos[0] + pos[1]) % 2 == 0);

    bundle.GetModifiableSampleID() = count;
    bundle.GetModifiablePosition() = geo;
    bundle.GetModifiableBlack() = black;
    bundle.GetModifiableIndex() = pos;
    count++;

  }

335
336
337
338
339
340
341
342
343
  /*
   * Samples are placed at regular intervals
   */
  void SampleChessboard()
  {

    std::vector<SampleBundle> bundles = AllocateSamples();

    unsigned int count = 0;
Cresson Remi's avatar
Cresson Remi committed
344
    auto lambda = [this, &count, &bundles]
345
                   (const UInt8ImageType::IndexType & pos, const UInt8ImageType::PointType & geo) {
Cresson Remi's avatar
Cresson Remi committed
346
      SetBlackOrWhiteBundle(bundles[count], count, pos, geo);
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
    };

    Apply(lambda);
    bundles.resize(count);

    // Export training/validation samples
    PopulateVectorData(bundles);
  }

  void SampleBalanced()
  {

    // 1. Compute distribution of all samples

    otbAppLogINFO("Computing samples distribution...");

363
    std::vector<SampleBundle> bundles = AllocateSamples(GetParameterInt("strategy.balanced.nclasses"));
364
365
366
367
368
369
370
371
372
373
374
375

    // Patch size
    UInt8ImageType::SizeType patchSize;
    patchSize.Fill(GetParameterInt("grid.psize"));
    unsigned int count = 0;
    auto lambda = [this, &bundles, &patchSize, &count]
                   (const UInt8ImageType::IndexType & pos, const UInt8ImageType::PointType & geo) {

      // Update this sample distribution
      if (tf::UpdateDistributionFromPatch<UInt8ImageType>(GetParameterUInt8Image("strategy.balanced.labelimage"),
          geo, patchSize, bundles[count].GetModifiableDistribution()))
      {
Cresson Remi's avatar
Cresson Remi committed
376
        SetBlackOrWhiteBundle(bundles[count], count, pos, geo);
377
378
379
380
381
      }
    };

    Apply(lambda);
    bundles.resize(count);
Cresson Remi's avatar
Cresson Remi committed
382

383
    otbAppLogINFO("Total number of candidates: " << count );
Cresson Remi's avatar
Cresson Remi committed
384

385
386
    // 2. Seed = spatially random samples

387
388
    otbAppLogINFO("Spatial sampling proportion " << GetParameterFloat("strategy.balanced.sp"));

389
    const int samplingStep = static_cast<int>(1.0 / std::sqrt(GetParameterFloat("strategy.balanced.sp")));
390
391
392

    otbAppLogINFO("Spatial sampling step " << samplingStep);

393
    float step = 0;
Cresson Remi's avatar
Cresson Remi committed
394
395
    std::vector<SampleBundle> seed(count);
    std::vector<SampleBundle> candidates(count);
396

Cresson Remi's avatar
Cresson Remi committed
397
398
    unsigned int seedCount = 0;
    unsigned int candidatesCount = 0;
399
400
    for (auto& d: bundles)
    {
401
      if (d.GetIndex()[0] % samplingStep + d.GetIndex()[1] % samplingStep == 0)
402
      {
Cresson Remi's avatar
Cresson Remi committed
403
404
        seed[seedCount] = d;
        seedCount++;
405
406
407
      }
      else
      {
Cresson Remi's avatar
Cresson Remi committed
408
409
        candidates[candidatesCount] = d;
        candidatesCount++;
410
      }
411
      step++;
412
    }
413

Cresson Remi's avatar
Cresson Remi committed
414
415
    seed.resize(seedCount);
    candidates.resize(candidatesCount);
416

417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
    otbAppLogINFO("Spatial seed has " << seedCount << " samples");

    unsigned int nbToRemove = static_cast<unsigned int>(seedCount - GetParameterFloat("strategy.balanced.sp") * count);

    otbAppLogINFO("Adjust spatial seed removing " << nbToRemove << " samples");

    float removalRate = static_cast<float>(seedCount) / static_cast<float>(nbToRemove);
    float removalStep = 0;
    auto removeSamples = [&removalStep, &removalRate](SampleBundle & b) -> bool {
      (void) b;
      bool ret = false;
      if (removalStep >= removalRate)
        {
        removalStep = fmod(removalStep, removalRate);
        ret = true;
        }
      else
        ret = false;
      removalStep++;
      return ret;;
    };
    auto iterator = std::remove_if(seed.begin(), seed.end(), removeSamples);
    seed.erase(iterator, seed.end());

441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
    otbAppLogINFO("Spatial seed size : " << seed.size());

    // 3. Compute seed distribution

    const unsigned int nbOfClasses = GetParameterInt("strategy.balanced.nclasses");
    DistributionType seedDist(nbOfClasses);
    for (auto& d: seed)
      seedDist.Update(d.GetDistribution());

    otbAppLogINFO("Spatial seed distribution: " << seedDist.ToString());

    // 4. Select other samples to feed the seed

    otbAppLogINFO("Balance seed candidates size: " << candidates.size());

Cresson Remi's avatar
Cresson Remi committed
456
    // Sort by cos
457
458
459
    auto comparator = [&seedDist](const SampleBundle & a, const SampleBundle & b) -> bool{
      return a.GetDistribution().Cosinus(seedDist) > b.GetDistribution().Cosinus(seedDist);
    };
Cresson Remi's avatar
Cresson Remi committed
460
    sort(candidates.begin(), candidates.end(), comparator);
461
462
463
464

    DistributionType idealDist(nbOfClasses, 1.0 / std::sqrt(static_cast<float>(nbOfClasses)));
    float minCos = 0;
    unsigned int samplesAdded = 0;
Cresson Remi's avatar
Cresson Remi committed
465
    seed.resize(seed.size()+candidates.size(), SampleBundle(nbOfClasses));
466
467
468
469
470
471
472
473
474
475
476
477
478
    while(candidates.size() > 0)
    {
      // Get the less correlated sample
      SampleBundle candidate = candidates.back();

      // Update distribution
      seedDist.Update(candidate.GetDistribution());

      // Compute cos of the updated distribution
      float idealCos = seedDist.Cosinus(idealDist);
      if (idealCos > minCos)
      {
        minCos = idealCos;
Cresson Remi's avatar
Cresson Remi committed
479
480
        seed[seedCount] = candidate;
        seedCount++;
481
482
483
484
485
486
487
488
        candidates.pop_back();
        samplesAdded++;
      }
      else
      {
        break;
      }
    }
Cresson Remi's avatar
Cresson Remi committed
489
    seed.resize(seedCount);
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537

    otbAppLogINFO("Final samples number: " << seed.size() << " (" << samplesAdded << " samples added)");
    otbAppLogINFO("Final samples distribution: " << seedDist.ToString());

    // 5. Export training/validation samples
    PopulateVectorData(seed);
  }

  void PopulateVectorData(std::vector<SampleBundle> & samples)
  {
    // Get data tree
    DataTreeType::Pointer treeTrain = m_OutVectorDataTrain->GetDataTree();
    DataTreeType::Pointer treeValid = m_OutVectorDataValid->GetDataTree();
    DataNodePointer rootTrain = treeTrain->GetRoot()->Get();
    DataNodePointer rootValid = treeValid->GetRoot()->Get();
    DataNodePointer documentTrain = DataNodeType::New();
    DataNodePointer documentValid = DataNodeType::New();
    documentTrain->SetNodeType(DOCUMENT);
    documentValid->SetNodeType(DOCUMENT);
    treeTrain->Add(documentTrain, rootTrain);
    treeValid->Add(documentValid, rootValid);

    unsigned int id = 0;
    for (const auto& sample: samples)
    {
      // Add point to the VectorData tree
      DataNodePointer newDataNode = DataNodeType::New();
      newDataNode->SetPoint(sample.GetPosition());
      newDataNode->SetFieldAsInt("id", id);
      id++;

      // select this sample
      if (sample.GetBlack())
      {
        // Train
        treeTrain->Add(newDataNode, documentTrain);
      }
      else
      {
        // Valid
        treeValid->Add(newDataNode, documentValid);
      }

    }
  }

  void DoExecute()
  {
Cresson Remi's avatar
Cresson Remi committed
538
539
540
    otbAppLogINFO("Grid step : " << this->GetParameterInt("grid.step"));
    otbAppLogINFO("Patch size : " << this->GetParameterInt("grid.psize"));

541
542
543
544
545
546

    // Compute no-data mask
    m_NoDataFilter = IsNoDataFilterType::New();
    m_NoDataFilter->GetFunctor().SetNoDataValue(GetParameterFloat("nodata"));
    m_NoDataFilter->SetInput(GetParameterFloatVectorImage("in"));
    m_NoDataFilter->UpdateOutputInformation();
547
548
549
550
551
552
553
554
555
556
557
    UInt8ImageType::Pointer src = m_NoDataFilter->GetOutput();

    // If mask available, use it
    if (HasValue("mask"))
      {
      m_MaskImageFilter = MaskImageFilterType::New();
      m_MaskImageFilter->SetInput(m_NoDataFilter->GetOutput());
      m_MaskImageFilter->SetMaskImage(GetParameterUInt8Image("mask"));
      m_MaskImageFilter->UpdateOutputInformation();
      src = m_MaskImageFilter->GetOutput();
      }
558
559

    // Padding 1 pixel
560
    UInt8ImageType::SizeType size = src->GetLargestPossibleRegion().GetSize();
561
562
    size[0] += 2;
    size[1] += 2;
563
564
    UInt8ImageType::SpacingType spacing = src->GetSignedSpacing();
    UInt8ImageType::PointType origin = src->GetOrigin();
565
566
567
    origin[0] -= spacing[0];
    origin[1] -= spacing[1];
    m_PadFilter = PadFilterType::New();
568
    m_PadFilter->SetInput( src );
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
    m_PadFilter->SetOutputOrigin(origin);
    m_PadFilter->SetOutputSpacing(spacing);
    m_PadFilter->SetOutputSize(size);
    m_PadFilter->SetEdgePaddingValue( 0 );
    m_PadFilter->UpdateOutputInformation();

    // Morpho
    m_Radius[0] = this->GetParameterInt("grid.psize")/2;
    m_Radius[1] = this->GetParameterInt("grid.psize")/2;
    StructuringType se = StructuringType::Box(m_Radius);
    m_MorphoFilter = MorphoFilterType::New();
    m_MorphoFilter->SetKernel(se);
    m_MorphoFilter->SetInput(m_PadFilter->GetOutput());
    m_MorphoFilter->SetForegroundValue(1);
    m_MorphoFilter->SetBackgroundValue(0);
    m_MorphoFilter->UpdateOutputInformation();

    // Prepare output vector data
    m_OutVectorDataTrain = VectorDataType::New();
    m_OutVectorDataValid = VectorDataType::New();
    m_OutVectorDataTrain->SetProjectionRef(m_MorphoFilter->GetOutput()->GetProjectionRef());
    m_OutVectorDataValid->SetProjectionRef(m_MorphoFilter->GetOutput()->GetProjectionRef());

    if (GetParameterAsString("strategy") == "chessboard")
    {
      otbAppLogINFO("Sampling at regular interval in space (\"Chessboard\" like)");

      SampleChessboard();
    }
    else if (GetParameterAsString("strategy") == "balanced")
    {
      otbAppLogINFO("Sampling with balancing strategy");

      SampleBalanced();
    }

    otbAppLogINFO( "Writing output samples positions");

    SetParameterOutputVectorData("outtrain", m_OutVectorDataTrain);
Cresson Remi's avatar
Cresson Remi committed
608
    SetParameterOutputVectorData("outvalid", m_OutVectorDataValid);
609
610
611
612

  }

private:
613
614
615
616
617
618
619
  RadiusType                   m_Radius;
  IsNoDataFilterType::Pointer  m_NoDataFilter;
  PadFilterType::Pointer       m_PadFilter;
  MorphoFilterType::Pointer    m_MorphoFilter;
  VectorDataType::Pointer      m_OutVectorDataTrain;
  VectorDataType::Pointer      m_OutVectorDataValid;
  MaskImageFilterType::Pointer m_MaskImageFilter;
620
621
622
623
624
625
}; // end of class

} // end namespace wrapper
} // end namespace otb

OTB_APPLICATION_EXPORT( otb::Wrapper::PatchesSelection )