atmospheric_lidar/scripts/licel2scc.py

Fri, 16 Feb 2018 14:39:54 +0200

author
Iannis <i.binietoglou@impworks.gr>
date
Fri, 16 Feb 2018 14:39:54 +0200
changeset 124
a1788b77f33b
parent 121
4522191fe936
child 126
4984793c500a
permissions
-rw-r--r--

Attempt to add cloudmasking options.

ioannis@48 1 """ Command line tool to convert Licel binary files to SCC NetCDF format.
ioannis@48 2 """
ioannis@55 3 import argparse
ioannis@55 4 import glob
ioannis@55 5 import importlib
ioannis@55 6 import logging
ioannis@48 7 import os
ioannis@48 8 import sys
ioannis@55 9
ioannis@48 10 from ..licel import LicelLidarMeasurement
ulalume3@67 11 from ..__init__ import __version__
ioannis@48 12
i@103 13 logger = logging.getLogger(__name__)
i@103 14
ioannis@48 15
i@101 16 def create_custom_class(custom_netcdf_parameter_path, use_id_as_name=False, temperature=25., pressure=1020.,
i@101 17 licel_timezone='UTC'):
ioannis@48 18 """ This funtion creates a custom LicelLidarMeasurement subclass,
ioannis@48 19 based on the input provided by the users.
ioannis@48 20
ioannis@48 21 Parameters
ioannis@48 22 ----------
i@101 23 custom_netcdf_parameter_path : str
ioannis@48 24 The path to the custom channels parameters.
i@101 25 use_id_as_name : bool
ioannis@48 26 Defines if channels names are descriptive or transient digitizer IDs.
i@101 27 temperature : float
ioannis@48 28 The ground temperature in degrees C (default 25.0).
i@101 29 pressure : float
ioannis@48 30 The ground pressure in hPa (default: 1020.0).
i@101 31 licel_timezone : str
i@101 32 String describing the timezone according to the tz database.
ioannis@48 33
ioannis@48 34 Returns
ioannis@48 35 -------
ioannis@48 36 CustomLidarMeasurement:
ioannis@48 37 A custom sub-class of LicelLidarMeasurement
ioannis@48 38 """
i@103 39 logger.debug('Reading parameter files: %s' % custom_netcdf_parameter_path)
ioannis@48 40 custom_netcdf_parameters = read_settings_file(custom_netcdf_parameter_path)
ioannis@48 41
ioannis@48 42 class CustomLidarMeasurement(LicelLidarMeasurement):
ioannis@48 43 extra_netcdf_parameters = custom_netcdf_parameters
ioannis@48 44
ulalume3@92 45 def __init__(self, file_list=None):
i@101 46 super(CustomLidarMeasurement, self).__init__(file_list, use_id_as_name, licel_timezone=licel_timezone)
ioannis@48 47
ulalume3@92 48 def set_PT(self):
ioannis@48 49 ''' Sets the pressure and temperature at station level. This is used if molecular_calc parameter is
ioannis@48 50 set to 0 (i.e. use US Standard atmosphere).
ioannis@48 51
ioannis@48 52 The results are stored in the info dictionary.
ioannis@48 53 '''
ioannis@48 54
ioannis@48 55 self.info['Temperature'] = temperature
ioannis@48 56 self.info['Pressure'] = pressure
ioannis@48 57
ioannis@48 58 return CustomLidarMeasurement
ioannis@48 59
ioannis@48 60
ioannis@48 61 def read_settings_file(settings_path):
ioannis@48 62 """ Read the settings file.
ioannis@48 63
ioannis@48 64 The file should contain python code."""
ioannis@48 65 if not os.path.isfile(settings_path):
ioannis@58 66 logging.error("The provided settings path does not correspond to a file.")
ioannis@58 67 sys.exit(1)
ioannis@48 68
ioannis@48 69 dirname, basename = os.path.split(settings_path)
ioannis@48 70 sys.path.append(dirname)
ioannis@48 71
ioannis@48 72 module_name, _ = os.path.splitext(basename)
ioannis@48 73 settings = importlib.import_module(module_name)
ioannis@48 74 return settings
ioannis@48 75
ioannis@48 76
i@124 77 def get_cloud_free_files(CustomLidarMeasurement, files, args):
i@124 78 logger.warning("Starting cloud mask procedure. This is an experimental feature.")
i@124 79
i@124 80 try:
i@124 81 from cloudmask import cloudmask # Import here until we setup a proper installation procedure
i@124 82 except ImportError:
i@124 83 logger.error("Cloud mask module could not be loaded. Please install manually.")
i@124 84 sys.exit(1)
i@124 85
i@124 86 measurement = CustomLidarMeasurement(files)
i@124 87 channel = measurement.channels[args.cloudmask_channel]
i@124 88 cloud_mask = cloudmask.CloudMaskRaw(channel)
i@124 89 idxs = cloud_mask.cloud_free_periods(args.cloudfree_period, args.cloud_search_height)
i@124 90
i@124 91 if len(idxs) == 0: # If no cloud-free period found
i@124 92 logger.info('No cloud free period found. Nothing converted.')
i@124 93 sys.exit(1)
i@124 94
i@124 95 logger.info("{0} cloud free period(s) found.".format(len(idxs)))
i@124 96 file_list = []
i@124 97 for idx_min, idx_max in idxs:
i@124 98 current_files = measurement.files[idx_min:idx_max]
i@124 99 file_list.append(current_files)
i@124 100
i@124 101 return file_list
i@124 102
i@124 103
i@124 104 def get_corrected_measurement_id(args, n):
i@124 105 """ Correct the provided measurement id, in case of multiple cloud-free periods. """
i@124 106 if args.measurement_id is not None:
i@124 107 order = float(args.measurement_id[-2:])
i@124 108 new_no = order + n
i@124 109 measurement_id = args.measurement_id[:-2] + str(new_no)
i@124 110 measurement_no = args.measurement_number # The same
i@124 111 else:
i@124 112 measurement_no = str(float(args.measurement_number) + n).zfill(2)
i@124 113 measurement_id = None
i@124 114
i@124 115 return measurement_id, measurement_no
i@124 116
i@124 117
i@124 118 def convert_to_scc(CustomLidarMeasurement, files, dark_pattern, measurement_id, measurement_number):
i@124 119 """ Convert files to SCC. """
i@124 120 measurement = CustomLidarMeasurement(files)
i@124 121 # Get a list of files containing dark measurements
i@124 122 if dark_pattern != "":
i@124 123 dark_files = glob.glob(dark_pattern)
i@124 124
i@124 125 if dark_files:
i@124 126 logger.debug("Using %s as dark measurements files!" % ', '.join(dark_files))
i@124 127 measurement.dark_measurement = CustomLidarMeasurement(dark_files)
i@124 128 else:
i@124 129 logger.warning(
i@124 130 'No dark measurement files found when searching for %s. Will not use any dark measurements.' % dark_pattern)
i@124 131 try:
i@124 132 measurement = measurement.subset_by_scc_channels()
i@124 133 except ValueError as err:
i@124 134 logger.error(err)
i@124 135 sys.exit(1)
i@124 136
i@124 137 # Save the netcdf
i@124 138 logger.info("Saving netcdf")
i@124 139 measurement.set_measurement_id(measurement_id, measurement_number)
i@124 140 measurement.save_as_SCC_netcdf()
i@124 141 logger.info("Created file %s" % measurement.scc_filename)
i@124 142
i@124 143
ioannis@48 144 def main():
ioannis@48 145 # Define the command line argument
ioannis@56 146 parser = argparse.ArgumentParser(description="A program to convert Licel binary files to the SCC NetCDF format.")
ioannis@48 147 parser.add_argument("parameter_file", help="The path to a parameter file linking licel and SCC channels.")
i@124 148 parser.add_argument("files",
i@124 149 help="Location of licel files. Use relative path and filename wildcards. (default './*.*')",
victor@74 150 default="./*.*")
ioannis@48 151 parser.add_argument("-i", '--id_as_name',
ioannis@48 152 help="Use transient digitizer ids as channel names, instead of descriptive names",
ioannis@48 153 action="store_true")
ioannis@48 154 parser.add_argument("-m", "--measurement_id", help="The new measurement id", default=None)
ioannis@48 155 parser.add_argument("-n", "--measurement_number",
ioannis@48 156 help="The measurement number for the date from 00 to 99. Used if no id is provided",
ioannis@48 157 default="00")
ioannis@48 158 parser.add_argument("-t", "--temperature", type=float,
ioannis@48 159 help="The temperature (in C) at lidar level, required if using US Standard atmosphere",
ioannis@48 160 default="25")
ioannis@48 161 parser.add_argument("-p", "--pressure", type=float,
ioannis@48 162 help="The pressure (in hPa) at lidar level, required if using US Standard atmosphere",
ioannis@48 163 default="1020")
i@124 164 parser.add_argument('-D', '--dark_measurements',
i@124 165 help="Location of files containing dark measurements. Use relative path and filename wildcars, see 'files' parameter for example.",
i@101 166 default="", dest="dark_files"
i@124 167 )
i@101 168 parser.add_argument('--licel_timezone', help="String describing the timezone according to the tz database.",
i@101 169 default="UTC", dest="licel_timezone",
i@124 170 )
i@124 171 parser.add_argument('--cloudmask', help="Experimental feature to automatically cloud mask measurements",
i@124 172 default=False, action='store_true',
i@124 173 )
i@124 174 parser.add_argument('--cloudmask_channel', help="Name of channel to apply the cloud mask.")
i@124 175 parser.add_argument('--cloudfree_period', type=float, help="Duration (in min) of cloud-free periods",
i@124 176 default="30",
i@124 177 )
i@124 178 parser.add_argument('--cloud_search_height', type=float, help="Maximum altitude (in m) to check for clouds.",
i@124 179 default="12000",
i@124 180 )
ioannis@55 181 # Verbosity settings from http://stackoverflow.com/a/20663028
ioannis@55 182 parser.add_argument('-d', '--debug', help="Print dubuging information.", action="store_const",
ioannis@55 183 dest="loglevel", const=logging.DEBUG, default=logging.INFO,
ioannis@55 184 )
ioannis@56 185 parser.add_argument('-s', '--silent', help="Show only warning and error messages.", action="store_const",
ioannis@55 186 dest="loglevel", const=logging.WARNING
ioannis@55 187 )
ulalume3@67 188 parser.add_argument('--version', help="Show current version.", action='store_true')
ioannis@55 189
ioannis@48 190 args = parser.parse_args()
ioannis@48 191
ioannis@55 192 # Get the logger with the appropriate level
ioannis@58 193 logging.basicConfig(format='%(levelname)s: %(message)s', level=args.loglevel)
ioannis@55 194 logger = logging.getLogger(__name__)
ioannis@55 195
i@101 196 # coloredlogs.install(fmt='%(levelname)s: %(message)s', level=args.loglevel)
ioannis@55 197
ulalume3@90 198 # Check for version
ulalume3@67 199 if args.version:
ulalume3@67 200 print("Version: %s" % __version__)
ulalume3@67 201 sys.exit(0)
ulalume3@67 202
ioannis@48 203 # Get a list of files to convert
victor@74 204 files = glob.glob(args.files)
ioannis@48 205
ulalume3@90 206 # If not files found, exit
ulalume3@90 207 if len(files) == 0:
ulalume3@90 208 logger.error("No files found when searching for %s." % args.files)
ulalume3@90 209 sys.exit(1)
ulalume3@90 210
ulalume3@90 211 # If everything OK, proceed
ulalume3@90 212 logger.info("Found {0} files matching {1}".format(len(files), os.path.abspath(args.files)))
ulalume3@90 213 CustomLidarMeasurement = create_custom_class(args.parameter_file, args.id_as_name, args.temperature,
i@101 214 args.pressure, args.licel_timezone)
ulalume3@90 215
i@124 216 if args.cloudmask:
i@124 217 file_lists = get_cloud_free_files(CustomLidarMeasurement, files, args)
ulalume3@90 218
i@124 219 for n, files in enumerate(file_lists):
i@124 220 measurement_id, measurement_no = get_corrected_measurement_id(args, n)
i@124 221 convert_to_scc(CustomLidarMeasurement, files, args.dark_files, measurement_id, measurement_no)
i@124 222 else:
i@124 223 convert_to_scc(CustomLidarMeasurement, files, args.dark_files, args.measurement_id, args.measurement_number)

mercurial