pktools  2.6.3
Processing Kernel for geospatial data
pkfilterdem.cc
1 /**********************************************************************
2 pkfilterdem.cc: Filter digital elevation model raster datasets
3 Copyright (C) 2008-2014 Pieter Kempeneers
4 
5 This file is part of pktools
6 
7 pktools is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11 
12 pktools is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with pktools. If not, see <http://www.gnu.org/licenses/>.
19 ***********************************************************************/
20 #include <assert.h>
21 #include <iostream>
22 #include <string>
23 #include "base/Optionpk.h"
24 #include "base/Vector2d.h"
25 #include "algorithms/Filter2d.h"
26 #include "imageclasses/ImgReaderGdal.h"
27 #include "imageclasses/ImgWriterGdal.h"
28 
29 using namespace std;
30 /*------------------
31  Main procedure
32  ----------------*/
33 int main(int argc,char **argv) {
34  Optionpk<std::string> input_opt("i","input","input image file");
35  Optionpk<std::string> output_opt("o", "output", "Output image file");
36  // Optionpk<std::string> tmpdir_opt("tmp", "tmp", "Temporary directory","/tmp",2);
37  Optionpk<bool> disc_opt("circ", "circular", "circular disc kernel for dilation and erosion", false);
38  Optionpk<string> postFilter_opt("f", "filter", "post processing filter: vito, etew_min, promorph (progressive morphological filter),open,close).");
39  Optionpk<double> dim_opt("dim", "dim", "maximum filter kernel size", 17);
40  Optionpk<double> maxSlope_opt("st", "st", "slope threshold used for morphological filtering. Use a low values to remove more height objects in flat terrains", 0.0);
41  Optionpk<double> hThreshold_opt("ht", "ht", "initial height threshold for progressive morphological filtering. Use low values to remove more height objects. Optionally, a maximum height threshold can be set via a second argument (e.g., -ht 0.2 -ht 2.5 sets an initial threshold at 0.2 m and caps the threshold at 2.5 m).", 0.2);
42  Optionpk<short> minChange_opt("minchange", "minchange", "Stop iterations when no more pixels are changed than this threshold.", 0);
43  Optionpk<std::string> otype_opt("ot", "otype", "Data type for output image ({Byte/Int16/UInt16/UInt32/Int32/Float32/Float64/CInt16/CInt32/CFloat32/CFloat64}). Empty string: inherit type from input image","");
44  Optionpk<string> oformat_opt("of", "oformat", "Output image format (see also gdal_translate). Empty string: inherit from input image");
45  Optionpk<string> colorTable_opt("ct", "ct", "color table (file with 5 columns: id R G B ALFA (0: transparent, 255: solid). Use none to ommit color table");
46  Optionpk<string> option_opt("co", "co", "Creation option for output file. Multiple options can be specified.");
47  Optionpk<short> nodata_opt("nodata", "nodata", "nodata value");
48  Optionpk<short> verbose_opt("v", "verbose", "verbose mode if > 0", 0,2);
49 
50  disc_opt.setHide(1);
51  maxSlope_opt.setHide(1);
52  hThreshold_opt.setHide(1);
53  minChange_opt.setHide(1);
54  otype_opt.setHide(1);
55  oformat_opt.setHide(1);
56  colorTable_opt.setHide(1);
57  nodata_opt.setHide(1);
58 
59  bool doProcess;//stop process when program was invoked with help option (-h --help)
60  try{
61  doProcess=input_opt.retrieveOption(argc,argv);
62  output_opt.retrieveOption(argc,argv);
63  // tmpdir_opt.retrieveOption(argc,argv);
64  postFilter_opt.retrieveOption(argc,argv);
65  dim_opt.retrieveOption(argc,argv);
66  disc_opt.retrieveOption(argc,argv);
67  maxSlope_opt.retrieveOption(argc,argv);
68  hThreshold_opt.retrieveOption(argc,argv);
69  minChange_opt.retrieveOption(argc,argv);
70  otype_opt.retrieveOption(argc,argv);
71  oformat_opt.retrieveOption(argc,argv);
72  colorTable_opt.retrieveOption(argc,argv);
73  nodata_opt.retrieveOption(argc,argv);
74  verbose_opt.retrieveOption(argc,argv);
75  }
76  catch(string predefinedString){
77  std::cout << predefinedString << std::endl;
78  exit(0);
79  }
80  if(!doProcess){
81  cout << endl;
82  cout << "Usage: pkfilterdem -i input.txt -o output" << endl;
83  cout << endl;
84  std::cout << "short option -h shows basic options only, use long option --help to show all options" << std::endl;
85  exit(0);//help was invoked, stop processing
86  }
87 
88  ImgReaderGdal input;
89  ImgWriterGdal outputWriter;
90  if(input_opt.empty()){
91  cerr << "Error: no input file selected, use option -i" << endl;
92  exit(1);
93  }
94  if(output_opt.empty()){
95  cerr << "Error: no outputWriter file selected, use option -o" << endl;
96  exit(1);
97  }
98  if(postFilter_opt.empty()){
99  cerr << "Error: no filter selected, use option -f" << endl;
100  exit(1);
101  }
102  input.open(input_opt[0]);
103  GDALDataType theType=GDT_Unknown;
104  if(verbose_opt[0])
105  cout << "possible output data types: ";
106  for(int iType = 0; iType < GDT_TypeCount; ++iType){
107  if(verbose_opt[0])
108  cout << " " << GDALGetDataTypeName((GDALDataType)iType);
109  if( GDALGetDataTypeName((GDALDataType)iType) != NULL
110  && EQUAL(GDALGetDataTypeName((GDALDataType)iType),
111  otype_opt[0].c_str()))
112  theType=(GDALDataType) iType;
113  }
114  if(theType==GDT_Unknown)
115  theType=input.getDataType();
116 
117  if(verbose_opt[0])
118  std::cout << std::endl << "Output pixel type: " << GDALGetDataTypeName(theType) << endl;
119 
120  string imageType=input.getImageType();
121  if(oformat_opt.size())
122  imageType=oformat_opt[0];
123 
124  if(option_opt.findSubstring("INTERLEAVE=")==option_opt.end()){
125  string theInterleave="INTERLEAVE=";
126  theInterleave+=input.getInterleave();
127  option_opt.push_back(theInterleave);
128  }
129 
130  if(verbose_opt[0])
131  cout << "opening output file " << output_opt[0] << endl;
132  outputWriter.open(output_opt[0],input.nrOfCol(),input.nrOfRow(),1,theType,imageType,option_opt);
133  //set projection
134  outputWriter.setProjection(input.getProjection());
135  outputWriter.copyGeoTransform(input);
136  if(colorTable_opt.size())
137  outputWriter.setColorTable(colorTable_opt[0]);
138 
139  //set nodata value
140  if(nodata_opt.size()){
141  for(int iband=0;iband<outputWriter.nrOfBand();++iband)
142  outputWriter.GDALSetNoDataValue(nodata_opt[0],iband);
143  }
144 
145  Vector2d<double> inputData(input.nrOfRow(),input.nrOfCol());
146  Vector2d<double> outputData(outputWriter.nrOfRow(),outputWriter.nrOfCol());
147  Vector2d<double> tmpData(outputWriter.nrOfRow(),outputWriter.nrOfCol());
148  input.readDataBlock(inputData,GDT_Float64,0,inputData.nCols()-1,0,inputData.nRows()-1);
149 
150  //apply post filter
151  std::cout << "Applying post processing filter: " << postFilter_opt[0] << std::endl;
152 
153  // const char* pszMessage;
154  // void* pProgressArg=NULL;
155  // GDALProgressFunc pfnProgress=GDALTermProgress;
156  // double progress=0;
157  // pfnProgress(progress,pszMessage,pProgressArg);
158 
159  //make sure dim_opt contains initial [0] and maximum [1] kernel sizes in this order
160  if(dim_opt.size()<2)
161  dim_opt.insert(dim_opt.begin(),3);
162  if(dim_opt[0]>dim_opt[1]){
163  dim_opt.insert(dim_opt.begin(),dim_opt[1]);
164  dim_opt.erase(dim_opt.begin()+2);
165  }
166 
167  filter2d::Filter2d theFilter;
168  if(nodata_opt.size()){
169  for(int inodata=0;inodata<nodata_opt.size();++inodata)
170  theFilter.pushNoDataValue(nodata_opt[inodata]);
171  }
172 
173  unsigned long int nchange=1;
174  if(postFilter_opt[0]=="vito"){
175  //todo: fill empty pixels
176  // hThreshold_opt.resize(4);
177  // hThreshold_opt[0]=0.7;
178  // hThreshold_opt[1]=0.3;
179  // hThreshold_opt[2]=0.1;
180  // hThreshold_opt[2]=-0.2;
181  vector<int> nlimit(4);
182  nlimit[0]=2;
183  nlimit[1]=3;
184  nlimit[2]=4;
185  nlimit[2]=2;
186  //init finalMask
187  for(int irow=0;irow<tmpData.nRows();++irow)
188  for(int icol=0;icol<tmpData.nCols();++icol)
189  tmpData[irow][icol]=1;
190  for(int iheight=0;iheight<hThreshold_opt.size();++iheight){
191  if(verbose_opt[0])
192  cout << "height: " << hThreshold_opt[iheight] << endl;
193  //todo:replace with binary mask (or short) -> adapt template with T1,T2 in Filter2d
194  Vector2d<double> tmpMask(input.nrOfRow(),input.nrOfCol());
195  for(int irow=0;irow<tmpMask.nRows();++irow)
196  for(int icol=0;icol<tmpMask.nCols();++icol)
197  tmpMask[irow][icol]=1;//1=surface, 0=terrain
198  if(verbose_opt[0])
199  cout << "filtering NWSE" << endl;
200  //from here
201  // Vector2d<double> tmpDSM(inputData);
202  // int dimX=dim_opt[0];
203  // int dimY=dim_opt[0];
204  // assert(dimX);
205  // assert(dimY);
206  // statfactory::StatFactory stat;
207  // Vector2d<double> inBuffer(dimY,inputData.nCols());
208  // if(tmpData.size()!=inputData.nRows())
209  // tmpData.resize(inputData.nRows());
210  // int indexI=0;
211  // int indexJ=inputData.nRows()-1;
212  // // int indexJ=0;
213  // //initialize last half of inBuffer
214  // for(int j=-(dimY-1)/2;j<=dimY/2;++j){
215  // for(int i=0;i<inputData.nCols();++i)
216  // inBuffer[indexJ][i]=tmpDSM[abs(j)][i];
217  // --indexJ;
218  // // ++indexJ;
219  // }
220  // for(int y=tmpDSM.nRows()-1;y>=0;--y){
221  // if(y){//inBuffer already initialized for y=0
222  // //erase first line from inBuffer
223  // inBuffer.erase(inBuffer.end()-1);
224  // // inBuffer.erase(inBuffer.begin());
225  // //read extra line and push back to inBuffer if not out of bounds
226  // if(y+dimY/2<tmpDSM.nRows()){
227  // //allocate buffer
228  // // inBuffer.push_back(inBuffer.back());
229  // inBuffer.insert(inBuffer.begin(),*(inBuffer.begin()));
230  // for(int i=0;i<tmpDSM.nCols();++i)
231  // inBuffer[0][i]=tmpDSM[y-dimY/2][i];
232  // // inBuffer[inBuffer.size()-1][i]=tmpDSM[y+dimY/2][i];
233  // }
234  // else{
235  // int over=y+dimY/2-tmpDSM.nRows();
236  // int index=(inBuffer.size()-1)-over;
237  // assert(index>=0);
238  // assert(index<inBuffer.size());
239  // inBuffer.push_back(inBuffer[index]);
240  // }
241  // }
242  // for(int x=tmpDSM.nCols()-1;x>=0;--x){
243  // double centerValue=inBuffer[(dimY-1)/2][x];
244  // //test
245  // cout << "pixel (" << x << "," << y << "): " << centerValue << endl;
246  // short nmasked=0;
247  // std::vector<double> neighbors;
248  // for(int j=-(dimY-1)/2;j<=dimY/2;++j){
249  // for(int i=-(dimX-1)/2;i<=dimX/2;++i){
250  // indexI=x+i;
251  // //check if out of bounds
252  // if(indexI<0)
253  // indexI=-indexI;
254  // else if(indexI>=tmpDSM.nCols())
255  // indexI=tmpDSM.nCols()-i;
256  // if(y+j<0)
257  // indexJ=-j;
258  // else if(y+j>=tmpDSM.nRows())
259  // indexJ=(dimY>2) ? (dimY-1)/2-j : 0;
260  // else
261  // indexJ=(dimY-1)/2+j;
262  // double difference=(centerValue-inBuffer[indexJ][indexI]);
263  // //test
264  // cout << "centerValue-inBuffer[" << indexJ << "][" << indexI << "]=" << centerValue << " - " << inBuffer[indexJ][indexI] << " = " << difference << endl;
265  // if(i||j)//skip centerValue
266  // neighbors.push_back(inBuffer[indexJ][indexI]);
267  // if(difference>hThreshold_opt[iheight])
268  // ++nmasked;
269  // }
270  // }
271  // //test
272  // cout << "pixel " << x << ", " << y << ": nmasked is " << nmasked << endl;
273  // if(nmasked<=nlimit[iheight]){
274  // ++nchange;
275  // //reset pixel in outputMask
276  // tmpData[y][x]=0;
277  // //test
278  // cout << "pixel " << x << ", " << y << " is ground" << endl;
279  // }
280  // else{
281  // //reset pixel height in tmpDSM
282  // sort(neighbors.begin(),neighbors.end());
283  // assert(neighbors.size()>1);
284  // inBuffer[(dimY-1)/2][x]=neighbors[1];
285  // //test
286  // cout << "pixel " << x << ", " << y << " is surface, reset DSM to " << neighbors[1] << endl;
287  // /* inBuffer[(dimY-1)/2][x]=stat.mymin(neighbors); */
288  // }
289  // }
290  // }
291  //to here
292 
293  theFilter.dsm2dtm_nwse(inputData,tmpData,hThreshold_opt[iheight],nlimit[iheight],dim_opt[0]);
294  if(verbose_opt[0])
295  cout << "filtering NESW" << endl;
296  theFilter.dsm2dtm_nesw(inputData,tmpData,hThreshold_opt[iheight],nlimit[iheight],dim_opt[0]);
297  if(verbose_opt[0])
298  cout << "filtering SENW" << endl;
299  theFilter.dsm2dtm_senw(inputData,tmpData,hThreshold_opt[iheight],nlimit[iheight],dim_opt[0]);
300  if(verbose_opt[0])
301  cout << "filtering SWNE" << endl;
302  theFilter.dsm2dtm_swne(inputData,tmpData,hThreshold_opt[iheight],nlimit[iheight],dim_opt[0]);
303  }
304  outputData=tmpData;
305  //todo: interpolate
306  //outputData.setMask(tmpData,1,0);
307  }
308  else if(postFilter_opt[0]=="etew_min"){
309  //Elevation Threshold with Expand Window (ETEW) Filter (p.73 from Airborne LIDAR Data Processing and Analysis Tools ALDPAT 1.0)
310  //first iteration is performed assuming only minima are selected using options -fir all -comp min
311  //increase cells and thresholds until no points from the previous iteration are discarded.
312  int dim=dim_opt[0];
313  // theFilter.setNoValue(0);
314  int iteration=1;
315  while(nchange>minChange_opt[0]&&dim<=dim_opt[1]){
316  double hThreshold=maxSlope_opt[0]*dim;
317  nchange=theFilter.morphology(inputData,outputData,"erode",dim,dim,disc_opt[0],hThreshold);
318  inputData=outputData;
319  dim+=2;//change from theory: originally double cellCize
320  std::cout << "iteration " << iteration << ": " << nchange << " pixels changed" << std::endl;
321  ++iteration;
322  }
323  }
324  else if(postFilter_opt[0]=="promorph"){
325  //Progressive morphological filter tgrs2003_zhang vol41 pp 872-882
326  //first iteration is performed assuming only minima are selected using options -fir all -comp min
327  //increase cells and thresholds until no points from the previous iteration are discarded.
328  int dim=dim_opt[0];
329  double hThreshold=hThreshold_opt[0];
330  int iteration=1;
331  while(nchange>minChange_opt[0]&&dim<=dim_opt[1]){
332  std::cout << "iteration " << iteration << " with window size " << dim << " and dh_max: " << hThreshold << std::endl;
333  try{
334  nchange=theFilter.morphology(inputData,outputData,"erode",dim,dim,disc_opt[0],hThreshold);
335  theFilter.morphology(outputData,inputData,"dilate",dim,dim,disc_opt[0],hThreshold);
336  theFilter.doit(inputData,outputData,"median",dim,dim,1,disc_opt[0]);
337  inputData=outputData;
338  }
339  catch(std::string errorString){
340  cout << errorString << endl;
341  exit(1);
342  }
343  int newdim=(dim==1)? 3: 2*(dim-1)+1;
344  hThreshold=hThreshold_opt[0]+maxSlope_opt[0]*(newdim-dim)*input.getDeltaX();
345  dim=newdim;
346  if(hThreshold_opt.size()>1){
347  if(hThreshold>hThreshold_opt[1]){
348  hThreshold=hThreshold_opt[1];
349  }
350  }
351  std::cout << "iteration " << iteration << ": " << nchange << " pixels changed" << std::endl;
352  ++iteration;
353  }
354  }
355  else if(postFilter_opt[0]=="open"){
356  try{
357  theFilter.morphology(inputData,tmpData,"erode",dim_opt[0],dim_opt[0],disc_opt[0],hThreshold_opt[0]);
358  theFilter.morphology(tmpData,outputData,"dilate",dim_opt[0],dim_opt[0],disc_opt[0],hThreshold_opt[0]);
359  outputData=inputData;
360  }
361  catch(std::string errorString){
362  cout << errorString << endl;
363  exit(1);
364  }
365  }
366  else if(postFilter_opt[0]=="close"){
367  try{
368  theFilter.morphology(inputData,tmpData,"dilate",dim_opt[0],dim_opt[0],disc_opt[0],hThreshold_opt[0]);
369  theFilter.morphology(tmpData,outputData,"erode",dim_opt[0],dim_opt[0],disc_opt[0],hThreshold_opt[0]);
370  }
371  catch(std::string errorString){
372  cout << errorString << endl;
373  exit(1);
374  }
375  }
376  //write outputData to outputWriter
377  outputWriter.writeDataBlock(outputData,GDT_Float64,0,outputData.nCols()-1,0,outputData.nRows()-1);
378 
379  // progress=1;
380  // pfnProgress(progress,pszMessage,pProgressArg);
381  input.close();
382  outputWriter.close();
383  return 0;
384 }