Project

General

Profile

Download (9.71 KB) Statistics
| Branch: | Revision:
1
#!/usr/bin/env python
2

    
3
############################################################################
4
# MODULE:   r.multiscalesmooth for GRASS
5
# AUTHOR(S):    Original algorithm and AML implementation by John
6
#               Gallant. Translation to GRASS/Python by Jim Regetz.
7
# REFERENCES:
8
#   Gallant, John. 2001. Adaptive smoothing for noisy DEMs.
9
#       Geomorphometry 2011.
10
#       http://geomorphometry.org/system/files/Gallant2011geomorphometry.pdf
11
#
12
#############################################################################
13

    
14
#%module
15
#%  description: Multiscale raster smoother
16
#%end
17
#%option
18
#%  key: input
19
#%  type: string
20
#%  gisprompt: old,cell,raster
21
#%  description: Raster input map
22
#%  required : yes
23
#%end
24
#%option
25
#%  key: output
26
#%  type: string
27
#%  gisprompt: new,cell,raster
28
#%  description: Smoothed output raster map
29
#%  required : yes
30
#%end
31
#%option
32
#%  key: sd
33
#%  type: string
34
#%  description: Noise standard deviation; can be a raster or a constant
35
#%  required : yes
36
#%end
37
#%option
38
#%  key: alpha
39
#%  type: double
40
#%  answer: 0.05
41
#%  description: Alpha used for chi-square test [0-1]
42
#%  required : no
43
#%end
44
#%option
45
#%  key: region
46
#%  type: string
47
#%  gisprompt: old,cell,raster
48
#%  description: Map used to set extent; default is to use current region
49
#%  required : no
50
#%end
51

    
52
# NOTES
53
# - make GRASS be quiet with this:
54
#     gs.os.environ['GRASS_VERBOSE'] = '0'
55

    
56
import atexit
57
import sys
58
import math
59
import grass.script as gs
60

    
61
# create set to store names of temporary maps to be deleted upon exit
62
tmp_rast = set()
63

    
64
def cleanup():
65
    gs.message("Removing temporary files...", flag='i')
66
    for rast in tmp_rast:
67
        gs.run_command("g.remove", rast=rast, quiet=True)
68

    
69
def coarsen_region(factor=3):
70
    gs.run_command('g.region',
71
        rows=gs.region()['rows']/3,
72
        cols=gs.region()['cols']/3)
73

    
74
def refine_region(factor=3):
75
    gs.run_command('g.region',
76
        rows=gs.region()['rows']*3,
77
        cols=gs.region()['cols']*3)
78

    
79
def multiscalesmooth(input, smooth, sd, alpha=0.05):
80

    
81
    chisqa = (2.807 - 0.6422 * math.log10(alpha) - 3.410 *
82
        math.pow(alpha, 0.3411))
83
    chisqb = (-5.871 - 3.675 * math.log10(alpha) + 4.690 *
84
        math.pow(alpha, 0.3377))
85

    
86
    gs.message("Preparing initial grids", flag='i')
87

    
88
    # create copy of initial grid of values using current region
89
    gs.mapcalc('z0 = ${input}', input=input, quiet=True)
90
    tmp_rast.add('z0')
91

    
92
    # set number of aggregation levels and neighborhood size
93
    NUM_LEVELS = 4
94
    NUM_CELLS = 3
95

    
96
    # expand region to accommodate integer number of cells at coarsest
97
    # level, by adding roungly equal number of cells on either side
98
    # (with one extra on top/right if an odd number of cells is needed)
99
    max_size = NUM_CELLS**NUM_LEVELS
100
    region = gs.region()
101
    extra = region['cols'] % max_size
102
    if (0 < extra):
103
        addx = (max_size-extra)/2.0
104
    else:
105
        addx = 0.0
106
    extra = region['rows'] % max_size
107
    if (0 < extra):
108
        addy = (max_size-extra)/2.0
109
    else:
110
        addy = 0.0
111
    gs.run_command('g.region', flags='a',
112
        w = region['w'] - math.floor(addx) * region['ewres'],
113
        e = region['e'] + math.ceil(addx) * region['ewres'],
114
        s = region['s'] - math.floor(addy) * region['nsres'],
115
        n = region['n'] + math.ceil(addy) * region['nsres'])
