PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/gci.py

https://bitbucket.org/rmallery/gci_algorithms
Python | 5993 lines | 5821 code | 47 blank | 125 comment | 58 complexity | 235216d8a21c72726dfe7d1fde242c86 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. #! /usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. # pylint: disable-msg=C0301,C0321,R0912,R0913,R0914,R0915
  4. '''
  5. This module provides a library of functions for use with the GCI system.
  6. GLOBAL CONSTANTS
  7. ================
  8. GCIDIR
  9. GCI_SERIAL_NUMBER
  10. TODO
  11. ====
  12. 1. Make "serial_number" a required argument for a bunch of these functions in order to ensure
  13. consistency.
  14. FUNCTION DESCRIPTIONS
  15. =====================
  16. '''
  17. from __future__ import print_function, division
  18. from numpy import *
  19. import zmq
  20. import platform
  21. import os
  22. import sys
  23. import glob
  24. import struct
  25. import simplejson as json
  26. import pdb #zzz
  27. from scipy.stats import nanmean, nanmedian, nanstd
  28. #global GCI_SERIAL_NUMBER
  29. #global GCIDIR
  30. GCI_SERIAL_NUMBER = 2
  31. ## Note that os.path.realpath() here converts a rlative path into an absolute one.
  32. thisfile = os.path.realpath(sys._current_frames().values()[0].f_globals['__file__'])
  33. GCIDIR = os.path.dirname(thisfile) + '/'
  34. __all__ = ['decode_gci_msg_meta','acquire_raw','acquire_data','acquire_dcb','acquire_vis','crop_dcb','crop_hypercube',
  35. 'show_dcb','get_offsets_and_gains','receive_command_replies','get_control_data','move_shutter',
  36. 'set_heater_setpoint','set_heater_state','replace_bad_pixels','degc_to_atsensor_radiance',
  37. 'gci_sequence_to_hcb','get_filter_spectra','get_filter_edge_wavelength','send_command',
  38. 'show_image_with_projections','dynamically_correct_dcb','read_gcifile_meta','read_gcifile',
  39. 'dcb_objtemp_to_atsensor_radiance','get_filterlist','acquire_manually_calibrated_hypercube',
  40. 'get_gas_spectra','calculate_hmatrix','get_filter_type',
  41. 'planck_blackbody','get_degc_to_atsensor_radiance_lut','dcb_atsensor_radiance_to_objtemp',
  42. 'read_rectanglesfile','update_rectangles_dict','write_rectanglesfile','draw_block_spectrum',
  43. 'get_mean_spect','dcb_region_stats','daisi_rect_to_xycoords','xycoords_to_daisi_rect','print_rects',
  44. 'xcorr','xcorr3d','convert_radiancecube_to_spectralcube','send_asynch_command','set_gci_serial_number',
  45. 'get_gci_serial_number','get_calib_dictionary','jcamp_reader','jcamp_spectrum','format_coord',
  46. 'find_nearest_brackets','is_float','getcolors','get_poly_coeffs','polyeval_Horner','get_parallax_mask',
  47. 'get_vismotion_mask','get_irmotion_mask','mask_overlay','draw_box','get_vis_steam_mask',
  48. 'get_ir_steam_mask','get_spectral_mask','get_detection_mask','get_spectral_detection_mask','set_saturated_to_nan','pullcube',
  49. 'gci_file_ismotioncalib','gci_msg_ismotioncalib','to_json','serial_number_from_filterlist',
  50. 'send_keepalive_command','send_trigger_cal_command']
  51. __docformat__ = 'restructuredtext en'
  52. ## =================================================================================================
  53. def decode_gci_msg(msg, use_ir=True, use_vis=True, use_masks=True, save_header=False, debug=False):
  54. '''
  55. Convert a GCI message string to a dictionary of numpy objects, reading only the metadata and \
  56. ignoring images and datacubes.
  57. Parameters
  58. ----------
  59. msg : list of str
  60. The message delivered by the GCI's zmq network interface, or through the contents of \
  61. a `*.gci` file
  62. use_ir : bool
  63. Whether to decode any datacubes. (Setting to False improves decoding speed.)
  64. use_vis : bool
  65. Whether to decode any visible images. (Setting to False improves decoding speed.)
  66. use_masks : bool
  67. Whether to decode any masks. (Setting to False improves decoding speed.)
  68. save_header : bool
  69. Whether to save the text-version of the metadata as "header".
  70. Returns
  71. -------
  72. data : dict
  73. A dictionary containing all data elements from the decoded gci msg. If present in the gci \
  74. file, the list of dictionary keys can include
  75. * "cool_shutter_temperatures"
  76. * "heated_shutter_temperatures"
  77. * "timestamp"
  78. * "accumulated_frames_list"
  79. * "gain_cube_filename"
  80. * "offset_cube_filename"
  81. * "cropping_xy"
  82. * "filter_to_camera_trans"
  83. * "serial_number"
  84. * ...
  85. '''
  86. ## The "algorithm output" image uses different subscription topics, shown in msg[0],
  87. ## to tell which one is being sent at a given time.
  88. assert isinstance(msg, (list, tuple, ndarray))
  89. topic = msg[0]
  90. if (topic != '') and debug: print('topic = ' + topic)
  91. d = json.loads(msg[1]) ## the "payload" dictionary
  92. ## Set up to grab the system data.
  93. meta = {}
  94. meta['topic'] = topic
  95. if save_header:
  96. import yaml
  97. meta['header'] = yaml.dump(d, indent=3, width=130)
  98. if debug:
  99. import yaml
  100. print(yaml.dump(d, indent=3, width=130))
  101. if ('gci-meta' in d) and ('metadata' in d['gci-meta']):
  102. ## The first three items are always present.
  103. meta['cool_shutter_temperatures'] = float32(d['gci-meta']['metadata']['unheated-temps-list'])
  104. meta['warm_shutter_temperatures'] = float32(d['gci-meta']['metadata']['heated-temps-list'])
  105. meta['accumulated_frames_list'] = d['gci-meta']['metadata']['frames-accum-list']
  106. meta['timestamp'] = d['gci-meta']['metadata']['ts-ms-since-epoch']
  107. Nw = alen(meta['accumulated_frames_list'])
  108. meta['Nw'] = Nw
  109. if ('cube' in d) and ('metadata' in d['cube']):
  110. meta['Nx'] = d['cube']['metadata']['height']
  111. else:
  112. meta['Nx'] = 240
  113. if ('cube-width' in d['gci-meta']['metadata']):
  114. meta['Ny'] = d['gci-meta']['metadata']['cube-width']
  115. if ('unheated-radiances-list' in d['gci-meta']['metadata']):
  116. meta['cool_shutter_radiances'] = float32(d['gci-meta']['metadata']['unheated-radiances-list'])
  117. if ('heated-radiances-list' in d['gci-meta']['metadata']):
  118. meta['warm_shutter_radiances'] = float32(d['gci-meta']['metadata']['heated-radiances-list'])
  119. if ('gain-cube-filename' in d['gci-meta']['metadata']):
  120. meta['gain_cube_filename'] = d['gci-meta']['metadata']['gain-cube-filename']
  121. if ('offset-cube-filename' in d['gci-meta']['metadata']):
  122. meta['offset_cube_filename'] = d['gci-meta']['metadata']['offset-cube-filename']
  123. if ('static-offset-type' in d['gci-meta']['metadata']):
  124. ## This can be either "cool" or "warm".
  125. meta['static_offset_type'] = d['gci-meta']['metadata']['static-offset-type'].lower()
  126. if ('reg-data-str' in d['gci-meta']['metadata']):
  127. ## Had to hard-code the Nx value here. Need a better structure for data to make it
  128. ## flexible for *any* detector array dimensions.
  129. rect_dict = d['gci-meta']['metadata']['reg-data-str']['IR']
  130. meta['rd'] = {}
  131. meta['rd']['cropping_xy'] = daisi_rect_to_xycoords(rect_dict, 240, meta['Nw'])
  132. #if ('scene-reference-rect' in d['gci-meta']['metadata']):
  133. scene_xy = daisi_rect_to_xycoords(d['gci-meta']['metadata']['scene-reference-rect'], meta['Nx'], 0)
  134. meta['rd']['scene_xy'] = scene_xy
  135. if ('aper-mean-temps' in d['gci-meta']['metadata']):
  136. ## Since not all w's are represented in the aperture values, you need to initialize the
  137. ## array and fill in only those values which are defined.
  138. meta['aper_mean_temps'] = float32([None]*Nw)
  139. for key in d['gci-meta']['metadata']['aper-mean-temps']:
  140. w = int(key)
  141. meta['aper_mean_temps'][w] = float32(d['gci-meta']['metadata']['aper-mean-temps'][key])
  142. if ('scene-mean-temps' in d['gci-meta']['metadata']):
  143. meta['sceneref_mean_temps'] = zeros(Nw, 'float32')
  144. for key in d['gci-meta']['metadata']['scene-mean-temps']:
  145. w = int(key)
  146. meta['sceneref_mean_temps'][w] = float32(d['gci-meta']['metadata']['scene-mean-temps'][key])
  147. if ('aper-offset-deltas' in d['gci-meta']['metadata']):
  148. meta['aper_offset_deltas'] = float32([None]*Nw)
  149. for key in d['gci-meta']['metadata']['aper-offset-deltas']:
  150. w = int(key)
  151. meta['aper_offset_deltas'][w] = float32(d['gci-meta']['metadata']['aper-offset-deltas'][key])
  152. if ('cropped-page-mean-temps' in d['gci-meta']['metadata']):
  153. meta['cropped_page_mean_temps'] = float32(d['gci-meta']['metadata']['cropped-page-mean-temps'])
  154. if ('spec-radiance-means' in d['gci-meta']['metadata']):
  155. meta['spec_radiance_means'] = float32(d['gci-meta']['metadata']['spec-radiance-means'])
  156. meta['Nc'] = alen(meta['spec_radiance_means'])
  157. if ('version-info' in d['gci-meta']['metadata']):
  158. meta['version_info'] = d['gci-meta']['metadata']['version-info']
  159. if ('calibration-state' in d['gci-meta']['metadata']):
  160. ## This string can be {'calibration-2-pt-heating','calibration-2-pt-running',
  161. ## 'calibration-1-pt-running','calibration-idle'}.
  162. meta['calibration_state'] = d['gci-meta']['metadata']['calibration-state'].lower()
  163. if ('pan-tilt-in-motion' in d['gci-meta']['metadata']):
  164. meta['pan_tilt_in_motion'] = d['gci-meta']['metadata']['pan-tilt-in-motion']
  165. if ('pan-tilt-position' in d['gci-meta']['metadata']):
  166. meta['pan_tilt_position'] = float32(d['gci-meta']['metadata']['pan-tilt-position'])
  167. if ('pan-tilt-setpoint' in d['gci-meta']['metadata']):
  168. meta['pan_tilt_setpoint'] = float32(d['gci-meta']['metadata']['pan-tilt-setpoint'])
  169. if ('gain-cube-timestamp' in d['gci-meta']['metadata']):
  170. meta['gain_cube_timestamp'] = d['gci-meta']['metadata']['gain-cube-timestamp']
  171. if ('offset-cube-timestamp' in d['gci-meta']['metadata']):
  172. meta['offset_cube_timestamp'] = d['gci-meta']['metadata']['offset-cube-timestamp']
  173. if ('heated-temp-ts-ms' in d['gci-meta']['metadata']):
  174. meta['heated_temp_timestamp'] = d['gci-meta']['metadata']['heated-temp-ts-ms']
  175. if ('scene-ref-timestamp' in d['gci-meta']['metadata']):
  176. meta['scene_ref_timestamp'] = d['gci-meta']['metadata']['scene-ref-timestamp']
  177. if ('is-scene-ref' in d['gci-meta']['metadata']):
  178. meta['is_scene_ref'] = d['gci-meta']['metadata']['is-scene-ref']
  179. if ('num-pixels-masked-by-ir-mc' in d['gci-meta']['metadata']):
  180. meta['npix_masked_ir_mc'] = d['gci-meta']['metadata']['num-pixels-masked-by-ir-mc']
  181. if ('num-pixels-masked-by-ir-steam' in d['gci-meta']['metadata']):
  182. meta['num_pixels_masked_by_ir_steam'] = d['gci-meta']['metadata']['num-pixels-masked-by-ir-steam']
  183. if ('num-pixels-masked-by-parallax' in d['gci-meta']['metadata']):
  184. meta['num_pixels_masked_by_parallax'] = d['gci-meta']['metadata']['num-pixels-masked-by-parallax']
  185. if ('num-pixels-masked-by-vis-mc' in d['gci-meta']['metadata']):
  186. meta['num_pixels_masked_by_vis_mc'] = d['gci-meta']['metadata']['num-pixels-masked-by-vis-mc']
  187. if ('num-pixels-masked-by-vis-steam' in d['gci-meta']['metadata']):
  188. meta['num_pixels_masked_by_vis_steam'] = d['gci-meta']['metadata']['num-pixels-masked-by-vis-steam']
  189. ## Look inside the filter map to determine which GCI you're talking to.
  190. if ('filt-chan-map' in d['gci-meta']['metadata']):
  191. if ('propylene0' in d['gci-meta']['metadata']['filt-chan-map']):
  192. filterlist = get_filterlist(d['gci-meta']['metadata']['filt-chan-map'])
  193. meta['filterlist'] = filterlist
  194. else:
  195. meta['filterlist'] = []
  196. for key in d['gci-meta']['metadata']['filt-chan-map']:
  197. w = int(key)
  198. meta['filterlist'].append(d['gci-meta']['metadata']['filt-chan-map'][key]['uniqueID'])
  199. meta['serial_number'] = serial_number_from_filterlist(meta['filterlist'], debug=debug)
  200. if ('shutter-state-info' in d['gci-meta']['metadata']):
  201. sh = d['gci-meta']['metadata']['shutter-state-info']
  202. if 'heater_enabled' in sh: meta['heater_enabled'] = sh['heater_enabled'] ## bool
  203. if 'heater_on_cond' in sh: meta['heater_on_cond'] = sh['heater_on_cond'] ## bool
  204. if 'heater_set_point' in sh: meta['heater_set_point'] = float32(sh['heater_set_point'])
  205. if 'cool_set_open' in sh: meta['cool_shutter_set_open'] = sh['cool_set_open'] ## bool
  206. if 'cool_pos' in sh: meta['cool_shutter_position'] = sh['cool_pos'].lower() ## can be 'open', 'closed', 'transit', 'invalid'
  207. if 'warm_set_open' in sh: meta['warm_shutter_set_open'] = sh['warm_set_open'] ## bool
  208. if 'warm_pos' in sh: meta['warm_shutter_position'] = sh['warm_pos'].lower() ## can be 'open', 'closed', 'transit', 'invalid'
  209. if ('force-temporal-ref-reset' in d['gci-meta']['metadata']):
  210. meta['force_temporal_ref_reset'] = d['gci-meta']['metadata']['force-temporal-ref-reset']
  211. ## Some heuristics to guess which port was the source for the frame.
  212. if ('version-info' in d['gci-meta']['metadata']):
  213. #if ('cube_chan_viewer' in d['gci-meta']['metadata']['version-info']):
  214. if ('detection-mask' in d):
  215. meta['source'] = '9501'
  216. elif ('spectral_cube_gen' in d['gci-meta']['metadata']['version-info']):
  217. meta['source'] = '9005'
  218. elif ('mask_gen' in d['gci-meta']['metadata']['version-info']):
  219. meta['source'] = '9004'
  220. elif ('degC_to_rad' in d['gci-meta']['metadata']['version-info']):
  221. meta['source'] = '9003'
  222. elif ('dynamic_cal' in d['gci-meta']['metadata']['version-info']):
  223. meta['source'] = '9002'
  224. elif ('rotate_vis_cam' in d['gci-meta']['metadata']['version-info']):
  225. meta['source'] = '9001'
  226. else:
  227. meta['source'] = '9000'
  228. elif('gain-cube-filename' in d['gci-meta']['metadata']):
  229. meta['source'] = '9000'
  230. else:
  231. meta['source'] = '6009'
  232. if debug: print('Input GCI message contains a "' + meta['source'] + '" type frame.')
  233. ## Grab the datacube metadata.
  234. if use_ir and ('cube' in d) and ('buffer' in d['cube']) and ('metadata' in d['cube']):
  235. #meta['Nx'] = d['cube']['metadata']['height']
  236. meta['Ny'] = d['cube']['metadata']['width']
  237. meta['Nz'] = d['cube']['metadata']['depth']
  238. meta['dcb'] = decode_datacube(d['cube'], msg)
  239. meta['dcb'+meta['source']] = meta['dcb']
  240. if (topic in ('spectral-cube','absorption-cube','normalized-absorption-cube','snr-cube','stdev-cube')):
  241. meta['Nc'] = meta['Nz']
  242. ## Grab the absorption cube metadata.
  243. if use_ir and ('absorption-cube' in d) and ('buffer' in d['absorption-cube']) and ('metadata' in d['absorption-cube']):
  244. meta['Ny'] = d['absorption-cube']['metadata']['width']
  245. meta['Nz'] = d['absorption-cube']['metadata']['depth']
  246. meta['dcb_absorption'] = decode_datacube(d['absorption-cube'], msg)
  247. #zzz
  248. #pdb.set_trace()
  249. ## Grab the standard-deviation cube metadata.
  250. if use_ir and ('std-dev-cube' in d) and ('buffer' in d['std-dev-cube']) and ('metadata' in d['std-dev-cube']):
  251. meta['Ny'] = d['std-dev-cube']['metadata']['width']
  252. meta['Nz'] = d['std-dev-cube']['metadata']['depth']
  253. meta['dcb_stdev'] = decode_datacube(d['std-dev-cube'], msg)
  254. ## Grab the SNR cube metadata.
  255. if use_ir and ('snr-cube' in d) and ('buffer' in d['snr-cube']) and ('metadata' in d['snr-cube']):
  256. meta['Ny'] = d['snr-cube']['metadata']['width']
  257. meta['Nz'] = d['snr-cube']['metadata']['depth']
  258. meta['dcb_snr'] = decode_datacube(d['snr-cube'], msg)
  259. ## Grab the visible image metadata.
  260. if use_vis and ('image' in d) and ('buffer' in d['image']) and ('metadata' in d['image']):
  261. meta['vis_Nx'] = d['image']['metadata']['height']
  262. meta['vis_Ny'] = d['image']['metadata']['width']
  263. meta['vis'] = decode_image(d['image'], msg)
  264. ## Grab the saturation mask.
  265. if use_masks and ('saturation-mask' in d) and ('buffer' in d['saturation-mask']) and ('metadata' in d['saturation-mask']):
  266. meta['saturation_mask'] = decode_image(d['saturation-mask'], msg) // 255
  267. ## Grab the IR motion mask.
  268. if use_masks and ('ir-motion-canc-mask' in d) and ('buffer' in d['ir-motion-canc-mask']) and ('metadata' in d['ir-motion-canc-mask']):
  269. meta['irmotion_mask'] = decode_image(d['ir-motion-canc-mask'], msg) // 255
  270. ## Grab the parallax mask.
  271. if use_masks and ('parallax-mask' in d) and ('buffer' in d['parallax-mask']) and ('metadata' in d['parallax-mask']):
  272. meta['parallax_mask'] = decode_image(d['parallax-mask'], msg) // 255
  273. ## Grab the VIS motion mask.
  274. if use_masks and ('vis-motion-canc-mask' in d) and ('buffer' in d['vis-motion-canc-mask']) and ('metadata' in d['vis-motion-canc-mask']):
  275. meta['vismotion_mask'] = decode_image(d['vis-motion-canc-mask'], msg) // 255
  276. ## Grab the detection mask.
  277. if use_masks and ('detection-mask' in d) and ('buffer' in d['detection-mask']) and ('metadata' in d['detection-mask']):
  278. meta['detmask'] = decode_image(d['detection-mask'], msg) // 255
  279. ## Grab the difference image. Note! When the detection algorithm is a Xcorr type, then the "difference image"
  280. ## is actually a xcorr image.
  281. if ('difference-image' in d) and ('buffer' in d['difference-image']) and ('metadata' in d['difference-image']):
  282. meta['diffimg'] = decode_image(d['difference-image'], msg)
  283. ## Grab the visible *overlay* image.
  284. if use_vis and ('vis-image' in d) and ('buffer' in d['vis-image']) and ('metadata' in d['vis-image']):
  285. meta['overlay'] = decode_image(d['vis-image'], msg)
  286. if debug:
  287. ## The line below cannot be implemented as-is, because the JSON module does not know how
  288. ## to serialize numpy objects.
  289. #print(json.dumps(meta, indent=4, sort_keys=True))
  290. #print(meta)
  291. pass
  292. #dynamic_gains = (meta['sceneref_mean_temps'][9] - meta['aper_mean_temps'][9]) / (meta['sceneref_mean_temps'] - meta['aper_mean_temps'])
  293. #dynamic_offsets = meta['aper_mean_temps'][9] + meta['aper_offset_deltas']
  294. ## Can't use cropped_mean_temps for calculating dynamic offsets of the referenced cameras
  295. ## because these values are *after* the dynamic cal was applied. And if we can't get the
  296. ## offsets, then we might as well skip the gains, because they're just 1.0.
  297. #rd = read_rectanglesfile(meta['serial_number'])
  298. #for key in rd['refcam']:
  299. # dynamic_gains[int(key)] = 1.0
  300. # #dynamic_offsets[int(key)] = meta['cropped_page_mean_temps'][9] - meta['cropped_page_mean_temps'][int(key)]
  301. #print('dynamic offsets = ', dynamic_offsets)
  302. #print('dynamic gains = ', dynamic_gains)
  303. return(meta)
  304. ## =================================================================================================
  305. def decode_datacube(cubedata, msg, debug=False):
  306. datatype = cubedata['type'].lower()
  307. raw_str = msg[cubedata['buffer']['index']]
  308. Nx = cubedata['metadata']['height']
  309. Ny = cubedata['metadata']['width']
  310. Nz = cubedata['metadata']['depth']
  311. if debug: print('%s: Nx, Ny, Nz = %3i %3i %3i' % (cubedata['topic'], Nx, Ny, Nz))
  312. if (datatype == 'ipp16u-cube'):
  313. bytesize = 2
  314. dcb = zeros((Nx,Ny,Nz), 'uint16')
  315. q = 0
  316. for z in arange(Nz):
  317. xvalues = Nx - 1 - arange(Nx)
  318. for x in xvalues:
  319. row = uint16(struct.unpack('<'+str(Ny)+'H',raw_str[q:q+(Ny*bytesize)]))
  320. dcb[x,:,z] = row
  321. q += Ny * bytesize
  322. elif (datatype == 'ipp32f-cube'):
  323. bytesize = 4
  324. dcb = zeros((Nx,Ny,Nz), 'float32')
  325. q = 0
  326. for z in arange(Nz):
  327. xvalues = Nx - 1 - arange(Nx)
  328. for x in xvalues:
  329. row = struct.unpack('<'+str(Ny)+'f',raw_str[q:q+(Ny*bytesize)])
  330. dcb[x,:,z] = row
  331. q += Ny * bytesize
  332. else:
  333. raise NotImplementedError('The data type "' + datatype + '" is not implemented.')
  334. ## If scaling is needed, then apply the gain and offset.
  335. if ('gain' in cubedata['metadata']):
  336. cubedata['dcb_dtype'] = 'float32'
  337. scalegain = float(cubedata['metadata']['gain'])
  338. scaleoffset = float(cubedata['metadata']['offset'])
  339. if (float(cubedata['metadata']['gain']) > 0.0):
  340. dcb = (float32(dcb) / scalegain) + scaleoffset
  341. return(dcb)
  342. ## =================================================================================================
  343. def decode_image(cubedata, msg, debug=False):
  344. raw_str = msg[cubedata['buffer']['index']]
  345. Nx = cubedata['metadata']['height']
  346. Ny = cubedata['metadata']['width']
  347. if (cubedata['metadata']['imageTypeString'] == 'RGB_UINT8'):
  348. bytesize = 1
  349. vis = zeros((Nx,Ny,3), 'uint8')
  350. q = 0
  351. xvalues = Nx - 1 - arange(Nx)
  352. for x in xvalues:
  353. row = struct.unpack('<'+str(Ny*3)+'B', raw_str[q:q+(Ny*3*bytesize)])
  354. vis[x,:,0] = row[0::3]
  355. vis[x,:,1] = row[1::3]
  356. vis[x,:,2] = row[2::3]
  357. q += Ny * 3 * bytesize
  358. elif (cubedata['metadata']['imageTypeString'] == 'MONO_UINT8'):
  359. bytesize = 1
  360. vis = zeros((Nx,Ny), 'uint8')
  361. q = 0
  362. xvalues = Nx - 1 - arange(Nx)
  363. for x in xvalues:
  364. row = struct.unpack('<'+str(Ny)+'B', raw_str[q:q+(Ny*bytesize)])
  365. vis[x,:] = row
  366. q += Ny * bytesize
  367. elif (cubedata['metadata']['imageTypeString'] == 'MONO_FLOAT32'):
  368. bytesize = 4
  369. vis = zeros((Nx,Ny), 'float32')
  370. q = 0
  371. xvalues = Nx - 1 - arange(Nx)
  372. for x in xvalues:
  373. row = struct.unpack('<'+str(Ny)+'f',raw_str[q:q+(Ny*bytesize)])
  374. vis[x,:] = row
  375. q += Ny * bytesize
  376. else:
  377. raise NotImplementedError('That visible image data type ("' + cubedata['metadata']['imageTypeString'] + \
  378. '") is not implemented.')
  379. return(vis)
  380. ## =================================================================================================
  381. def acquire_raw(nframes, port=6009, gcinum=3, username=None, keyfile=None, topic='', verbose=False):
  382. '''
  383. Acquire a raw data sequence from the GCI. This is the lowest-level acquisition function, called
  384. by everything that wants live data from hardware.
  385. Parameters
  386. ----------
  387. nframes : int
  388. The number of frames of data to acquire
  389. port : int
  390. The port you want to pull from (i.e. 6009, 9000, 9001, 9002, 9003, 9004, 9005, 9006, or 9501).
  391. gcinum : int, optional
  392. The GCI serial number.
  393. username : str, optional
  394. The username to use for connecting to the remote host
  395. keyfile : str, optional
  396. The RSA keyfile file to use for connecting to the remote host.
  397. topic : str
  398. The GCI message topic to wait for. This is used primarily when listening to ports \
  399. 9005 (which can have topics "spectral-cube","absorption-cube", or "normalized-absorption-cube") \
  400. and 9501 (which can have a variety of topics, such as "methane_binary", etc --- see the \
  401. detection algorithm config file).
  402. Returns
  403. -------
  404. raw : dict
  405. A dictionary whose keys are "0", "1", etc. labelling the time index of the frames.\
  406. raw['0'] contains the GCI message string that can be used as input to `decode_gci_msg()`.
  407. Example
  408. -------
  409. import gci
  410. ## To acquire data from hardware connected to the local network.
  411. raw = gci.acquire_raw(9002)
  412. ## To acquire data from a remote system.
  413. raw = gci.acquire_raw(9002, 2, 'nhagen', '/home/nhagen/.ssh/nhagen_toledo_rsa')
  414. ## Or even using an SSH tunnel to a locally-connected system.
  415. raw = gci.acquire_raw(9002, 3, 'nhagen', '/home/nhagen/.ssh/nhagen_lab_rsa')
  416. ## Or, for acquiring a detection mask from a remote system.
  417. raw = gci.acquire_raw(9501, 2, 'nhagen', '/home/nhagen/.ssh/nhagen_toledo_rsa', topic='methane_binary')
  418. '''
  419. assert (nframes == int(nframes)), '"nframes" must be an integer type.'
  420. assert isinstance(port, basestring) or isinstance(port, int), '"units" must be either a string or integer type.'
  421. gcinum = int(gcinum)
  422. assert gcinum in (2,3), 'Only GCI serial numbers 2 and 3 are mapped to known systems.'
  423. ctx = zmq.Context()
  424. subsock = ctx.socket(zmq.SUB)
  425. subsock.setsockopt_string(zmq.SUBSCRIBE, u'')
  426. subsock.setsockopt(zmq.HWM, nframes)
  427. ## If the input uses a units string rather than a port number, then translate to a number.
  428. trans = {'counts':6009, '6009':6009, 'degc':9000, '9000':9000, 'corrected_degc':9002, '9002':9002,
  429. 'corrected_radiance':9003, '9003':9003, 'radiance':9003, 'mask':9004, '9004':9004,
  430. 'spectrum':9005, '9005':9005, '9006':9006, 'detection':9501, '9501':9501}
  431. if isinstance(port, str):
  432. port = trans[port.lower()]
  433. if (port not in (6009,9000,9001,9002,9003,9004,9005,9006,9007,9501)):
  434. raise ValueError('That port number ("' + str(port) + '") is not recognized.')
  435. if (port == 9005):
  436. if (topic == ''): topic = 'spectral-cube'
  437. if (port == 9501):
  438. send_keepalive_command(topic, debug=verbose)
  439. if (username == None) or (keyfile == None):
  440. ## Use the local network to acquire data.
  441. if (port == 6009):
  442. ## Talk to the embedded system.
  443. address = 'tcp://10.1.10.10:'
  444. else:
  445. ## Talk to the server system.
  446. address = 'tcp://10.1.10.11:'
  447. #address = 'tcp://localhost:'
  448. subsock.connect(address + str(port))
  449. raw = {}
  450. for t in arange(nframes):
  451. #raw[str(t)] = subsock.recv_multipart()
  452. poller = zmq.Poller()
  453. poller.register(subsock, zmq.POLLIN)
  454. REQUEST_TIMEOUT = 2500
  455. q = 1 ## counter to see how many requests for messages are made
  456. while True:
  457. sockets = dict(poller.poll(REQUEST_TIMEOUT))
  458. q += 1
  459. if (subsock in sockets) and (sockets[subsock] == zmq.POLLIN):
  460. msg = subsock.recv_multipart()
  461. if (topic != '') and (msg[0] != topic): continue
  462. raw[str(t)] = msg
  463. break
  464. if (q > 5):
  465. subsock.close()
  466. raise ImportError('Failed to receive a datacube message.')
  467. if verbose: print('Frame index = ' + str(t))
  468. subsock.close()
  469. return(raw)
  470. else:
  471. ## Here we try to build an SSH tunnel to a remotely-connected GCI system.
  472. import subprocess
  473. import signal
  474. import time
  475. username = str(username)
  476. keyfile = str(keyfile)
  477. if not os.path.exists(keyfile): raise LookupError, 'Cannot find file "' + keyfile + '"'
  478. try:
  479. if (gcinum == 2): ## Toledo system
  480. if (port == 6009):
  481. connect_str = 'ssh -i %s -p 22001 %s@24.52.111.98 -L %i:127.0.0.1:%i -N' % (keyfile, username, port, port)
  482. address = 'tcp://localhost:6009'
  483. else:
  484. connect_str = 'ssh -i %s -p 22000 %s@24.52.111.98 -L %i:127.0.0.1:%i -N' % (keyfile, username, port, port)
  485. address = 'tcp://localhost:' + str(port)
  486. elif (gcinum == 3): ## lab system
  487. if (port == 6009):
  488. connect_str = 'ssh -i %s -p 22 %s@10.1.10.11 -L %i:10.1.20.10:%i -N' % (keyfile, username, port, port)
  489. address = 'tcp://localhost:6009'
  490. else:
  491. connect_str = 'ssh -i %s -p 22 %s@10.1.10.11 -L %i:127.0.0.1:%i -N' % (keyfile, username, port, port)
  492. address = 'tcp://localhost:' + str(port)
  493. ## Note: SSH creates a shell, and so any commands running in the shell are its children. When you try to
  494. ## kill the shell, its children are not terminated with it, leaving them orphan. One way around this is
  495. ## to create the shell and its children as a process group (via "os.setsid") and send a command to kill
  496. ## all processes in that group. This works on Linux, but not on MS Windows, which doesn't have os.setsid
  497. ## available.
  498. raw = {}
  499. ssh_process = subprocess.Popen(connect_str, shell=True, preexec_fn=os.setsid)
  500. time.sleep(2.0) ## give it some time to connect
  501. ctx = zmq.Context()
  502. subsock = ctx.socket(zmq.SUB)
  503. subsock.setsockopt_string(zmq.SUBSCRIBE, u'')
  504. subsock.setsockopt(zmq.HWM, 1)
  505. subsock.connect(address)
  506. raw = {}
  507. for t in arange(nframes):
  508. poller = zmq.Poller()
  509. poller.register(subsock, zmq.POLLIN)
  510. REQUEST_TIMEOUT = 2500
  511. q = 1 ## counter to see how many requests for messages are made
  512. while True:
  513. sockets = dict(poller.poll(REQUEST_TIMEOUT))
  514. q += 1
  515. if (subsock in sockets) and (sockets[subsock] == zmq.POLLIN):
  516. msg = subsock.recv_multipart()
  517. if (msg[0] != topic): continue
  518. raw[str(t)] = msg
  519. break
  520. if (q > 5):
  521. subsock.close()
  522. raise ImportError('Failed to receive a datacube message with topic="' + topic + '".')
  523. if verbose: print('Frame index = ' + str(t))
  524. subsock.close()
  525. time.sleep(0.1) ## give it some time to close the connection before you terminate the SSH process
  526. ## Send the terminate signal to all the processes in the group. This apparently doesn't work on MS Windows.
  527. ## Note that ssh_process.kill() does *not* work, since that is a child process and not a parent.
  528. os.killpg(ssh_process.pid, signal.SIGTERM)
  529. except (OSError,ImportError), errmsg:
  530. print('Tunnel failed:', errmsg)
  531. time.sleep(0.1) ## give it some time to close the connection before you terminate the SSH process
  532. ## Send the terminate signal to all the processes in the group. This apparently doesn't work on MS Windows.
  533. ## Note that ssh_process.kill() does *not* work, since that is a child process and not a parent.
  534. os.killpg(ssh_process.pid, signal.SIGTERM)
  535. raise Exception(errmsg)
  536. return(raw)
  537. ## =================================================================================================
  538. def acquire_data(port=6009, gcinum=3, username=None, keyfile=None, use_ir=True, use_vis=True, use_masks=True,
  539. topic='', save_header=False, debug=False):
  540. '''
  541. Acquire a *single frame* of data containing the datacube, vis image, and all associated metadata.
  542. Parameters
  543. ----------
  544. port : int
  545. The port to acquire data from. (This can be 6009, 9000, 9001, 9002, 9003, 9004, 9005, 9051).
  546. gcinum : int, optional
  547. The serial number of the GCI to talk to.
  548. username : str, optional
  549. The username to use for connecting to the remote host
  550. keyfile : str, optional
  551. The RSA keyfile file to use for connecting to the remote host
  552. use_ir : bool, optional
  553. Whether to load the IR datacube. (Set to false to improve loading speed.)
  554. use_vis : bool, optional
  555. Whether to load the visible image. (Set to false to improve loading speed.)
  556. use_masks : bool, optional
  557. Whether to load the full set of masks. (Set to false to improve loading speed.)
  558. topic : str
  559. The GCI message topic to wait for. This is used primarily when listening to ports \
  560. 9005 (which can have topics "spectral-cube","absorption-cube", or "normalized-absorption-cube") \
  561. and 9501 (which can have a variety of topics, such as "methane_binary", etc --- see the \
  562. detection algorithm config file).
  563. save_header : bool
  564. Whether to save the text version of the metadata as "header".
  565. Returns
  566. -------
  567. data : dict
  568. A dictionary containing keys 'dcb', 'vis'', and all the other elements returned by\
  569. `decode_gci_msg()`.
  570. Example
  571. -------
  572. import gci
  573. ## To acquire data from hardware connected to the local network.
  574. data = gci.acquire_data(9002)
  575. ## To acquire data from a remote system.
  576. data = gci.acquire_data(9002, 2, 'nhagen', '/home/nhagen/.ssh/nhagen_toledo_rsa')
  577. ## Or even using an SSH tunnel to a locally-connected system.
  578. data = gci.acquire_data(9002, 3, 'nhagen', '/home/nhagen/.ssh/nhagen_lab_rsa')
  579. '''
  580. raw = acquire_raw(1, port=port, gcinum=gcinum, username=username, keyfile=keyfile, topic=topic, verbose=debug)
  581. data = decode_gci_msg(raw['0'], use_ir=use_ir, use_vis=use_vis, use_masks=use_masks, save_header=save_header, debug=debug)
  582. return(data)
  583. ## =================================================================================================
  584. def acquire_dcb(ncubes, port=6009, debug=False):
  585. '''
  586. Acquire a sequence of infrared datacubes (i.e. hypercube if ncubes>1) from the GCI. This throws away
  587. all of the associated metadata and keeps only the final cubes.
  588. Parameters
  589. ----------
  590. ncubes : int
  591. the number of datacubes to acquire
  592. port : int
  593. The port to acquire data from. (This can be 6009, 9000, 9001, 9002, 9003, 9004, 9005, 9051).
  594. Returns
  595. -------
  596. hcb : (list of ndarrays)
  597. If ncubes==1, a single datacube is returned; if ncubes>1, a Nt-long list of them is
  598. returned.
  599. '''
  600. assert (ncubes == int(ncubes)), '"ncubes" must be an integer type.'
  601. raw = acquire_raw(ncubes, port)
  602. gcidata = decode_gci_msg(raw['0'], use_ir=True, use_vis=False, use_masks=False, debug=debug)
  603. if (ncubes == 1):
  604. hcb = gcidata['dcb']
  605. else:
  606. hcb = []
  607. hcb.append(gcidata['dcb'])
  608. for t in arange(1,ncubes):
  609. gcidata = decode_gci_msg(raw[str(t)], use_ir=True, use_vis=False, use_masks=False, debug=debug)
  610. hcb.append(gcidata['dcb'])
  611. return(hcb)
  612. ## =================================================================================================
  613. def acquire_vis(nframes, debug=False):
  614. '''
  615. Acquire a sequence of visible images (i.e. hypercube if nframes>1) from the GCI. This throws away
  616. all of the associated metadata and keeps only the final images.
  617. Parameters
  618. ----------
  619. nframes : int
  620. The number of images to acquire
  621. Returns
  622. -------
  623. hcb : ndarray
  624. * If `nframes==1`, and using a color camera: a (Nx,Ny,3) image is returned
  625. * If `nframes>1`, and using a color camera: a (Nx,Ny,3,Nt) image is returned
  626. * If `nframes==1`, and using a monochrome camera: a (Nx,Ny) image is returned
  627. * If `nframes>1`, and using a monochrome camera: a (Nx,Ny,Nt) image is returned
  628. '''
  629. assert (nframes == int(nframes)), '"nframes" must be an integer type.'
  630. raw = acquire_raw(nframes)
  631. gcidata = decode_gci_msg(raw['0'], use_ir=False, use_vis=True, use_masks=False, debug=debug)
  632. if (nframes == 1):
  633. vcb = gcidata['vis']
  634. else:
  635. if (gcidata['vis'].ndim == 2):
  636. (vis_Nx,vis_Ny) = gcidata['vis'].shape
  637. vcb = zeros((vis_Nx,vis_Ny,nframes), gcidata['vis'].dtype)
  638. for t in arange(1,nframes):
  639. gcidata = decode_gci_msg(raw[str(t)], use_ir=False, use_vis=True, use_masks=False, debug=debug)
  640. vcb[:,:,t] = gcidata['vis']
  641. elif (gcidata['vis'].ndim == 3):
  642. (vis_Nx,vis_Ny,_) = gcidata['vis'].shape
  643. vcb = zeros((vis_Nx,vis_Ny,3,nframes), gcidata['vis'].dtype)
  644. for t in arange(1,nframes):
  645. gcidata = decode_gci_msg(raw[str(t)], use_ir=False, use_vis=True, use_masks=False, debug=debug)
  646. vcb[:,:,:,t] = gcidata['vis']
  647. return(vcb)
  648. ## =================================================================================================
  649. ## Convert all of the GCI files in a folder to a Numpy hypercube, and save as .npz.
  650. def gci_sequence_to_hcb(path, debug=True):
  651. '''
  652. Glob a directory for `*.gci` files, and decode each file into an infrared datacube and (if use_vis==True)
  653. a visible image.
  654. If there is more than one file in the directory, assemble the datacubes and images
  655. into an infrared hypercube and a visible hypercube. Throw away all of the associated metadata.
  656. Parameters
  657. ----------
  658. path : str
  659. The directory where to find all of the *.gci files.
  660. Returns
  661. -------
  662. hcb : (list of ndarrays)
  663. A list of datacubes.
  664. '''
  665. assert isinstance(path, str), 'The input "path" must be a string type.'
  666. if os.path.isfile(path):
  667. f = path
  668. nfiles = 1
  669. elif os.path.isdir(path):
  670. gci_files = sorted(glob.glob(path+'*.gci'))
  671. nfiles = len(gci_files)
  672. f = gci_files[0]
  673. if debug: print('Converting "' + os.path.basename(f) + '" (' + str(1) + ' of ' + str(nfiles) + ') ...')
  674. else:
  675. print('No files found in folder "' + path + '". Exiting ...')
  676. return
  677. ## Open the first file nd grab the dcb and vis image to figure out the data dimensions. This will allow you to create the
  678. ## hcb and vcb objects, which we can later fill in by looping over the remaining files.
  679. print('Decoding image #1 from "' + os.path.basename(f) + '"')
  680. gcidata = decode_gci_msg(f, use_vis=False, use_masks=False, debug=False)
  681. if (nfiles == 1):
  682. return(gcidata['dcb'])
  683. (Nx,Ny,Nw) = gcidata['dcb'].shape
  684. if debug: print('(Nx,Ny,Nw)=', (Nx,Ny,Nw))
  685. hcb = []
  686. hcb.append(gcidata['dcb'])
  687. for t in arange(1,nfiles):
  688. print('Decoding image #' + str(t+1) + ' from "' + os.path.basename(gci_files[t]) + '"')
  689. gcidata = decode_gci_msg(gci_files[t], use_vis=False, use_masks=False, debug=False)
  690. hcb.append(gcidata['dcb'])
  691. return(hcb)
  692. ## =================================================================================================
  693. def crop_dcb(dcb, xycoords):
  694. '''
  695. Crop a datacube using a matrix of cropping coordinates.
  696. Parameters
  697. ----------
  698. dcb : ndarray
  699. The (Nx,Ny,Nw) datacube to be cropped
  700. xycoords : ndarray
  701. The (Nw,4) array of (xlo,xhi,ylo,yhi) cropping coordinates for all Nw cameras
  702. Returns
  703. -------
  704. dcb_cropped : ndarray
  705. The cropped datacube, with x-dimension equal to xycoords[w,1]-xycoords[w,0],\
  706. y-dimension equal to xycoords[w,3]-xycoords[w,2], and w-dimension equal to Nw.
  707. '''
  708. dcb = asarray(dcb)
  709. assert (dcb.ndim == 3), 'Input "dcb" must have three dimensions.'
  710. xycoords = asarray(xycoords)
  711. (_,_,Nw) = dcb.shape
  712. Nx = xycoords[0,1] - xycoords[0,0]
  713. Ny = xycoords[0,3] - xycoords[0,2]
  714. dcb_cropped = zeros((Nx,Ny,Nw), dtype(dcb[0,0,0]))
  715. for w in arange(Nw):
  716. xlo = xycoords[w,0]
  717. xhi = xycoords[w,1]
  718. ylo = xycoords[w,2]
  719. yhi = xycoords[w,3]
  720. dcb_cropped[:,:,w] = dcb[xlo:xhi,ylo:yhi,w]
  721. return(dcb_cropped)
  722. ## =================================================================================================
  723. def crop_hypercube(hcb, xycoords):
  724. '''
  725. Crop a hypercube (datacube sequence) using a matrix of cropping coordinates.
  726. Parameters
  727. ----------
  728. hcb : ndarray
  729. The (Nx,Ny,Nw,Nt) hypercube to be cropped
  730. xycoords : ndarray
  731. The (Nw,4) array of (xlo,xhi,ylo,yhi) cropping coordinates for all Nw cameras
  732. Returns
  733. -------
  734. hcb_cropped : ndarray
  735. The cropped hypercube, with x-dimension equal to `xycoords[w,1]-xycoords[w,0]`, y-dimension \
  736. equal to `xycoords[w,3]-xycoords[w,2]`, w-dimension equal to Nw, and t-dimension equal to \
  737. Nt. Here `w` can be any index value 0 through Nw-1.
  738. '''
  739. hcb = asarray(hcb)
  740. assert (hcb.ndim == 4), 'Input "hcb" must have three dimensions.'
  741. xycoords = asarray(xycoords)
  742. (_,_,Nw,Nt) = hcb.shape
  743. Nx = xycoords[0,1] - xycoords[0,0]
  744. Ny = xycoords[0,3] - xycoords[0,2]
  745. hcb_cropped = zeros((Nx,Ny,Nw,Nt), dtype(hcb[0,0,0,0]))
  746. for t in arange(Nt):
  747. for w in arange(Nw):
  748. xlo = xycoords[w,0]
  749. xhi = xycoords[w,1]
  750. ylo = xycoords[w,2]
  751. yhi = xycoords[w,3]
  752. hcb_cropped[:,:,w] = hcb[xlo:xhi,ylo:yhi,w,t]
  753. return(hcb_cropped)
  754. ## =================================================================================================
  755. ## keyword "plottype" allows options ['image', 'histogram']
  756. ## The "box1", "box2", "box3", and "box4" optional arguments can be used to draw boxes in the datacube slices images.
  757. ## Each argument can be either a 4-element vector (i.e. same box coordinates in each wavelength image) or a Nw*4
  758. ## matrix (giving different box coordinates for each wavelength image).
  759. def show_dcb(dcb, waves=None, figsize=None, title='', plottype='image', showstats=False, \
  760. box1=None, box2=None, box3=None, box4=None, invert_display=False, show=False,
  761. use_colorbars=True, **kwargs):
  762. '''
  763. Display a datacube in a single window, showing each w-plane of the cube as an image.
  764. Parameters
  765. ----------
  766. dcb : ndarray
  767. A (Nx,Ny,Nw) datacube.
  768. waves : ndarray, optional
  769. A vector of Nw values with which to label the datacube images.
  770. figsize : tuple, optional
  771. The size of the plotting window.
  772. title : str, optional
  773. The figure title.
  774. plottype : str, optional ('image','histogram','difference1','difference2')
  775. What type of figure to draw --- the images themselves, their histograms, or their \
  776. difference cubes.
  777. showstats : bool
  778. Whether or now to show the image stats as text overlaid on the image.
  779. box1 : ndarray or list, optional
  780. A (Nw,4) matrix of (xlo,xhi,ylo,yhi) rectangle drawing coords. Box1 is drawn in blue.
  781. box2 : ndarray or list, optional
  782. A (Nw,4) matrix of (xlo,xhi,ylo,yhi) rectangle drawing coords. Box2 is drawn in red.
  783. box3 : ndarray or list, optional
  784. A (Nw,4) matrix of (xlo,xhi,ylo,yhi) rectangle drawing coords. Box3 is drawn in green.
  785. box4 : ndarray or list, optional
  786. A (Nw,4) matrix of (xlo,xhi,ylo,yhi) rectangle drawing coords. Box4 is drawn in yellow.
  787. invert_display : bool, optional
  788. Whether or not to draw the datacube planes with 0 at the bottom (invert_display==True), \
  789. or 0 at the top (invert_display==False).
  790. show : bool, optional
  791. Whether or not to call matplotlib's `show()` function.
  792. use_colorbars : bool, optional
  793. Whether to display colorbars with the datacube images.
  794. **kwargs: any, optional
  795. Ay keyword arguments that can be used by matplotlib's `imshow()` function.
  796. Notes
  797. -----
  798. Also, the inputs to the `box1`, `box2`, `box3`, and `box4` keywords can be 4-element vectors \
  799. rather than (Nw,4) size matrices. This forces the box coordinates to be the same for all \
  800. `w`.
  801. The dark gray shaded regions of the histograms extend out to the 1*sigma value, while the \
  802. light gray shaded regions of the histograms extend out to the 2*sigma value. The central \
  803. white line in the background shows the position of the mean.
  804. Setting `log=True` will allow logarithmic scaling of the histogram plots.
  805. '''
  806. import matplotlib.pyplot as plt
  807. import matplotlib.cm as cm
  808. from matplotlib.ticker import MaxNLocator
  809. import matplotlib.patheffects as PathEffects
  810. #print('PLATFORM = ', platform.system())
  811. #dcb = asarray(dcb)
  812. assert (dcb.ndim == 3), 'Input "dcb" must have three dimensions.'
  813. assert (plottype.lower() in ['image','histogram','difference1','difference2']), 'That plottype value ("' + plottype + '") is not recognized.'
  814. (Nx,Ny,Nw) = dcb.shape
  815. if (waves != None):
  816. waves = asarray(waves)
  817. assert (alen(waves) == Nw), 'The input "waves" should have Nw elements, not ' + str(alen(waves)) + '.'
  818. if (plottype == 'difference1'):
  819. dcb = array(dcb[:,:,1:] - dcb[:,:,:-1])
  820. Nw -= 1
  821. print('DIFFERENCE1')
  822. elif (plottype == 'difference2'):
  823. dcb = array(dcb - dcb[:,:,::-1])
  824. print('DIFFERENCE2')
  825. aspect_ratio = Ny / float(Nx)
  826. if (Nw == 1):
  827. if (figsize == None): figsize=(6*1.65*aspect_ratio,6)
  828. plt.figure(figsize=figsize)
  829. if (plottype.lower() == 'image'):
  830. plt.imshow(dcb[:,:,0], zorder=1, **kwargs)
  831. plt.axis('tight')
  832. if use_colorbars: plt.colorbar()
  833. elif (plottype.lower() == 'histogram'):
  834. ## Note: there is a matplotlib bug for logarithmic plotting when NaNs are present.
  835. (n, bin_edges) = histogram(log10(dcb[:,:,0].flatten()), 40)
  836. meanval = mean(dcb[:,:,0].flatten())
  837. stdval = std(dcb[:,:,0].flatten())
  838. ax = plt.subplot(111)
  839. ax.fill_between([meanval-2.0*stdval, meanval+2.0*stdval, meanval+2.0*stdval, meanval-2.0*stdval, meanval-2.0*stdval], [0, 0, amax(n)*1.1, amax(n)*1.1, 0], 0.5, color='0.75', zorder=1)
  840. ax.fill_between([meanval-stdval, meanval+stdval, meanval+stdval, meanval-stdval, meanval-stdval], [0, 0, amax(n)*1.1, amax(n)*1.1, 0], 0.5, color='0.5', zorder=2)
  841. ax.plot([meanval, meanval], [0, amax(n)*1.1], 'w-', linewidth=1.5, zorder=3)
  842. (counts, edges, patches) = ax.hist(dcb[:,:,0].flatten(), 40, zorder=4, edgecolor='0.5', log=True, **kwargs)
  843. if (meanval > 100):
  844. statstr = 'mean='+('%.0f' % meanval) + '\nstd='+('%.0f' % stdval)
  845. else:
  846. statstr = 'mean='+('%.2f' % meanval) + '\nstd='+('%.2f' % stdval)
  847. ax.text(0.8, 0.8, statstr, horizontalalignment='center', transform=ax.transAxes, bbox=dict(facecolor='white', alpha=0.5), zorder=5)
  848. ax.axis([amin(edges), amax(edges), 0.5, amax(counts)*1.1])
  849. else:
  850. if (figsize == None): figsize=(8*1.65*aspect_ratio,8)
  851. ncols = (ceil(sqrt(Nw)))
  852. nrows = (ceil(Nw / ncols))
  853. plt.figure(figsize=figsize)
  854. dimstr = '(Nx,Ny,Nw)=(' + str(Nx) + ',' + str(Ny) + ',' + str(Nw) + ')'
  855. plt.suptitle(dimstr + ' ' + title)
  856. for w in arange(Nw):
  857. if invert_display:
  858. c = int(w % ncols)
  859. r = int((Nw - w - 1) // ncols)
  860. else:
  861. c = int(w % ncols)
  862. r = int(w // ncols)
  863. if (plottype == 'image') or (plottype == 'histogram'):
  864. titlestr = 'w='+str(w) if (waves == None) else 'w='+str(waves[w])
  865. elif (plottype == 'difference1'):
  866. titlestr = '(w=' + str(w+1) + ') - (w=' + str(w) + ') difference'
  867. elif (plottype == 'difference2'):
  868. titlestr = '(w=' + str(Nw-1-w) + ') - (w=' + str(w) + ') difference'
  869. ## The "sharex=ax0" and "sharey=ax0" business is needed in order for zooming of one of the
  870. ## subplots is to propagate into all of the subplots. Don't turn this on for the histogram
  871. ## plot type, since we want to allow the axes to be different for each plot in that case.
  872. if (w == 0):
  873. ax0 = plt.subplot2grid((int(nrows), int(ncols)), (r,c))
  874. elif (w != 0) and (plottype.lower() == 'histogram'):
  875. ax = plt.subplot2grid((int(nrows), int(ncols)), (r,c))
  876. else:
  877. ax = plt.subplot2grid((int(nrows), int(ncols)), (r,c), sharex=ax0, sharey=ax0)
  878. if (plottype.lower() in ('image','difference1','difference2')):
  879. if (w == 0):
  880. plt.imshow(dcb[:,:,w], zorder=1, **kwargs)
  881. else:
  882. plt.imshow(dcb[:,:,w], zorder=1, **kwargs)
  883. plt.axis('tight')
  884. if use_colorbars:
  885. cb = plt.colorbar()
  886. cb.formatter.set_useOffset(False) ## turn off any offset that it may apply
  887. cb.update_ticks() ## update the new ticks for no-offset
  888. if (w == 0):
  889. ax0.set_yticklabels([])
  890. ax0.set_xticklabels([])
  891. else:
  892. ax.set_yticklabels([])
  893. ax.set_xticklabels([])
  894. plt.gca().axes.get_xaxis().set_visible(False)
  895. plt.gca().axes.get_yaxis().set_visible(False)
  896. if use_colorbars:
  897. cl = plt.getp(cb.ax, 'ymajorticklabels')
  898. plt.setp(cl, fontsize=10)
  899. plt.title(titlestr)
  900. if showstats:
  901. minval = min(dcb[:,:,w].flatten())
  902. meanval = mean(dcb[:,:,w].flatten())
  903. maxval = max(dcb[:,:,w].flatten())
  904. stdval = std(dcb[:,:,w].flatten())
  905. if isnan(meanval):
  906. sigdig = 0
  907. else:
  908. logmean = log10(abs(meanval))
  909. sigdig = 2 if (logmean >= -1) else int(-floor(logmean))
  910. fmt = '%.0f' if (meanval > 100) else '%.' + str(sigdig) + 'f'
  911. statstr = (('min=' + fmt + '\nmean=' + fmt + '\nmax=' + fmt + '\nstd=' + fmt) % (minval, meanval, maxval, stdval))
  912. if (w == 0):
  913. stattext = ax0.text(0.4, 0.4, statstr, horizontalalignment='center', transform=ax0.transA

Large files files are truncated, but you can click here to view the full file