create_iwave.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. #!/usr/bin/env python3
  2. """
  3. Created on Sat Mar 27 11:35:32 2010
  4. Program to interpolate filter function to correct
  5. step. Should be 2.5 nm
  6. Then output filter function in a format similar to
  7. what is needed in the Iwave.cpp file
  8. Needs numpy and scipy
  9. @author: daniel victoria, 2010
  10. contact: daniel {dot} victoria {at} gmail {dot} com
  11. usage() explains how this is supposed to work
  12. Basically it needs a .csv file with spectral response for each
  13. band in a column. First column has to be wavelength (nm)
  14. First line (and only first) is a header with Wl, followed by band names
  15. file name is used for sensor name
  16. Updated by: Anne Ghisla, 2010
  17. Bug fix (9/12/2010) by Daniel:
  18. 1) function interpolate_band was not generating the spectral response for the last value in the filter function. Fixed
  19. 2) function pretty_print was not printing the 8th value of every line, cutting the filter function short.
  20. """
  21. import os
  22. import sys
  23. import numpy as np
  24. from scipy import interpolate
  25. def usage():
  26. """How to use this..."""
  27. print("create_iwave.py <csv file>")
  28. print
  29. print("Generates filter function template for iwave.cpp from csv file. Note:")
  30. print("- csv file must have wl response for each band in each column")
  31. print("- first line must be a header with wl followed by band names")
  32. print("- all following lines will be the data.")
  33. print("If spectral response is null, leave field empty in csv file. Example:")
  34. print
  35. print("WL(nm),band 1,band 2,band 3,band 4")
  36. print("455,0.93,,,")
  37. print("485,0.94,0.00,,")
  38. print("545,0.00,0.87,0.00,")
  39. print
  40. print("This script will interpolate the filter functions to 2.5 nm steps")
  41. print("and output a cpp template file in the IWave format to be added to iwave.cpp")
  42. def read_input(csvfile):
  43. """
  44. Function to read input file
  45. return number of bands and array of values for each band
  46. should be a .csv file with the values
  47. of the filter function for each band in the sensor
  48. one column for band
  49. first line must have a header with sensor band names
  50. first column is wavelength
  51. values are those of the discrete band filter functions
  52. """
  53. infile = open(csvfile, "r")
  54. # get number of bands and band names
  55. bands = infile.readline().split(",")
  56. bands.remove(bands[0])
  57. bands[-1] = bands[-1].strip()
  58. print(" > Number of bands found: %d" % len(bands))
  59. infile.close()
  60. # create converter dictionary for import
  61. # fix nodata or \n
  62. conv = {}
  63. for b in range(len(bands)):
  64. conv[b + 1] = lambda s: float(s or 0)
  65. values = np.loadtxt(csvfile, delimiter=",", skiprows=1, converters=conv)
  66. return (bands, values)
  67. def interpolate_band(values, step=2.5):
  68. """
  69. Receive wavelength and response for one band
  70. interpolate at 2.5 nm steps
  71. return interpolated filter func
  72. and min, max wl values
  73. values must be numpy array with 2 columns
  74. """
  75. # removing nodata and invalid values
  76. w = values[:, 1] >= 0
  77. values_clean = values[w]
  78. wavelengths = values_clean[:, 0] # 1st column of input array
  79. responses = values_clean[:, 1] # 2nd column
  80. assert len(wavelengths) == len(
  81. responses
  82. ), "Number of wavelength slots and spectral responses are not equal!"
  83. # spectral responses are written out with .4f in pretty_print()
  84. # anything smaller than 0.0001 will become 0.0000 -> discard with ...
  85. rthresh = 0.0001
  86. # i.atcorr not only expects steps of 2.5 nm, it also expects
  87. # wavelentgh values to be exact multiples of 2.5 nm
  88. # in the range 250 - 4000 nm
  89. # find first response > rthresh
  90. nvalues = len(responses)
  91. start = 0
  92. i = 0
  93. while i < nvalues - 1 and responses[i] <= rthresh:
  94. i += 1
  95. start = wavelengths[i] - step
  96. # align to multiple of step
  97. nsteps = int(start / step)
  98. start = step * nsteps
  99. while start < wavelengths[0]:
  100. start += step
  101. # find last response > rthresh
  102. stop = 0
  103. i = nvalues - 1
  104. while i > 0 and responses[i] <= rthresh:
  105. i -= 1
  106. stop = wavelengths[i] + step
  107. # align to multiple of step
  108. nsteps = np.ceil(stop / step)
  109. stop = step * nsteps
  110. while stop > wavelengths[nvalues - 1]:
  111. stop -= step
  112. assert start < stop, "Empty spectral responses!"
  113. # interpolating
  114. f = interpolate.interp1d(wavelengths, responses)
  115. filter_f = f(np.arange(start, stop, step))
  116. # how many spectral responses?
  117. expected = np.ceil((stop - start) / step)
  118. assert (
  119. len(filter_f) == expected
  120. ), "Number of interpolated spectral responses not equal to expected number of interpolations"
  121. # convert limits from nanometers to micrometers
  122. lowerlimit = start / 1000
  123. upperlimit = stop / 1000
  124. return (filter_f, (lowerlimit, upperlimit))
  125. def plot_filter(values):
  126. """Plot wl response values and interpolated
  127. filter function. This is just for checking...
  128. value is a 2 column numpy array
  129. function has to be used inside Spyder python environment
  130. """
  131. filter_f, limits = interpolate_band(values)
  132. # removing nodata
  133. w = values[:, 1] >= 0
  134. response = values[w]
  135. plot(response[:, 0], response[:, 1], "ro")
  136. plot(arange(limits[0], limits[1], 2.5), filter_f)
  137. return
  138. def pretty_print(filter_f):
  139. """
  140. Create pretty string out of filter function
  141. 8 values per line, with spaces, commas and all the rest
  142. """
  143. pstring = ""
  144. for i in range(len(filter_f) + 1):
  145. if i % 8 is 0:
  146. if i is not 0:
  147. value_wo_leading_zero = ("%.4f" % (filter_f[i - 1])).lstrip("0")
  148. pstring += value_wo_leading_zero
  149. if i > 1 and i < len(filter_f):
  150. pstring += ", "
  151. if i is not 1:
  152. # trim the trailing whitespace at the end of line
  153. pstring = pstring.rstrip()
  154. pstring += "\n "
  155. else:
  156. value_wo_leading_zero = ("%.4f" % (filter_f[i - 1])).lstrip("0")
  157. pstring += value_wo_leading_zero
  158. if i < len(filter_f):
  159. pstring += ", "
  160. # trim starting \n and trailing ,
  161. pstring = pstring.lstrip("\n").rstrip(", ")
  162. return pstring
  163. def write_cpp(bands, values, sensor, folder):
  164. """
  165. from bands, values and sensor name
  166. create output file in cpp style
  167. needs other functions: interpolate_bands, pretty_print
  168. """
  169. # keep in sync with IWave::parse()
  170. rthresh = 0.01
  171. print
  172. print(" > Response peaks from interpolation to 2.5 nm steps:")
  173. # getting necessary data
  174. # single or multiple bands?
  175. if len(bands) == 1:
  176. filter_f, limits = interpolate_band(values)
  177. fi = filter_f
  178. li = limits
  179. # Get wavelength range for spectral response in band
  180. maxresponse_idx = np.argmax(fi)
  181. # Get minimum wavelength with spectral response
  182. c = maxresponse_idx
  183. while c > 0 and fi[c - 1] > rthresh:
  184. c = c - 1
  185. min_wavelength = np.ceil(li[0] * 1000 + (2.5 * c))
  186. # Get maximum wavelength with spectral response
  187. c = maxresponse_idx
  188. while c < len(fi) - 1 and fi[c + 1] > rthresh:
  189. c = c + 1
  190. max_wavelength = np.floor(li[0] * 1000 + (2.5 * c))
  191. print(" %s (%inm - %inm)" % (bands[b], min_wavelength, max_wavelength))
  192. else:
  193. filter_f = []
  194. limits = []
  195. for b in range(len(bands)):
  196. fi, li = interpolate_band(values[:, [0, b + 1]])
  197. filter_f.append(fi)
  198. limits.append(li)
  199. # Get wavelength range for spectral response in band
  200. maxresponse_idx = np.argmax(fi)
  201. # Get minimum wavelength with spectral response
  202. c = maxresponse_idx
  203. while c > 0 and fi[c - 1] > rthresh:
  204. c = c - 1
  205. min_wavelength = np.ceil(li[0] * 1000 + (2.5 * c))
  206. # Get maximum wavelength with spectral response
  207. c = maxresponse_idx
  208. while c < len(fi) - 1 and fi[c + 1] > rthresh:
  209. c = c + 1
  210. max_wavelength = np.floor(li[0] * 1000 + (2.5 * c))
  211. print(" %s (%inm - %inm)" % (bands[b], min_wavelength, max_wavelength))
  212. # writing...
  213. outfile = open(os.path.join(folder, sensor + "_cpp_template.txt"), "w")
  214. outfile.write("/* Following filter function created using create_iwave.py */\n\n")
  215. if len(bands) == 1:
  216. outfile.write("void IWave::%s()\n{\n\n" % (sensor.lower()))
  217. else:
  218. outfile.write("void IWave::%s(int iwa)\n{\n\n" % (sensor.lower()))
  219. # single band case
  220. if len(bands) == 1:
  221. outfile.write(" /* %s of %s */\n" % (bands[0], sensor))
  222. outfile.write(" static const float sr[%i] = {" % (len(filter_f)))
  223. filter_text = pretty_print(filter_f)
  224. outfile.write(filter_text)
  225. # calculate wl slot for band start
  226. # slots range from 250 to 4000 at 2.5 increments (total 1500)
  227. s_start = int((limits[0] * 1000 - 250) / 2.5)
  228. outfile.write("\n")
  229. outfile.write(" ffu.wlinf = %.4ff;\n" % (limits[0]))
  230. outfile.write(" ffu.wlsup = %.4ff;\n" % (limits[1]))
  231. outfile.write(" int i = 0;\n")
  232. outfile.write(" for(i = 0; i < %i; i++)\tffu.s[i] = 0;\n" % (s_start))
  233. outfile.write(
  234. " for(i = 0; i < %i; i++)\tffu.s[%i+i] = sr[i];\n"
  235. % (len(filter_f), s_start)
  236. )
  237. outfile.write(
  238. " for(i = %i; i < 1501; i++)\tffu.s[i] = 0;\n"
  239. % (s_start + len(filter_f))
  240. )
  241. outfile.write("}\n")
  242. else: # more than 1 band
  243. # writing bands
  244. for b in range(len(bands)):
  245. outfile.write(" /* %s of %s */\n" % (bands[b], sensor))
  246. outfile.write(
  247. " static const float sr%i[%i] = {\n" % (b + 1, len(filter_f[b]))
  248. )
  249. filter_text = pretty_print(filter_f[b])
  250. outfile.write(filter_text + "\n };\n\t\n")
  251. # writing band limits
  252. for b in range(len(bands)):
  253. inf = ", ".join(["%.4f" % i[0] for i in limits])
  254. sup = ", ".join(["%.4f" % i[1] for i in limits])
  255. outfile.write(" static const float wli[%i] = {%s};\n" % (len(bands), inf))
  256. outfile.write(" static const float wls[%i] = {%s};\n" % (len(bands), sup))
  257. outfile.write("\n")
  258. outfile.write(" ffu.wlinf = (float)wli[iwa-1];\n")
  259. outfile.write(" ffu.wlsup = (float)wls[iwa-1];\n\n")
  260. outfile.write(" int i;\n")
  261. outfile.write(" for(i = 0; i < 1501; i++) ffu.s[i] = 0;\n\n")
  262. outfile.write(" switch(iwa)\n {\n")
  263. # now start of case part...
  264. for b in range(len(bands)):
  265. s_start = int((limits[b][0] * 1000 - 250) / 2.5)
  266. outfile.write(
  267. " case %i: for(i = 0; i < %i; i++) ffu.s[%i+i] = sr%i[i];\n"
  268. % ((b + 1), len(filter_f[b]), s_start, (b + 1))
  269. )
  270. outfile.write(" break;\n")
  271. outfile.write(" }\n}\n")
  272. return
  273. def main():
  274. """control function"""
  275. inputfile = sys.argv[1]
  276. # getting sensor name from full csv file name
  277. sensor = os.path.splitext(os.path.basename(inputfile))[0]
  278. print
  279. print(" > Getting sensor name from csv file: %s" % (sensor))
  280. # getting data from file
  281. bands, values = read_input(inputfile)
  282. # print spectral ranges from input file
  283. # consider only wavelengths with a reasonably large response
  284. # around the peak response, keep in sync with IWave::parse()
  285. rthresh = 0.01
  286. print
  287. print(" > Response peaks from input file:")
  288. for b in range(1, len(bands) + 1):
  289. lowl = 0
  290. hiwl = 0
  291. maxr = 0
  292. maxi = 0
  293. for i in range(len(values[:, 0])):
  294. if maxr < values[i, b]:
  295. maxr = values[i, b]
  296. maxi = i
  297. i = maxi
  298. while i >= 0 and values[i, b] > rthresh:
  299. lowl = values[i, 0]
  300. i -= 1
  301. i = maxi
  302. nvalues = len(values[:, 0])
  303. while i < nvalues and values[i, b] > rthresh:
  304. hiwl = values[i, 0]
  305. i += 1
  306. print(" %s (%inm - %inm)" % (bands[b - 1], lowl, hiwl))
  307. # writing file in same folder of input file
  308. write_cpp(bands, values, sensor, os.path.dirname(inputfile))
  309. print
  310. print(
  311. " > Filter functions exported to %s"
  312. % ("sensors_csv/" + sensor + "_cpp_template.txt")
  313. )
  314. print(
  315. " > Please check this file for possible errors before inserting"
  316. " the code into file iwave.cpp"
  317. )
  318. print(
  319. " > Don't forget to add the necessary data to the files"
  320. " iwave.h, geomcond.h, geomcond.cpp, and to i.atcorr.html"
  321. )
  322. print
  323. return
  324. if __name__ == "__main__":
  325. if len(sys.argv) == 1:
  326. usage()
  327. sys.exit()
  328. else:
  329. main()