116
    gs.debug('\n'.join(gs.parse_command('g.region', 'up').keys()))
117

    
118
    # create initial grid of variances; sd can be a raster or a constant
119
    gs.mapcalc('v0 = if(isnull(z0), null(), ${sd}^2)', sd=sd, quiet=True)
120
    tmp_rast.add('v0')
121
    # set initial "group variance" to individual msmt variance (noise)
122
    gs.run_command('g.copy', rast='v0,v.g')
123
    tmp_rast.add('v.g')
124
    # weights for aggregation, based on total variance
125
    gs.mapcalc('w = if(isnull(v0), 0, 1/v0)', quiet=True)
126
    tmp_rast.add('w')
127
    # squared weights
128
    gs.mapcalc('wsq = w^2', quiet=True)
129
    tmp_rast.add('wsq')
130
    # effective number of measurements
131
    gs.mapcalc('n = if(isnull(z0), 0, 1)', quiet=True)
132
    tmp_rast.add('n')
133

    
134
    # aggregate to broader scales
135
    for j in range(NUM_LEVELS):
136
        i = j + 1
137
        gs.message('Aggregating from %d to %d' % (j, i), flag='i')
138
        gs.debug(_('Region dimensions: %d x %d' % (gs.region()['rows'],
139
            gs.region()['cols'])))
140

    
141
        # rename previous (finer scale) weights grid
142
        gs.run_command('g.rename', rast='w,w.finer', overwrite=True,
143
            quiet=True)
144
        tmp_rast.add('w.finer')
145

    
146
        # calc neighborhood weights, num cells, effective num cells
147
        coarsen_region()
148
        gs.run_command('r.resamp.stats', method='sum', input='w.finer',
149
            output='w', quiet=True)
150
        gs.run_command('r.resamp.stats', method='sum', input='wsq',
151
            output='wsq', overwrite=True, quiet=True)
152
        gs.run_command('r.resamp.stats', method='sum', input='n',
153
            output='n', overwrite=True, quiet=True)
154

    
155
        # calc variance-weighted neighborhood mean
156
        refine_region()
157
        gs.mapcalc('tmp = w.finer * z%d / w' % j, quiet=True,
158
            overwrite=True)
159
        tmp_rast.add('tmp')
160
        coarsen_region()
161
        gs.run_command('r.resamp.stats', method='sum', input='tmp',
162
            output='z%d' % i, quiet=True)
163
        tmp_rast.add('z%d' % i)
164

    
165
        # calc between-cell variance, taken over neighborhood
166
        refine_region()
167
        gs.mapcalc('tmp = w.finer * (z%d - z%d)^2 / w' % (j, i),
168
            quiet=True, overwrite=True)
169
        tmp_rast.add('tmp')
170
        coarsen_region()
171
        gs.run_command('r.resamp.stats', method='sum', input='tmp',
172
            output='v.bg', overwrite=True, quiet=True)
173
        tmp_rast.add('v.bg')
174

    
175
        # calc wtd avg of within-cell variance, taken over neighborhood
176
        if (i==1):
177
            gs.mapcalc('v.wg = 0', quiet=True, overwrite=True)
178
            tmp_rast.add('v.wg')
179
        else:
180
            refine_region()
181
            gs.mapcalc('tmp = w.finer * v.g / w', quiet=True,
182
                overwrite=True)
183
            tmp_rast.add('tmp')
184
            coarsen_region()
185
            gs.run_command('r.resamp.stats', method='sum', input='tmp',
186
                output='v.wg', overwrite=True, quiet=True)
187

    
188
        # calc total group variance
189
        # ~= total variance of cell vals in the underlying neighborhood
190
        gs.mapcalc('v.g = v.bg + v.wg', quiet=True, overwrite=True)
191
        tmp_rast.add('v.g')
192

    
193
        # calc chisq critical values (where df = n.eff - 1)
194
        gs.mapcalc('chisq = 1 + ${chisqa}/sqrt(${df}) + ${chisqb}/${df}',
195
            chisqa=chisqa, chisqb=chisqb, df='(w^2/wsq - 1)', quiet=True)
196
        tmp_rast.add('chisq')
