Project

General

Profile

« Previous | Next » 

Revision a92ae83f

Added by Jim Regetz over 11 years ago

added GRASS/Python implementation of terrain noise calculation

View differences:

terrain/research/oregon/noisemag3sec.aml
1
&args DEMgrid
2

  
3
/* Calculates a measure of the noisiness of the surface.
4
/* John Gallant 8 Jan 2009
5
/*
6
/* Modified from noisemag on 15 Oct 2010 to work at 3 second resolution
7
/*
8
/* The principle is that noise is characterised by short-range variation.
9
/* The difference from the local mean will vary erratically over a short
10
/* distance in the presence of noise. Terrain features are generally smoother
11
/* so do not vary as erratically. The noise is measured as the local standard
12
/* deviation of the difference from the mean.
13
/*
14
/* The local mean is calculated using an annulus so that local peaks and
15
/* holes are further accentuated.
16
/*
17
/* The raw noise magnitude is smoothed using two steps of median filtering
18
/* to give the median value over a 25-cell circle.
19
/*
20
/* The calculated values can be used as the standard deviation of the
21
/* noise of the surface.
22
/*
23
/* One flaw is that higher relief terrain is considered "noisy" by this method.
24
/* (Lower relief terrain is not.)
25
/*
26
/* Could remove the effect of steeper terrain by using a fitted polynomial
27
/* rather than a simple mean as the basis for calculating the difference.
28
/* Not pursued at this time because the application is to control where
29
/* vegetation edges are visible in SRTM data, and high relief areas also
30
/* obscure the veg edges, so including the high relief as "high noise"
31
/* is not a problem.
32

  
33
setwindow %DEMgrid%
34
setcell %DEMgrid%
35

  
36
noisemean = focalmean(%DEMgrid% , annulus, 1, 2)
37
noisediffmn = %DEMgrid%  - noisemean
38
noiseraw = focalstd(noisediffmn, circle, 2)
39
/* setcell minof
40
/* noisemag2 = focalmedian(aggregate(noiseraw, 2, median), circle, 2)
41
/* setcell %DEMgrid%
42
/* noisemag = resample(noisemag2, #, bilinear)
43

  
44
/* testing rejection of terrain signal by capturing longer-range differences from mean
45
/* noisemag_d3 looks particularly good - terrain effects almost completely absent, for both small and extended features
46

  
47
noisemndf = focalmean(noisediffmn, circle, 2)		/* calculate mean difference from mean
48
noisemndf_abs = sqrt(1 + sqr(noisemndf)) - 1		/* soft absolute value of that
49
noisemndf_mdn = focalmedian(noisemndf_abs, circle, 3)	/* smooth, this is the terrain signal
50
noiseraw_rdc = noiseraw / (1 + 2 * mndiffmn2bfmd)	/* reduce noiseraw by the terrain signal
51

  
52
setcell minof
53
noisemag_2_2 = focalmedian(aggregate(noiseraw_rdc, 2, median), circle, 2)
54
setcell %DEMgrid%
55
noisemag = resample(noisemag_2_2, #, bilinear)
terrain/research/oregon/noisemag3sec.py
1
#!/usr/bin/env python
2

  
3
############################################################################
4
# MODULE:   r.noisemag3sec 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: Calculates a measure of surface noisiness
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: Noise standard deviation output raster map
29
#%  required : yes
30
#%end
31
#%option
32
#%  key: region
33
#%  type: string
34
#%  gisprompt: old,cell,raster
35
#%  description: Map used to set extent; default is to use current region
36
#%  required : no
37
#%end
38

  
39
# NOTES
40
# - make GRASS be quiet with this:
41
#     gs.os.environ['GRASS_VERBOSE'] = '0'
42

  
43
import atexit
44
import sys
45
import math
46
import grass.script as gs
47
import tempfile
48

  
49
# create set to store names of temporary maps to be deleted upon exit
50
tmp_rast = set()
51

  
52
def cleanup():
53
    gs.message("Removing temporary files...", flag='i')
54
    for rast in tmp_rast:
55
        gs.run_command("g.remove", rast=rast, quiet=True)
56

  
57
def coarsen_region(factor=3):
58
    gs.run_command('g.region',
59
        rows=gs.region()['rows']/factor,
60
        cols=gs.region()['cols']/factor)
61

  
62
def refine_region(factor=3):
63
    gs.run_command('g.region',
64
        rows=gs.region()['rows']*factor,
65
        cols=gs.region()['cols']*factor)
66

  
67
def calculate_noise(input, output):
68

  
69
    # create temporary annulus weights file
70
    weightfile = tempfile.NamedTemporaryFile()
71
    weightfile.write('0 0 1 0 0\n'
72
                   + '0 1 1 1 0\n'
73
                   + '1 1 0 1 1\n'
74
                   + '0 1 1 1 0\n'
75
                   + '0 0 1 0 0\n')
76
    weightfile.flush()
77

  
78
    #todo: do we want flag -a (don't align output with input)?
79
    gs.run_command('r.neighbors', input=input, output='noisemean',
80
        method='average', weight=weightfile.name, size=5, quiet=True)
81
    tmp_rast.add('noisemean')
82

  
83
    gs.mapcalc('noisediffmn = %s - noisemean' % input)
84
    tmp_rast.add('noisediffmn')
85

  
86
    gs.run_command('r.neighbors', input='noisediffmn',
87
        output='noiseraw', method='stddev', flags='c', size=5,
88
        quiet=True)
89
    tmp_rast.add('noiseraw')
90

  
91
    # [jg] 'calculate mean difference from mean'
92
    #noisemndf = focalmean(noisediffmn, circle, 2)
93
    gs.run_command('r.neighbors', input='noisediffmn',
94
        output='noisemndf', method='average', flags='c', size=5,
95
        quiet=True)
96
    tmp_rast.add('noisemndf')
97
    # [jg] 'soft absolute value of that'
98
    #noisemndf_abs = sqrt(1 + sqr(noisemndf)) - 1
99
    gs.mapcalc('noisemndf_abs = sqrt(1 + pow(noisemndf,2)) - 1')
100
    tmp_rast.add('noisemndf_abs')
101

  
102
    # [jg] 'smooth, this is the terrain signal'
103
    #noisemndf_mdn = focalmedian(noisemndf_abs, circle, 3)
104
    gs.run_command('r.neighbors', input='noisemndf_abs',
105
        output='noisemndf_mdn', method='median', flags='c', size=7,
106
        quiet=True)
107
    tmp_rast.add('noisemndf_mdn')
108

  
109
    # [jg] 'reduce noiseraw by the terrain signal'
110
    #noiseraw_rdc = noiseraw / (1 + 2 * mndiffmn2bfmd)
111
    # jr: looks like a typo in the 2nd input grid name above...
112
    gs.mapcalc('noiseraw_rdc = noiseraw / (1 + 2 * noisemndf_mdn)')
113
    tmp_rast.add('noiseraw_rdc')
114

  
115
    #setcell minof
116
    #noisemag_2_2 = focalmedian(aggregate(noiseraw_rdc, 2, median), circle, 2)
117
    coarsen_region(factor=2)
118
    gs.run_command('r.resamp.stats', method='median',
119
        input='noiseraw_rdc', output='tmp', quiet=True)
120
    tmp_rast.add('tmp')
121
    #todo: check if we're doing this next one at the right resolution...
122
    gs.run_command('r.neighbors', input='tmp',
123
        output='noisemag_2_2', method='median', flags='c', size=5,
124
        quiet=True)
125
    tmp_rast.add('noisemag_2_2')
126

  
127
    #setcell %DEMgrid%
128
    #noisemag = resample(noisemag_2_2, #, bilinear)
129
    refine_region(factor=2)
130
    gs.run_command('r.resamp.interp', method='bilinear',
131
        input='noisemag_2_2', output=output, quiet=True)
132

  
133
    cleanup()
134
    return None
135

  
136

  
137
def main():
138

  
139
    # process command options
140
    input = options['input']
141
    if not gs.find_file(input)['file']:
142
        gs.fatal(_("Raster map <%s> not found") % input)
143

  
144
    output = options['output']
145
    if gs.find_file(output)['file'] and not gs.overwrite():
146
        gs.fatal(_("Output map <%s> already exists") % output)
147

  
148
    # set aside region for internal use
149
    gs.use_temp_region()
150

  
151
    # subset input if desired
152
    region = options.get('region')
153
    if region:
154
        if not gs.find_file(region)['file']:
155
            gs.fatal(_("Raster map <%s> not found") % region)
156
        gs.message("Setting region to %s" % region, flag='i')
157
        gs.run_command('g.region', rast=region, align=input)
158
    else:
159
        gs.message("Using existing GRASS region", flag='i')
160
    gs.debug('='*50)
161
    gs.debug('\n'.join(gs.parse_command('g.region', 'p').keys()))
162
    gs.debug('='*50)
163

  
164
    calculate_noise(input, output)
165

  
166
    # restore original region
167
    gs.del_temp_region()
168

  
169
    return None
170

  
171

  
172
if __name__ == '__main__':
173
    options, flags = gs.parser()
174
    atexit.register(cleanup)
175
    main()

Also available in: Unified diff