├── .gitignore ├── CHANGES.txt ├── HELP ├── LICENSE.md ├── MFS-33219 NOSA.doc ├── README.md ├── data ├── mc3e_ampr_20110420_tbs_v01.txt.zip └── mc3e_ampr_20110524_tbs_v01.txt.zip ├── notebooks └── PyAMPR_Demo.ipynb ├── pyampr ├── __init__.py ├── defaults.py ├── google_earth_tools.py ├── misc_tools.py ├── pyampr.py └── udf_cmap.py ├── setup.py └── test ├── PyAMPR_Testing_Notebook.ipynb ├── test_ampr_qc_kmz.png ├── test_google_earth.png ├── test_iphex_kmz_colorbar.png ├── test_iphex_kmz_track.png ├── test_iphex_pol.png ├── test_iphex_strip.png ├── test_iphex_strip_zoom.png ├── test_iphex_track.png ├── test_iphex_track_zoom.png ├── test_kwajex_strip.png ├── test_kwajex_track.png ├── test_qc_channels.png ├── test_qc_legend_kmz.png ├── test_qc_track.png └── test_qc_track_kmz.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.bak 3 | build 4 | notebooks/*.png 5 | notebooks/*.kmz 6 | pyampr/old 7 | pyampr/*.bak 8 | notebooks/.ipynb_checkpoints 9 | test/.ipynb* 10 | test/*.kmz 11 | test/overlay.png 12 | test/legend.png 13 | test/QC_Colorbar_Testing.ipynb 14 | test/Python_2to3_Transition.ipynb 15 | 16 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | Change Log 2 | ---------- 3 | v1.7.1 major changes (08/07/2019): 4 | 1. Added CAMP2EX to the available project list. 5 | 2. Fixed visual bugs in plot_ampr_channels method to account for recent 6 | matplotlib updates. Please update to matplotlib 3.0+ if possible. 7 | 3. Fixed map resolution bug in plot_ampr_track_4panel. Note that this 8 | method runs slowly when plotting low-altitude AMPR data on high- 9 | resolution background maps. 10 | 11 | v1.7 major changes (07/22/2019): 12 | 1. Removed Basemap as a dependency. 13 | 2. Added Cartopy as a dependency. All maps (e.g., AmprTb.plot_ampr_track) are now created 14 | using Cartopy. 15 | 3. Miscellaneous small updates and bug fixes. 16 | 17 | v1.6 major changes (06/30/2017): 18 | 1. Enabled ingest of new CF-compliant formatted netCDF data from various dual-pol 19 | and pre-dual-pol campaigns. 20 | 2. Miscellaneous bug fixes, refactoring, and documentation. 21 | 22 | v1.5.3 major changes (03/03/2017): 23 | 1. Updated track plotting routines to recreate a Basemap object for each plot. 24 | This is slower but necessary as matplotlib 1.5+ no longer allows passing old 25 | Basemaps to new subplots. 26 | 2. Support for ingesting devolved H & V brightness temperatures if present in 27 | the netCDF Level 2 file. 28 | 3. Miscellaneous bug fixes and refactoring. 29 | 30 | v1.5.2 major changes (11/26/2015): 31 | 1. Added AmprTb.plot_ampr_track_4panel() method. This method uses the new 32 | _FourPanelTrack class in pyampr’s misc_tools to quickly produce a nice-looking 33 | four-panel geolocated figure for the A, B, H, or V channels (the latter two 34 | require running AmprTb.calc_polarization first). 35 | 2. Moved the PyAMPR default global variables to a separate file. 36 | 3. Default Basemap for the track plots now has the land colored light brown, 37 | the water light blue, and the latitude labels rotated 90 degrees. Since the 38 | Basemap can be returned, these can all be modified by the end user. 39 | 4. Demo notebook updates. 40 | 41 | v1.5.1 major changes (11/19/2015): 42 | 1. Offered more Basemap options in AmprTb.plot_ampr_track(), including area_thresh 43 | and projection keywords. 44 | 2. Added ability to incorporate other axes/figure objects into AmprTb.plot_ampr_track() 45 | calls, which enables multi-panel plots. 46 | 3. Added independent function called read_aircraft_nav_into_awot to facilitate using 47 | AWOT (https://github.com/nguy/AWOT) to help display flight tracks. This does not 48 | add AWOT as a dependency (even an optional one). 49 | 50 | v1.5 major changes (07/08/2015): 51 | 1. Made PyAMPR and its notebooks work under Python 3.4. Python 2.7 still supported, but 52 | users may have to install transition modules like six, etc. 53 | 2. Add capability to display QC flags, which are available in IPHEx and later datasets. 54 | Use the show_qc Boolean keyword in the display routines (e.g., plot_ampr_track). 55 | 3. Added ability to change resolution of Basemap in plot_ampr_track, via the resolution 56 | keyword. 57 | 58 | v1.4.2 major changes (07/06/2015): 59 | 1. Updated netCDF read routines to account for IPHEx 37 GHz channel swap being fixed. 60 | 2. Made all code pep8 compliant. 61 | 62 | v1.4.1 major changes (06/26/2015): 63 | 1. Updated netCDF read routines to be able to handle lab data with no geolocations. 64 | 2. Refactoring to better compartmentalize netCDF read routines. 65 | 66 | v1.4 major changes (03/22/2015): 67 | 1. Added support for Level 2B netCDF files. Starting with IPHEX, 68 | processed AMPR instrument files will be provided in a netCDF-4 format. 69 | 2. Renamed read_ampr_tb_leve1b to read_ampr_tb_level2b. Technically, the published 70 | AMPR data are Level 2B, and Level 1B data consist of pre-QC’d lower-level data. 71 | 3. Swapped to standard AMPR convention for AmprTb.swath_angle and AmprTb.swath_left. 72 | Now go from -44.1 to 44.1 deg from left to right. Note AMPR scans right to left 73 | but by convention reports its data left to right. 74 | 75 | v1.3.2 major changes (09/24/2014): 76 | 1. Deleted write_ampr_kml method from AmprTb and all associated helper functions in 77 | google_earth_tools. 78 | 2. Removed private but dead code. 79 | 80 | v1.3.1 major changes: 81 | 1. Global constant variable renaming to conform better with PEP8 standards. 82 | 2. Converted to installable module (complete with setup.py script). 83 | 3. Added support for reading gzipped AMPR TB files without first decompressing 84 | 85 | v1.3 major changes: 86 | 1. Significant refactoring of major methods to make use of new internal 87 | methods. This reduced code duplication and makes the major methods 88 | more readable. It also reduced the number of local variables in the 89 | major methods. 90 | 2. Significant variable renaming to improve clarity. Most notably, 91 | Ampr_Tb class is now AmprTb to conform to PEP8 standard. 92 | 3. Put hard coding of constants at top of program to make it obvious. 93 | 4. Added the ability to suppress plotting of swaths during aircraft maneuvers 94 | in plot_ampr_track() and write_ampr_kmz() 95 | 5. Added timerange keyword to main plotting methods to allow filtering by 96 | time instead of scan number. 97 | 6. Added flag to enable returning of figure, Basemap, etc. objects from 98 | plot_ampr_track(). This empowers the creation of highly customized plots 99 | while still using plot_ampr_track() as a baseline for the AMPR component. 100 | 7. Added timespan stamp to write_ampr_kmz() and google_earth_tools.py. Now 101 | multiple KMZ files from different times can be viewed in sequence in 102 | Google Earth. 103 | 104 | v1.2 major changes: 105 | 1. Further adjustments to the plotting routines to handle grossly 106 | bad geolocations. Now, the presence of bad_data, 0s, -1s, or wildly 107 | varying Latitude/Longitude values within an individual scan gets the scan 108 | not considered for plotting purposes. There is a flag, equator, that can 109 | be set to True if the aircraft is flying near the Equator or Prime Meridian 110 | and 0s and -1s are normally expected. 111 | 2. Added internal methods _get_scan_indices() and _filter_bad_geolocations() 112 | to handle repeated tasks in the plotting routines. 113 | 3. Changed calc_polarization() to no longer consider aircraft roll angle. 114 | Now much quicker due to matrix multiplication. Flags are available 115 | to switch between simple substitution or constrained linear inversion, 116 | and to switch between forcing nadir matching in channels A & B or not. 117 | Can also now just call deconvolution for individual freqs if desired. 118 | 4. Changed default colormap to Brent Robert's amprTB_cmap, available in 119 | udf_cmap.py. If not available will just use cm.GMT_wysiwyg, available 120 | from Basemap (a required dependency). 121 | 5. Adjusted the read routine to set infinites/NaNs in Level 1B TBs and 122 | Latitude/Longitude to bad_data. Was blowing up Python during the 123 | plotting routines otherwise. 124 | 125 | v1.1 major changes: 126 | 1. Added support for all projects on the GHRC, plus IPHEX. 127 | 2. Adjustments to the plotting routines to handle grossly bad geolocations 128 | 3. Adjusted plot_ampr_track() to put the colorbar on its own separate axis, 129 | so it is not varying in size with the plot. 130 | 4. Strip charts from plot_ampr_channels() now can show deconvolved H & V 131 | via a keyword argument. 132 | 5. ER2 now called Aircraft to keep things generalized. 133 | -------------------------------------------------------------------------------- /HELP: -------------------------------------------------------------------------------- 1 | Help on package pyampr: 2 | 3 | NAME 4 | pyampr - pyampr v1.4.0 by Timothy J. Lang 5 | 6 | FILE 7 | /Users/tjlang/anaconda/lib/python2.7/site-packages/pyampr/__init__.py 8 | 9 | DESCRIPTION 10 | PyAMPR is a package to read, analyze, and display AMPR data 11 | 12 | Please e-mail bug reports to: timothy.j.lang@nasa.gov 13 | 14 | PACKAGE CONTENTS 15 | google_earth_tools 16 | pyampr 17 | udf_cmap 18 | 19 | DATA 20 | __author__ = 'Timothy J. Lang' 21 | __email__ = 'timothy.j.lang@nasa.gov' 22 | __version__ = '1.4.0' 23 | 24 | VERSION 25 | 1.4.0 26 | 27 | AUTHOR 28 | Timothy J. Lang 29 | 30 | Help on class AmprTb in module pyampr.pyampr: 31 | 32 | class AmprTb(__builtin__.object) 33 | | Methods defined here: 34 | | 35 | | __init__(self, full_path_and_filename=None, project='IPHEX') 36 | | If passed a filename, call the read_ampr_tb_level2b() method, 37 | | otherwise just instance the class with nothing 38 | | 39 | | calc_polarization(self, simple=False, force_match=True, chan_list=['10', '19', '37', '85']) 40 | | *** THIS METHOD IS EXPERIMENTAL *** 41 | | 42 | | This method calculates H & V given the mixed-pol A & B channels. 43 | | Solves Equation 1 in Vivekanandan et al. (1993) for Tb in H and V. 44 | | Where A or B are not good will be populated with AmprTb.bad_data. 45 | | If successful, TB10H, TB10V, TB19H, TB19V, TB37H, TB37V, TB85H, TB85V 46 | | will now be attributes of the AmprTb instance. Missing channels will 47 | | not be processed. Calculation performed via methodology of Brent Roberts. 48 | | 49 | | Creates attributes called TB##_offset, where ## is channel 50 | | frequency (in GHz). This is Channel A TB - Channel B TB (in K) 51 | | at nadir scan angle. 52 | | 53 | | Author: Brent Roberts w/ adjustments by Timothy Lang 54 | | 55 | | simple = Boolean flag to swap between simple linear substitution or 56 | | constrained linear inversion. 57 | | (Default = constrained linear inversion) 58 | | force_match = Boolean flag to force A & B channels to match at nadir 59 | | (Default = match forced) 60 | | chan_list = List of strings to enable individual freqs to be deconvolved 61 | | using different methodologies (Default = All 4 freqs). 62 | | 63 | | help(self) 64 | | 65 | | plot_ampr_channels(self, scanrange=None, cmap=None, clevs=[75, 325], save=None, show_pol=False, timerange=None) 66 | | This method plots a strip chart akin to those seen here: 67 | | ftp://gpm.nsstc.nasa.gov/gpm_validation/mc3e/ampr/browse/ 68 | | matplotlib.pyplot.pcolormesh() is the workhorse plotting routine 69 | | 70 | | clevs = List with contour levels. Only max and min values are used. 71 | | cmap = Colormap desired. 72 | | See http://wiki.scipy.org/Cookbook/Matplotlib/Show_colormaps 73 | | and dir(cm) for more 74 | | save = Filename+path as string to save plot to. Type determined from suffix. 75 | | Careful - .ps/.eps/.pdf files can get huge! 76 | | scanrange = List of scan numbers (from AmprTb.Scan) to plot. 77 | | Only max/min are used. 78 | | show_pol = Set to True to show deconvolved H & V polarizations. Will call 79 | | calc_polarization() beforehand if these channels are missing. 80 | | timerange = Time range to plot. Overrides scanrange if both are set. 81 | | Format: timerange = ['hh:mm:ss', 'HH:MM:SS'] 82 | | 83 | | plot_ampr_track(self, var='10A', latrange=None, lonrange=None, parallels=2.0, meridians=2.0, title=None, clevs=[75, 325], cmap=None, save=None, show_track=False, maneuver=True, scanrange=None, show_grid=True, equator=False, timerange=None, return_flag=False) 84 | | This method plots geolocated AMPR data, along with the Aircraft track if 85 | | requested. matplotlib.pyplot.pcolormesh() on a Basemap is the workhorse 86 | | plotting routine. 87 | | 88 | | var = String with channel number and letter (e.g., 10A for 10 GHz (A) channel 89 | | latrange = List with lat range defined. Order and size (>= 2) is irrelevant 90 | | as max and min are retrieved 91 | | lonrange = List with lon range defined. Order and size (>= 2) is irrelevant 92 | | as max and min are retrieved 93 | | parallels = Scalar spacing (deg) for parallels (i.e., constant latitude) 94 | | meridians = Scalar spacing (deg) for meridians (i.e., constant longitude) 95 | | ptitle = Plot title as string 96 | | clevs = List with contour levels. Only max and min values are used. 97 | | cmap = Colormap desired. See http://wiki.scipy.org/Cookbook/Matplotlib/Show_colormaps 98 | | and dir(cm) for more 99 | | save = Filename+path as string to save plot to. Type determined from suffix. 100 | | Careful - .ps/.eps/.pdf files can get huge! 101 | | show_track = Boolean to plot Aircraft track along with AMPR data. 102 | | Plotted in black with white highlights for significant maneuvers 103 | | (abs(Aircraft_Nav['Roll']) > 5). 104 | | scanrange = List of scan numbers (from AmprTb.Scan) to plot. 105 | | Only max/min are used. 106 | | show_grid = Set to False to turn off gridlines 107 | | equator = Boolean to consider 0s/-1s in Latitude/Longitude as good geolocations 108 | | (e.g., flight crosses Equator or Prime Meridian). 109 | | Default is bad geolocations. 110 | | maneuver = Set to False to suppress the plotting of swaths during significant 111 | | aircraft maneuvers. 112 | | timerange = Time range to plot. Overrides scanrange if both are set. 113 | | Format: timerange = ['hh:mm:ss', 'HH:MM:SS'] 114 | | return_flag = Set to True to return Basemap, plot axes, etc. Order of items 115 | | returned is fig (figure instance), ax (main axis instance), 116 | | m (Basemap instance), cax (colorbar axis instance), 117 | | cbar (colorbar instance). 118 | | 119 | | read_ampr_tb_level2b(self, full_path_and_filename, project='IPHEX') 120 | | Reads Level 2B AMPR data in text or netCDF files provided from 121 | | http://ghrc.msfc.nasa.gov. Tested and working with all AMPR 122 | | data from this site, as well as IPHEX. Depending on project, 123 | | some variables are unused or are duplicates. Most notably, 124 | | pre-MC3E there are no B channels. 125 | | 126 | | Currently available field projects: IPHEX, MC3E, TC4, TCSP, JAX90, COARE, 127 | | CAMEX1, CAMEX2, CAMEX3, CAMEX4, TRMMLBA, KWAJEX, TEFLUNA, FIRE3ACE, CAPE 128 | | If you read one project's data while mistakenly telling PyAMPR the data 129 | | are from a different project, then errors are likely. 130 | | 131 | | Notable attributes in output data class 132 | | (Note - Order in documentation does not necessarily match order in data files) 133 | | --------------------------------------- 134 | | nscans = Number of scans (depends on file) 135 | | swath_size = 50 (hard coded) 136 | | nav_size = 18 (hard coded) 137 | | 138 | | shape = (nscans) 139 | | ***** 140 | | Scan - Individual scan record number 141 | | (usually thousands of scans per flight) 142 | | Year, Month, Day, Hour, Minute, Second, Day_of_Year, Second _of_Day - 143 | | Scan timing info (UTC) 144 | | Icon - QC flag (not currently used by PyAMPR) 145 | | 146 | | shape = (nscans, swath_size) 147 | | ***** 148 | | TB10A, TB10B - 10 GHz brightness temperatures 149 | | (A: Left V -> Right H, B: Left H -> Right V, units: K) 150 | | TB19A, TB19B - 19 GHz brightness temperatures (V->H, H->V, K) 151 | | TB37A, TB37B - 37 GHz brightness temperatures (V->H, H->V, K) 152 | | TB85A, TB85B - 85 GHz brightness temperatures (V->H, H->V, K) 153 | | Latitude, Longitude - Geolocation for the AMPR beam (degrees) 154 | | Land_Fraction10 - Estimated fraction of land in 10/19 GHz pixel 155 | | Land_Fraction37 - Estimated fraction of land in 37 GHz pixel 156 | | Land_Fraction85 - Estimated fraction of land in 85 GHz pixel 157 | | (0 = All Ocean, 1 = All Land) 158 | | Elevation - Topographic elevation (m MSL) 159 | | 160 | | shape = (nscans, nav_size) 161 | | ***** 162 | | Aircraft_Nav - Python dict of Aircraft navigation info: key (units) 163 | | GPS Latitude (deg) 164 | | GPS Longitude (deg) 165 | | GPS Altitude (m MSL) 166 | | Pitch (deg, + is nose up) 167 | | Roll (deg, + is right wing down) 168 | | Yaw (deg from N) 169 | | Heading (deg from N) 170 | | Ground Speed (m/s) 171 | | Air Speed (m/s) 172 | | Static Pressure (hPa) 173 | | Total Pressure (hPa) 174 | | Total Temperature (C) 175 | | Static Temperature (C) 176 | | Wind Speed (m/s) 177 | | Wind Direction (deg from N) 178 | | INS Latitude (deg) 179 | | INS Longitude (deg) 180 | | INS Altitude (m MSL) 181 | | 182 | | Version 1.4.0: Added support for Level 2B netCDF files. Starting with IPHEX, 183 | | processed AMPR instrument files will be provided in a netCDF-4 format. 184 | | 185 | | write_ampr_kmz(self, var='10A', latrange=None, lonrange=None, clevs=[75, 325], cmap=None, timerange=None, file_path=None, file_name=None, scanrange=None, show_legend=True, equator=False, maneuver=True) 186 | | This method plots geolocated AMPR data as a filled color Google Earth 187 | | kmz. Qualitatively similar plot to plot_ampr_track() but for Google 188 | | Earth. 189 | | Will produce overlay.png and, if a legend is created, 190 | | legend.png as temporary image files in the current working 191 | | directory. 192 | | 193 | | var = AMPR channel to plot (Default = DEFAULT_VAR) 194 | | scanrange = List of scan numbers (from AmprTb.Scan) to plot. 195 | | Only max/min are used. 196 | | file_path = Desired path to kmz file (Default = '') 197 | | file_name = Desired name for kmz file (Default = YYYYMMDD_TB###.kmz, 198 | | ### = channel) 199 | | latrange = List with lat range defined. Order and size (>= 2) 200 | | is irrelevant as max and min are retrieved 201 | | lonrange = List with lon range defined. Order and size (>= 2) 202 | | is irrelevant as max and min are retrieved 203 | | clevs = List with contour levels. Only max and min values are used. 204 | | (Default = DEFAULT_CLEVS) 205 | | cmap = Colormap desired. 206 | | See http://wiki.scipy.org/Cookbook/Matplotlib/Show_colormaps 207 | | and dir(cm) for more 208 | | equator = Boolean to consider 0s/-1s in Latitude/Longitude as 209 | | good geolocations 210 | | (e.g., flight crosses Equator or Prime Meridian). 211 | | Default is bad geolocations. 212 | | show_legend = Set to False to suppress the color bar 213 | | maneuver = Set to False to suppress plotting of swaths during 214 | | significant aircraft maneuvers. 215 | | timerange = Time range to plot. Overrides scanrange if both are set. 216 | | Format: timerange = ['hh:mm:ss', 'HH:MM:SS'] 217 | | 218 | | ---------------------------------------------------------------------- 219 | | Data descriptors defined here: 220 | | 221 | | __dict__ 222 | | dictionary for instance variables (if defined) 223 | | 224 | | __weakref__ 225 | | list of weak references to the object (if defined) 226 | 227 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | NASA OPEN SOURCE AGREEMENT VERSION 1.3 2 | 3 | THIS OPEN SOURCE AGREEMENT (“AGREEMENT”) DEFINES THE RIGHTS OF USE, REPRODUCTION, DISTRIBUTION, MODIFICATION AND REDISTRIBUTION OF CERTAIN COMPUTER SOFTWARE ORIGINALLY RELEASED BY THE UNITED STATES GOVERNMENT AS REPRESENTED BY THE GOVERNMENT AGENCY LISTED BELOW ("GOVERNMENT AGENCY"). THE UNITED STATES GOVERNMENT, AS REPRESENTED BY GOVERNMENT AGENCY, IS AN INTENDED THIRD-PARTY BENEFICIARY OF ALL SUBSEQUENT DISTRIBUTIONS OR REDISTRIBUTIONS OF THE SUBJECT SOFTWARE. ANYONE WHO USES, REPRODUCES, DISTRIBUTES, MODIFIES OR REDISTRIBUTES THE SUBJECT SOFTWARE, AS DEFINED HEREIN, OR ANY PART THEREOF, IS, BY THAT ACTION, ACCEPTING IN FULL THE RESPONSIBILITIES AND OBLIGATIONS CONTAINED IN THIS AGREEMENT. 4 | 5 | Government Agency: NASA Marshall Space Flight Center 6 | Government Agency Original Software Designation: MFS-33219-1 7 | Government Agency Original Software Title: Python Advanced Microwave Precipitation Radiometer Data Toolkit (PyAMPR) 8 | User Registration Requested. Please visit https://github.com/nasa/PyAMPR 9 | Government Agency Point of Contact for Original Software: timothy.j.lang@nasa.gov 10 | 11 | 12 | 1. DEFINITIONS 13 | 14 | A. “Contributor” means Government Agency, as the developer of the Original Software, and any entity that makes a Modification. 15 | B. “Covered Patents” mean patent claims licensable by a Contributor that are necessarily infringed by the use or sale of its Modification alone or when combined with the Subject Software. 16 | C. “Display” means the showing of a copy of the Subject Software, either directly or by means of an image, or any other device. 17 | D. “Distribution” means conveyance or transfer of the Subject Software, regardless of means, to another. 18 | E. “Larger Work” means computer software that combines Subject Software, or portions thereof, with software separate from the Subject Software that is not governed by the terms of this Agreement. 19 | F. “Modification” means any alteration of, including addition to or deletion from, the substance or structure of either the Original Software or Subject Software, and includes derivative works, as that term is defined in the Copyright Statute, 17 USC 101. However, the act of including Subject Software as part of a Larger Work does not in and of itself constitute a Modification. 20 | G. “Original Software” means the computer software first released under this Agreement by Government Agency with Government Agency designation MFS-33219-1 and entitled Python Advanced Microwave Precipitation Radiometer Data Toolkit (PyAMPR) including source code, object code and accompanying documentation, if any. 21 | H. “Recipient” means anyone who acquires the Subject Software under this Agreement, including all Contributors. 22 | I. “Redistribution” means Distribution of the Subject Software after a Modification has been made. 23 | J. “Reproduction” means the making of a counterpart, image or copy of the Subject Software. 24 | K. “Sale” means the exchange of the Subject Software for money or equivalent value. 25 | L. “Subject Software” means the Original Software, Modifications, or any respective parts thereof. 26 | M. “Use” means the application or employment of the Subject Software for any purpose. 27 | 28 | 2. GRANT OF RIGHTS 29 | 30 | A. Under Non-Patent Rights: Subject to the terms and conditions of this Agreement, each Contributor, with respect to its own contribution to the Subject Software, hereby grants to each Recipient a non-exclusive, world-wide, royalty-free license to engage in the following activities pertaining to the Subject Software: 31 | 32 | 1. Use 33 | 2. Distribution 34 | 3. Reproduction 35 | 4. Modification 36 | 5. Redistribution 37 | 6. Display 38 | 39 | B. Under Patent Rights: Subject to the terms and conditions of this Agreement, each Contributor, with respect to its own contribution to the Subject Software, hereby grants to each Recipient under Covered Patents a non-exclusive, world-wide, royalty-free license to engage in the following activities pertaining to the Subject Software: 40 | 41 | 1. Use 42 | 2. Distribution 43 | 3. Reproduction 44 | 4. Sale 45 | 5. Offer for Sale 46 | 47 | C. The rights granted under Paragraph B. also apply to the combination of a Contributor’s Modification and the Subject Software if, at the time the Modification is added by the Contributor, the addition of such Modification causes the combination to be covered by the Covered Patents. It does not apply to any other combinations that include a Modification. 48 | 49 | D. The rights granted in Paragraphs A. and B. allow the Recipient to sublicense those same rights. Such sublicense must be under the same terms and conditions of this Agreement. 50 | 51 | 3. OBLIGATIONS OF RECIPIENT 52 | 53 | A. Distribution or Redistribution of the Subject Software must be made under this Agreement except for additions covered under paragraph 3H. 54 | 55 | 1. Whenever a Recipient distributes or redistributes the Subject Software, a copy of this Agreement must be included with each copy of the Subject Software; and 56 | 2. If Recipient distributes or redistributes the Subject Software in any form other than source code, Recipient must also make the source code freely available, and must provide with each copy of the Subject Software information on how to obtain the source code in a reasonable manner on or through a medium customarily used for software exchange. 57 | 58 | B. Each Recipient must ensure that the following copyright notice appears prominently in the Subject Software: 59 | 60 | Copyright © {2014} United States Government as represented by 61 | Marshall Space Flight Center. 62 | No copyright is claimed in the United States under Title 17, U.S.Code. 63 | All Other Rights Reserved. 64 | 65 | C. Each Contributor must characterize its alteration of the Subject Software as a Modification and must identify itself as the originator of its Modification in a manner that reasonably allows subsequent Recipients to identify the originator of the Modification. In fulfillment of these requirements, Contributor must include a file (e.g., a change log file) that describes the alterations made and the date of the alterations, identifies Contributor as originator of the alterations, and consents to characterization of the alterations as a Modification, for example, by including a statement that the Modification is derived, directly or indirectly, from Original Software provided by Government Agency. Once consent is granted, it may not thereafter be revoked. 66 | 67 | D. A Contributor may add its own copyright notice to the Subject Software. Once a copyright notice has been added to the Subject Software, a Recipient may not remove it without the express permission of the Contributor who added the notice. 68 | 69 | E. A Recipient may not make any representation in the Subject Software or in any promotional, advertising or other material that may be construed as an endorsement by Government Agency or by any prior Recipient of any product or service provided by Recipient, or that may seek to obtain commercial advantage by the fact of Government Agency's or a prior Recipient’s participation in this Agreement. 70 | 71 | 72 | F. In an effort to track usage and maintain accurate records of the Subject Software, each Recipient, upon receipt of the Subject Software, is requested to provide Government Agency, by e-mail to the Government Agency Point of Contact listed in clause 5.F., the following information: Timothy Lang, timothy.j.lang@nasa.gov Recipient’s name and personal information shall be used for statistical purposes only. Once a Recipient makes a Modification available, it is requested that the Recipient inform Government Agency, by e-mail to the Government Agency Point of Contact listed in clause 5.F., how to access the Modification. 73 | 74 | G. Each Contributor represents that that its Modification is believed to be Contributor’s original creation and does not violate any existing agreements, regulations, statutes or rules, and further that Contributor has sufficient rights to grant the rights conveyed by this Agreement. 75 | 76 | H. A Recipient may choose to offer, and to charge a fee for, warranty, support, indemnity and/or liability obligations to one or more other Recipients of the Subject Software. A Recipient may do so, however, only on its own behalf and not on behalf of Government Agency or any other Recipient. Such a Recipient must make it absolutely clear that any such warranty, support, indemnity and/or liability obligation is offered by that Recipient alone. Further, such Recipient agrees to indemnify Government Agency and every other Recipient for any liability incurred by them as a result of warranty, support, indemnity and/or liability offered by such Recipient. 77 | 78 | I. A Recipient may create a Larger Work by combining Subject Software with separate software not governed by the terms of this agreement and distribute the Larger Work as a single product. In such case, the Recipient must make sure Subject Software, or portions thereof, included in the Larger Work is subject to this Agreement. 79 | 80 | J. Notwithstanding any provisions contained herein, Recipient is hereby put on notice that export of any goods or technical data from the United States may require some form of export license from the U.S. Government. Failure to obtain necessary export licenses may result in criminal liability under U.S. laws. Government Agency neither represents that a license shall not be required nor that, if required, it shall be issued. Nothing granted herein provides any such export license. 81 | 82 | 4. DISCLAIMER OF WARRANTIES AND LIABILITIES; WAIVER AND INDEMNIFICATION 83 | 84 | A. No Warranty: THE SUBJECT SOFTWARE IS PROVIDED “AS IS” WITHOUT ANY WARRANTY OF ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS, RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM USE OF THE SUBJECT SOFTWARE. FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT “AS IS.” 85 | 86 | B. Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT. IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW. RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT. 87 | 88 | 89 | 5. GENERAL TERMS 90 | 91 | A. Termination: This Agreement and the rights granted hereunder will terminate automatically if a Recipient fails to comply with these terms and conditions, and fails to cure such noncompliance within thirty (30) days of becoming aware of such noncompliance. Upon termination, a Recipient agrees to immediately cease use and distribution of the Subject Software. All sublicenses to the Subject Software properly granted by the breaching Recipient shall survive any such termination of this Agreement. 92 | 93 | B. Severability: If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement. 94 | 95 | C. Applicable Law: This Agreement shall be subject to United States federal law only for all purposes, including, but not limited to, determining the validity of this Agreement, the meaning of its provisions and the rights, obligations and remedies of the parties. 96 | 97 | D. Entire Understanding: This Agreement constitutes the entire understanding and agreement of the parties relating to release of the Subject Software and may not be superseded, modified or amended except by further written agreement duly executed by the parties. 98 | 99 | E. Binding Authority: By accepting and using the Subject Software under this Agreement, a Recipient affirms its authority to bind the Recipient to all terms and conditions of this Agreement and that that Recipient hereby agrees to all terms and conditions herein. 100 | 101 | F. Point of Contact: Any Recipient contact with Government Agency is to be directed to the designated representative as follows: __Timothy Lang, timothy.j.lang@nasa.gov 102 | 103 | -------------------------------------------------------------------------------- /MFS-33219 NOSA.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/MFS-33219 NOSA.doc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *************************** 2 | IF YOU ARE USING PYAMPR FOR IPHEX DATA, GO BACK TO THE GHRC SERVER AND GET THE 3 | LATEST VERSION OF THE DATASET, AS WE HAVE FIXED THE 37 GHZ CHANNEL A AND B SWAP ISSUE. 4 | *************************** 5 | 6 | Title/Version 7 | ------------- 8 | Python AMPR Data Toolkit (PyAMPR) v1.7.1 9 | Last changed 08/07/2019 10 | 11 | 12 | Lead Author 13 | ----------- 14 | Timothy Lang 15 | NASA MSFC 16 | timothy.j.lang@nasa.gov 17 | (256) 961-7861 18 | 19 | 20 | Contributing Authors 21 | -------------------- 22 | Brent Roberts 23 | NASA MSFC 24 | jason.b.roberts@nasa.gov 25 | (256) 961-7477 26 | 27 | 28 | Overview 29 | -------- 30 | The Advanced Microwave Precipitation Radiometer (AMPR) is an airborne 31 | passive microwave radiometer managed by NASA Marshall Space Flight Center. 32 | Download AMPR data from http://ghrc.nsstc.nasa.gov. 33 | AMPR brightness temperature data from NASA field projects 34 | are in ASCII or netCDF format. This python script defines a class that will 35 | read in single file from an individual aircraft flight and pull out 36 | timing, brightness temperatures from each channel, geolocation, and 37 | other information and store them as attributes using numpy 38 | arrays of the appropriate type. The file is read and the data are populated when 39 | the class is instantiated with the full path and name of an AMPR file. 40 | Numerous visualization methods are provided, including track plots, 41 | strip charts, and Google Earth KMZs. In addition, polarization 42 | deconvolution is available. 43 | 44 | 45 | Installation and Use 46 | -------------------- 47 | Dependencies: Python 2.7 thru 3.7, `numpy`, `matplotlib`, `cartopy`, 48 | `os`, `time`, `simplekml`, `datetime`, `calendar`, 49 | `codecs`, `gzip`, `netCDF4` 50 | Most of these are provided with standard Python distributions. 51 | You may need to install `cartopy` via your Python distribution's 52 | package manager. The `simplekml` package can be found [here.](https://pypi.python.org/pypi/simplekml/ ) 53 | 54 | In the same directory as this `README` is `setup.py`, to install this 55 | package enter the following command at the prompt: 56 | ``` 57 | python setup.py install 58 | ``` 59 | 60 | Then to import, in your python program include: 61 | ``` 62 | import pyampr 63 | ``` 64 | 65 | To read an AMPR TB file type: 66 | ``` 67 | ampr_data = pyampr.AmprTb('FILE_NAME_HERE', project='PROJECT_NAME_HERE') 68 | ``` 69 | 70 | Then the ampr_data object will have access to all the plotting and analysis 71 | methods. Use `help(pyampr.AmprTb)` to find out more. 72 | 73 | In particular, `help(pyampr.AmprTb.read_ampr_tb_level2b)` will give a full 74 | rundown on the data structure. 75 | 76 | A demonstration IPython notebook can be found in the notebooks directory. 77 | 78 | A simple interactive testing notebook is available in the test directory. 79 | 80 | This [conference presentation](https://ams.confex.com/ams/95Annual/webprogram/Paper262779.html) describes PyAMPR (among other modules). 81 | 82 | 83 | -------------------------------------------------------------------------------- /data/mc3e_ampr_20110420_tbs_v01.txt.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/data/mc3e_ampr_20110420_tbs_v01.txt.zip -------------------------------------------------------------------------------- /data/mc3e_ampr_20110524_tbs_v01.txt.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/data/mc3e_ampr_20110524_tbs_v01.txt.zip -------------------------------------------------------------------------------- /pyampr/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | # !/usr/bin/python 3 | # 4 | # The Python Advanced Microwave Precipitation Radiometer Data Toolkit (PyAMPR) 5 | # A package to read, analyze, and display AMPR data 6 | # 7 | # 8 | from .pyampr import AmprTb 9 | # from .pyampr import (AmprTb, _get_timestring_and_sod, _get_sod, 10 | # _method_footer_printout, _method_header_printout, 11 | # _print_times_not_valid) 12 | from .misc_tools import read_aircraft_nav_into_awot 13 | 14 | __author__ = "Timothy J. Lang" 15 | 16 | __email__ = "timothy.j.lang@nasa.gov" 17 | 18 | __version__ = "1.5.2" 19 | 20 | __doc__ = """pyampr v%s by %s 21 | 22 | PyAMPR is a package to read, analyze, and display AMPR data 23 | 24 | Please e-mail bug reports to: %s""" % (__version__, __author__, __email__) 25 | -------------------------------------------------------------------------------- /pyampr/defaults.py: -------------------------------------------------------------------------------- 1 | VERSION = '1.6' 2 | 3 | # Fixed constants used by PyAMPR set here 4 | DEFAULT_CLEVS = [75, 325] 5 | DEFAULT_GRID_DEL = 2.0 6 | DEFAULT_VAR = '10A' 7 | DEFAULT_CHAN_LIST = ['10', '19', '37', '85'] 8 | DEFAULT_SWATH_SIZE = 50 9 | DEFAULT_NAV_SIZE = 18 10 | DEFAULT_BAD_DATA = -999.0 11 | DEFAULT_SWATH_LEFT = -44.1 12 | DEFAULT_PROJECT_NAME = 'OLYMPEX' 13 | -------------------------------------------------------------------------------- /pyampr/google_earth_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | google_earth_tools v1.3 3 | Collator and Editor - Timothy J. Lang of NASA MSFC (timothy.j.lang@nasa.gov) 4 | Library of tools for creating KMZ files. Amalgamated from several sources and 5 | edited. Requires simplekml from https://code.google.com/p/simplekml/. 6 | Also requires numpy, and matplotlib. Tested with Python 2.7 and 3.4. 7 | Last edited - 07/08/2015 8 | 9 | Change Log 10 | ---------- 11 | v1.3 Major Changes (07/08/2015) 12 | 1. Made code pep8 and Python 3.4 compatible. 13 | 14 | v1.2 Major Changes (09/24/2014): 15 | 1. Deleted kml_contour, kml_begin, kml_end, line begin, line_end, 16 | and place_label functions as the write_ampr_kml method was removed 17 | from pyampr.AmprTb, given the superior write_ampr_kmz method. 18 | 19 | v1.1 Major Changes: 20 | 1. Added timespan stamping capability via passing the times string list as 21 | an argument to make_kml(). 22 | 23 | """ 24 | from __future__ import absolute_import 25 | from __future__ import print_function 26 | import numpy as np 27 | import matplotlib.pyplot as plt 28 | try: 29 | from simplekml import (Kml, OverlayXY, ScreenXY, Units, RotationXY, 30 | AltitudeMode, Camera) 31 | SIMPLEKML_FLAG = True 32 | except ImportError: 33 | SIMPLEKML_FLAG = False 34 | 35 | 36 | def gearth_fig(llcrnrlon, llcrnrlat, urcrnrlon, urcrnrlat, pixels=1024): 37 | """ 38 | Return a Matplotlib `fig` and `ax` handles for a Google-Earth Image. 39 | TJL - Obtained from 40 | http://ocefpaf.github.io/python4oceanographers/blog/2014/03/10/gearth/ 41 | 42 | """ 43 | aspect = np.cos(np.mean([llcrnrlat, urcrnrlat]) * np.pi/180.0) 44 | xsize = np.ptp([urcrnrlon, llcrnrlon]) * aspect 45 | ysize = np.ptp([urcrnrlat, llcrnrlat]) 46 | aspect = ysize / xsize 47 | 48 | if aspect > 1.0: 49 | figsize = (10.0 / aspect, 10.0) 50 | else: 51 | figsize = (10.0, 10.0 * aspect) 52 | 53 | if False: 54 | plt.ioff() # Make `True` to prevent KML components from popping up 55 | fig = plt.figure(figsize=figsize, frameon=False, dpi=pixels//10) 56 | # KML friendly image. If using basemap try: `fix_aspect=False`. 57 | ax = fig.add_axes([0, 0, 1, 1]) 58 | ax.set_xlim(llcrnrlon, urcrnrlon) 59 | ax.set_ylim(llcrnrlat, urcrnrlat) 60 | return fig, ax 61 | 62 | 63 | def make_kml(llcrnrlon, llcrnrlat, urcrnrlon, urcrnrlat, 64 | figs, colorbar=None, times=None, **kw): 65 | """ 66 | TODO: LatLon bbox, list of figs, optional colorbar figure, 67 | and several simplekml kw ... 68 | TJL - Obtained from 69 | http://ocefpaf.github.io/python4oceanographers/blog/2014/03/10/gearth/ 70 | 71 | """ 72 | if not SIMPLEKML_FLAG: 73 | print('***ERROR!***') 74 | print('simplekml not installed, download from', 75 | 'https://pypi.python.org/pypi/simplekml/') 76 | return 77 | kml = Kml() 78 | altitude = kw.pop('altitude', 2e7) 79 | roll = kw.pop('roll', 0) 80 | tilt = kw.pop('tilt', 0) 81 | altitudemode = kw.pop('altitudemode', AltitudeMode.relativetoground) 82 | camera = Camera(latitude=np.mean([urcrnrlat, llcrnrlat]), 83 | longitude=np.mean([urcrnrlon, llcrnrlon]), 84 | altitude=altitude, roll=roll, tilt=tilt, 85 | altitudemode=altitudemode) 86 | 87 | kml.document.camera = camera 88 | draworder = 0 89 | 90 | for fig in figs: # NOTE: Overlays are limited to the same bbox. 91 | draworder += 1 92 | ground = kml.newgroundoverlay(name='GroundOverlay') 93 | ground.draworder = draworder 94 | ground.visibility = kw.pop('visibility', 1) 95 | ground.name = kw.pop('name', 'overlay') 96 | ground.color = kw.pop('color', '9effffff') 97 | ground.atomauthor = kw.pop('author', 'ocefpaf') 98 | ground.latlonbox.rotation = kw.pop('rotation', 0) 99 | ground.description = kw.pop('description', 'Matplotlib figure') 100 | ground.gxaltitudemode = kw.pop('gxaltitudemode', 101 | 'clampToSeaFloor') 102 | if times: 103 | ground.timespan.begin = times[0] 104 | ground.timespan.end = times[1] 105 | ground.icon.href = fig 106 | # TJL - swapping west/east to match LL vs. UR properly 107 | ground.latlonbox.west = llcrnrlon 108 | ground.latlonbox.south = llcrnrlat 109 | ground.latlonbox.north = urcrnrlat 110 | ground.latlonbox.east = urcrnrlon 111 | 112 | if colorbar: # Options for colorbar are hard-coded (to avoid a big mess). 113 | screen = kml.newscreenoverlay(name='ScreenOverlay') 114 | screen.icon.href = colorbar 115 | screen.overlayxy = OverlayXY(x=0, y=0, 116 | xunits=Units.fraction, 117 | yunits=Units.fraction) 118 | screen.screenxy = ScreenXY(x=0.015, y=0.075, 119 | xunits=Units.fraction, 120 | yunits=Units.fraction) 121 | screen.rotationXY = RotationXY(x=0.5, y=0.5, 122 | xunits=Units.fraction, 123 | yunits=Units.fraction) 124 | screen.size.x = 0 125 | screen.size.y = 0 126 | screen.size.xunits = Units.fraction 127 | screen.size.yunits = Units.fraction 128 | screen.visibility = 1 129 | 130 | kmzfile = kw.pop('kmzfile', 'overlay.kmz') 131 | kml.savekmz(kmzfile) 132 | -------------------------------------------------------------------------------- /pyampr/misc_tools.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import datetime as dt 5 | import cartopy 6 | from .defaults import DEFAULT_CLEVS, DEFAULT_GRID_DEL 7 | 8 | 9 | def read_aircraft_nav_into_awot( 10 | AmprTB, project='OLYMPEX', platform='NASA ER-2', flight_number=None): 11 | """ 12 | Import the AmprTb aicraft nav into a format suitable for AWOT 13 | (https://github.com/nguy/AWOT). This simplifies plotting the track 14 | with time stamps, if the user has AWOT installed. 15 | 16 | Note: AWOT is not required for this function to work 17 | """ 18 | 19 | if not hasattr(AmprTB, 'Aircraft_Nav'): 20 | print('No aircraft information in argument, failing ...') 21 | return 22 | 23 | flight = {} 24 | varlist = ['latitude', 'longitude', 'altitude', 'time'] 25 | for var in varlist: 26 | flight[var] = {} 27 | flight['latitude']['data'] = AmprTB.Aircraft_Nav['GPS Latitude'] 28 | flight['longitude']['data'] = AmprTB.Aircraft_Nav['GPS Longitude'] 29 | flight['altitude']['data'] = AmprTB.Aircraft_Nav['GPS Altitude'] 30 | 31 | ampr_datetime = [] 32 | for et in AmprTB.Epoch_Time: 33 | ampr_datetime.append(dt.datetime(1970, 1, 1) + 34 | dt.timedelta(seconds=np.float(et))) 35 | flight['time']['data'] = ampr_datetime 36 | 37 | for var in varlist: 38 | flight[var]['data'] = np.ma.masked_array( 39 | flight[var]['data'], mask=False) 40 | flight['flight_number'] = flight_number 41 | flight['project'] = project 42 | flight['platform'] = platform 43 | flight['Uwind'] = None 44 | flight['Vwind'] = None 45 | return flight 46 | 47 | 48 | class _FourPanelTrack(object): 49 | 50 | def __init__(self, amprtb, **kwargs): 51 | self.kw_dict = { 52 | 'latrange': None, 'lonrange': None, 'parallels': DEFAULT_GRID_DEL, 53 | 'meridians': DEFAULT_GRID_DEL, 'clevs': DEFAULT_CLEVS, 54 | 'cmap': None, 'save': None, 'show_track': False, 'scanrange': None, 55 | 'show_grid': True, 'equator': False, 'maneuver': True, 56 | 'timerange': None, 'show_qc': False, 'resolution': '50m', 57 | 'projection': cartopy.crs.PlateCarree(), 'verbose': False, 58 | 'wmts_layer': None} 59 | self.parse_kwargs(**kwargs) 60 | self.flag = self.construct_plot(amprtb) 61 | 62 | def parse_kwargs(self, **kwargs): 63 | if 'chan' in kwargs.keys(): 64 | self.chan = kwargs['chan'] 65 | else: 66 | self.chan = 'A' 67 | for key in self.kw_dict.keys(): 68 | if key not in kwargs.keys(): 69 | setattr(self, key, self.kw_dict[key]) 70 | else: 71 | setattr(self, key, kwargs[key]) 72 | 73 | def make_title(self, freq, amprtb, ind1, ind2): 74 | lab_dict = {'10': '(a) ', '19': '(b) ', '37': '(c) ', '85': '(d) '} 75 | title = lab_dict[freq] + \ 76 | str(amprtb._get_ampr_title(freq+self.chan)) + \ 77 | '\n' + str(amprtb._get_date_string(ind1)) + \ 78 | str(', ') + str(amprtb.Time_String[ind1]) + str('-') + \ 79 | str(amprtb.Time_String[ind2-1]) + str(' UTC') 80 | return title 81 | 82 | def construct_plot(self, amprtb): 83 | """ 84 | This makes the actual 4-panel plot using repeated calls to the 85 | pyampr.AmprTb.plot_ampr_track() method. 86 | """ 87 | self.fig, [[self.ax1, self.ax2], [self.ax3, self.ax4]] = \ 88 | plt.subplots(2, 2, figsize=(10, 10), 89 | subplot_kw={'projection': self.projection}) 90 | ind1, ind2 = amprtb._get_scan_indices( 91 | self.scanrange, self.timerange, False) 92 | 93 | # 10 GHz plot 94 | stuff = amprtb.plot_ampr_track( 95 | var='10'+self.chan, latrange=self.latrange, 96 | lonrange=self.lonrange, parallels=self.parallels, 97 | meridians=self.meridians, title='', wmts_layer=self.wmts_layer, 98 | clevs=self.clevs, cmap=self.cmap, show_track=self.show_track, 99 | maneuver=self.maneuver, scanrange=self.scanrange, 100 | show_grid=self.show_grid, equator=self.equator, 101 | show_qc=self.show_qc, resolution=self.resolution, 102 | projection=self.projection, ax=self.ax1, fig=self.fig, 103 | verbose=self.verbose, timerange=self.timerange, return_flag=True) 104 | self.ax1.set_title(self.make_title('10', amprtb, ind1, ind2)) 105 | 106 | # 19 GHz plot 107 | amprtb.plot_ampr_track( 108 | var='19'+self.chan, latrange=self.latrange, 109 | lonrange=self.lonrange, parallels=self.parallels, 110 | meridians=self.meridians, title='', wmts_layer=self.wmts_layer, 111 | clevs=self.clevs, cmap=self.cmap, show_track=self.show_track, 112 | maneuver=self.maneuver, scanrange=self.scanrange, 113 | show_grid=self.show_grid, equator=self.equator, 114 | show_qc=self.show_qc, resolution=self.resolution, 115 | projection=self.projection, ax=self.ax2, fig=self.fig, 116 | verbose=self.verbose, timerange=self.timerange) 117 | self.ax2.set_title(self.make_title('19', amprtb, ind1, ind2)) 118 | 119 | # 37 GHz plot 120 | amprtb.plot_ampr_track( 121 | var='37'+self.chan, latrange=self.latrange, 122 | lonrange=self.lonrange, parallels=self.parallels, 123 | meridians=self.meridians, title='', wmts_layer=self.wmts_layer, 124 | clevs=self.clevs, cmap=self.cmap, show_track=self.show_track, 125 | maneuver=self.maneuver, scanrange=self.scanrange, 126 | show_grid=self.show_grid, equator=self.equator, 127 | show_qc=self.show_qc, resolution=self.resolution, 128 | projection=self.projection, ax=self.ax3, fig=self.fig, 129 | verbose=self.verbose, timerange=self.timerange) 130 | self.ax3.set_title(self.make_title('37', amprtb, ind1, ind2)) 131 | 132 | # 85 GHz plot 133 | amprtb.plot_ampr_track( 134 | var='85'+self.chan, latrange=self.latrange, 135 | lonrange=self.lonrange, parallels=self.parallels, 136 | meridians=self.meridians, title='', wmts_layer=self.wmts_layer, 137 | clevs=self.clevs, cmap=self.cmap, show_track=self.show_track, 138 | maneuver=self.maneuver, scanrange=self.scanrange, 139 | show_grid=self.show_grid, equator=self.equator, 140 | show_qc=self.show_qc, resolution=self.resolution, 141 | projection=self.projection, ax=self.ax4, fig=self.fig, 142 | verbose=self.verbose, timerange=self.timerange) 143 | self.ax4.set_title(self.make_title('85', amprtb, ind1, ind2)) 144 | 145 | # plt.tight_layout() 146 | return True 147 | -------------------------------------------------------------------------------- /pyampr/pyampr.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | import matplotlib.ticker as mticker 8 | import time 9 | import datetime 10 | import calendar 11 | import gzip 12 | from netCDF4 import Dataset, num2date, date2num 13 | import codecs 14 | import cartopy 15 | from .google_earth_tools import gearth_fig, make_kml 16 | from .misc_tools import _FourPanelTrack 17 | from .defaults import ( 18 | DEFAULT_CLEVS, DEFAULT_GRID_DEL, DEFAULT_VAR, DEFAULT_CHAN_LIST, 19 | DEFAULT_SWATH_SIZE, DEFAULT_NAV_SIZE, DEFAULT_BAD_DATA, 20 | DEFAULT_SWATH_LEFT, DEFAULT_PROJECT_NAME, VERSION) 21 | from .udf_cmap import amprTB_cmap, amprQC_cmap 22 | 23 | CMAP_FLAG = True 24 | FREQS = ['10', '19', '37', '85'] 25 | POLS = ['H', 'V'] 26 | CHANS = ['A', 'B'] 27 | 28 | ####################### 29 | # Main class definition 30 | ####################### 31 | 32 | 33 | class AmprTb(object): 34 | 35 | def __init__(self, full_path_and_filename=None, 36 | project=DEFAULT_PROJECT_NAME): 37 | """ 38 | If passed a filename, call the read_ampr_tb_level2b() method, 39 | otherwise just instance the class with nothing 40 | """ 41 | if full_path_and_filename is None: 42 | print('Class instantiated,', 43 | 'call read_ampr_tb_level2b() to populate') 44 | else: 45 | self.read_ampr_tb_level2b(full_path_and_filename, project=project) 46 | 47 | ######################################### 48 | 49 | def read_ampr_tb_level2b(self, full_path_and_filename, 50 | project=DEFAULT_PROJECT_NAME): 51 | """ 52 | Reads Level 2B AMPR data in text or netCDF files provided from 53 | http://ghrc.msfc.nasa.gov. Tested and working with all AMPR 54 | data from this site, as well as IPHEX. Depending on project, 55 | some variables are unused or are duplicates. Most notably, 56 | pre-MC3E there are no B channels. 57 | 58 | Currently available field projects: CAMP2EX, ORACLES, OLYMPEX, IPHEX, MC3E, 59 | TC4, TCSP, JAX90, COARE, CAMEX1, CAMEX2, CAMEX3, CAMEX4, TRMMLBA, KWAJEX, 60 | TEFLUNA, FIRE3ACE, CAPE 61 | If you read one project's data while mistakenly telling PyAMPR the data 62 | are from a different project, then errors are likely. 63 | 64 | Notable attributes in output data class 65 | (Note - Order in documentation does not necessarily match order in data files) 66 | --------------------------------------- 67 | nscans = Number of scans (depends on file) 68 | swath_size = 50 (hard coded) 69 | nav_size = 18 (hard coded) 70 | 71 | shape = (nscans) 72 | ***** 73 | Scan - Individual scan record number 74 | (usually thousands of scans per flight) 75 | Year, Month, Day, Hour, Minute, Second, Day_of_Year, Second _of_Day - 76 | Scan timing info (UTC) 77 | Icon - QC flag (not currently used by PyAMPR) 78 | 79 | shape = (nscans, swath_size) 80 | ***** 81 | TB10A, TB10B - 10 GHz brightness temperatures 82 | (A: Left V -> Right H, B: Left H -> Right V, units: K) 83 | TB19A, TB19B - 19 GHz brightness temperatures (V->H, H->V, K) 84 | TB37A, TB37B - 37 GHz brightness temperatures (V->H, H->V, K) 85 | TB85A, TB85B - 85 GHz brightness temperatures (V->H, H->V, K) 86 | Latitude, Longitude - Geolocation for the AMPR beam (degrees) 87 | Land_Fraction10 - Estimated fraction of land in 10/19 GHz pixel 88 | Land_Fraction19 - Estimated fraction of land in 19 GHz pixel (IPHEX only) 89 | Land_Fraction37 - Estimated fraction of land in 37 GHz pixel 90 | Land_Fraction85 - Estimated fraction of land in 85 GHz pixel 91 | (0 = All Ocean, 1 = All Land) 92 | Elevation - Topographic elevation (m MSL) 93 | 94 | shape = (nscans, nav_size) 95 | ***** 96 | Aircraft_Nav - Python dict of Aircraft navigation info: key (units) 97 | GPS Latitude (deg) 98 | GPS Longitude (deg) 99 | GPS Altitude (m MSL) 100 | Pitch (deg, + is nose up) 101 | Roll (deg, + is right wing down) 102 | Yaw (deg from N) 103 | Heading (deg from N) 104 | Ground Speed (m/s) 105 | Air Speed (m/s) 106 | Static Pressure (hPa) 107 | Total Pressure (hPa) 108 | Total Temperature (C) 109 | Static Temperature (C) 110 | Wind Speed (m/s) 111 | Wind Direction (deg from N) 112 | INS Latitude (deg) 113 | INS Longitude (deg) 114 | INS Altitude (m MSL) 115 | 116 | Version 1.4.0: Added support for Level 2B netCDF files. Starting with IPHEX, 117 | processed AMPR instrument files will be provided in a netCDF-4 format. 118 | """ 119 | _method_header_printout() 120 | print('read_ampr_tb_level2b(): Reading', full_path_and_filename) 121 | 122 | try: 123 | self._read_level2b_netcdf(full_path_and_filename, project=project) 124 | except IOError: 125 | print('Not netCDF file, trying ASCII read ...') 126 | self._read_level2b_ascii(full_path_and_filename, project=project) 127 | _method_footer_printout() 128 | 129 | ######################################### 130 | 131 | def help(self): 132 | """AmprTb.help() = help(AmprTb)""" 133 | help(self) 134 | 135 | ######################################### 136 | 137 | def plot_ampr_track( 138 | self, var=DEFAULT_VAR, latrange=None, lonrange=None, 139 | parallels=DEFAULT_GRID_DEL, meridians=DEFAULT_GRID_DEL, 140 | title=None, clevs=DEFAULT_CLEVS, cmap=None, 141 | save=None, show_track=False, maneuver=True, 142 | scanrange=None, show_grid=True, equator=False, 143 | timerange=None, return_flag=False, show_qc=False, 144 | resolution='50m', projection=cartopy.crs.PlateCarree(), 145 | ax=None, fig=None, title_flag=True, 146 | colorbar_label=True, verbose=False, 147 | show_borders=True, colorbar_flag=True, 148 | wmts_layer=None): 149 | 150 | """ 151 | This method plots geolocated AMPR data, along with the Aircraft track if 152 | requested. matplotlib.pyplot.pcolormesh() on a Cartopy basemap is the workhorse 153 | plotting routine. 154 | 155 | var = String with channel number and letter (e.g., 10A for 10 GHz (A) channel 156 | latrange = List with lat range defined. Order and size (>= 2) is irrelevant 157 | as max and min are retrieved. 158 | lonrange = List with lon range defined. Order and size (>= 2) is irrelevant 159 | as max and min are retrieved. 160 | parallels = Scalar spacing (deg) for parallels (i.e., constant latitude) 161 | meridians = Scalar spacing (deg) for meridians (i.e., constant longitude) 162 | ptitle = Plot title as string 163 | clevs = List with contour levels. Only max and min values are used. 164 | cmap = Colormap desired. See, e.g., 165 | https://matplotlib.org/3.1.0/gallery/color/colormap_reference.html 166 | save = Filename+path as string to save plot to. Type determined from suffix. 167 | Careful - .ps/.eps/.pdf files can get huge! 168 | show_track = Boolean to plot Aircraft track along with AMPR data. 169 | Plotted in black with white highlights for significant maneuvers 170 | (abs(Aircraft_Nav['Roll']) > 5). 171 | scanrange = List of scan numbers (from AmprTb.Scan) to plot. 172 | Only max/min are used. 173 | show_grid = Set to False to turn off gridlines 174 | equator = Boolean to consider 0s/-1s in Latitude/Longitude as good geolocations 175 | (e.g., flight crosses Equator or Prime Meridian). 176 | Default is bad geolocations. 177 | maneuver = Set to False to suppress the plotting of swaths during significant 178 | aircraft maneuvers. 179 | timerange = Time range to plot. Overrides scanrange if both are set. 180 | Format: timerange = ['hh:mm:ss', 'HH:MM:SS'] 181 | return_flag = Set to True to return figure, plot axes, etc. Order of items 182 | returned is fig (figure instance), ax (main axis instance), 183 | cbar (colorbar instance). 184 | show_qc = Set to True to show QC flags instead of TB variables. 185 | resolution = Resolution of Cartopy map ('10m', '50m', '110m') 186 | projection = Cartopy map projection to use. 187 | ax, fig = Matplotlib Axes and Figure objects. Either both must be None 188 | or both must be valid objects for the plot to work right. 189 | title_flag = Set to False to suppress a title 190 | colorbar_label = Set to False to suppress the colorbar label 191 | verbose = Set to True for text output 192 | show_borders = False removes coastlines and state/national borders 193 | wmts_layer = Use this to add a WMTS map layer from 194 | https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi, like 195 | 'ASTER_GDEM_Color_Shaded_Relief' 196 | """ 197 | 198 | # plt.close() # mpl seems buggy if you don't clean up old windows 199 | if verbose: 200 | _method_header_printout() 201 | print('plot_ampr_track():') 202 | 203 | # 10 GHz (A) channel plotted by default if mistake made 204 | if not isinstance(var, str): 205 | var = DEFAULT_VAR 206 | 207 | # Check to make sure data exist! 208 | if not hasattr(self, 'TB'+var.upper()): 209 | self._missing_channel_printout() 210 | if verbose: 211 | _method_footer_printout() 212 | return 213 | 214 | # Check that QC data exist 215 | show_qc, clevs = self._check_qc(show_qc, clevs) 216 | 217 | if self.Year[0] < 2011 and verbose is True: 218 | print('Warning: Older projects commonly had bad or missing', 219 | 'geolocation data.') 220 | print('If there are plotting problems, try a strip chart with', 221 | 'plot_ampr_channels(),') 222 | print('or try adjusting scanrange, lonrange, or latrange.') 223 | 224 | # Adjustable scan range limits 225 | # Fairly robust - will go down to a width of 10 scans or so before 226 | # plotting artifacts begin to occur. 227 | ind1, ind2 = self._get_scan_indices(scanrange, timerange, verbose) 228 | plon, plat, zdata = self._get_data_subsection( 229 | var, ind1, ind2, maneuver, show_qc, verbose) 230 | plon, plat, zdata = self._filter_bad_geolocations( 231 | plon, plat, zdata, equator, verbose) 232 | enough_data = self._check_for_enough_data_to_plot(plon, plat) 233 | if not enough_data: 234 | return 235 | 236 | latrange, lonrange = self._get_latrange_lonrange( 237 | plat, plon, latrange, lonrange) 238 | self._check_aspect_ratio(latrange, lonrange, verbose) 239 | ax, fig = parse_ax_fig(ax, fig, projection=projection) 240 | 241 | llcrnrlon = np.min(lonrange) 242 | urcrnrlon = np.max(lonrange) 243 | llcrnrlat = np.min(latrange) 244 | urcrnrlat = np.max(latrange) 245 | ax.set_extent([llcrnrlon, urcrnrlon, llcrnrlat, urcrnrlat]) 246 | 247 | if wmts_layer is not None: 248 | url = 'https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi' 249 | ax.add_wmts(url, wmts_layer) 250 | 251 | if show_borders: 252 | ax.coastlines(resolution=resolution) 253 | countries = cartopy.feature.NaturalEarthFeature( 254 | category='cultural', name='admin_0_boundary_lines_land', 255 | scale=resolution, facecolor='none') 256 | ax.add_feature(countries, edgecolor='black') 257 | states_provinces = cartopy.feature.NaturalEarthFeature( 258 | category='cultural', name='admin_1_states_provinces_lines', 259 | scale=resolution, facecolor='none') 260 | ax.add_feature(states_provinces, edgecolor='black') 261 | 262 | if show_grid: 263 | gl = ax.gridlines(draw_labels=True, linestyle='--') 264 | gl.xlabels_top = False 265 | gl.ylabels_right = False 266 | vmeridians = np.arange(-180, 180, meridians) 267 | gl.xlocator = mticker.FixedLocator(vmeridians) 268 | vparallels = np.arange(-90, 90, parallels) 269 | gl.ylocator = mticker.FixedLocator(vparallels) 270 | del vparallels, vmeridians 271 | 272 | # Draw filled contours 273 | cmap = self._get_colormap(cmap, CMAP_FLAG, show_qc) 274 | cs = ax.pcolormesh(plon, plat, zdata, vmin=np.min(clevs), 275 | vmax=np.max(clevs), cmap=cmap, zorder=2, 276 | transform=projection) 277 | 278 | # Add Aircraft track 279 | if show_track: 280 | aplon = self.Aircraft_Nav['GPS Longitude'][ind1:ind2] 281 | aplat = self.Aircraft_Nav['GPS Latitude'][ind1:ind2] 282 | # Black dots during normal flight 283 | ax.plot(aplon, aplat, 'k.', transform=projection) 284 | indices = np.where( 285 | np.abs(self.Aircraft_Nav['Roll'][ind1:ind2]) >= 5) 286 | # White dots during maneuvers 287 | ax.plot(aplon[indices[0]], aplat[indices[0]], 'w.', 288 | transform=projection) 289 | 290 | # Plot title & display 291 | if title_flag: 292 | if title is None: 293 | title = str(self._get_ampr_title(var)) + '\n' + \ 294 | str(self._get_date_string(ind1)) + \ 295 | str(', ') + str(self.Time_String[ind1]) + str('-') + \ 296 | str(self.Time_String[ind2-1]) + str(' UTC') 297 | ax.set_title(title) 298 | 299 | # Add colorbar 300 | # Colorbar independent of Basemap 301 | # cax = fig.add_axes([0.20, 0.07, 0.60, 0.02]) 302 | # cbar = plt.colorbar(cs, cax=cax, orientation='horizontal', 303 | # extend='both') 304 | if colorbar_flag: 305 | cbar = plt.colorbar(cs, ax=ax, orientation='vertical', 306 | extend='both', shrink=0.75) 307 | cbar = self._adjust_colorbar(cbar, show_qc, colorbar_label) 308 | 309 | # Save image to file 310 | if save is not None: 311 | plt.savefig(save) 312 | 313 | if verbose: 314 | _method_footer_printout() 315 | if return_flag: 316 | return fig, ax, cbar # cax, cbar 317 | 318 | ######################################### 319 | 320 | def plot_ampr_track_4panel(self, **kwargs): 321 | 322 | """ 323 | This method plots 4 panels of geolocated AMPR data, along with the aircraft 324 | track if requested. matplotlib.pyplot.pcolormesh() on a Cartopy basemap is the 325 | workhorse plotting routine. 326 | 327 | chan = String with channel letter. Default is 'A' 328 | latrange = List with lat range defined. Order and size (>= 2) is irrelevant 329 | as max and min are retrieved. 330 | lonrange = List with lon range defined. Order and size (>= 2) is irrelevant 331 | as max and min are retrieved. 332 | parallels = Scalar spacing (deg) for parallels (i.e., constant latitude) 333 | meridians = Scalar spacing (deg) for meridians (i.e., constant longitude) 334 | ptitle = Plot title as string 335 | clevs = List with contour levels. Only max and min values are used. 336 | cmap = Colormap desired. See, e.g., 337 | https://matplotlib.org/3.1.0/gallery/color/colormap_reference.html 338 | save = Filename+path as string to save plot to. Type determined from suffix. 339 | Careful - .ps/.eps/.pdf files can get huge! 340 | show_track = Boolean to plot Aircraft track along with AMPR data. 341 | Plotted in black with white highlights for significant maneuvers 342 | (abs(Aircraft_Nav['Roll']) > 5). 343 | scanrange = List of scan numbers (from AmprTb.Scan) to plot. 344 | Only max/min are used. 345 | show_grid = Set to False to turn off gridlines 346 | equator = Boolean to consider 0s/-1s in Latitude/Longitude as good geolocations 347 | (e.g., flight crosses Equator or Prime Meridian). 348 | Default is bad geolocations. 349 | maneuver = Set to False to suppress the plotting of swaths during significant 350 | aircraft maneuvers. 351 | timerange = Time range to plot. Overrides scanrange if both are set. 352 | Format: timerange = ['hh:mm:ss', 'HH:MM:SS'] 353 | return_flag = Set to True to return the _FourPanelTrack instance to make 354 | additional figure modifications 355 | show_qc = Set to True to show QC flags instead of TB variables. 356 | resolution = Resolution of Cartopy map ('10m', '50m', '110m') 357 | projection = Cartopy map projection to use. 358 | title_flag = Set to False to suppress a title 359 | colorbar_label = Set to False to suppress the colorbar label 360 | verbose = Set to True for text output 361 | wmts_layer = Use this to add a WMTS map layer from 362 | https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi, like 363 | 'ASTER_GDEM_Color_Shaded_Relief' 364 | """ 365 | fourpan = _FourPanelTrack(self, **kwargs) 366 | if 'return_flag' in kwargs.keys(): 367 | if kwargs['return_flag']: 368 | return fourpan 369 | 370 | ######################################### 371 | 372 | def plot_ampr_channels(self, scanrange=None, cmap=None, 373 | clevs=DEFAULT_CLEVS, show_qc=False, 374 | save=None, show_pol=False, timerange=None): 375 | """ 376 | This method plots a strip chart akin to those seen here: 377 | ftp://gpm.nsstc.nasa.gov/gpm_validation/mc3e/ampr/browse/ 378 | matplotlib.pyplot.pcolormesh() is the workhorse plotting routine 379 | 380 | clevs = List with contour levels. Only max and min values are used. 381 | cmap = Colormap desired. 382 | See http://wiki.scipy.org/Cookbook/Matplotlib/Show_colormaps 383 | and dir(cm) for more 384 | save = Filename+path as string to save plot to. Type determined from suffix. 385 | Careful - .ps/.eps/.pdf files can get huge! 386 | scanrange = List of scan numbers (from AmprTb.Scan) to plot. 387 | Only max/min are used. 388 | show_pol = Set to True to show deconvolved H & V polarizations. Will call 389 | calc_polarization() beforehand if these channels are missing. 390 | show_qc = Set to True to show QC flags instead of TB variables. 391 | Overrides show_pol. 392 | timerange = Time range to plot. Overrides scanrange if both are set. 393 | Format: timerange = ['hh:mm:ss', 'HH:MM:SS'] 394 | """ 395 | 396 | plt.close() # mpl seems buggy if you don't clean up old windows 397 | _method_header_printout() 398 | print('plot_ampr_channels():') 399 | 400 | # Check that QC data exist 401 | show_qc, clevs = self._check_qc(show_qc, clevs) 402 | 403 | tb_list = self._get_list_of_channels_to_plot(show_pol, show_qc) 404 | if show_pol is True and show_qc is False: 405 | pol_flag = self._check_for_pol_data() 406 | if not pol_flag: 407 | _method_footer_printout() 408 | return 409 | 410 | # Set up plot - 10 rows, 1 column, 8 channels for 8 rows, 411 | # one row for colorbar, and one row for Aircraft_Nav info 412 | fig, axes = plt.subplots(nrows=10, ncols=1, sharex=False) 413 | fig.set_size_inches(11, 8.5) 414 | 415 | # Adjustable scan range limits 416 | # Fairly robust - will go down to a width of 10 scans before plotting 417 | # artifacts begin to occur. 418 | ind1, ind2 = self._get_scan_indices(scanrange, timerange) 419 | xran = [self.Scan[ind1], self.Scan[ind2-1]] 420 | plt.xlim(xran) 421 | 422 | # The following will put the title above the x-axis tick labels 423 | ptitle = 'AMPR ' + self._get_date_string(ind1) 424 | plt.text(0.5, 1.60, ptitle, horizontalalignment='center', fontsize=14, 425 | transform=axes[0].transAxes) 426 | 427 | colormap = self._get_colormap(cmap, CMAP_FLAG, show_qc) 428 | 429 | # Loop and plot available channels 430 | itest = 0 431 | for index, chan in enumerate(tb_list): 432 | if hasattr(self, 'TB'+chan): 433 | itest = itest + 1 434 | if show_qc: 435 | var = np.transpose(getattr(self, 'qctb'+chan.lower())) 436 | else: 437 | var = np.transpose(getattr(self, 'TB'+chan)) 438 | # AMPR data arranged L-to-R in PyAMPR (index 0 to index 49 439 | # in a scan), 440 | # so need to reverse order to have L on top in strip charts. 441 | im = axes[index].pcolormesh( 442 | self.Scan, (self.swath_size-1)-np.arange(self.swath_size), 443 | var, vmin=np.min(clevs), vmax=np.max(clevs), cmap=colormap) 444 | axes[index].set_xlim(xran) 445 | axes[index].set_ylim([0, self.swath_size-1]) 446 | axes[index].set_ylabel(chan) 447 | axes[index].yaxis.set_ticks([2, self.swath_size-5]) 448 | axes[index].tick_params(axis='y', labelsize=7) 449 | axes[index].tick_params(axis='x', labelbottom=False, top=True, 450 | length=2.5) 451 | axes[index].tick_params(axis='y', which='both', left=False, 452 | right=False) 453 | # Shows how V and H vary with beam position 454 | if show_pol is False or show_qc is True: 455 | if index % 2 == 0: 456 | axes[index].set_yticklabels(['H', 'V']) 457 | else: 458 | axes[index].set_yticklabels(['V', 'H']) 459 | else: 460 | axes[index].set_yticklabels(['R', 'L']) 461 | 462 | # Get scan timing and plot it as tick labels 463 | if index == 0: 464 | axes[index].tick_params( 465 | axis='x', labelsize=7, labeltop=True, 466 | direction='out', labelbottom=False, pad=0) 467 | locs, labels = plt.xticks() 468 | new_labels = [] 469 | for t in locs: 470 | indt = np.where(self.Scan == int(t)) 471 | tmpst = np.array(self.Time_String)[indt[0]] 472 | if not isinstance(tmpst, str): 473 | if len(tmpst) > 0: 474 | tmpst = tmpst[0] 475 | else: 476 | tmpst = '' 477 | new_labels.append(''.join(str(tmpst))) 478 | axes[index].set_xticklabels(new_labels) 479 | 480 | # Check if missing too much data! 481 | if itest == 0: 482 | print('No data to plot, try reading in a file') 483 | _method_footer_printout() 484 | return 485 | 486 | # Add colorbar 487 | axes[8].axis('off') 488 | cax = fig.add_axes([0.126, 0.245, 0.775, 0.01]) 489 | cbar = plt.colorbar(im, cax=cax, orientation='horizontal', 490 | extend='both') 491 | cbar = self._adjust_colorbar(cbar, show_qc) 492 | cbar.ax.tick_params(labelsize=7) 493 | 494 | # Add Aircraft roll angle time series 495 | if hasattr(self, 'Aircraft_Nav'): 496 | axes[9].plot(self.Scan, self.Aircraft_Nav['Roll'], 'b-') 497 | axes[9].plot(self.Scan, 0.0*self.Aircraft_Nav['Roll'], 'k:') 498 | axes[9].plot(self.Scan, 5.0+0.0*self.Aircraft_Nav['Roll'], 'k:') 499 | axes[9].plot(self.Scan, -5.0+0.0*self.Aircraft_Nav['Roll'], 'k:') 500 | axes[9].set_xlabel('Scan Number or Time (UTC)', fontsize=10) 501 | axes[9].tick_params(axis='x', labelsize=9, top=False, 502 | direction='out') 503 | axes[9].set_ylabel('Aircraft Roll (deg)', fontsize=10) 504 | axes[9].tick_params(axis='y', labelsize=7) 505 | axes[9].set_xlim(xran) 506 | axes[9].set_ylim([-10, 10]) 507 | axes[9].yaxis.set_ticks([-10, 0, 10]) 508 | 509 | # Save the plot and clean up 510 | if save is not None: 511 | plt.savefig(save) 512 | _method_footer_printout() 513 | 514 | ######################################### 515 | 516 | def calc_polarization(self, simple=False, force_match=True, 517 | chan_list=DEFAULT_CHAN_LIST): 518 | 519 | """ 520 | *** THIS METHOD IS EXPERIMENTAL *** 521 | 522 | This method calculates H & V given the mixed-pol A & B channels. 523 | Solves Equation 1 in Vivekanandan et al. (1993) for Tb in H and V. 524 | Where A or B are not good will be populated with AmprTb.bad_data. 525 | If successful, TB10H, TB10V, TB19H, TB19V, TB37H, TB37V, TB85H, TB85V 526 | will now be attributes of the AmprTb instance. Missing channels will 527 | not be processed. Calculation performed via methodology of Brent Roberts. 528 | 529 | Creates attributes called TB##_offset, where ## is channel 530 | frequency (in GHz). This is Channel A TB - Channel B TB (in K) 531 | at nadir scan angle. 532 | 533 | Author: Brent Roberts w/ adjustments by Timothy Lang 534 | 535 | simple = Boolean flag to swap between simple linear substitution or 536 | constrained linear inversion. 537 | (Default = constrained linear inversion) 538 | force_match = Boolean flag to force A & B channels to match at nadir 539 | (Default = match forced) 540 | chan_list = List of strings to enable individual freqs to be deconvolved 541 | using different methodologies (Default = All 4 freqs). 542 | 543 | """ 544 | begin_time = time.time() 545 | _method_header_printout() 546 | print('calc_polarization():') 547 | 548 | if self.Year[0] < 2011: 549 | print('Pre-2011, AMPR only had one channel per frequency.') 550 | print('Thus, PyAMPR cannot deconvolve polarization for') 551 | print('this project\'s data. Sorry!') 552 | _method_footer_printout() 553 | return 554 | 555 | for chan in chan_list: 556 | if hasattr(self, 'TB'+chan+'A') and hasattr(self, 'TB'+chan+'B'): 557 | 558 | print('Calculating for', chan, 'GHz channel') 559 | # Use dummy variables and setattr to get the right-sized arrays 560 | T1 = 1.0 * getattr(self, 'TB'+chan+'A') 561 | T2 = 1.0 * getattr(self, 'TB'+chan+'B') 562 | 563 | # Get angular argument and convert to radians. 564 | # angle = np.deg2rad(data.Incidence_Angle - 45.0); 565 | angle = np.deg2rad( 566 | np.linspace(self.swath_left, 567 | -1.0*self.swath_left, self.swath_size) - 45.0) 568 | 569 | # There appears to be an offset between the mixed-pol 570 | # brightness temperatures at 0-deg incidence angle. 571 | # Theoretically these should be the same. 572 | T2 = self._compute_nadir_offset_and_apply_if_desired( 573 | angle, T1, T2, force_match, chan) 574 | 575 | if simple: 576 | tbv, tbh = \ 577 | self._solve_using_simple_linear_substitution( 578 | angle, T1, T2) 579 | 580 | # Solve using Tikhonov regularization 581 | else: 582 | tbv, tbh = \ 583 | self._solve_using_constrained_linear_inversion( 584 | angle, T1, T2) 585 | 586 | # Finalize V & H attributes and clean up 587 | setattr(self, 'TB'+chan+'V', tbv) 588 | setattr(self, 'TB'+chan+'H', tbh) 589 | 590 | else: 591 | print('TB' + chan, 'does not have both A and B channels,', 592 | 'read in a file to obtain.') 593 | print('Scene H & V not produced for this channel') 594 | 595 | print(time.time() - begin_time, 'seconds to calculate H & V') 596 | print('If successful, following attributes are now available:') 597 | for chan in chan_list: 598 | print('TB'+chan+'H', 'TB'+chan+'V', end=' ') 599 | print('') 600 | _method_footer_printout() 601 | 602 | ######################################### 603 | 604 | def write_ampr_kmz(self, var=DEFAULT_VAR, latrange=None, lonrange=None, 605 | clevs=DEFAULT_CLEVS, cmap=None, timerange=None, 606 | file_path=None, file_name=None, scanrange=None, 607 | show_legend=True, equator=False, maneuver=True, 608 | show_qc=False): 609 | """ 610 | This method plots geolocated AMPR data as a filled color Google Earth 611 | kmz. Qualitatively similar plot to plot_ampr_track() but for Google 612 | Earth. 613 | Will produce overlay.png and, if a legend is created, 614 | legend.png as temporary image files in the current working 615 | directory. 616 | 617 | var = AMPR channel to plot (Default = DEFAULT_VAR) 618 | scanrange = List of scan numbers (from AmprTb.Scan) to plot. 619 | Only max/min are used. 620 | file_path = Desired path to kmz file (Default = '') 621 | file_name = Desired name for kmz file (Default = YYYYMMDD_TB###.kmz, 622 | ### = channel) 623 | latrange = List with lat range defined. Order and size (>= 2) 624 | is irrelevant as max and min are retrieved 625 | lonrange = List with lon range defined. Order and size (>= 2) 626 | is irrelevant as max and min are retrieved 627 | clevs = List with contour levels. Only max and min values are used. 628 | (Default = DEFAULT_CLEVS) 629 | cmap = Colormap desired. 630 | See http://wiki.scipy.org/Cookbook/Matplotlib/Show_colormaps 631 | and dir(cm) for more 632 | equator = Boolean to consider 0s/-1s in Latitude/Longitude as 633 | good geolocations 634 | (e.g., flight crosses Equator or Prime Meridian). 635 | Default is bad geolocations. 636 | show_legend = Set to False to suppress the color bar 637 | maneuver = Set to False to suppress plotting of swaths during 638 | significant aircraft maneuvers. 639 | timerange = Time range to plot. Overrides scanrange if both are set. 640 | Format: timerange = ['hh:mm:ss', 'HH:MM:SS'] 641 | show_qc = Set to True to show QC flags instead of TB variables. 642 | """ 643 | plt.close() # mpl seems buggy if multiple windows are left open 644 | _method_header_printout() 645 | print('write_ampr_kmz():') 646 | 647 | # 10 GHz (A) channel plotted by default 648 | if var is None or isinstance(var, str) is False: 649 | var = DEFAULT_VAR 650 | 651 | # Check to make sure data exist! 652 | if not hasattr(self, 'TB'+var.upper()): 653 | self._missing_channel_printout() 654 | _method_footer_printout() 655 | return 656 | 657 | # Check that QC data exist 658 | show_qc, clevs = self._check_qc(show_qc, clevs) 659 | 660 | # Adjustable scan range limits 661 | # Fairly robust - will go down to a width of 25 scans before plotting 662 | # artifacts begin to occur. 663 | ind1, ind2 = self._get_scan_indices(scanrange, timerange) 664 | plon, plat, zdata = self._get_data_subsection( 665 | var, ind1, ind2, maneuver, show_qc) 666 | plon, plat, zdata = self._filter_bad_geolocations(plon, plat, 667 | zdata, equator) 668 | enough_data = self._check_for_enough_data_to_plot(plon, plat) 669 | if not enough_data: 670 | return 671 | 672 | latrange, lonrange = self._get_latrange_lonrange( 673 | plat, plon, latrange, lonrange) 674 | times = self._get_timestamps_for_gearth(ind1, ind2) 675 | 676 | # Set file info 677 | if file_path is None: 678 | file_path = '' 679 | if file_name is None: 680 | file_name = self._get_gearth_file_name(var, ind1, '.kmz') 681 | 682 | # Google Earth image production 683 | fig, ax = gearth_fig(np.min(lonrange), np.min(latrange), 684 | np.max(lonrange), np.max(latrange)) 685 | cmap = self._get_colormap(cmap, CMAP_FLAG, show_qc) 686 | cs = ax.pcolormesh(plon, plat, zdata, 687 | vmin=np.min(clevs), vmax=np.max(clevs), cmap=cmap) 688 | ax.set_axis_off() 689 | fig.savefig('overlay.png', transparent=True, format='png') 690 | 691 | # Now we convert to KMZ 692 | if show_legend is True: 693 | fig = plt.figure(figsize=(1.0, 4.0), facecolor=None, frameon=False) 694 | ax = fig.add_axes([0.0, 0.05, 0.2, 0.9]) 695 | cb = fig.colorbar(cs, cax=ax) 696 | cbytick_obj = plt.getp(cb.ax.axes, 'yticklabels') 697 | plt.setp(cbytick_obj, color='w', weight='bold') 698 | ptitle = self._get_ampr_title(var) 699 | if show_qc: 700 | cb.set_ticks([1, 2, 3, 4, 5]) 701 | clabel = 'QC Flag' 702 | else: 703 | clabel = 'TB [K]' 704 | cb.set_label(ptitle+clabel, rotation=-90, color='w', 705 | labelpad=20, weight='bold') 706 | fig.savefig('legend.png', transparent=True, format='png') 707 | make_kml(np.min(lonrange), np.min(latrange), np.max(lonrange), 708 | np.max(latrange), figs=['overlay.png'], 709 | kmzfile=str(file_path+file_name), colorbar='legend.png', 710 | times=times) 711 | else: 712 | make_kml(np.min(lonrange), np.min(latrange), np.max(lonrange), 713 | np.max(latrange), figs=['overlay.png'], 714 | kmzfile=str(file_path+file_name), times=times) 715 | 716 | print('Google Earth image hopefully written to:', 717 | file_path + file_name) 718 | _method_footer_printout() 719 | 720 | ################################################################# 721 | # Internal methods below here. Average user can stop reading now. 722 | ################################################################# 723 | 724 | def _read_level2b_netcdf(self, inputFile, project=DEFAULT_PROJECT_NAME): 725 | """ 726 | Internal method to read L2 netCDF-format AMPR data files. 727 | Accounts for whether the data are from older netCDF formats 728 | (2014-2017) or the new CF-compliant format (2017-). 729 | 730 | Parameters 731 | ---------- 732 | inputFile : str 733 | Name of input AMPR data file. 734 | """ 735 | # Open the data 736 | level2b = Dataset(inputFile, mode="r") 737 | 738 | # Set bad data and nav_size 739 | self.bad_data = DEFAULT_BAD_DATA 740 | self.nav_size = DEFAULT_NAV_SIZE 741 | 742 | # Assigning project name (self.Project) based on user input 743 | self._assign_project_name(project) 744 | self.keylist = list(level2b.variables.keys()) 745 | 746 | if 'TB' in self.keylist: 747 | self.CF_flag = True 748 | else: 749 | self.CF_flag = False 750 | 751 | # Check for navigation 752 | if 'lat' in self.keylist or 'Lat' in self.keylist: 753 | print('Found Navigation Data!') 754 | self.hasNav = True 755 | else: 756 | self.hasNav = False 757 | print('No navigation data, track plots unavailable ...') 758 | 759 | self._initialize_vars_netcdf(level2b) 760 | self._populate_time_vars_netcdf() 761 | self._fill_epoch_time() # defines and populates self.Epoch_Time 762 | 763 | if self.hasNav: # Add the geospatial information 764 | if self.CF_flag: 765 | self.Latitude = level2b.variables['Lat'][:, :] 766 | self.Longitude = level2b.variables['Lon'][:, :] 767 | if 'IncidenceAngle' in self.keylist: 768 | self.Incidence_Angle = level2b.variables[ 769 | 'IncidenceAngle'][:, :] 770 | if 'RelativeAzimuth' in self.keylist: 771 | self.Relative_Azimuth = level2b.variables[ 772 | 'RelativeAzimuth'][:, :] 773 | else: 774 | self.Latitude = level2b.variables['lat'][:, :] 775 | self.Longitude = level2b.variables['lon'][:, :] 776 | self.Incidence_Angle = level2b.variables[ 777 | 'incidence_angle'][:, :] 778 | self.Relative_Azimuth = level2b.variables[ 779 | 'relative_azimuth'][:, :] 780 | 781 | # Add the Calibrated TBs 782 | self._assign_tbs_netcdf(level2b) 783 | 784 | # Other Variables -- Set to bad data for now. 785 | self._set_old_vars_to_bad_netcdf(level2b) 786 | 787 | # Aircraft navigation info 788 | self._consider_aircraft_nav_netcdf(level2b) 789 | 790 | # QC flags and FOV (IPHEx V2 Release) 791 | self._consider_qc_flags_netcdf(level2b) 792 | self._consider_land_frac_netcdf(level2b) 793 | self._remove_nan_inf() 794 | 795 | ######################################### 796 | 797 | def _initialize_vars_netcdf(self, level2b): 798 | """ 799 | Helper method to _read_level2b_netcdf() 800 | Initializes miscellaneous variables based on level2b netCDF input. 801 | Accounts for whether the data are from older netCDF formats 802 | (2014-2017) or the new CF-compliant format (2017-). 803 | 804 | Parameters 805 | ---------- 806 | level2b : netCDF4.Dataset object 807 | The Dataset object read in from the AMPR data file. 808 | """ 809 | # Handle the times, convert to a datetime object 810 | # Add the scan and swath information 811 | if self.CF_flag: 812 | self.netcdfTimes = level2b.variables['Time'] 813 | self.dateTimes = num2date(self.netcdfTimes[:], 814 | self.netcdfTimes.units) 815 | self.nscans = len(level2b.dimensions['AlongTrackDim']) 816 | self.ncross = len(level2b.dimensions['CrossTrackDim']) 817 | self.swath_size = self.ncross 818 | self.Scan = np.arange(self.nscans, dtype='int') + 1 819 | self.Scan_Position = np.arange(self.ncross, dtype='int') + 1 820 | if 'ScanAngle' in self.keylist: 821 | self.swath_angle = level2b.variables['ScanAngle'][:] 822 | self.swath_left = self.swath_angle[0] 823 | else: 824 | self.swath_left = DEFAULT_SWATH_LEFT 825 | self.swath_angle = self.swath_left - \ 826 | (2.0 * self.swath_left / float(self.swath_size - 1.0)) * \ 827 | np.arange(self.swath_size) 828 | else: 829 | self.netcdfTimes = level2b.variables['time'] 830 | self.dateTimes = num2date(self.netcdfTimes[:], 831 | self.netcdfTimes.units) 832 | self.nscans = len(level2b.dimensions['scan_number']) 833 | self.ncross = len(level2b.dimensions['scan_position']) 834 | self.swath_size = self.ncross 835 | self.Scan = level2b.variables['scan_number'][:] 836 | self.Scan_Position = level2b.variables['scan_position'][:] 837 | self.swath_angle = level2b.variables['scan_angle'][:] 838 | self.swath_left = self.swath_angle[0] 839 | 840 | # Common to both formats 841 | self._initialize_time_fields() 842 | 843 | ######################################### 844 | 845 | def _assign_tbs_netcdf(self, level2b): 846 | """ 847 | Helper method to _read_level2b_netcdf() 848 | Assigns all brightness temperature variables to their appropriate 849 | attributes. Accounts for whether the data are from older netCDF 850 | formats (2014-2017) or the new CF-compliant format (2017-). 851 | 852 | Parameters 853 | ---------- 854 | level2b : netCDF4.Dataset object 855 | The Dataset object read in from the AMPR data file. 856 | """ 857 | if self.CF_flag: 858 | tbdata = np.array(level2b.variables['TB'][:, :, :, :]) 859 | nchan = np.shape(tbdata)[0] 860 | self.hasDeconvolvedHV = False 861 | if nchan == 1: # CF files may be from pre-dual-pol era 862 | chanlist = [CHANS[0]] 863 | elif nchan == 2: 864 | chanlist = CHANS 865 | else: 866 | chanlist = np.concatenate([CHANS, POLS]) 867 | self.hasDeconvolvedHV = True 868 | for j, pol in enumerate(chanlist): 869 | for i, freq in enumerate(FREQS): 870 | setattr(self, 'TB' + freq + pol, tbdata[j, i, :, :]) 871 | else: 872 | if 'tbs_10h' in self.keylist: 873 | self.hasDeconvolvedHV = True 874 | chanlist = np.concatenate([CHANS, POLS]) 875 | else: 876 | self.hasDeconvolvedHV = False 877 | chanlist = CHANS # Older netCDF always dual-pol 878 | for freq in FREQS: 879 | for pol in chanlist: 880 | setattr( 881 | self, 'TB' + freq + pol, 882 | level2b.variables['tbs_'+freq+pol.lower()][:, :]) 883 | if self.hasDeconvolvedHV: 884 | print('Found deconvolved H & V data!') 885 | 886 | ######################################### 887 | 888 | def _set_old_vars_to_bad_netcdf(self, level2b): 889 | """ 890 | Helper method to _read_level2b_netcdf() 891 | Checks for presence of older variables in the netCDF file, and 892 | if they are not present sets the corresponding attributes to bad. 893 | Accounts for whether the data are from older netCDF 894 | formats (2014-2017) or the new CF-compliant format (2017-). 895 | 896 | Parameters 897 | ---------- 898 | level2b : netCDF4.Dataset object 899 | The Dataset object read in from the AMPR data file. 900 | """ 901 | # Icon (whatever that is ...) 902 | if 'Icon' in self.keylist: 903 | self.Icon = level2b.variables['Icon'][:] 904 | else: 905 | self.Icon = self.bad_data * np.ones(self.nscans, dtype=np.int32) 906 | # Noise 907 | if 'Noise' in self.keylist: 908 | for j, freq in enumerate(FREQS): 909 | setattr(self, 'Noise' + freq, level2b.variables['Noise'][j, :]) 910 | else: 911 | for freq in FREQS: 912 | setattr(self, 'Noise' + freq, 913 | self.bad_data * np.ones(self.nscans, dtype='float')) 914 | # Land Fraction 915 | for freq in FREQS: 916 | setattr(self, 'Land_Fraction' + freq, 917 | self.bad_data * np.ones((self.nscans, self.ncross), 918 | dtype='float')) 919 | 920 | ######################################### 921 | 922 | def _consider_aircraft_nav_netcdf(self, level2b): 923 | """ 924 | Helper method to _read_level2b_netcdf() 925 | Assigns all aircraft navigation variables to their appropriate 926 | attributes. Accounts for whether the data are from older netCDF 927 | formats (2014-2017) or the new CF-compliant format (2017-). 928 | 929 | Parameters 930 | ---------- 931 | level2b : netCDF4.Dataset object 932 | The Dataset object read in from the AMPR data file. 933 | """ 934 | self._initialize_aircraft_dict() 935 | for var in self.Aircraft_varlist: 936 | self.Aircraft_Nav[var][:] = self.bad_data 937 | # Now, return a structured array of Aircraft_Nav parameters. 938 | if self.hasNav: 939 | if self.CF_flag: 940 | self.Aircraft_Nav['GPS Latitude'] = \ 941 | level2b.variables['GPSLatitude'][:] 942 | self.Aircraft_Nav['GPS Longitude'] = \ 943 | level2b.variables['GPSLongitude'][:] 944 | self.Aircraft_Nav['GPS Altitude'] = \ 945 | level2b.variables['GPSAltitude'][:] 946 | self.Aircraft_Nav['Pitch'] = level2b.variables['Pitch'][:] 947 | self.Aircraft_Nav['Roll'] = level2b.variables['Roll'][:] 948 | self.Aircraft_Nav['Yaw'] = level2b.variables['Yaw'][:] 949 | self.Aircraft_Nav['Heading'] = level2b.variables['Head'][:] 950 | self.Aircraft_Nav['Ground Speed'] = \ 951 | level2b.variables['GroundSpeed'][:] 952 | self.Aircraft_Nav['Air Speed'] = \ 953 | level2b.variables['AirSpeed'][:] 954 | if 'WindSpeed' in self.keylist: 955 | self.Aircraft_Nav['Wind Speed'] = \ 956 | level2b.variables['WindSpeed'][:] 957 | if 'WindDirection' in self.keylist: 958 | self.Aircraft_Nav['Wind Direction'] = \ 959 | level2b.variables['WindDirection'][:] 960 | if 'Pressure' in self.keylist: 961 | self.Aircraft_Nav['Static Pressure'] = \ 962 | level2b.variables['Pressure'][:] 963 | if 'Temperature' in self.keylist: 964 | self.Aircraft_Nav['Total Temperature'] = \ 965 | level2b.variables['Temperature'][:] 966 | # INS Lat/Lon ignored in CF datasets 967 | else: 968 | self.Aircraft_Nav['GPS Latitude'] = \ 969 | level2b.variables['gLat'][:] 970 | self.Aircraft_Nav['GPS Longitude'] = \ 971 | level2b.variables['gLon'][:] 972 | self.Aircraft_Nav['GPS Altitude'] = \ 973 | level2b.variables['gAlt'][:] 974 | self.Aircraft_Nav['Pitch'] = level2b.variables['pitch'][:] 975 | self.Aircraft_Nav['Roll'] = level2b.variables['roll'][:] 976 | self.Aircraft_Nav['Yaw'] = level2b.variables['track_angle'][:] 977 | self.Aircraft_Nav['Heading'] = level2b.variables['heading'][:] 978 | self.Aircraft_Nav['Ground Speed'] = \ 979 | level2b.variables['groundSpeed'][:] 980 | self.Aircraft_Nav['Air Speed'] = \ 981 | level2b.variables['airSpeed'][:] 982 | self.Aircraft_Nav['Static Pressure'] = \ 983 | level2b.variables['staticPressure'][:] 984 | self.Aircraft_Nav['Total Temperature'] = \ 985 | level2b.variables['totalTemp'][:] 986 | self.Aircraft_Nav['Wind Speed'] = \ 987 | level2b.variables['iWindSpeed'][:] 988 | self.Aircraft_Nav['Wind Direction'] = \ 989 | level2b.variables['iWindDir'][:] 990 | self.Aircraft_Nav['INS Latitude'] = \ 991 | level2b.variables['iLat'][:] 992 | self.Aircraft_Nav['INS Longitude'] = \ 993 | level2b.variables['iLon'][:] 994 | 995 | ######################################### 996 | 997 | def _consider_qc_flags_netcdf(self, level2b): 998 | """ 999 | Helper method to _read_level2b_netcdf() 1000 | Assigns QC variables to their appropriate attributes. 1001 | Accounts for whether the data are from older netCDF 1002 | formats (2014-2017) or the new CF-compliant format (2017-). 1003 | 1004 | Parameters 1005 | ---------- 1006 | level2b : netCDF4.Dataset object 1007 | The Dataset object read in from the AMPR data file. 1008 | """ 1009 | if self.CF_flag: 1010 | if 'QC' in self.keylist: 1011 | qcdata = np.array(level2b.variables['QC']) 1012 | if np.ndim(qcdata) != 1: # Filter out pre-dual-pol QC 1013 | self.qcIncidence = \ 1014 | level2b.variables['IncidenceAngleQC'][:, :] 1015 | for j, chan in enumerate(CHANS): 1016 | for i, freq in enumerate(FREQS): 1017 | setattr(self, 'qctb' + freq + chan.lower(), 1018 | qcdata[j, i, :, :]) 1019 | else: 1020 | if 'qctb10a' in self.keylist: 1021 | # If one there, assumes all are in file 1022 | self.qcIncidence = level2b.variables['qcIncidence'][:, :] 1023 | for freq in FREQS: 1024 | for chan in CHANS: 1025 | setattr( 1026 | self, 'qctb' + freq + chan, 1027 | level2b.variables['qctb'+freq+chan.lower()][:, :]) 1028 | 1029 | ######################################### 1030 | 1031 | def _consider_land_frac_netcdf(self, level2b): 1032 | """ 1033 | Helper method to _read_level2b_netcdf() 1034 | Assigns land fraction variables to their appropriate attributes. 1035 | Accounts for whether the data are from older netCDF 1036 | formats (2014-2017) or the new CF-compliant format (2017-). 1037 | 1038 | Parameters 1039 | ---------- 1040 | level2b : netCDF4.Dataset object 1041 | The Dataset object read in from the AMPR data file. 1042 | """ 1043 | if self.CF_flag: 1044 | if 'LandFraction' in self.keylist: 1045 | lf = np.array(level2b.variables['LandFraction']) 1046 | if np.ndim(lf) == 2: 1047 | self.Land_Fraction10 = lf 1048 | else: 1049 | for j, freq in enumerate(FREQS): 1050 | setattr(self, 'Land_Fraction' + freq, lf[j, :, :]) 1051 | else: 1052 | if 'FovWaterFrac10' in level2b.variables: 1053 | self.Land_Fraction10 = 1.0 - \ 1054 | level2b.variables['FovWaterFrac10'][:, :] 1055 | self.Land_Fraction19 = 1.0 - \ 1056 | level2b.variables['FovWaterFrac19'][:, :] 1057 | self.Land_Fraction37 = 1.0 - \ 1058 | level2b.variables['FovWaterFrac37'][:, :] 1059 | self.Land_Fraction85 = 1.0 - \ 1060 | level2b.variables['FovWaterFrac85'][:, :] 1061 | 1062 | ######################################### 1063 | 1064 | def _populate_time_vars_netcdf(self): 1065 | """Extracts needed info from dateTime attribute""" 1066 | for icount in np.arange(self.nscans): 1067 | self.Year[icount] = np.int32(self.dateTimes[icount].year) 1068 | self.Month[icount] = np.int32(self.dateTimes[icount].month) 1069 | self.Day[icount] = np.int32(self.dateTimes[icount].day) 1070 | self.Day_of_Year[icount] = \ 1071 | np.int32(self.dateTimes[icount].timetuple().tm_yday) 1072 | self.Hour[icount] = np.int32(self.dateTimes[icount].hour) 1073 | self.Minute[icount] = np.int32(self.dateTimes[icount].minute) 1074 | self.Second[icount] = np.int32(self.dateTimes[icount].second) 1075 | self._set_timestring_and_sod(icount) 1076 | 1077 | ######################################### 1078 | 1079 | def _set_timestring_and_sod(self, index): 1080 | """Create a Time String and get Second of Day for each scan""" 1081 | ts, self.Second_of_Day[index] = \ 1082 | _get_timestring_and_sod(self.Hour[index], self.Minute[index], 1083 | self.Second[index]) 1084 | self.Time_String.append(ts) 1085 | 1086 | ######################################### 1087 | 1088 | def _read_level2b_ascii(self, full_path_and_filename, 1089 | project=DEFAULT_PROJECT_NAME): 1090 | 1091 | # Following puts entire file contents into self.ampr_string 1092 | read_successful = self._read_ampr_ascii_file(full_path_and_filename) 1093 | if not read_successful: 1094 | return 1095 | 1096 | # Assigning project name (self.Project) based on user input 1097 | self._assign_project_name(project) 1098 | 1099 | # Determine number of scans 1100 | self.nscans = np.size(self.ampr_string) 1101 | print('Number of scans =', self.nscans) 1102 | 1103 | # Hard-coding sizes of AMPR arrays and dictionaries 1104 | self._hard_code_ampr_array_sizes_and_other_metadata() 1105 | self._declare_ampr_variables() 1106 | 1107 | # Populate the variables with the file's data. 1108 | # Begin master loop 1109 | for index, line in enumerate(self.ampr_string): 1110 | line_split = line.split() 1111 | 1112 | # Header info 1113 | if self.Project in ['CAMP2EX', 'ORACLES', 'OLYMPEX', 1114 | 'IPHEX', 'MC3E']: 1115 | # 2011+, header info placed before TBs 1116 | self._fill_2011on_header_info(line_split, index) 1117 | else: 1118 | # All pre-MC3E projects, aircraft data placed with header 1119 | # info before TBs 1120 | self._fill_pre2011_header_and_aircraft_info(line_split, 1121 | index) 1122 | self._set_timestring_and_sod(index) 1123 | 1124 | # Get TBs, Latitudes, Longitudes, etc. 1125 | for i in np.arange(self.swath_size): 1126 | 1127 | if self.Project == 'IPHEX': 1128 | self._fill_2011on_ampr_variables(line_split, index, i) 1129 | # Below are IPHEX-specific fixes 1130 | # 37 GHZ A&B accidentally swapped during IPHEX 1131 | # Remains unfixed in ASCII data (fixed in netCDF) 1132 | self.TB37B[index, i] = float(line_split[i + 9 + 1133 | 4 * self.swath_size]) 1134 | self.TB37A[index, i] = float(line_split[i + 9 + 1135 | 5 * self.swath_size]) 1136 | # Note: Currently (May 2014) Land_Fraction## is set to 1137 | # bad data in IPHEX ASCII files 1138 | # Terrain elevation data not recorded, instead incidence 1139 | # angle is in its position 1140 | self.Elevation[index, i] = self.bad_data 1141 | # IPHEX incidence angle currently ignored by PyAMPR 1142 | # self.Incidence_Angle[index, i] = float(line_split[i 1143 | # +27+13*self.swath_size]) 1144 | 1145 | elif self.Project in ['CAMP2EX', 'ORACLES', 'OLYMPEX', 'MC3E']: 1146 | self._fill_2011on_ampr_variables(line_split, index, i) 1147 | 1148 | else: # Pre-MC3E projects 1149 | self._fill_pre2011_ampr_variables(line_split, index, i) 1150 | 1151 | if self.Project in ['CAMP2EX', 'ORACLES', 'OLYMPEX', 1152 | 'IPHEX', 'MC3E']: 1153 | # Aircraft_Nav out of order to improve efficiency 1154 | # for 2011+ projects 1155 | self._fill_2011on_aircraft_info(line_split, index) 1156 | 1157 | self._fill_epoch_time() # defines and populates self.Epoch_Time 1158 | 1159 | # Replace unfilled data. 1160 | self._set_unfilled_data_to_bad() 1161 | if self.Year[0] < 2011: 1162 | print('Only A channels available in this project\'s data') 1163 | 1164 | ######################################### 1165 | 1166 | def _filter_bad_geolocations(self, plon=None, plat=None, zdata=None, 1167 | equator=False, verbose=True): 1168 | """ 1169 | Internal method to filter bad geolocation data. 1170 | Called by following: 1171 | plot_ampr_track() 1172 | write_ampr_kmz() 1173 | """ 1174 | # Attempt to deal with bad geolocation data 1175 | # (e.g., Latitude/Longitude=bad_data) 1176 | cond1 = np.logical_or(plon < -180, plon > 180) 1177 | cond2 = np.logical_or(plat < -90, plat > 90) 1178 | condition = np.logical_or(cond1, cond2) 1179 | indices = np.where(condition) 1180 | if np.shape(indices)[1] > 0: 1181 | if verbose: 1182 | print(np.shape(indices)[1], 1183 | 'bad geolocation(s) (e.g., ' + str(self.bad_data) + 1184 | 's) exist, attempting correction.') 1185 | zdata = np.delete(zdata, indices[0], axis=0) 1186 | plon = np.delete(plon, indices[0], axis=0) 1187 | plat = np.delete(plat, indices[0], axis=0) 1188 | 1189 | # Attempt to deal with bad 0s/-1s in Latitude/Longitude 1190 | if not equator: 1191 | cond1 = np.logical_or(plon == 0, plat == 0) 1192 | cond2 = np.logical_or(plon == -1, plat == -1) 1193 | condition = np.logical_or(cond1, cond2) 1194 | indices = np.where(condition) 1195 | if np.shape(indices)[1] > 0: 1196 | if verbose: 1197 | print(np.shape(indices)[1], 1198 | 'bad geolocation(s) (0s/-1s) exist,', 1199 | 'attempting correction.') 1200 | print('If aircraft crossed Equator or Prime Meridian,', 1201 | 'try keyword equator=True.') 1202 | zdata = np.delete(zdata, indices[0], axis=0) 1203 | plon = np.delete(plon, indices[0], axis=0) 1204 | plat = np.delete(plat, indices[0], axis=0) 1205 | 1206 | # Attempt to deal with remaining wildly varying 1207 | # Latitude/Longitude in single scan 1208 | plat_max = np.amax(plat, axis=1) 1209 | plat_min = np.amin(plat, axis=1) 1210 | plon_max = np.amax(plon, axis=1) 1211 | plon_min = np.amin(plon, axis=1) 1212 | cond1 = plat_max - plat_min > 5 # Usually 2 deg is sufficient, 1213 | cond2 = plon_max - plon_min > 5 # being extra safe here. 1214 | condition = np.logical_or(cond1, cond2) 1215 | indices = np.where(condition) 1216 | if np.shape(indices)[1] > 0: 1217 | if verbose: 1218 | print(np.shape(indices)[1], 1219 | 'scan(s) with high intra-scan geolocation variance', 1220 | 'exist, attempting correction.') 1221 | zdata = np.delete(zdata, indices[0], axis=0) 1222 | plon = np.delete(plon, indices[0], axis=0) 1223 | plat = np.delete(plat, indices[0], axis=0) 1224 | return plon, plat, zdata 1225 | 1226 | ######################################### 1227 | 1228 | def _get_scan_indices(self, scanrange=None, timerange=None, verbose=True): 1229 | 1230 | """ 1231 | Internal method to get scan indices. Used by: 1232 | plot_ampr_track() 1233 | write_ampr_kml() 1234 | write_ampr_kmz() 1235 | plot_ampr_channels() 1236 | Prioritizes timerange over scanrange. Currently cannot handle date 1237 | changes between times. User must manually break down timerange by 1238 | dates and call for each date separately. 1239 | 1240 | """ 1241 | if verbose: 1242 | print('Available scans =', 1243 | np.min(self.Scan), 'to', np.max(self.Scan)) 1244 | print('Available times =', str(self.Time_String[0]), str('-'), 1245 | str(self.Time_String[self.nscans-1])) 1246 | bt = time.time() 1247 | 1248 | if not scanrange and not timerange: 1249 | ind1, ind2 = self._get_min_max_indices() 1250 | 1251 | elif scanrange is not None and timerange is None: 1252 | indices = np.where(self.Scan == np.min(scanrange)) 1253 | if np.shape(indices[0])[0] == 0: 1254 | if verbose: 1255 | print('Scan number too small,', 1256 | 'using first scan for beginning') 1257 | ind1 = 0 1258 | else: 1259 | ind1 = indices[0][0] 1260 | indices = np.where(self.Scan == np.max(scanrange)) 1261 | if np.shape(indices[0])[0] == 0: 1262 | if verbose: 1263 | print('Scan number too high, using last scan for end') 1264 | ind2 = self.nscans 1265 | else: 1266 | ind2 = indices[0][0] 1267 | 1268 | else: 1269 | try: 1270 | t1 = _get_sod( 1271 | float(timerange[0][0:2]), float(timerange[0][3:5]), 1272 | float(timerange[0][6:8])) 1273 | t2 = _get_sod( 1274 | float(timerange[1][0:2]), float(timerange[1][3:5]), 1275 | float(timerange[1][6:8])) 1276 | cond1 = self.Second_of_Day >= np.min([t1, t2]) 1277 | cond2 = self.Second_of_Day <= np.max([t1, t2]) 1278 | condition = np.logical_and(cond1, cond2) 1279 | indices = np.where(condition) 1280 | if np.shape(indices[0])[0] > 0: 1281 | ind1 = np.min(indices[0]) 1282 | ind2 = np.max(indices[0]) 1283 | else: 1284 | if verbose: 1285 | _print_times_not_valid() 1286 | ind1, ind2 = self._get_min_max_indices() 1287 | except: 1288 | if verbose: 1289 | _print_times_not_valid() 1290 | ind1, ind2 = self._get_min_max_indices() 1291 | 1292 | if verbose: 1293 | print(time.time()-bt, 'seconds to process scan indices') 1294 | return ind1, ind2 1295 | 1296 | ######################################### 1297 | 1298 | def _adjust_colorbar(self, cbar, show_qc, colorbar_label=True): 1299 | if show_qc: 1300 | cbar.set_ticks([1, 2, 3, 4, 5]) 1301 | if colorbar_label: 1302 | cbar.set_label('QC Flag') 1303 | else: 1304 | if colorbar_label: 1305 | cbar.set_label('Brightness Temperature (K)') 1306 | return cbar 1307 | 1308 | ######################################### 1309 | 1310 | def _check_qc(self, show_qc, clevs): 1311 | """Used by strip charts, track plots, and Google Earth maps""" 1312 | if show_qc: 1313 | if not hasattr(self, 'qctb10a'): 1314 | print('No QC flags available, plotting TB data instead') 1315 | show_qc = False 1316 | else: 1317 | clevs = [0, 6] 1318 | return show_qc, clevs 1319 | 1320 | ######################################### 1321 | 1322 | def _initialize_time_fields(self): 1323 | """ 1324 | Common to ASCII and netCDF reads. 1325 | Before looping through the datetime object, initialize numpy arrays. 1326 | """ 1327 | self.Year = np.zeros(self.nscans, dtype=np.int32) 1328 | self.Month = np.zeros(self.nscans, dtype=np.int32) 1329 | self.Day = np.zeros(self.nscans, dtype=np.int32) 1330 | self.Day_of_Year = np.zeros(self.nscans, dtype=np.int32) 1331 | self.Hour = np.zeros(self.nscans, dtype=np.int32) 1332 | self.Minute = np.zeros(self.nscans, dtype=np.int32) 1333 | self.Second = np.zeros(self.nscans, dtype=np.int32) 1334 | self.Second_of_Day = np.zeros(self.nscans, dtype=np.int32) 1335 | self.Time_String = [] 1336 | 1337 | ######################################### 1338 | 1339 | def _declare_ampr_variables(self): 1340 | """Define variables to be populated""" 1341 | # Timing, Icon, and Noise 1342 | self._initialize_time_fields() 1343 | self.Scan = np.zeros(self.nscans, dtype=np.int32) 1344 | self.Icon = np.zeros(self.nscans, dtype=np.int32) 1345 | self.Noise10 = np.zeros(self.nscans, dtype='float') 1346 | self.Noise19 = np.zeros(self.nscans, dtype='float') 1347 | self.Noise37 = np.zeros(self.nscans, dtype='float') 1348 | self.Noise85 = np.zeros(self.nscans, dtype='float') 1349 | 1350 | # Geolocation and land information 1351 | self.Latitude = np.zeros((self.nscans, self.swath_size), 1352 | dtype='float') 1353 | self.Longitude = np.zeros((self.nscans, self.swath_size), 1354 | dtype='float') 1355 | self.Land_Fraction10 = np.zeros((self.nscans, self.swath_size), 1356 | dtype='float') 1357 | self.Land_Fraction37 = np.zeros((self.nscans, self.swath_size), 1358 | dtype='float') 1359 | self.Land_Fraction85 = np.zeros((self.nscans, self.swath_size), 1360 | dtype='float') 1361 | self.Elevation = np.zeros((self.nscans, self.swath_size), 1362 | dtype='float') 1363 | 1364 | # Brightness Temperatures 1365 | self.TB10A = np.zeros((self.nscans, self.swath_size), dtype='float') 1366 | self.TB10B = np.zeros((self.nscans, self.swath_size), dtype='float') 1367 | self.TB19A = np.zeros((self.nscans, self.swath_size), dtype='float') 1368 | self.TB19B = np.zeros((self.nscans, self.swath_size), dtype='float') 1369 | self.TB37A = np.zeros((self.nscans, self.swath_size), dtype='float') 1370 | self.TB37B = np.zeros((self.nscans, self.swath_size), dtype='float') 1371 | self.TB85A = np.zeros((self.nscans, self.swath_size), dtype='float') 1372 | self.TB85B = np.zeros((self.nscans, self.swath_size), dtype='float') 1373 | # Aircraft Nav 1374 | self._initialize_aircraft_dict() 1375 | 1376 | ######################################### 1377 | 1378 | def _initialize_aircraft_dict(self): 1379 | """Aircraft Navgation info""" 1380 | self.Aircraft_varlist = [u'GPS Latitude', u'GPS Longitude', 1381 | u'GPS Altitude', u'Pitch', u'Roll', 1382 | u'Yaw', u'Heading', u'Ground Speed', 1383 | u'Air Speed', u'Static Pressure', 1384 | u'Total Pressure', u'Total Temperature', 1385 | u'Static Temperature', u'Wind Speed', 1386 | u'Wind Direction', u'INS Latitude', 1387 | u'INS Longitude', u'INS Altitude'] 1388 | self.Aircraft_Nav = {} 1389 | for var in self.Aircraft_varlist: 1390 | self.Aircraft_Nav[var] = np.zeros(self.nscans, dtype=float) 1391 | 1392 | ######################################### 1393 | 1394 | def _fill_epoch_time(self): 1395 | """Calculate Epoch_Time attribute""" 1396 | self.Epoch_Time = 0 * self.Second 1397 | for i in np.arange(self.nscans): 1398 | self.Epoch_Time[i] = calendar.timegm( 1399 | (int(self.Year[i]), int(self.Month[i]), int(self.Day[i]), 1400 | int(self.Hour[i]), int(self.Minute[i]), int(self.Second[i]))) 1401 | 1402 | ######################################### 1403 | 1404 | def _set_unfilled_data_to_bad(self): 1405 | """ 1406 | Vectorizing remaining data assignments for unused/duplicate variables 1407 | Project dependent which variables are or are not used 1408 | """ 1409 | if self.Project in ['CAMP2EX', 'ORACLES', 'OLYMPEX', 'IPHEX', 'MC3E']: 1410 | self.Noise10[:] = self.bad_data 1411 | self.Noise19[:] = self.bad_data 1412 | self.Noise37[:] = self.bad_data 1413 | self.Noise85[:] = self.bad_data 1414 | else: 1415 | self.Aircraft_Nav['Static Pressure'][:] = self.bad_data 1416 | self.Aircraft_Nav['Total Pressure'][:] = self.bad_data 1417 | self.Aircraft_Nav['Static Temperature'][:] = self.bad_data 1418 | self.Aircraft_Nav['Total Temperature'][:] = self.bad_data 1419 | self.Aircraft_Nav['Wind Speed'][:] = self.bad_data 1420 | self.Aircraft_Nav['Wind Direction'][:] = self.bad_data 1421 | self.Aircraft_Nav['INS Latitude'][:] = self.bad_data 1422 | self.Aircraft_Nav['INS Longitude'][:] = self.bad_data 1423 | self.Aircraft_Nav['INS Altitude'][:] = self.bad_data 1424 | self.TB10B[:, :] = self.bad_data 1425 | self.TB19B[:, :] = self.bad_data 1426 | self.TB37B[:, :] = self.bad_data 1427 | self.TB85B[:, :] = self.bad_data 1428 | # Only one Land_Fraction variable with old projects 1429 | self.Land_Fraction37[:, :] = self.bad_data 1430 | self.Land_Fraction85[:, :] = self.bad_data 1431 | self._remove_nan_inf() 1432 | 1433 | ######################################### 1434 | 1435 | def _remove_nan_inf(self): 1436 | """ 1437 | Attempting to control for infinite and nan numbers. 1438 | They can blow up plot_ampr_track() and write_ampr_kmz(). 1439 | """ 1440 | # infinite and nan check 1441 | self.TB10A[np.isfinite(self.TB10A) is False] = self.bad_data 1442 | self.TB19A[np.isfinite(self.TB19A) is False] = self.bad_data 1443 | self.TB37A[np.isfinite(self.TB37A) is False] = self.bad_data 1444 | self.TB85A[np.isfinite(self.TB85A) is False] = self.bad_data 1445 | if hasattr(self, 'TB10B'): # Assume if one, all are there 1446 | self.TB10B[np.isfinite(self.TB10B) is False] = self.bad_data 1447 | self.TB19B[np.isfinite(self.TB19B) is False] = self.bad_data 1448 | self.TB37B[np.isfinite(self.TB37B) is False] = self.bad_data 1449 | self.TB85B[np.isfinite(self.TB85B) is False] = self.bad_data 1450 | else: # Set all the B channels to bad, these are single-channel data 1451 | for freq in FREQS: 1452 | setattr(self, 'TB' + freq + 'B', 1453 | self.bad_data * np.ones((self.nscans, self.ncross), 1454 | dtype='float')) 1455 | # Address geolocations if available 1456 | if hasattr(self, 'Latitude'): 1457 | self.Latitude[np.isfinite(self.Latitude) is False] = self.bad_data 1458 | self.Longitude[np.isfinite(self.Longitude) is False] = \ 1459 | self.bad_data 1460 | 1461 | ######################################### 1462 | 1463 | def _get_gearth_file_name(self, var=None, index=0, suffix=None): 1464 | """Obtains default file name for Google Earth KMZ""" 1465 | mo, dy = self._get_month_and_day_string(index) 1466 | timestamp = str(self.Time_String[index]).replace(':', '') 1467 | return str(self.Year[index]) + str(mo) + str(dy) + str('_') + \ 1468 | str(timestamp) + str('z_TB') + str(var.upper()) + str(suffix) 1469 | 1470 | ######################################### 1471 | 1472 | def _get_data_subsection(self, var=None, ind1=None, ind2=None, 1473 | maneuver=True, show_qc=False, verbose=True): 1474 | """Subsections the data for later plotting""" 1475 | if show_qc: 1476 | zdata = 1.0 * getattr(self, 'qctb'+var.lower()) 1477 | else: 1478 | zdata = 1.0 * getattr(self, 'TB'+var.upper()) 1479 | zdata = zdata[ind1:ind2] 1480 | plon = 1.0 * getattr(self, 'Longitude') 1481 | plon = plon[ind1:ind2] 1482 | plat = 1.0 * getattr(self, 'Latitude') 1483 | plat = plat[ind1:ind2] 1484 | if not maneuver: 1485 | if verbose: 1486 | print('Filtering out significant aircraft maneuvers') 1487 | roll = self.Aircraft_Nav['Roll'][ind1:ind2] 1488 | indices = np.where(np.abs(roll) >= 5) 1489 | if np.shape(indices)[1] > 0: 1490 | zdata = np.delete(zdata, indices[0], axis=0) 1491 | plon = np.delete(plon, indices[0], axis=0) 1492 | plat = np.delete(plat, indices[0], axis=0) 1493 | return plon, plat, zdata 1494 | 1495 | ######################################### 1496 | 1497 | def _fill_2011on_header_info(self, line_split=None, index=None): 1498 | """For ASCII data, fill 2011+ metadata variables""" 1499 | self.Scan[index] = int(line_split[0]) 1500 | self.Year[index] = int(line_split[1]) 1501 | self.Month[index] = int(line_split[2]) 1502 | self.Day[index] = int(line_split[3]) 1503 | self.Day_of_Year[index] = int(line_split[4]) 1504 | self.Hour[index] = int(line_split[5]) 1505 | self.Minute[index] = int(line_split[6]) 1506 | self.Second[index] = int(line_split[7]) 1507 | self.Icon[index] = int(line_split[8]) 1508 | 1509 | ######################################### 1510 | 1511 | def _fill_pre2011_header_and_aircraft_info(self, line_split=None, 1512 | index=None): 1513 | """For ASCII data, fill < 2011 metadata variables""" 1514 | # Begin theatrics to handle Year appearing only in first line, 1515 | # but in different positions depending on project. 1516 | # In JAX90, COARE, & CAPE files it wipes out Day_of_Year[0]. 1517 | # In other files it occupies the place of Scan[0]. 1518 | self.Scan[index] = index + 1 1519 | if (self.Project == 'JAX90' or self.Project == 'CAPE' or 1520 | self.Project == 'COARE') and index == 0: 1521 | self.Year[:] = int(line_split[1]) 1522 | if (self.Project != 'JAX90' and self.Project != 'CAPE' and 1523 | self.Project != 'COARE' and index == 0): 1524 | self.Year[:] = int(line_split[0]) 1525 | self.Day_of_Year[index] = int(line_split[1]) 1526 | doy = int(line_split[1]) - 1 1527 | sdate = str(datetime.date(self.Year[index], 1, 1) + 1528 | datetime.timedelta(doy)) 1529 | self.Month[index] = int(sdate[5:7]) 1530 | self.Day[index] = int(sdate[8:10]) 1531 | if (self.Project == 'JAX90' or self.Project == 'CAPE' or 1532 | self.Project == 'COARE') and index == 1: 1533 | # Assuming date change doesn't occur between lines 1 and 2 1534 | self.Day_of_Year[0] = self.Day_of_Year[1] 1535 | self.Month[0] = self.Month[1] 1536 | self.Day[0] = self.Day[1] 1537 | # End theatrics - WHEW! 1538 | 1539 | self.Hour[index] = int(line_split[2]) 1540 | self.Minute[index] = int(line_split[3]) 1541 | self.Second[index] = int(line_split[4]) 1542 | self.Icon[index] = int(line_split[5]) 1543 | self.Aircraft_Nav['GPS Latitude'][index] = float(line_split[6]) 1544 | self.Aircraft_Nav['GPS Longitude'][index] = float(line_split[7]) 1545 | self.Aircraft_Nav['GPS Altitude'][index] = float(line_split[8]) 1546 | self.Aircraft_Nav['Pitch'][index] = float(line_split[9]) 1547 | self.Aircraft_Nav['Roll'][index] = float(line_split[10]) 1548 | self.Aircraft_Nav['Yaw'][index] = float(line_split[11]) 1549 | self.Aircraft_Nav['Heading'][index] = float(line_split[12]) 1550 | self.Aircraft_Nav['Air Speed'][index] = float(line_split[13]) 1551 | self.Aircraft_Nav['Ground Speed'][index] = float(line_split[14]) 1552 | self.Noise10[index] = float(line_split[15]) 1553 | self.Noise19[index] = float(line_split[16]) 1554 | self.Noise37[index] = float(line_split[17]) 1555 | self.Noise85[index] = float(line_split[18]) 1556 | 1557 | ######################################### 1558 | 1559 | def _fill_2011on_ampr_variables(self, line_split=None, index=None, 1560 | i=None): 1561 | """ 1562 | Data written out left to right; i=0 is Left edge, 1563 | i=49 is Right edge 1564 | """ 1565 | self.TB10A[index, i] = float(line_split[i + 9]) 1566 | self.TB10B[index, i] = float(line_split[i + 9 + self.swath_size]) 1567 | self.TB19A[index, i] = float(line_split[i + 9 + 2 * self.swath_size]) 1568 | self.TB19B[index, i] = float(line_split[i + 9 + 3 * self.swath_size]) 1569 | self.TB37A[index, i] = float(line_split[i + 9 + 4 * self.swath_size]) 1570 | self.TB37B[index, i] = float(line_split[i + 9 + 5 * self.swath_size]) 1571 | self.TB85A[index, i] = float(line_split[i + 9 + 6 * self.swath_size]) 1572 | self.TB85B[index, i] = float(line_split[i + 9 + 7 * self.swath_size]) 1573 | self.Latitude[index, i] = float( 1574 | line_split[i + 9 + 8 * self.swath_size]) 1575 | self.Longitude[index, i] = float( 1576 | line_split[i + 9 + 9 * self.swath_size]) 1577 | self.Land_Fraction10[index, i] = float( 1578 | line_split[i + 27 + 10 * self.swath_size]) 1579 | self.Land_Fraction37[index, i] = float( 1580 | line_split[i + 27 + 11 * self.swath_size]) 1581 | self.Land_Fraction85[index, i] = float( 1582 | line_split[i + 27 + 12 * self.swath_size]) 1583 | self.Elevation[index, i] = float( 1584 | line_split[i + 27 + 13 * self.swath_size]) 1585 | 1586 | ######################################### 1587 | 1588 | def _fill_pre2011_ampr_variables(self, line_split=None, index=None, 1589 | i=None): 1590 | """ 1591 | Data written out left to right; i=0 is Left edge, 1592 | i=49 is Right edge 1593 | """ 1594 | self.TB10A[index, i] = float(line_split[i + 19]) 1595 | self.TB19A[index, i] = float(line_split[i + 19 + self.swath_size]) 1596 | self.TB37A[index, i] = float(line_split[i + 19 + 2 * self.swath_size]) 1597 | self.TB85A[index, i] = float(line_split[i + 19 + 3 * self.swath_size]) 1598 | self.Latitude[index, i] = float( 1599 | line_split[i + 19 + 4 * self.swath_size]) 1600 | self.Longitude[index, i] = float( 1601 | line_split[i + 19 + 5 * self.swath_size]) 1602 | self.Elevation[index, i] = float( 1603 | line_split[i + 19 + 6 * self.swath_size]) 1604 | self.Land_Fraction10[index, i] = float( 1605 | line_split[i + 19 + 7 * self.swath_size]) 1606 | 1607 | ######################################### 1608 | 1609 | def _check_for_enough_data_to_plot(self, plon=None, plat=None): 1610 | """Test on amount of data available to plot, returns True/False""" 1611 | cond1 = np.logical_and(plon >= -180, plon <= 180) 1612 | cond2 = np.logical_and(plat >= -90, plat <= 90) 1613 | condition = np.logical_and(cond1, cond2) 1614 | indices = np.where(condition) 1615 | if np.shape(indices)[1] < 100: 1616 | print(np.shape(indices)[1], 'good gelocations,', 1617 | 'need 100+ (i.e., 2+ scans).') 1618 | print('Not enough good geolocation data to plot, returning.') 1619 | return False 1620 | else: 1621 | return True 1622 | 1623 | ######################################### 1624 | 1625 | def _check_aspect_ratio(self, latrange=None, lonrange=None, 1626 | verbose=True): 1627 | """ 1628 | Provides a warning if the prospective plot's aspect ratio will 1629 | cause colorbar to be far removed from plot window itself. 1630 | """ 1631 | aspect_ratio = (float(np.max(latrange)) - float(np.min(latrange))) /\ 1632 | (float(np.max(lonrange)) - float(np.min(lonrange))) 1633 | if aspect_ratio < 0.5 or aspect_ratio > 2: 1634 | if verbose: 1635 | print('Warning: Your aspect ratio choice could lead to poor', 1636 | 'plotting results.') 1637 | print('Best results are obtained when latrange ~ lonrange.') 1638 | 1639 | ######################################### 1640 | 1641 | def _hard_code_ampr_array_sizes_and_other_metadata(self): 1642 | """Hard coding certain fixed AMPR characteristics""" 1643 | self.swath_size = DEFAULT_SWATH_SIZE 1644 | self.nav_size = DEFAULT_NAV_SIZE 1645 | self.bad_data = DEFAULT_BAD_DATA 1646 | self.swath_left = DEFAULT_SWATH_LEFT 1647 | self.swath_angle = self.swath_left - \ 1648 | (2.0 * self.swath_left / float(self.swath_size - 1.0)) * \ 1649 | np.arange(self.swath_size) 1650 | 1651 | ######################################### 1652 | 1653 | def _fill_2011on_aircraft_info(self, line_split, index): 1654 | """For ASCII data, fill 2011+ aircraft navigation variables""" 1655 | aircraft_i = 0 1656 | for name in self.Aircraft_varlist: 1657 | self.Aircraft_Nav[name][index] = \ 1658 | float(line_split[aircraft_i + 9 + 10 * self.swath_size]) 1659 | aircraft_i += 1 1660 | 1661 | ######################################### 1662 | 1663 | def _get_list_of_channels_to_plot(self, show_pol=False, show_qc=False): 1664 | """Used by plot_ampr_channels() to figure out what variables to plot""" 1665 | if show_qc: 1666 | return ['10A', '10B', '19A', '19B', '37A', '37B', '85A', '85B'] 1667 | if show_pol: 1668 | tb_list = ['10V', '10H', '19V', '19H', '37V', '37H', '85V', '85H'] 1669 | if (not hasattr(self, 'TB10V') or not hasattr(self, 'TB19V') or not 1670 | hasattr(self, 'TB37V') or not hasattr(self, 'TB85V')): 1671 | print('Missing some pol channels,', 1672 | 'trying calc_polarization() before plot') 1673 | self.calc_polarization() 1674 | else: 1675 | tb_list = ['10A', '10B', '19A', '19B', '37A', '37B', '85A', '85B'] 1676 | return tb_list 1677 | 1678 | ######################################### 1679 | 1680 | def _missing_channel_printout(self): 1681 | """Simple warning message if requested channel is missing""" 1682 | print('Channel doesn\'t exist, check typing or try reading in a file') 1683 | print('Acceptable channels = 10A, 10B, 19A, 19B, 37A, 37B, 85A, 85B') 1684 | print('If calculated, also = 10H, 10V, 19H, 19V, 37H, 37V, 85H, 85V') 1685 | 1686 | ######################################### 1687 | 1688 | def _get_latrange_lonrange(self, plat=None, plon=None, 1689 | latrange=None, lonrange=None): 1690 | """Determine domain of plot based on what user provided""" 1691 | if latrange is None: 1692 | latrange = [np.min(plat), np.max(plat)] 1693 | if lonrange is None: 1694 | lonrange = [np.min(plon), np.max(plon)] 1695 | return latrange, lonrange 1696 | 1697 | ######################################### 1698 | 1699 | def _get_colormap(self, cmap, flag, qc_flag=False): 1700 | """Figure out colormap based on user input""" 1701 | if cmap is None: 1702 | if flag: 1703 | if qc_flag: 1704 | cmap = amprQC_cmap 1705 | else: 1706 | cmap = amprTB_cmap 1707 | else: 1708 | cmap = 'jet' 1709 | return cmap 1710 | 1711 | ######################################### 1712 | 1713 | def _get_date_string(self, index=0): 1714 | """Get date string that is used in plot titles""" 1715 | return str(self.Month[index]) + '/' + str(self.Day[index]) + '/' + \ 1716 | str(self.Year[index]) 1717 | 1718 | ######################################### 1719 | 1720 | def _get_ampr_title(self, var=None): 1721 | """Get default""" 1722 | return 'AMPR '+var[0:2]+' GHz ('+var[2].upper()+') ' 1723 | 1724 | ######################################### 1725 | 1726 | def _read_ampr_ascii_file(self, full_path_and_filename): 1727 | """ 1728 | Ingest the AMPR ASCII file as a huge string array, which 1729 | will be parsed for data later on. Support for gzipped files 1730 | and error checking provided. 1731 | """ 1732 | if full_path_and_filename[-3:] == '.gz': 1733 | try: 1734 | fileobj = gzip.open(full_path_and_filename) 1735 | ascii = codecs.getreader('ASCII') 1736 | fileobj = ascii(fileobj) 1737 | except: 1738 | print('Incorrect file or file doesn\'t exist, returning') 1739 | return False 1740 | else: 1741 | try: 1742 | fileobj = open(full_path_and_filename, 'r') 1743 | except: 1744 | print('Incorrect file or file doesn\'t exist, returning') 1745 | return False 1746 | try: 1747 | contents = fileobj.readlines() 1748 | except: 1749 | print('File not ASCII format, returning') 1750 | fileobj.close() 1751 | return False 1752 | fileobj.close() 1753 | self.ampr_string = np.array(contents) 1754 | return True 1755 | 1756 | ######################################### 1757 | 1758 | def _assign_project_name(self, project=DEFAULT_PROJECT_NAME): 1759 | """Check user-provided project name and keep track of it""" 1760 | if not isinstance(project, str): 1761 | print('Bad project name, provide actual string') 1762 | print('Assuming', DEFAULT_PROJECT_NAME, 'data structure.') 1763 | project = DEFAULT_PROJECT_NAME 1764 | else: 1765 | print('Assuming', project.upper(), 'data structure.') 1766 | print('Change to proper project if incorrect, otherwise errors', 1767 | 'will occur.') 1768 | print('Currently available field projects: CAMP2EX, ORACLES, OLYMPEX,' 1769 | 'IPHEX, MC3E, TC4, TCSP, JAX90, COARE,') 1770 | print('CAMEX1, CAMEX2, CAMEX3, CAMEX4, TRMMLBA, KWAJEX, TEFLUNA,', 1771 | 'FIRE3ACE, CAPE') 1772 | print('Default: project = \''+DEFAULT_PROJECT_NAME+'\'') 1773 | self.Project = project.upper() 1774 | 1775 | ######################################### 1776 | 1777 | def _get_min_max_indices(self): 1778 | """Return all possible scan indices""" 1779 | return 0, self.nscans 1780 | 1781 | ######################################### 1782 | 1783 | def _solve_using_simple_linear_substitution(self, angle=None, 1784 | T1=None, T2=None): 1785 | """Lead author: Brent Robers""" 1786 | tbv = (T1 - T1 * np.cos(angle)**2 - T2 * np.cos(angle)**2) /\ 1787 | (np.sin(angle)**2 - np.cos(angle)**2) 1788 | tbh = T1 + T2 - tbv 1789 | return tbv, tbh 1790 | 1791 | ######################################### 1792 | 1793 | def _solve_using_constrained_linear_inversion(self, angle=None, 1794 | T1=None, T2=None): 1795 | """Lead author: Brent Robers""" 1796 | # Set regularization parameter 1797 | gam = 10.0**(-1) 1798 | # Solve 1799 | # n.b. I solved this on paper so that a single set of equations 1800 | # can be used and applied using matrix notation. 1801 | # Define parameters needed. 1802 | a = np.cos(angle)**2 1803 | b = np.sin(angle)**2 1804 | c = np.sin(angle)**2 1805 | d = np.cos(angle)**2 1806 | zeta = 1.0 / ((a**2 + c**2 + gam**2) * 1807 | (b**2 + d**2 + gam**2) - (a * b + c * d)**2) 1808 | # Get tbv and tbh 1809 | tbv = T1 * zeta * (b * (a**2 + c**2 + gam**2) - a * (a * b + c * d)) +\ 1810 | T2 * zeta * (d * (a**2 + c**2 + gam**2) - c * (a * b + c * d)) 1811 | tbh = T1 * zeta * (a * (b**2 + d**2 + gam**2) - b * (a * b + c * d)) +\ 1812 | T2 * zeta * (c * (b**2 + d**2 + gam**2) - d * (a * b + c * d)) 1813 | return tbv, tbh 1814 | 1815 | ######################################### 1816 | 1817 | def _compute_nadir_offset_and_apply_if_desired(self, angle=None, T1=None, 1818 | T2=None, force_match=True, 1819 | chan=None): 1820 | """Lead author: Brent Roberts""" 1821 | scanang = np.rad2deg(angle) 1822 | # xoff = np.empty(self.nscans) * np.nan 1823 | xoff = np.zeros(self.nscans) 1824 | for iscan in np.arange(self.nscans): 1825 | # Get value. 1826 | x1 = T1[iscan, :] 1827 | x2 = T2[iscan, :] 1828 | # Interpolate x1 and x2 to 0 degrees. 1829 | x1_zero = np.interp(-45.0, scanang, x1) 1830 | x2_zero = np.interp(-45.0, scanang, x2) 1831 | # Compute the offset. 1832 | xoffset = x1_zero - x2_zero 1833 | # Adjust T2 to match T1. 1834 | if force_match: 1835 | T2[iscan, :] = T2[iscan, :] + xoffset 1836 | xoff[iscan] = xoffset 1837 | setattr(self, 'TB'+chan+'_offset', xoff) 1838 | return T2 1839 | 1840 | ######################################### 1841 | 1842 | def _check_for_pol_data(self): 1843 | """Simple check on whether polarization deconvolved data exist""" 1844 | if (not hasattr(self, 'TB10V') and not hasattr(self, 'TB19V') and not 1845 | hasattr(self, 'TB37V') and not hasattr(self, 'TB85V')): 1846 | print('No polarization data to plot! Returning ...') 1847 | return False 1848 | else: 1849 | return True 1850 | 1851 | ######################################### 1852 | 1853 | def _get_timestamps_for_gearth(self, ind1=None, ind2=None): 1854 | """Format example: '1997-07-16T07:30:15Z'""" 1855 | mo, dy = self._get_month_and_day_string(ind1) 1856 | time1 = str(self.Year[ind1]) + str('-') + str(mo) + str('-') + \ 1857 | str(dy) + str('T') + str(self.Time_String[ind1]) + str('Z') 1858 | mo, dy = self._get_month_and_day_string(ind2-1) 1859 | time2 = str(self.Year[ind2-1]) + str('-') + str(mo) + str('-') + \ 1860 | str(dy) + str('T') + str(self.Time_String[ind2-1]) + str('Z') 1861 | times = [time1, time2] 1862 | return times 1863 | 1864 | ######################################### 1865 | 1866 | def _get_month_and_day_string(self, index): 1867 | """ 1868 | Return month and day as strings for use in creating file 1869 | names. It places 0s in front single-digit numbers. 1870 | """ 1871 | smo = str(self.Month[index]) 1872 | if self.Month[index] < 10: 1873 | smo = '0' + smo 1874 | sdy = str(self.Day[index]) 1875 | if self.Day[index] < 10: 1876 | sdy = '0' + sdy 1877 | return str(smo), str(sdy) 1878 | 1879 | ######################################### 1880 | 1881 | ######################################### 1882 | 1883 | ####################################### 1884 | # Add more attributes and methods here! 1885 | ####################################### 1886 | 1887 | ######################################### 1888 | 1889 | # End class AmprTb definition 1890 | ########################################################### 1891 | 1892 | # Stand-alone functions 1893 | 1894 | 1895 | def parse_ax_fig(ax, fig, projection=None): 1896 | """ Parse and return ax and fig parameters. """ 1897 | if fig is None: 1898 | fig = plt.figure(figsize=(8, 8)) 1899 | if ax is None: 1900 | ax = fig.add_axes([0.1, 0.15, 0.8, 0.8], projection=projection) 1901 | # if ax is None: 1902 | # ax = plt.gca() 1903 | # if fig is None: 1904 | # fig = plt.gcf() 1905 | return ax, fig 1906 | 1907 | 1908 | def _get_timestring_and_sod(hour=None, minute=None, second=None): 1909 | """Time_String: Fill size gaps with 0s""" 1910 | d = str(hour) 1911 | if hour < 10: 1912 | d = str('0' + d) 1913 | e = str(minute) 1914 | if minute < 10: 1915 | e = str('0' + e) 1916 | f = str(second) 1917 | if second < 10: 1918 | f = str('0' + f) 1919 | return str(d + str(':') + e + str(':') + f), _get_sod(hour, minute, second) 1920 | 1921 | 1922 | def _get_sod(hour=None, minute=None, second=None): 1923 | """Calculate second of day given hour, minute, second""" 1924 | return 3600.0 * hour + 60.0 * minute + second 1925 | 1926 | 1927 | def _method_footer_printout(): 1928 | """Helps clarify text output""" 1929 | print('********************') 1930 | print('') 1931 | 1932 | 1933 | def _method_header_printout(): 1934 | """Helps clarify text output""" 1935 | print('') 1936 | print('********************') 1937 | 1938 | 1939 | def _print_times_not_valid(): 1940 | """Warning message if user provided bad timerange keyword""" 1941 | print('Times not valid, just plotting everything') 1942 | print('Next time try timerange=[\'hh:mm:ss\',\'HH:MM:SS\']') 1943 | -------------------------------------------------------------------------------- /pyampr/udf_cmap.py: -------------------------------------------------------------------------------- 1 | """ 2 | Program: udf_cmap.py 3 | 4 | 5 | Purpose: Define colormaps to be used for plotting AMPR data. 6 | 7 | 8 | Description: A colormap is defined with relatively sharp transitions to 9 | help highlight transitions in observed data. 10 | 11 | 12 | Author: Brent Roberts, jason.b.roberts@nasa.gov 13 | Contributor: Timothy Lang, timothy.j.lang@nasa.gov 14 | 15 | Rev 1.1, 07.08.2015 - Added amprQC_cmap 16 | Rev 1.0, 05.04.2014 - Created amprTB_cmap 17 | 18 | """ 19 | from __future__ import absolute_import 20 | import numpy as np 21 | from matplotlib.colors import LinearSegmentedColormap, ListedColormap 22 | 23 | # Specify colors in list: 24 | rgb = [] 25 | rgb.append([50, 50, 50]) 26 | rgb.append([81, 81, 81]) 27 | rgb.append([107, 107, 107]) 28 | rgb.append([139, 139, 139]) 29 | rgb.append([160, 160, 160]) 30 | rgb.append([178, 178, 178]) 31 | rgb.append([200, 200, 200]) 32 | 33 | rgb.append([226, 219, 254]) 34 | rgb.append([190, 178, 252]) 35 | rgb.append([125, 106, 224]) 36 | rgb.append([115, 100, 215]) 37 | rgb.append([74, 54, 212]) 38 | rgb.append([37, 34, 177]) 39 | 40 | rgb.append([24, 104, 233]) 41 | rgb.append([43, 132, 244]) 42 | rgb.append([55, 144, 250]) 43 | rgb.append([76, 160, 253]) 44 | rgb.append([144, 214, 255]) 45 | 46 | rgb.append([183, 252, 163]) 47 | rgb.append([142, 242, 133]) 48 | rgb.append([80, 238, 80]) 49 | rgb.append([33, 183, 33]) 50 | rgb.append([13, 161, 13]) 51 | 52 | rgb.append([254, 249, 163]) 53 | rgb.append([254, 230, 121]) 54 | rgb.append([244, 194, 56]) 55 | rgb.append([253, 158, 14]) 56 | rgb.append([254, 94, 0]) 57 | rgb.append([254, 48, 0]) 58 | rgb.append([235, 15, 0]) 59 | rgb.append([188, 2, 0]) 60 | rgb.append([166, 0, 0]) 61 | 62 | rgb.append([96, 63, 46]) 63 | rgb.append([114, 78, 61]) 64 | rgb.append([137, 101, 86]) 65 | rgb.append([155, 119, 104]) 66 | rgb.append([177, 141, 126]) 67 | rgb.append([198, 161, 153]) 68 | rgb.append([215, 196, 175]) 69 | rgb.append([223, 151, 167]) 70 | rgb.append([231, 128, 138]) 71 | rgb.append([226, 96, 107]) 72 | rgb.append([218, 75, 84]) 73 | rgb.append([209, 55, 46]) 74 | 75 | rgbarray = np.asarray(rgb) 76 | # normalize rgb array. 77 | rgbarray = rgbarray / 255.0 78 | 79 | # Use "from_list" 80 | # amprTB_cmap=LinearSegmentedColormap.from_list('amprTB',rgbarray); 81 | 82 | nrows = rgbarray.shape[0] 83 | # create y values. 84 | xvals = np.linspace(0, 1, nrows) 85 | # Now create dictionary of list of tuples (x,y0,y1), x=R, G, or B 86 | cdict = {} 87 | cdict['red'] = [tuple([xvals[x], rgbarray[x, 0], rgbarray[x, 0]]) 88 | for x in np.arange(nrows)] 89 | cdict['green'] = [tuple([xvals[x], rgbarray[x, 1], rgbarray[x, 1]]) 90 | for x in np.arange(nrows)] 91 | cdict['blue'] = [tuple([xvals[x], rgbarray[x, 2], rgbarray[x, 2]]) 92 | for x in np.arange(nrows)] 93 | 94 | amprTB_cmap = LinearSegmentedColormap('testcmap', cdict) 95 | 96 | # amprQC_cmap definition - TJL 97 | qc_colors = ['#000000', '#33CC33', '#66FF33', '#FFFF00', 98 | '#FFCC00', '#FF0000'] 99 | amprQC_cmap = ListedColormap(qc_colors) 100 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | # Setup script for the pyampr package 3 | # $Id: setup.py,v 1.0 2014/07/28 tjlang Exp $ 4 | # 5 | # Usage: python setup.py install 6 | # 7 | from distutils.core import setup 8 | 9 | try: 10 | # add download_url syntax to distutils 11 | from distutils.dist import DistributionMetadata 12 | DistributionMetadata.classifiers = None 13 | DistributionMetadata.download_url = None 14 | except: 15 | pass 16 | 17 | VERSION = '1.6' 18 | 19 | DESCRIPTION = "The Python Advanced Microwave Precipitation Radiometer " + \ 20 | "Data Toolkit (PyAMPR) - a package to read, analyze, and display AMPR data" 21 | 22 | LONG_DESCRIPTION = """The Advanced Microwave Precipitation Radiometer (AMPR) 23 | is an airborne passive microwave radiometer managed by NASA Marshall Space 24 | Flight Center. Download AMPR data from http://ghrc.nsstc.nasa.gov. 25 | AMPR brightness temperature data from NASA field projects 26 | are in ASCII or netCDF format. This python script defines a class that will 27 | read in single file from an individual aircraft flight and pull out 28 | timing, brightness temperatures from each channel, geolocation, and 29 | other information and store them as attributes using numpy 30 | arrays of the appropriate type. The file is read and the data are populated 31 | when the class is instantiated with the full path and name of an AMPR file. 32 | Numerous visualization methods are provided, including track plots, 33 | strip charts, and Google Earth KMZs. In addition, polarization 34 | deconvolution is available.""" 35 | 36 | setup( 37 | name="pyampr", 38 | version=VERSION, 39 | author="Timothy J. Lang", 40 | author_email="timothy.j.lang@nasa.gov", 41 | url="http://github.com/nasa/PyAMPR/", 42 | description=DESCRIPTION, 43 | long_description=LONG_DESCRIPTION, 44 | download_url="http://github.com/nasa/PyAMPR/", 45 | license="LICENSE.md", 46 | packages=["pyampr"], 47 | platforms="Python 2.7, 3.4", 48 | classifiers=[""" 49 | Development Status :: Beta, 50 | Programming Language :: Python :: 2.7, 3.4 51 | Topic :: Scientific/Engineering 52 | Topic :: Scientific/Engineering :: Atmospheric Science 53 | Operating System :: Unix 54 | Operating System :: POSIX :: Linux 55 | Operating System :: MacOS 56 | """] 57 | ) 58 | -------------------------------------------------------------------------------- /test/test_ampr_qc_kmz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_ampr_qc_kmz.png -------------------------------------------------------------------------------- /test/test_google_earth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_google_earth.png -------------------------------------------------------------------------------- /test/test_iphex_kmz_colorbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_iphex_kmz_colorbar.png -------------------------------------------------------------------------------- /test/test_iphex_kmz_track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_iphex_kmz_track.png -------------------------------------------------------------------------------- /test/test_iphex_pol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_iphex_pol.png -------------------------------------------------------------------------------- /test/test_iphex_strip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_iphex_strip.png -------------------------------------------------------------------------------- /test/test_iphex_strip_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_iphex_strip_zoom.png -------------------------------------------------------------------------------- /test/test_iphex_track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_iphex_track.png -------------------------------------------------------------------------------- /test/test_iphex_track_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_iphex_track_zoom.png -------------------------------------------------------------------------------- /test/test_kwajex_strip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_kwajex_strip.png -------------------------------------------------------------------------------- /test/test_kwajex_track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_kwajex_track.png -------------------------------------------------------------------------------- /test/test_qc_channels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_qc_channels.png -------------------------------------------------------------------------------- /test/test_qc_legend_kmz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_qc_legend_kmz.png -------------------------------------------------------------------------------- /test/test_qc_track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_qc_track.png -------------------------------------------------------------------------------- /test/test_qc_track_kmz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/PyAMPR/9047d416d0671335d0ea8489009c9569b7dbca2f/test/test_qc_track_kmz.png --------------------------------------------------------------------------------