i.pansharpen.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. #!/usr/bin/env python3
  2. ############################################################################
  3. #
  4. # MODULE: i.pansharpen
  5. #
  6. # AUTHOR(S): Overall script by Michael Barton (ASU)
  7. # Brovey transformation in i.fusion.brovey by Markus Neteler <<neteler at osgeo org>>
  8. # i.fusion brovey converted to Python by Glynn Clements
  9. # IHS and PCA transformation added by Michael Barton (ASU)
  10. # histogram matching algorithm by Michael Barton and Luca Delucchi, Fondazione E. Mach (Italy)
  11. # Thanks to Markus Metz for help with PCA inversion
  12. # Thanks to Hamish Bowman for parallel processing algorithm
  13. #
  14. # PURPOSE: Sharpening of 3 RGB channels using a high-resolution panchromatic channel
  15. #
  16. # COPYRIGHT: (C) 2002-2019 by the GRASS Development Team
  17. #
  18. # This program is free software under the GNU General Public
  19. # License (>=v2). Read the file COPYING that comes with GRASS
  20. # for details.
  21. #
  22. # REFERENCES:
  23. # Roller, N.E.G. and Cox, S., 1980. Comparison of Landsat MSS and merged MSS/RBV
  24. # data for analysis of natural vegetation. Proc. of the 14th International
  25. # Symposium on Remote Sensing of Environment, San Jose, Costa Rica, 23-30 April, pp. 1001-1007.
  26. #
  27. # Amarsaikhan, D., & Douglas, T. (2004). Data fusion and multisource image classification.
  28. # International Journal of Remote Sensing, 25(17), 3529-3539.
  29. #
  30. # Behnia, P. (2005). Comparison between four methods for data fusion of ETM+
  31. # multispectral and pan images. Geo-spatial Information Science, 8(2), 98-103
  32. #
  33. # for LANDSAT 5: see Pohl, C 1996 and others
  34. #
  35. #############################################################################
  36. # %Module
  37. # % description: Image fusion algorithms to sharpen multispectral with high-res panchromatic channels
  38. # % keyword: imagery
  39. # % keyword: fusion
  40. # % keyword: sharpen
  41. # % keyword: Brovey
  42. # % keyword: IHS
  43. # % keyword: HIS
  44. # % keyword: PCA
  45. # % overwrite: yes
  46. # %End
  47. # %option G_OPT_R_INPUT
  48. # % key: red
  49. # % description: Name of raster map to be used for <red>
  50. # %end
  51. # %option G_OPT_R_INPUT
  52. # % key: green
  53. # % description: Name of raster map to be used for <green>
  54. # %end
  55. # %option G_OPT_R_INPUT
  56. # % key: blue
  57. # % description: Name of raster map to be used for <blue>
  58. # %end
  59. # % option G_OPT_R_INPUT
  60. # % key: pan
  61. # % description: Name of raster map to be used for high resolution panchromatic channel
  62. # %end
  63. # %option G_OPT_R_BASENAME_OUTPUT
  64. # %end
  65. # %option
  66. # % key: method
  67. # % description: Method for pan sharpening
  68. # % options: brovey,ihs,pca
  69. # % answer: ihs
  70. # % required: yes
  71. # %end
  72. # %option
  73. # % key: bitdepth
  74. # % type: integer
  75. # % description: Bit depth of image (must be in range of 2-30)
  76. # % options: 2-32
  77. # % answer: 8
  78. # % required: yes
  79. # %end
  80. # %flag
  81. # % key: s
  82. # % description: Serial processing rather than parallel processing
  83. # %end
  84. # %flag
  85. # % key: l
  86. # % description: Rebalance blue channel for LANDSAT
  87. # %end
  88. # %flag
  89. # % key: r
  90. # % description: Rescale (stretch) the range of pixel values in each channel to the entire 0-255 8-bit range for processing (see notes)
  91. # %end
  92. import os
  93. try:
  94. import numpy as np
  95. hasNumPy = True
  96. except ImportError:
  97. hasNumPy = False
  98. import grass.script as grass
  99. def main():
  100. if not hasNumPy:
  101. grass.fatal(_("Required dependency NumPy not found. Exiting."))
  102. sharpen = options["method"] # sharpening algorithm
  103. ms1_orig = options["blue"] # blue channel
  104. ms2_orig = options["green"] # green channel
  105. ms3_orig = options["red"] # red channel
  106. pan_orig = options["pan"] # high res pan channel
  107. out = options["output"] # prefix for output RGB maps
  108. bits = options["bitdepth"] # bit depth of image channels
  109. bladjust = flags["l"] # adjust blue channel
  110. sproc = flags["s"] # serial processing
  111. rescale = flags["r"] # rescale to spread pixel values to entire 0-255 range
  112. # Checking bit depth
  113. bits = float(bits)
  114. if bits < 2 or bits > 30:
  115. grass.warning(_("Bit depth is outside acceptable range"))
  116. return
  117. outb = grass.core.find_file("%s_blue" % out)
  118. outg = grass.core.find_file("%s_green" % out)
  119. outr = grass.core.find_file("%s_red" % out)
  120. if (
  121. outb["name"] != "" or outg["name"] != "" or outr["name"] != ""
  122. ) and not grass.overwrite():
  123. grass.warning(
  124. _(
  125. "Maps with selected output prefix names already exist."
  126. " Delete them or use overwrite flag"
  127. )
  128. )
  129. return
  130. pid = str(os.getpid())
  131. # convert input image channels to 8 bit for processing
  132. ms1 = "tmp%s_ms1" % pid
  133. ms2 = "tmp%s_ms2" % pid
  134. ms3 = "tmp%s_ms3" % pid
  135. pan = "tmp%s_pan" % pid
  136. if not rescale:
  137. if bits == 8:
  138. grass.message(_("Using 8bit image channels"))
  139. if sproc:
  140. # serial processing
  141. grass.run_command(
  142. "g.copy",
  143. raster="%s,%s" % (ms1_orig, ms1),
  144. quiet=True,
  145. overwrite=True,
  146. )
  147. grass.run_command(
  148. "g.copy",
  149. raster="%s,%s" % (ms2_orig, ms2),
  150. quiet=True,
  151. overwrite=True,
  152. )
  153. grass.run_command(
  154. "g.copy",
  155. raster="%s,%s" % (ms3_orig, ms3),
  156. quiet=True,
  157. overwrite=True,
  158. )
  159. grass.run_command(
  160. "g.copy",
  161. raster="%s,%s" % (pan_orig, pan),
  162. quiet=True,
  163. overwrite=True,
  164. )
  165. else:
  166. # parallel processing
  167. pb = grass.start_command(
  168. "g.copy",
  169. raster="%s,%s" % (ms1_orig, ms1),
  170. quiet=True,
  171. overwrite=True,
  172. )
  173. pg = grass.start_command(
  174. "g.copy",
  175. raster="%s,%s" % (ms2_orig, ms2),
  176. quiet=True,
  177. overwrite=True,
  178. )
  179. pr = grass.start_command(
  180. "g.copy",
  181. raster="%s,%s" % (ms3_orig, ms3),
  182. quiet=True,
  183. overwrite=True,
  184. )
  185. pp = grass.start_command(
  186. "g.copy",
  187. raster="%s,%s" % (pan_orig, pan),
  188. quiet=True,
  189. overwrite=True,
  190. )
  191. pb.wait()
  192. pg.wait()
  193. pr.wait()
  194. pp.wait()
  195. else:
  196. grass.message(_("Converting image chanels to 8bit for processing"))
  197. maxval = pow(2, bits) - 1
  198. if sproc:
  199. # serial processing
  200. grass.run_command(
  201. "r.rescale",
  202. input=ms1_orig,
  203. from_="0,%f" % maxval,
  204. output=ms1,
  205. to="0,255",
  206. quiet=True,
  207. overwrite=True,
  208. )
  209. grass.run_command(
  210. "r.rescale",
  211. input=ms2_orig,
  212. from_="0,%f" % maxval,
  213. output=ms2,
  214. to="0,255",
  215. quiet=True,
  216. overwrite=True,
  217. )
  218. grass.run_command(
  219. "r.rescale",
  220. input=ms3_orig,
  221. from_="0,%f" % maxval,
  222. output=ms3,
  223. to="0,255",
  224. quiet=True,
  225. overwrite=True,
  226. )
  227. grass.run_command(
  228. "r.rescale",
  229. input=pan_orig,
  230. from_="0,%f" % maxval,
  231. output=pan,
  232. to="0,255",
  233. quiet=True,
  234. overwrite=True,
  235. )
  236. else:
  237. # parallel processing
  238. pb = grass.start_command(
  239. "r.rescale",
  240. input=ms1_orig,
  241. from_="0,%f" % maxval,
  242. output=ms1,
  243. to="0,255",
  244. quiet=True,
  245. overwrite=True,
  246. )
  247. pg = grass.start_command(
  248. "r.rescale",
  249. input=ms2_orig,
  250. from_="0,%f" % maxval,
  251. output=ms2,
  252. to="0,255",
  253. quiet=True,
  254. overwrite=True,
  255. )
  256. pr = grass.start_command(
  257. "r.rescale",
  258. input=ms3_orig,
  259. from_="0,%f" % maxval,
  260. output=ms3,
  261. to="0,255",
  262. quiet=True,
  263. overwrite=True,
  264. )
  265. pp = grass.start_command(
  266. "r.rescale",
  267. input=pan_orig,
  268. from_="0,%f" % maxval,
  269. output=pan,
  270. to="0,255",
  271. quiet=True,
  272. overwrite=True,
  273. )
  274. pb.wait()
  275. pg.wait()
  276. pr.wait()
  277. pp.wait()
  278. else:
  279. grass.message(_("Rescaling image chanels to 8bit for processing"))
  280. min_ms1 = int(grass.raster_info(ms1_orig)["min"])
  281. max_ms1 = int(grass.raster_info(ms1_orig)["max"])
  282. min_ms2 = int(grass.raster_info(ms2_orig)["min"])
  283. max_ms2 = int(grass.raster_info(ms2_orig)["max"])
  284. min_ms3 = int(grass.raster_info(ms3_orig)["min"])
  285. max_ms3 = int(grass.raster_info(ms3_orig)["max"])
  286. min_pan = int(grass.raster_info(pan_orig)["min"])
  287. max_pan = int(grass.raster_info(pan_orig)["max"])
  288. maxval = pow(2, bits) - 1
  289. if sproc:
  290. # serial processing
  291. grass.run_command(
  292. "r.rescale",
  293. input=ms1_orig,
  294. from_="%f,%f" % (min_ms1, max_ms1),
  295. output=ms1,
  296. to="0,255",
  297. quiet=True,
  298. overwrite=True,
  299. )
  300. grass.run_command(
  301. "r.rescale",
  302. input=ms2_orig,
  303. from_="%f,%f" % (min_ms2, max_ms2),
  304. output=ms2,
  305. to="0,255",
  306. quiet=True,
  307. overwrite=True,
  308. )
  309. grass.run_command(
  310. "r.rescale",
  311. input=ms3_orig,
  312. from_="%f,%f" % (min_ms3, max_ms3),
  313. output=ms3,
  314. to="0,255",
  315. quiet=True,
  316. overwrite=True,
  317. )
  318. grass.run_command(
  319. "r.rescale",
  320. input=pan_orig,
  321. from_="%f,%f" % (min_pan, max_pan),
  322. output=pan,
  323. to="0,255",
  324. quiet=True,
  325. overwrite=True,
  326. )
  327. else:
  328. # parallel processing
  329. pb = grass.start_command(
  330. "r.rescale",
  331. input=ms1_orig,
  332. from_="%f,%f" % (min_ms1, max_ms1),
  333. output=ms1,
  334. to="0,255",
  335. quiet=True,
  336. overwrite=True,
  337. )
  338. pg = grass.start_command(
  339. "r.rescale",
  340. input=ms2_orig,
  341. from_="%f,%f" % (min_ms2, max_ms2),
  342. output=ms2,
  343. to="0,255",
  344. quiet=True,
  345. overwrite=True,
  346. )
  347. pr = grass.start_command(
  348. "r.rescale",
  349. input=ms3_orig,
  350. from_="%f,%f" % (min_ms3, max_ms3),
  351. output=ms3,
  352. to="0,255",
  353. quiet=True,
  354. overwrite=True,
  355. )
  356. pp = grass.start_command(
  357. "r.rescale",
  358. input=pan_orig,
  359. from_="%f,%f" % (min_pan, max_pan),
  360. output=pan,
  361. to="0,255",
  362. quiet=True,
  363. overwrite=True,
  364. )
  365. pb.wait()
  366. pg.wait()
  367. pr.wait()
  368. pp.wait()
  369. # get PAN resolution:
  370. kv = grass.raster_info(map=pan)
  371. nsres = kv["nsres"]
  372. ewres = kv["ewres"]
  373. panres = (nsres + ewres) / 2
  374. # clone current region
  375. grass.use_temp_region()
  376. grass.run_command("g.region", res=panres, align=pan)
  377. # Select sharpening method
  378. grass.message(_("Performing pan sharpening with hi res pan image: %f" % panres))
  379. if sharpen == "brovey":
  380. brovey(pan, ms1, ms2, ms3, out, pid, sproc)
  381. elif sharpen == "ihs":
  382. ihs(pan, ms1, ms2, ms3, out, pid, sproc)
  383. elif sharpen == "pca":
  384. pca(pan, ms1, ms2, ms3, out, pid, sproc)
  385. # Could add other sharpening algorithms here, e.g. wavelet transformation
  386. grass.message(_("Assigning grey equalized color tables to output images..."))
  387. # equalized grey scales give best contrast
  388. grass.message(_("setting pan-sharpened channels to equalized grey scale"))
  389. for ch in ["red", "green", "blue"]:
  390. grass.run_command(
  391. "r.colors", quiet=True, map="%s_%s" % (out, ch), flags="e", color="grey"
  392. )
  393. # Landsat too blue-ish because panchromatic band less sensitive to blue
  394. # light, so output blue channed can be modified
  395. if bladjust:
  396. grass.message(_("Adjusting blue channel color table..."))
  397. blue_colors = ["0 0 0 0\n5% 0 0 0\n67% 255 255 255\n100% 255 255 255"]
  398. # these previous colors are way too blue for landsat
  399. # blue_colors = ['0 0 0 0\n10% 0 0 0\n20% 200 200 200\n40% 230 230 230\n67% 255 255 255\n100% 255 255 255']
  400. bc = grass.feed_command("r.colors", quiet=True, map="%s_blue" % out, rules="-")
  401. bc.stdin.write(grass.encode("\n".join(blue_colors)))
  402. bc.stdin.close()
  403. # output notice
  404. grass.verbose(_("The following pan-sharpened output maps have been generated:"))
  405. for ch in ["red", "green", "blue"]:
  406. grass.verbose(_("%s_%s") % (out, ch))
  407. grass.verbose(_("To visualize output, run: g.region -p raster=%s_red" % out))
  408. grass.verbose(_("d.rgb r=%s_red g=%s_green b=%s_blue" % (out, out, out)))
  409. grass.verbose(
  410. _("If desired, combine channels into a single RGB map with 'r.composite'.")
  411. )
  412. grass.verbose(_("Channel colors can be rebalanced using i.colors.enhance."))
  413. # write cmd history:
  414. for ch in ["red", "green", "blue"]:
  415. grass.raster_history("%s_%s" % (out, ch))
  416. # create a group with the three outputs
  417. # grass.run_command('i.group', group=out,
  418. # input="{n}_red,{n}_blue,{n}_green".format(n=out))
  419. # Cleanup
  420. grass.message(_("cleaning up temp files"))
  421. try:
  422. grass.run_command(
  423. "g.remove", flags="f", type="raster", pattern="tmp%s*" % pid, quiet=True
  424. )
  425. except:
  426. pass
  427. def brovey(pan, ms1, ms2, ms3, out, pid, sproc):
  428. grass.verbose(_("Using Brovey algorithm"))
  429. # pan/intensity histogram matching using linear regression
  430. grass.message(_("Pan channel/intensity histogram matching using linear regression"))
  431. outname = "tmp%s_pan1" % pid
  432. panmatch1 = matchhist(pan, ms1, outname)
  433. outname = "tmp%s_pan2" % pid
  434. panmatch2 = matchhist(pan, ms2, outname)
  435. outname = "tmp%s_pan3" % pid
  436. panmatch3 = matchhist(pan, ms3, outname)
  437. outr = "%s_red" % out
  438. outg = "%s_green" % out
  439. outb = "%s_blue" % out
  440. # calculate brovey transformation
  441. grass.message(_("Calculating Brovey transformation..."))
  442. if sproc:
  443. # serial processing
  444. e = """eval(k = "$ms1" + "$ms2" + "$ms3")
  445. "$outr" = 1 * round("$ms3" * "$panmatch3" / k)
  446. "$outg" = 1 * round("$ms2" * "$panmatch2" / k)
  447. "$outb" = 1 * round("$ms1" * "$panmatch1" / k)"""
  448. grass.mapcalc(
  449. e,
  450. outr=outr,
  451. outg=outg,
  452. outb=outb,
  453. panmatch1=panmatch1,
  454. panmatch2=panmatch2,
  455. panmatch3=panmatch3,
  456. ms1=ms1,
  457. ms2=ms2,
  458. ms3=ms3,
  459. overwrite=True,
  460. )
  461. else:
  462. # parallel processing
  463. pb = grass.mapcalc_start(
  464. "%s_blue = 1 * round((%s * %s) / (%s + %s + %s))"
  465. % (out, ms1, panmatch1, ms1, ms2, ms3),
  466. overwrite=True,
  467. )
  468. pg = grass.mapcalc_start(
  469. "%s_green = 1 * round((%s * %s) / (%s + %s + %s))"
  470. % (out, ms2, panmatch2, ms1, ms2, ms3),
  471. overwrite=True,
  472. )
  473. pr = grass.mapcalc_start(
  474. "%s_red = 1 * round((%s * %s) / (%s + %s + %s))"
  475. % (out, ms3, panmatch3, ms1, ms2, ms3),
  476. overwrite=True,
  477. )
  478. pb.wait(), pg.wait(), pr.wait()
  479. try:
  480. pb.terminate(), pg.terminate(), pr.terminate()
  481. except:
  482. pass
  483. # Cleanup
  484. try:
  485. grass.run_command(
  486. "g.remove",
  487. flags="f",
  488. quiet=True,
  489. type="raster",
  490. name="%s,%s,%s" % (panmatch1, panmatch2, panmatch3),
  491. )
  492. except:
  493. pass
  494. def ihs(pan, ms1, ms2, ms3, out, pid, sproc):
  495. grass.verbose(_("Using IHS<->RGB algorithm"))
  496. # transform RGB channels into IHS color space
  497. grass.message(_("Transforming to IHS color space..."))
  498. grass.run_command(
  499. "i.rgb.his",
  500. overwrite=True,
  501. red=ms3,
  502. green=ms2,
  503. blue=ms1,
  504. hue="tmp%s_hue" % pid,
  505. intensity="tmp%s_int" % pid,
  506. saturation="tmp%s_sat" % pid,
  507. )
  508. # pan/intensity histogram matching using linear regression
  509. target = "tmp%s_int" % pid
  510. outname = "tmp%s_pan_int" % pid
  511. panmatch = matchhist(pan, target, outname)
  512. # substitute pan for intensity channel and transform back to RGB color space
  513. grass.message(_("Transforming back to RGB color space and sharpening..."))
  514. grass.run_command(
  515. "i.his.rgb",
  516. overwrite=True,
  517. hue="tmp%s_hue" % pid,
  518. intensity="%s" % panmatch,
  519. saturation="tmp%s_sat" % pid,
  520. red="%s_red" % out,
  521. green="%s_green" % out,
  522. blue="%s_blue" % out,
  523. )
  524. # Cleanup
  525. try:
  526. grass.run_command(
  527. "g.remove", flags="f", quiet=True, type="raster", name=panmatch
  528. )
  529. except:
  530. pass
  531. def pca(pan, ms1, ms2, ms3, out, pid, sproc):
  532. grass.verbose(_("Using PCA/inverse PCA algorithm"))
  533. grass.message(_("Creating PCA images and calculating eigenvectors..."))
  534. # initial PCA with RGB channels
  535. pca_out = grass.read_command(
  536. "i.pca",
  537. quiet=True,
  538. rescale="0,0",
  539. input="%s,%s,%s" % (ms1, ms2, ms3),
  540. output="tmp%s.pca" % pid,
  541. )
  542. if len(pca_out) < 1:
  543. grass.fatal(_("Input has no data. Check region settings."))
  544. b1evect = []
  545. b2evect = []
  546. b3evect = []
  547. for line in pca_out.replace("(", ",").replace(")", ",").splitlines():
  548. b1evect.append(float(line.split(",")[1]))
  549. b2evect.append(float(line.split(",")[2]))
  550. b3evect.append(float(line.split(",")[3]))
  551. # inverse PCA with hi res pan channel substituted for principal component 1
  552. pca2 = "tmp%s.pca.2" % pid
  553. pca3 = "tmp%s.pca.3" % pid
  554. b1evect1 = b1evect[0]
  555. b1evect2 = b1evect[1]
  556. b1evect3 = b1evect[2]
  557. b2evect1 = b2evect[0]
  558. b2evect2 = b2evect[1]
  559. b2evect3 = b2evect[2]
  560. b3evect1 = b3evect[0]
  561. b3evect2 = b3evect[1]
  562. b3evect3 = b3evect[2]
  563. # Histogram matching
  564. outname = "tmp%s_pan1" % pid
  565. panmatch1 = matchhist(pan, ms1, outname)
  566. outname = "tmp%s_pan2" % pid
  567. panmatch2 = matchhist(pan, ms2, outname)
  568. outname = "tmp%s_pan3" % pid
  569. panmatch3 = matchhist(pan, ms3, outname)
  570. grass.message(_("Performing inverse PCA ..."))
  571. # Get mean value of each channel
  572. stats1 = grass.parse_command(
  573. "r.univar", map=ms1, flags="g", parse=(grass.parse_key_val, {"sep": "="})
  574. )
  575. stats2 = grass.parse_command(
  576. "r.univar", map=ms2, flags="g", parse=(grass.parse_key_val, {"sep": "="})
  577. )
  578. stats3 = grass.parse_command(
  579. "r.univar", map=ms3, flags="g", parse=(grass.parse_key_val, {"sep": "="})
  580. )
  581. b1mean = float(stats1["mean"])
  582. b2mean = float(stats2["mean"])
  583. b3mean = float(stats3["mean"])
  584. if sproc:
  585. # serial processing
  586. outr = "%s_red" % out
  587. outg = "%s_green" % out
  588. outb = "%s_blue" % out
  589. cmd1 = "$outb = 1 * round(($panmatch1 * $b1evect1) + ($pca2 * $b1evect2) + ($pca3 * $b1evect3) + $b1mean)"
  590. cmd2 = "$outg = 1 * round(($panmatch2 * $b2evect1) + ($pca2 * $b2evect2) + ($pca3 * $b2evect3) + $b2mean)"
  591. cmd3 = "$outr = 1 * round(($panmatch3 * $b3evect1) + ($pca2 * $b3evect2) + ($pca3 * $b3evect3) + $b3mean)"
  592. cmd = "\n".join([cmd1, cmd2, cmd3])
  593. grass.mapcalc(
  594. cmd,
  595. outb=outb,
  596. outg=outg,
  597. outr=outr,
  598. panmatch1=panmatch1,
  599. panmatch2=panmatch2,
  600. panmatch3=panmatch3,
  601. pca2=pca2,
  602. pca3=pca3,
  603. b1evect1=b1evect1,
  604. b2evect1=b2evect1,
  605. b3evect1=b3evect1,
  606. b1evect2=b1evect2,
  607. b2evect2=b2evect2,
  608. b3evect2=b3evect2,
  609. b1evect3=b1evect3,
  610. b2evect3=b2evect3,
  611. b3evect3=b3evect3,
  612. b1mean=b1mean,
  613. b2mean=b2mean,
  614. b3mean=b3mean,
  615. overwrite=True,
  616. )
  617. else:
  618. # parallel processing
  619. pb = grass.mapcalc_start(
  620. "%s_blue = 1 * round((%s * %f) + (%s * %f) + (%s * %f) + %f)"
  621. % (out, panmatch1, b1evect1, pca2, b1evect2, pca3, b1evect3, b1mean),
  622. overwrite=True,
  623. )
  624. pg = grass.mapcalc_start(
  625. "%s_green = 1 * round((%s * %f) + (%s * %f) + (%s * %f) + %f)"
  626. % (out, panmatch2, b2evect1, pca2, b2evect2, pca3, b2evect3, b2mean),
  627. overwrite=True,
  628. )
  629. pr = grass.mapcalc_start(
  630. "%s_red = 1 * round((%s * %f) + (%s * %f) + (%s * %f) + %f)"
  631. % (out, panmatch3, b3evect1, pca2, b3evect2, pca3, b3evect3, b3mean),
  632. overwrite=True,
  633. )
  634. pb.wait(), pg.wait(), pr.wait()
  635. try:
  636. pb.terminate(), pg.terminate(), pr.terminate()
  637. except:
  638. pass
  639. # Cleanup
  640. grass.run_command(
  641. "g.remove",
  642. flags="f",
  643. quiet=True,
  644. type="raster",
  645. name="%s,%s,%s" % (panmatch1, panmatch2, panmatch3),
  646. )
  647. def matchhist(original, target, matched):
  648. # pan/intensity histogram matching using numpy arrays
  649. grass.message(_("Histogram matching..."))
  650. # input images
  651. original = original.split("@")[0]
  652. target = target.split("@")[0]
  653. images = [original, target]
  654. # create a dictionary to hold arrays for each image
  655. arrays = {}
  656. for img in images:
  657. # calculate number of cells for each grey value for for each image
  658. stats_out = grass.pipe_command("r.stats", flags="cin", input=img, sep=":")
  659. stats = grass.decode(stats_out.communicate()[0]).split("\n")[:-1]
  660. stats_dict = dict(s.split(":", 1) for s in stats)
  661. total_cells = 0 # total non-null cells
  662. for j in stats_dict:
  663. stats_dict[j] = int(stats_dict[j])
  664. if j != "*":
  665. total_cells += stats_dict[j]
  666. if total_cells < 1:
  667. grass.fatal(_("Input has no data. Check region settings."))
  668. # Make a 2x256 structured array for each image with a
  669. # cumulative distribution function (CDF) for each grey value.
  670. # Grey value is the integer (i4) and cdf is float (f4).
  671. arrays[img] = np.zeros((256,), dtype=("i4,f4"))
  672. cum_cells = (
  673. 0 # cumulative total of cells for sum of current and all lower grey values
  674. )
  675. for n in range(0, 256):
  676. if str(n) in stats_dict:
  677. num_cells = stats_dict[str(n)]
  678. else:
  679. num_cells = 0
  680. cum_cells += num_cells
  681. # cdf is the the number of cells at or below a given grey value
  682. # divided by the total number of cells
  683. cdf = float(cum_cells) / float(total_cells)
  684. # insert values into array
  685. arrays[img][n] = (n, cdf)
  686. # open file for reclass rules
  687. outfile = open(grass.tempfile(), "w")
  688. for i in arrays[original]:
  689. # for each grey value and corresponding cdf value in original, find the
  690. # cdf value in target that is closest to the target cdf value
  691. difference_list = []
  692. for j in arrays[target]:
  693. # make a list of the difference between each original cdf value and
  694. # the target cdf value
  695. difference_list.append(abs(i[1] - j[1]))
  696. # get the smallest difference in the list
  697. min_difference = min(difference_list)
  698. for j in arrays[target]:
  699. # find the grey value in target that corresponds to the cdf
  700. # closest to the original cdf
  701. if j[1] <= i[1] + min_difference and j[1] >= i[1] - min_difference:
  702. # build a reclass rules file from the original grey value and
  703. # corresponding grey value from target
  704. out_line = "%d = %d\n" % (i[0], j[0])
  705. outfile.write(out_line)
  706. break
  707. outfile.close()
  708. # create reclass of target from reclass rules file
  709. result = grass.core.find_file(matched, element="cell")
  710. if result["fullname"]:
  711. grass.run_command(
  712. "g.remove", flags="f", quiet=True, type="raster", name=matched
  713. )
  714. grass.run_command("r.reclass", input=original, out=matched, rules=outfile.name)
  715. else:
  716. grass.run_command("r.reclass", input=original, out=matched, rules=outfile.name)
  717. # Cleanup
  718. # remove the rules file
  719. grass.try_remove(outfile.name)
  720. # return reclass of target with histogram that matches original
  721. return matched
  722. if __name__ == "__main__":
  723. options, flags = grass.parser()
  724. main()