197
        # set coarsened cell variances: if group variance is small
198
        # relative to noise variance (mean of finer scale variances),
199
        # use variance of neighborhood mean instead of group variance
200
        gs.mapcalc('v%d = if(v.g/chisq < ${mv}, ${vm}, v.g)' % i,
201
            vm = '(1.0/w)', mv = '(n/w)', quiet=True)
202
        tmp_rast.add('v%d' % i)
203

    
204
    # get arbitrarily large value to fill null variance cells
205
    bigvar = gs.raster_info('v0')['max'] * 10
206

    
207
    # prep for refinement phase
208
    gs.run_command('g.rename', rast='z%d,%s' % (NUM_LEVELS, smooth),
209
        quiet=True)
210
    tmp_rast.remove('z%d' % NUM_LEVELS)
211
    gs.run_command('g.rename', rast='v%d,v.smooth' % NUM_LEVELS,
212
        quiet=True)
213
    tmp_rast.add('v.smooth')
214
    tmp_rast.remove('v%d' % NUM_LEVELS)
215

    
216
    # refine, smooth, and combine each layer in turn
217
    for j in reversed(range(NUM_LEVELS)):
218

    
219
        i = j + 1
220
        gs.message('Refining from %d to %d' % (i, j), flag='i')
221

    
222
        # create smoothed higher resolution versions of z and v
223
        # using weight matrix equivalent to ArcGIS circle with radius 2
224
        refine_region()
225
        gs.run_command('r.neighbors', flags='ac', input=smooth,
226
            output='zs', method='average', size=5, overwrite=True,
227
            quiet=True)
228
        tmp_rast.add('zs')
229
        gs.run_command('r.neighbors', flags='ac', input='v.smooth',
230
            output='vs', method='average', size=5, overwrite=True,
231
            quiet=True)
232
        tmp_rast.add('vs')
233

    
234
        # combine two levels using least variance, using no-null
235
        # versions of finer z and v
236
        z_c = 'if(isnull(z%d), 0, z%d)' % (j, j)
237
        v_c = 'if(isnull(v%d), %f, v%d)' % (j, bigvar, j)
238
        gs.mapcalc('${smooth} = (${z_c}/${v_c} + zs/vs) / (1/${v_c} + 1/vs)',
239
            smooth=smooth, z_c=z_c, v_c=v_c, quiet=True, overwrite=True)
240
        gs.mapcalc('v.smooth = 1 / (1/${v_c} + 1/vs)', v_c = v_c,
241
            quiet=True, overwrite=True)
242

    
243
    cleanup()
244
    return None
245

    
246

    
247
def main():
248

    
249
    # process command options
250
    input = options['input']
251
    if not gs.find_file(input)['file']:
252
        gs.fatal(_("Raster map <%s> not found") % input)
253

    
254
    smooth = options['output']
255
    if gs.find_file(smooth)['file'] and not gs.overwrite():
256
        gs.fatal(_("Output map <%s> already exists") % smooth)
257

    
258
    sd = options['sd']
259
    try:
260
        sd = float(sd)
261
    except ValueError:
262
        if not gs.find_file(sd)['file']:
263
            gs.fatal(_("Raster map <%s> not found") % sd)
264

    
265
    alpha = float(options['alpha'])
266

    
267
    # set aside region for internal use
268
    gs.use_temp_region()
269

    
270
    # subset input if desired
271
    region = options.get('region')
272
    if region:
273
        if not gs.find_file(region)['file']:
274
            gs.fatal(_("Raster map <%s> not found") % region)
275
        gs.message("Setting region to %s" % region, flag='i')
276
        gs.run_command('g.region', rast=region, align=input)
277
    else:
278
        gs.message("Using existing GRASS region", flag='i')
279
    gs.debug('='*50)
280
    gs.debug('\n'.join(gs.parse_command('g.region', 'p').keys()))
281
    gs.debug('='*50)
282

    
283
    multiscalesmooth(input, smooth, sd, alpha)
284

    
285
    # restore original region
286
    gs.del_temp_region()
287

    
288
    return None
289

    
290

    
291
if __name__ == '__main__':
292
    options, flags = gs.parser()
293
    atexit.register(cleanup)
294
    main()
(2-2/5)