forked from cageo/Trevisani-2015
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MADfunctionsV1.py
453 lines (365 loc) · 18 KB
/
MADfunctionsV1.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
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
441
442
443
444
445
446
447
448
449
450
451
452
453
#Venice, June 15, 2014
#Begin of license
#Copyright (c) 2014 Sebastiano Trevisani
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#1)The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#2)When using this software, in particular for publications, please cite the related paper:
#"S. Trevisani and M. Rocca. MAD: robust image texture analysis for applications in high resolution geomorphometry. Computer & Geosciences (2015), 10.1016/j.cageo.2015.04.003."
#(substitute the DOI with the correct volume and pages numbers when available).
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
#End of License
#In this code we define the core functions of MAD based indexes presented
#in the paper "Trevisani S., Rocca M.,MAD: robust image texture analysis for applications
#in high resolution geomorphometry. Submitted to Computer & Geosciences, 2014".
#These functions are written in order to be easy to understand and
#to modify or re implement in other softwares. Where relevant, we suggest possible modifications,
#generalizations and special use of functions. We also inserted some "classical" geostatistical
#functions such as variogram and madogram for comparison purposes (but we use our modified sampling
#approach, i.e. the search window increases with the lag size).
#We avoided to be too much pythonic in programming styles so as to make
#clear the different steps of algorithms. Moreover we opted to code the program
#with "static" kernels files in order to facilitate their use in other softwares,
#i.e. just copy the weights. However it is clear that these kernels can be created
#on the fly in relation to specific needs and also more directions can be calculated.
#Important note: in ArcGIS is not possible to calculate
#the median with focalstastics on a float raster (!!!).
#So, to do that we need to convert temporary the float raster to an integer
# using a multiplying factor (1000)see the function "medFloat()"
#These functions make use of spatial analyst extension
#Anyway, in all those software codes implementing custom kernels
# these function can be easily implemented
from arcpy.sa import *
###Directional differences
#The basic step for MAD as well as other bivariate spatial continuity indexes (variogram, madogram, etc.)
#is to calculate quickly and accurately differences between points pairs. This is accomplished via ad-hoc
#defined kernels (in the folder kernels) that permit via focal statistic function to perform bilinear interpolation
# and the difference between interpolated points in one step. We mainly defined kernels for different lag distances
# considering four directions (N-S,SW-NE,W-E, NW-SE). Clearly, other kernels can be defined,
#in order to perform the calculation in more directions (if necessary).
#The calculation of directional differences, at least
#for not large kernels is not computationally demanding.
##Core directional differences functions
#This is the core function for performing multiscale directional analysis of surface texture
#Use these directional differences for calculating anisotropy parameters and basic surface texture indexes
def calcDelta(inRaster,kernel):
"""
Calculate directional differences on a raster "inRaster" using a kernel file
"kernel". calcDelta returns a raster with directional differences with the same
resolution of input raster. The "kernel" should report the full path to kernel file.
"""
myNbrWeight=NbrWeight(kernel)
delta=FocalStatistics(inRaster,myNbrWeight,"SUM","NODATA")
return delta
#End calcDelta.
#A special function for playing with increments of order-K
def calcDeltak2(inRaster,kernel):
"""
This function calculate difference of differences, i.e. increments of order 2, and can be
easily expanded to higher order k. Applying this function directly on a DTM, without detrending,
permits to highlight fine scale morphologies.
"""
deltak2=calcDelta(calcDelta(inRaster,kernel),kernel)
return deltak2
#End calcDelta.
#This functions is the most used for deriving directional differences for surface texture
#analysis
#For a given list of kernels for directional differences and a given input raster (likely a residual DTM)
# return a list of rasters with the corresponding directional differences
def calcAllDelta(inRaster,kernels):
"""
This function calculates for an input raster "inRaster" all the directional differences
reported in a list of kernels "kernels", reporting the full path to kernels.
"""
return [calcDelta(inRaster,X) for X in kernels]
#End calcAllDelta.
##End Core directional differences functions
##Special (Absolute) directional differences functions for lag of 1 pixel and 1.44 pixels.
#these functions are useful for relative roughness calculation and for special applications.
#These are not intended for the calculation of anisotropy parameters.
##Roughness computed with these kernels can be also useful if you need
#to filter out some fine scale artifacts from the DTM still mantaining
#high detail.
#Lag 1 pixel
#This function calculates the absolute value of directional differences for lag of 1 cell.
#Given that for a specific direction we have two opposite directional differences (e.g. in NS direction
# we have the difference between central pixel and the pixel one step at N and the difference between
# the central pixel and the pixel at one step at S) we need two average their absolute value to have a
#symmetric value to associate to the central pixel and to be used for MAD calculation. So the results of this
# kernels can be used in analogy to output of the basic kernels, but consider some smoothing of roughness.
#This kind of kernel can be also modified to compute non-symmetrical surface texture indexes i.e. where Index(h) is
#different from index(-h).
def calcAbsDelta1c(inRaster,kernelDir):
"""
Function for calculating the absolute value of directional differences for lag o 1 pixel.
It take 2 inputs: inRaster (the input raster) and kernelDir (the directory where kernels are stored).
The output, even if we have already derived the absolute value, can be used with MAD functions
as the Basic kernels. We don't suggest the use of these kernels for the calculation of anisotropy indexes.
These are conceived mainly for the calculation of relative roughness or other specific needs.
"""
asym1c=["N1cAsym","NE1cAsym","E1cAsym","SE1cAsym","S1cAsym","SW1cAsym","W1cAsym","NW1cAsym"]
dirAsym=fullPath(kernelDir,asym1c,".txt")
deltas1c=calcAllDelta(inRaster,dirAsym)
#number of differences
nDeltas=len(deltas1c)
symAbsDelta1c=[(Abs(deltas1c[i])+Abs(deltas1c[i+nDeltas/2]))/2 for i in range(nDeltas/2)]
return symAbsDelta1c
#End calcAbsDelta1c.
##End lag 1 pixel
#Lag 1.4142 Pixel
#These set of functions are used for calculating directional
#differences using the shortest lag along diagonals. This function is a variant
#for calculating directional differences and can be useful in some circumstances.
#The first step is to use the kernels:
#myKernels=((WeightDir+"NE1.txt"),(myWeightDir+"SE1.txt"))
#these are 2x2 kernels, that calculate differences along diagonals and the output value is stored on the
#NW corner, but geometrically these differences are representative of the center on the kernel 2x2.
#These differences are then manipulated to derive cell centered absolute directional differences.
#Using a kernel 2x2 we can use simple trigonometry to derive directional differences in any direction
#starting from the 2 calculated differences along diagonals. In this case we consider simply NS and EW directions
#to be compatible with the output of basic kernels (the value 0.7071 come from the sin or cos of 45):
#It is easy to generalize this function for calculating directional differences in any direction.
#This function can be useful for special needs and eventually for calculation of relative roughness.
#Also in this case some smoothing of roughness is expected.
def deltaDiag(inRaster,kernels):
"""
Utility function.
Calculates directional differences in four directions (N-S, NE-SW, E-W, SE-NW) using
2x2 kernels calculating differences along diagonals. The value is stored on the NW pixel.
The kernels to be used are "NE1Diag.txt" and "SE1Diag.txt"
"""
myNbrWeight = NbrWeight(kernels[0])
deltaR2NE=FocalStatistics(inRaster,myNbrWeight,"SUM","NODATA")
myNbrWeight = NbrWeight(kernels[1])
deltaR2SE=FocalStatistics(inRaster,myNbrWeight,"SUM","NODATA")
deltaR2N=(-deltaR2SE+deltaR2NE)*0.7071
deltaR2E=(deltaR2SE+deltaR2NE)*0.7071
return deltaR2N,deltaR2NE,deltaR2E,deltaR2SE
#end deltaDiag
def centerDiag(inDelta,shifted):
"""
Utility function:
We use the mean of the Abs of four deltas to re center on the cell
using kernel "shifted", see kernel "shifted3x3.txt".
"""
myNbrWeight = NbrWeight(shifted)
delta=FocalStatistics(Abs(inDelta),myNbrWeight,"SUM","NODATA")/4
return delta
#End absDiag.
def absDiagCentered(inRaster,kernelDir):
"""
Function for calculating the absolute value of directional differences for lag o 1.4142 pixel.
It take 2 inputs: inRaster (the input raster) and kernelDir (the directory where kernels are stored).
The output, even if we have already derived the absolute value, can be used with MAD functions
as the Basic kernels. We don't suggest the use of these kernels for the calculation of anisotropy indexes.
These are conceived for the calculation of surface roughness, relative roughness and in those situations
where we need to calculate directional differences quickly (and approximately) in any possible direction
(in this case this function and the function deltaDiag should be modified to take as input also the desidered
angle direction(s)).
"""
kernels=((kernelDir+"NE1Diag.txt"),(kernelDir+"SE1Diag.txt"))
shifted=kernelDir+"shifted3X3.txt"
return [centerDiag(X,shifted) for X in deltaDiag(inRaster,kernels)]
#End absDiagCentered.
#End Lag 1.4142 Pixel
##End Special (Absolute) directional differences functions for lag of 1 pixel and 1.4142 pixels.
###End Directional differences
###Calculation of indexes
#Utility function for calculating the median of a float raster with focalstatistics in ArcGIS 10.2 (
#which doesn't permit to calculate with focal statistics the median of a float!!!).
#We are thinking to DTM so using as multiplying factor of 1000 is ok.
#You should change this maybe with other kind of data.
def medFloat(floatRaster,window):
"""
Function for performing median calculation on float raster.This is necessary in ArcGis10.2.
The input parameters "window" is a search window defined according
to spatial analyst terminology, e.g.:
radius=3
window=NbrCircle(radius,"CELL")
"""
med=Float(FocalStatistics(Int(floatRaster*1000),window,"MEDIAN","NODATA"))/1000
return med
#End medFloat.
#This function calculates MAD from directional differences. Using the median as estimator
#we can also start the calculation with squared differences and then convert to absolute
#values after computing the median (the ordering of values doesn't change!)
def medAbs(inDelta,window):
"""
Calculate MAD i.e. the median absolute directional differences
from differences "inDelta" and using a search window "window".
Given the way in which directional differences are calculated and stored
the effective search area is linked to lag size. In order to use other sampling strategies ad-hoc
kernels can be defined.
"""
medabs=medFloat(Abs(inDelta),window)
return medabs
#end Medabs.
def medAbsAll(myDeltas,window):
"""
For all furnished deltas, for example coming from calcAlldelta(),
calc all median absolute differences with a given search window "window"
"""
return [medAbs(X,window) for X in myDeltas]
#end medAbsList.
## Anisotropy parameters
#The multiplication to 57.2957 is related to radiant conversion
#and for 0.5 because of we double the angles for not considering
#the orientation. See Davis, J.C., 2002. Statistics and Data Analysis in Geology:
#John Wiley & Sons Inc., New York(here we consider also the modulo of vectors).
#This function could be generalized to consider more directions.
def anisoDir(N,NE,E,SE):
"""
AnisoDir calculates the direction of maximum continuity
using a circular statistics approach and using four directions
along N, NE, E and SE (in this precise order!).
"""
direction=180-(57.2957*0.5*ATan2((NE-SE),(E-N)))
return direction
#end anisoDir.
#The same of anisoDir but working with a list, so this will be the most used.
def anisoDirL (x):
"""
AnisoDirL calculates the direction of maximum continuity
using a circular statistics approach and using a list of four
rasters of directional differences stored in the following order of
directions:N, NE, E and SE.
See function anisoDir() for details.
"""
direction=anisoDir(x[0],x[1],x[2],x[3])
return direction
#end anisoDirL
#Standardized resultant length. This is used as anisotropy index
def anisoR(N,NE,E,SE):
"""
Standardized resultant length. This is used as anisotropy index
Use four rasters with directional differences: N, NE,E, SE
"""
meanR=SquareRoot(Square(NE-SE)+Square(E-N))/(N+NE+E+SE)
return meanR
#end anisoR
#Standardized resultant length
def anisoRL(x):
"""
Standardized resultant length. This is used as anisotropy index
Use a list of four rasters with directional differences: N, NE,E, SE
"""
meanR=anisoR(x[0],x[1],x[2],x[3])
return meanR
#end anisoRL
#####Compounds functions##############
#These functions makes with one step all the steps required for basic surface texture indexes calculation
#This function is implemented using circular windows
def madscan(inRaster,kernels,windowRadius):
"""
this functions calculates directly isoMad, anisotropy direction and anisotropy R
having 3 inputs: 1) input raster, 2)list of kernels (in the 4 directions), 3) Search window radius
"""
#calculate deltas in 4 directions
deltas=calcAllDelta(inRaster,kernels)
#define search window
radius=windowRadius
window=NbrCircle(radius,"CELL")
#calculate MAD
myAbs=medAbsAll(deltas,window)
#calculate desired indexes (these are the outputs of the function)
isoMad=CellStatistics(myAbs,"MEAN","NODATA")
anisoMad=anisoDirL(myAbs)
anisoRMad=anisoRL(myAbs)
return isoMad,anisoMad,anisoRMad
###End madscan
#This function calculate isotropic MAD for a lag of 1 pixel
def madscan1c(inRaster,kernelDir,windowRadius):
deltasAbs1c=calcAbsDelta1c(inRaster,kernelDir)
radius=windowRadius
window=NbrCircle(radius,"CELL")
MAD1c=medAbsAll(deltasAbs1c,window)
MAD1cIso=CellStatistics(MAD1c,"MEAN","NODATA")
return MAD1cIso
#End mad1c
#if you need to use spatial analyst aggregate tools, to get
#a "low resolution" (i.e. non overlapping moving windows) result use these functions
def medAbsAllAg(myDeltas,cellFactor):
"""
For all furnished deltas, for example coming from calcAlldelta(),
calc all median absolute differences and aggregate according to a "cellfactor"
"""
return [Aggregate(Abs(X),cellFactor, "MEDIAN", "TRUNCATE", "NODATA") for X in myDeltas]
#end medAbsAllAg.
#madscan with use aggregate spatial analyst tool. For a "low resolution" result (i.e. non overlapping
#moving windows)
def madscanAg(inRaster,kernels,cellFactor):
"""
this functions calculates directly isoMad, anisotropy direction and anisotropy R
having 3 inputs: 1) input raster, 2)list of kernels (in the 4 directions), 3) Search window radius
"""
#calculate deltas in 4 directions
deltas=calcAllDelta(inRaster,kernels)
#calculate MAD
myAbs=medAbsAllAg(deltas,cellFactor)
#calculate desired indexes (these are the outputs of the function)
isoMad=CellStatistics(myAbs,"MEAN","NODATA")
anisoMad=anisoDirL(myAbs)
anisoRMad=anisoRL(myAbs)
return isoMad,anisoMad,anisoRMad
###End madscanAg.
### Utilities
## Classical geostatistical estimators
#This function is just used for comparing MAD based indexes with variogram based,
#using the improved sampling approach. To use a classical sampling approach (i.e. fixed
#search window for all lags define custom kernels).
def gamma(inDelta,window):
"""
Just an example of function for computing variogram for specific lags e.g. gamma
Take care that this function uses the improved sampling approach, i.e.
search window increasing with lag size.
"""
gamma=(FocalStatistics(Square(inDelta),window,"MEAN","NODATA"))/2
return gamma
#end gamma
def gammaAll(myDeltas,window):
"""
For all furnished deltas, for example coming from calcAlldelta(), calculate variogram
"""
return [gamma(X,window) for X in myDeltas]
#end gammaAll
def madogram(inDelta,window):
"""
Just an example of function for computing madogram for specific lags.
Take care that this function uses the improved sampling approach, i.e.
search window increasing with lag size.
"""
mado=(FocalStatistics(Abs(inDelta),window,"MEAN","NODATA"))/2
return mado
#end madogram
def madogramAll(myDeltas,window):
"""
For all furnished deltas, for example coming from calcAlldelta(), calculate madogram
"""
return [madogram(X,window) for X in myDeltas]
#end madogramAll
##End classical geostatistical estimators
#this standardize a raster
def standardize(x):
return (x-x.minimum)/(x.maximum-x.minimum)
#end standardize
#build the full path for kernels
##an helper function
def fullPath(myDir,myNames,mySuffix):
fullDir=[]
for i in range(len(myNames)):fullDir.append(myDir+myNames[i]+mySuffix)
return fullDir
#End fullPath