source: grass/branches/releasebranch_7_2/scripts/i.pansharpen/i.pansharpen.py

Last change on this file was 69812, checked in by neteler, 8 years ago

Numerous typos fixed (identified with tools/fix_typos.sh) (trunk, r69811)

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:mime-type set to text/x-python
File size: 17.1 KB
Line 
1#!/usr/bin/env python
2
3############################################################################
4#
5# MODULE: i.panmethod
6#
7# AUTHOR(S): Overall script by Michael Barton (ASU)
8# Brovey transformation in i.fusion.brovey by Markus Neteler <<neteler at osgeo org>>
9# i.fusion brovey converted to Python by Glynn Clements
10# IHS and PCA transformation added by Michael Barton (ASU)
11# histogram matching algorithm by Michael Barton and Luca Delucchi, Fondazione E. Mach (Italy)
12# Thanks to Markus Metz for help with PCA inversion
13# Thanks to Hamish Bowman for parallel processing algorithm
14#
15# PURPOSE: Sharpening of 3 RGB channels using a high-resolution panchromatic channel
16#
17# COPYRIGHT: (C) 2002-2012 by the GRASS Development Team
18#
19# This program is free software under the GNU General Public
20# License (>=v2). Read the file COPYING that comes with GRASS
21# for details.
22#
23# REFERENCES:
24# Roller, N.E.G. and Cox, S., 1980. Comparison of Landsat MSS and merged MSS/RBV
25# data for analysis of natural vegetation. Proc. of the 14th International
26# Symposium on Remote Sensing of Environment, San Jose, Costa Rica, 23-30 April, pp. 1001-1007.
27#
28# Amarsaikhan, D., & Douglas, T. (2004). Data fusion and multisource image classification.
29# International Journal of Remote Sensing, 25(17), 3529-3539.
30#
31# Behnia, P. (2005). Comparison between four methods for data fusion of ETM+
32# multispectral and pan images. Geo-spatial Information Science, 8(2), 98-103
33#
34# for LANDSAT 5: see Pohl, C 1996 and others
35#
36#############################################################################
37
38#%Module
39#% description: Image fusion algorithms to sharpen multispectral with high-res panchromatic channels
40#% keyword: imagery
41#% keyword: fusion
42#% keyword: sharpen
43#% keyword: Brovey
44#% keyword: IHS
45#% keyword: HIS
46#% keyword: PCA
47#% overwrite: yes
48#%End
49#%option G_OPT_R_INPUT
50#% key: red
51#% description: Name of raster map to be used for <red>
52#%end
53#%option G_OPT_R_INPUT
54#% key: green
55#% description: Name of raster map to be used for <green>
56#%end
57#%option G_OPT_R_INPUT
58#% key: blue
59#% description: Name of raster map to be used for <blue>
60#%end
61#% option G_OPT_R_INPUT
62#% key: pan
63#% description: Name of raster map to be used for high resolution panchromatic channel
64#%end
65#%option G_OPT_R_BASENAME_OUTPUT
66#%end
67#%option
68#% key: method
69#% description: Method for pan sharpening
70#% options: brovey,ihs,pca
71#% answer: ihs
72#% required: yes
73#%end
74#%flag
75#% key: s
76#% description: Serial processing rather than parallel processing
77#%end
78#%flag
79#% key: l
80#% description: Rebalance blue channel for LANDSAT
81#%end
82
83import os
84
85try:
86 import numpy as np
87 hasNumPy = True
88except ImportError:
89 hasNumPy = False
90
91import grass.script as grass
92
93
94def main():
95 if not hasNumPy:
96 grass.fatal(_("Required dependency NumPy not found. Exiting."))
97
98 sharpen = options['method'] # sharpening algorithm
99 ms1 = options['blue'] # blue channel
100 ms2 = options['green'] # green channel
101 ms3 = options['red'] # red channel
102 pan = options['pan'] # high res pan channel
103 out = options['output'] # prefix for output RGB maps
104 bladjust = flags['l'] # adjust blue channel
105 sproc = flags['s'] # serial processing
106
107 outb = grass.core.find_file('%s_blue' % out)
108 outg = grass.core.find_file('%s_green' % out)
109 outr = grass.core.find_file('%s_red' % out)
110
111 if (outb['name'] != '' or outg['name'] != '' or outr['name'] != '') and not grass.overwrite():
112 grass.warning(_('Maps with selected output prefix names already exist.'
113 ' Delete them or use overwrite flag'))
114 return
115
116 pid = str(os.getpid())
117
118 # get PAN resolution:
119 kv = grass.raster_info(map=pan)
120 nsres = kv['nsres']
121 ewres = kv['ewres']
122 panres = (nsres + ewres) / 2
123
124 # clone current region
125 grass.use_temp_region()
126
127 grass.run_command('g.region', res=panres, align=pan)
128
129 grass.message(_("Performing pan sharpening with hi res pan image: %f" % panres))
130
131 if sharpen == "brovey":
132 grass.verbose(_("Using Brovey algorithm"))
133
134 # pan/intensity histogram matching using linear regression
135 outname = 'tmp%s_pan1' % pid
136 panmatch1 = matchhist(pan, ms1, outname)
137
138 outname = 'tmp%s_pan2' % pid
139 panmatch2 = matchhist(pan, ms2, outname)
140
141 outname = 'tmp%s_pan3' % pid
142 panmatch3 = matchhist(pan, ms3, outname)
143
144 outr = '%s_red' % out
145 outg = '%s_green' % out
146 outb = '%s_blue' % out
147
148 # calculate brovey transformation
149 grass.message(_("Calculating Brovey transformation..."))
150
151 if sproc:
152 # serial processing
153 e = '''eval(k = "$ms1" + "$ms2" + "$ms3")
154 "$outr" = 1.0 * "$ms3" * "$panmatch3" / k
155 "$outg" = 1.0 * "$ms2" * "$panmatch2" / k
156 "$outb" = 1.0 * "$ms1" * "$panmatch1" / k'''
157 grass.mapcalc(e, outr=outr, outg=outg, outb=outb,
158 panmatch1=panmatch1, panmatch2=panmatch2,
159 panmatch3=panmatch3, ms1=ms1, ms2=ms2, ms3=ms3,
160 overwrite=True)
161 else:
162 # parallel processing
163 pb = grass.mapcalc_start('%s_blue = (1.0 * %s * %s) / (%s + %s + %s)' %
164 (out, ms1, panmatch1, ms1, ms2, ms3),
165 overwrite=True)
166 pg = grass.mapcalc_start('%s_green = (1.0 * %s * %s) / (%s + %s + %s)' %
167 (out, ms2, panmatch2, ms1, ms2, ms3),
168 overwrite=True)
169 pr = grass.mapcalc_start('%s_red = (1.0 * %s * %s) / (%s + %s + %s)' %
170 (out, ms3, panmatch3, ms1, ms2, ms3),
171 overwrite=True)
172
173 pb.wait()
174 pg.wait()
175 pr.wait()
176
177 # Cleanup
178 grass.run_command('g.remove', flags='f', quiet=True, type='raster',
179 name='%s,%s,%s' % (panmatch1, panmatch2, panmatch3))
180
181 elif sharpen == "ihs":
182 grass.verbose(_("Using IHS<->RGB algorithm"))
183 # transform RGB channels into IHS color space
184 grass.message(_("Transforming to IHS color space..."))
185 grass.run_command('i.rgb.his', overwrite=True,
186 red=ms3,
187 green=ms2,
188 blue=ms1,
189 hue="tmp%s_hue" % pid,
190 intensity="tmp%s_int" % pid,
191 saturation="tmp%s_sat" % pid)
192
193 # pan/intensity histogram matching using linear regression
194 target = "tmp%s_int" % pid
195 outname = "tmp%s_pan_int" % pid
196 panmatch = matchhist(pan, target, outname)
197
198 # substitute pan for intensity channel and transform back to RGB color space
199 grass.message(_("Transforming back to RGB color space and sharpening..."))
200 grass.run_command('i.his.rgb', overwrite=True,
201 hue="tmp%s_hue" % pid,
202 intensity="%s" % panmatch,
203 saturation="tmp%s_sat" % pid,
204 red="%s_red" % out,
205 green="%s_green" % out,
206 blue="%s_blue" % out)
207
208 # Cleanup
209 grass.run_command('g.remove', flags='f', quiet=True, type='raster',
210 name=panmatch)
211
212 elif sharpen == "pca":
213 grass.verbose(_("Using PCA/inverse PCA algorithm"))
214 grass.message(_("Creating PCA images and calculating eigenvectors..."))
215
216 # initial PCA with RGB channels
217 pca_out = grass.read_command('i.pca', quiet=True, rescale='0,0',
218 input='%s,%s,%s' % (ms1, ms2, ms3),
219 output='tmp%s.pca' % pid)
220 if len(pca_out) < 1:
221 grass.fatal(_("Input has no data. Check region settings."))
222
223 b1evect = []
224 b2evect = []
225 b3evect = []
226 for l in pca_out.replace('(', ',').replace(')', ',').splitlines():
227 b1evect.append(float(l.split(',')[1]))
228 b2evect.append(float(l.split(',')[2]))
229 b3evect.append(float(l.split(',')[3]))
230
231 # inverse PCA with hi res pan channel substituted for principal component 1
232 pca1 = 'tmp%s.pca.1' % pid
233 pca2 = 'tmp%s.pca.2' % pid
234 pca3 = 'tmp%s.pca.3' % pid
235 b1evect1 = b1evect[0]
236 b1evect2 = b1evect[1]
237 b1evect3 = b1evect[2]
238 b2evect1 = b2evect[0]
239 b2evect2 = b2evect[1]
240 b2evect3 = b2evect[2]
241 b3evect1 = b3evect[0]
242 b3evect2 = b3evect[1]
243 b3evect3 = b3evect[2]
244
245 outname = 'tmp%s_pan' % pid
246 panmatch = matchhist(pan, ms1, outname)
247
248 grass.message(_("Performing inverse PCA ..."))
249
250 stats1 = grass.parse_command("r.univar", map=ms1, flags='g',
251 parse=(grass.parse_key_val,
252 {'sep': '='}))
253 stats2 = grass.parse_command("r.univar", map=ms2, flags='g',
254 parse=(grass.parse_key_val,
255 {'sep': '='}))
256 stats3 = grass.parse_command("r.univar", map=ms3, flags='g',
257 parse=(grass.parse_key_val,
258 {'sep': '='}))
259
260 b1mean = float(stats1['mean'])
261 b2mean = float(stats2['mean'])
262 b3mean = float(stats3['mean'])
263
264 if sproc:
265 # serial processing
266 e = '''eval(k = "$ms1" + "$ms2" + "$ms3")
267 "$outr" = 1.0 * "$ms3" * "$panmatch3" / k
268 "$outg" = 1.0 * "$ms2" * "$panmatch2" / k
269 "$outb" = 1.0* "$ms1" * "$panmatch1" / k'''
270
271 outr = '%s_red' % out
272 outg = '%s_green' % out
273 outb = '%s_blue' % out
274
275 cmd1 = "$outb = (1.0 * $panmatch * $b1evect1) + ($pca2 * $b2evect1) + ($pca3 * $b3evect1) + $b1mean"
276 cmd2 = "$outg = (1.0 * $panmatch * $b1evect2) + ($pca2 * $b2evect1) + ($pca3 * $b3evect2) + $b2mean"
277 cmd3 = "$outr = (1.0 * $panmatch * $b1evect3) + ($pca2 * $b2evect3) + ($pca3 * $b3evect3) + $b3mean"
278
279 cmd = '\n'.join([cmd1, cmd2, cmd3])
280
281 grass.mapcalc(cmd, outb=outb, outg=outg, outr=outr,
282 panmatch=panmatch, pca2=pca2, pca3=pca3,
283 b1evect1=b1evect1, b2evect1=b2evect1, b3evect1=b3evect1,
284 b1evect2=b1evect2, b2evect2=b2evect2, b3evect2=b3evect2,
285 b1evect3=b1evect3, b2evect3=b2evect3, b3evect3=b3evect3,
286 b1mean=b1mean, b2mean=b2mean, b3mean=b3mean,
287 overwrite=True)
288 else:
289 # parallel processing
290 pb = grass.mapcalc_start('%s_blue = (%s * %f) + (%s * %f) + (%s * %f) + %f'
291 % (out, panmatch, b1evect1, pca2,
292 b2evect1, pca3, b3evect1, b1mean),
293 overwrite=True)
294
295 pg = grass.mapcalc_start('%s_green = (%s * %f) + (%s * %f) + (%s * %f) + %f'
296 % (out, panmatch, b1evect2, pca2,
297 b2evect2, pca3, b3evect2, b2mean),
298 overwrite=True)
299
300 pr = grass.mapcalc_start('%s_red = (%s * %f) + (%s * %f) + (%s * ''%f) + %f'
301 % (out, panmatch, b1evect3, pca2,
302 b2evect3, pca3, b3evect3, b3mean),
303 overwrite=True)
304
305 pr.wait()
306 pg.wait()
307 pb.wait()
308
309 # Cleanup
310 grass.run_command('g.remove', flags='f', quiet=True, type="raster",
311 pattern='tmp%s*,%s' % (pid, panmatch))
312
313 # Could add other sharpening algorithms here, e.g. wavelet transformation
314
315 grass.message(_("Assigning grey equalized color tables to output images..."))
316 # equalized grey scales give best contrast
317 for ch in ['red', 'green', 'blue']:
318 grass.run_command('r.colors', quiet=True, map="%s_%s" % (out, ch),
319 flags="e", color='grey')
320
321 # Landsat too blue-ish because panchromatic band less sensitive to blue
322 # light, so output blue channed can be modified
323 if bladjust:
324 grass.message(_("Adjusting blue channel color table..."))
325 rules = grass.tempfile()
326 colors = open(rules, 'w')
327 colors.write('5 0 0 0\n20 200 200 200\n40 230 230 230\n67 255 255 255 \n')
328 colors.close()
329
330 grass.run_command('r.colors', map="%s_blue" % out, rules=rules)
331 os.remove(rules)
332
333 # output notice
334 grass.verbose(_("The following pan-sharpened output maps have been generated:"))
335 for ch in ['red', 'green', 'blue']:
336 grass.verbose(_("%s_%s") % (out, ch))
337
338 grass.verbose(_("To visualize output, run: g.region -p raster=%s_red" % out))
339 grass.verbose(_("d.rgb r=%s_red g=%s_green b=%s_blue" % (out, out, out)))
340 grass.verbose(_("If desired, combine channels into a single RGB map with 'r.composite'."))
341 grass.verbose(_("Channel colors can be rebalanced using i.colors.enhance."))
342
343 # write cmd history:
344 for ch in ['red', 'green', 'blue']:
345 grass.raster_history("%s_%s" % (out, ch))
346
347 # create a group with the three output
348 grass.run_command('i.group', group=out,
349 input="{n}_red,{n}_blue,{n}_green".format(n=out))
350
351 # Cleanup
352 grass.run_command('g.remove', flags="f", type="raster",
353 pattern="tmp%s*" % pid, quiet=True)
354
355
356def matchhist(original, target, matched):
357 # pan/intensity histogram matching using numpy arrays
358 grass.message(_("Histogram matching..."))
359
360 # input images
361 original = original.split('@')[0]
362 target = target.split('@')[0]
363 images = [original, target]
364
365 # create a dictionary to hold arrays for each image
366 arrays = {}
367
368 for i in images:
369 # calculate number of cells for each grey value for for each image
370 stats_out = grass.pipe_command('r.stats', flags='cin', input=i,
371 sep=':')
372 stats = stats_out.communicate()[0].split('\n')[:-1]
373 stats_dict = dict(s.split(':', 1) for s in stats)
374 total_cells = 0 # total non-null cells
375 for j in stats_dict:
376 stats_dict[j] = int(stats_dict[j])
377 if j != '*':
378 total_cells += stats_dict[j]
379
380 if total_cells < 1:
381 grass.fatal(_("Input has no data. Check region settings."))
382
383 # Make a 2x256 structured array for each image with a
384 # cumulative distribution function (CDF) for each grey value.
385 # Grey value is the integer (i4) and cdf is float (f4).
386
387 arrays[i] = np.zeros((256, ), dtype=('i4,f4'))
388 cum_cells = 0 # cumulative total of cells for sum of current and all lower grey values
389
390 for n in range(0, 256):
391 if str(n) in stats_dict:
392 num_cells = stats_dict[str(n)]
393 else:
394 num_cells = 0
395
396 cum_cells += num_cells
397
398 # cdf is the the number of cells at or below a given grey value
399 # divided by the total number of cells
400 cdf = float(cum_cells) / float(total_cells)
401
402 # insert values into array
403 arrays[i][n] = (n, cdf)
404
405 # open file for reclass rules
406 outfile = open(grass.tempfile(), 'w')
407
408 for i in arrays[original]:
409 # for each grey value and corresponding cdf value in original, find the
410 # cdf value in target that is closest to the target cdf value
411 difference_list = []
412 for j in arrays[target]:
413 # make a list of the difference between each original cdf value and
414 # the target cdf value
415 difference_list.append(abs(i[1] - j[1]))
416
417 # get the smallest difference in the list
418 min_difference = min(difference_list)
419
420 for j in arrays[target]:
421 # find the grey value in target that corresponds to the cdf
422 # closest to the original cdf
423 if j[1] == i[1] + min_difference or j[1] == i[1] - min_difference:
424 # build a reclass rules file from the original grey value and
425 # corresponding grey value from target
426 out_line = "%d = %d\n" % (i[0], j[0])
427 outfile.write(out_line)
428 break
429
430 outfile.close()
431
432 # create reclass of target from reclass rules file
433 result = grass.core.find_file(matched, element='cell')
434 if result['fullname']:
435 grass.run_command('g.remove', flags='f', quiet=True, type='raster',
436 name=matched)
437 grass.run_command('r.reclass', input=original, out=matched,
438 rules=outfile.name)
439 else:
440 grass.run_command('r.reclass', input=original, out=matched,
441 rules=outfile.name)
442
443 # Cleanup
444 # remove the rules file
445 grass.try_remove(outfile.name)
446
447 # return reclass of target with histogram that matches original
448 return matched
449
450if __name__ == "__main__":
451 options, flags = grass.parser()
452 main()
Note: See TracBrowser for help on using the repository browser.