| |
1 import os |
| |
2 import fnmatch |
| |
3 import numpy as np |
| |
4 import netCDF4 as netcdf |
| |
5 |
| |
6 |
| |
7 ERROR_ORDER = {'notification': 1, |
| |
8 'warning': 2, |
| |
9 'error': 3, |
| |
10 } |
| |
11 |
| |
12 # Used when printing the report |
| |
13 header_template = """\n----- Report for file {0.filename} ----- |
| |
14 Checked against specification: {0.specs[name]}. |
| |
15 |
| |
16 Output summary: |
| |
17 Errors: {1[error]}, Warnings: {1[warning]}, Notifications: {1[notification]}""" |
| |
18 |
| |
19 |
| |
20 |
| |
21 # We firstly define the possible specification elements |
| |
22 # In the end of this file we use them to define complete specifications |
| |
23 |
| |
24 |
| |
25 class SpecGenericError: |
| |
26 def __repr__(self): |
| |
27 return "{0}: {1}".format(self.level.title(), self.message) |
| |
28 |
| |
29 |
| |
30 class SpecError(SpecGenericError): |
| |
31 def __init__(self, message): |
| |
32 self.message = message |
| |
33 self.level = 'error' |
| |
34 |
| |
35 |
| |
36 class SpecWarning(SpecGenericError): |
| |
37 def __init__(self, message): |
| |
38 self.message = message |
| |
39 self.level = 'warning' |
| |
40 |
| |
41 |
| |
42 class SpecNotification(SpecGenericError): |
| |
43 def __init__(self, message): |
| |
44 self.message = message |
| |
45 self.level = 'notification' |
| |
46 |
| |
47 |
| |
48 class GenericSpecification: |
| |
49 @property |
| |
50 def continue_check(self): |
| |
51 return True |
| |
52 |
| |
53 class DimensionMandatory(GenericSpecification): |
| |
54 def __init__(self, is_mandatory = True): |
| |
55 self.block_next = True # if true the next checks for this dimension will not be performed. |
| |
56 self.is_mandatory = is_mandatory |
| |
57 |
| |
58 def check(self, netcdf_file, dimension_name): |
| |
59 the_dimension = netcdf_file.dimensions.get(dimension_name, None) |
| |
60 |
| |
61 error = [] |
| |
62 if the_dimension: |
| |
63 # If the dimension is found in the file |
| |
64 self.dimension_exists = True |
| |
65 check_passed = True |
| |
66 |
| |
67 else: |
| |
68 self.dimension_exists = False |
| |
69 if self.is_mandatory: |
| |
70 # If a mandatory dimension is not found in the file |
| |
71 check_passed = False |
| |
72 error.append(SpecError('The dimension {0} is obligatory but was not found in the file.'.format(dimension_name))) |
| |
73 else: |
| |
74 check_passed = True |
| |
75 error.append(SpecNotification('The optional dimension {0} was not found in the file.'.format(dimension_name))) |
| |
76 |
| |
77 self.check_passed = check_passed |
| |
78 self.error = error |
| |
79 |
| |
80 return check_passed, error |
| |
81 |
| |
82 @property |
| |
83 def continue_check(self): |
| |
84 if (not self.dimension_exists) and (self.block_next): |
| |
85 return False |
| |
86 else: |
| |
87 return True |
| |
88 |
| |
89 |
| |
90 class DimensionUnlimited(GenericSpecification): |
| |
91 def __init__(self, is_unlimited): |
| |
92 self.block_next = False |
| |
93 self.is_unlimited = is_unlimited |
| |
94 |
| |
95 def check(self, netcdf_file, dimension_name): |
| |
96 the_dimension = netcdf_file.dimensions.get(dimension_name, None) |
| |
97 error = [] |
| |
98 |
| |
99 if the_dimension: |
| |
100 if the_dimension.isunlimited() == True and self.is_unlimited == False: |
| |
101 check_passed = False |
| |
102 error.append(SpecWarning('Dimension {0} should not be unlimited but is.'.format(dimension_name))) |
| |
103 elif the_dimension.isunlimited() == False and self.is_unlimited == True: |
| |
104 check_passed = False |
| |
105 error.append(SpecWarning('Dimension {0} should be unlimited but it is not.'.format(dimension_name))) |
| |
106 else: |
| |
107 check_passed = True |
| |
108 else: |
| |
109 check_passed = True |
| |
110 error.append(SpecError('Dimension {0} should be unlimited, but was not found in the file.'.format(dimension_name))) |
| |
111 |
| |
112 self.check_passed = check_passed |
| |
113 self.error = error |
| |
114 |
| |
115 return check_passed, error |
| |
116 |
| |
117 |
| |
118 class VariableMandatory(GenericSpecification): |
| |
119 def __init__(self, is_mandatory = True): |
| |
120 self.block_next = True # if true the next checks for this variable will not be performed. |
| |
121 self.is_mandatory = is_mandatory |
| |
122 |
| |
123 def check(self, netcdf_file, variable_name): |
| |
124 the_variable = netcdf_file.variables.get(variable_name, None) |
| |
125 error = [] |
| |
126 |
| |
127 if the_variable != None: |
| |
128 # If the variable is found in the file |
| |
129 self.variable_exists = True |
| |
130 check_passed = True |
| |
131 |
| |
132 else: |
| |
133 self.variable_exists = False |
| |
134 if self.is_mandatory: |
| |
135 # If a mandatory variable is not found in the file |
| |
136 check_passed = False |
| |
137 error.append(SpecError('The variable {0} is obligatory but was not found in the file.'.format(variable_name))) |
| |
138 else: |
| |
139 check_passed = True |
| |
140 error.append(SpecNotification('The optional variable {0} was not found in the file.'.format(variable_name))) |
| |
141 |
| |
142 self.check_passed = check_passed |
| |
143 self.error = error |
| |
144 |
| |
145 return check_passed, error |
| |
146 |
| |
147 @property |
| |
148 def continue_check(self): |
| |
149 if (not self.variable_exists) and (self.block_next): |
| |
150 return False |
| |
151 else: |
| |
152 return True |
| |
153 |
| |
154 class VariableDimensions(GenericSpecification): |
| |
155 def __init__(self, dimensions): |
| |
156 self.dimensions = dimensions |
| |
157 |
| |
158 def check(self, netcdf_file, variable_name): |
| |
159 the_variable = netcdf_file.variables.get(variable_name, None) |
| |
160 |
| |
161 if the_variable != None: |
| |
162 variable_dimensions = list(the_variable.dimensions) |
| |
163 error = [] |
| |
164 check_passed = True |
| |
165 for dimension in self.dimensions: |
| |
166 if not (dimension in variable_dimensions): |
| |
167 check_passed = False |
| |
168 error.append(SpecError("Variable {0} does not have dimension {1}.".format(variable_name, dimension))) |
| |
169 |
| |
170 # If all dimensions are present, check if the variables are in the |
| |
171 # correct order. |
| |
172 if check_passed: |
| |
173 if list(self.dimensions) != variable_dimensions: |
| |
174 check_passed = False |
| |
175 error.append(SpecError("Variable {0} has wrong dimension order: {1} instead of {2}.".format(variable_name, |
| |
176 variable_dimensions, |
| |
177 list(self.dimensions)))) |
| |
178 for dimension in variable_dimensions: |
| |
179 if dimension not in self.dimensions: |
| |
180 error.append(SpecWarning('Dimension {0} found in variable {1} but is not defined in the specifications'.format(dimension, variable_name))) |
| |
181 |
| |
182 else: |
| |
183 check_passed = False |
| |
184 error = [SpecError('Variable {0} should be checked for dimensions, but was not found in the file.'.format(variable_name)),] |
| |
185 |
| |
186 self.check_passed = check_passed |
| |
187 self.error = error |
| |
188 |
| |
189 return check_passed, error |
| |
190 |
| |
191 |
| |
192 class VariableType(GenericSpecification): |
| |
193 def __init__(self, dtype): |
| |
194 self.dtype = dtype |
| |
195 |
| |
196 def check(self, netcdf_file, variable_name): |
| |
197 the_variable = netcdf_file.variables.get(variable_name, None) |
| |
198 error = [] |
| |
199 |
| |
200 if the_variable != None: |
| |
201 # Get the internal python type and not the numpy.dtype. |
| |
202 # The conversions guarantee (?) that a single element is always returned |
| |
203 variable_type_python = type(np.asscalar(np.asarray(np.asarray(the_variable[:]).item(0)))) |
| |
204 |
| |
205 if not (variable_type_python == self.dtype): |
| |
206 check_passed = False |
| |
207 error.append(SpecError('Variable {0} is of type {1} while it should be {2}'.format(variable_name, the_variable.dtype, self.dtype))) |
| |
208 else: |
| |
209 check_passed = True |
| |
210 else: |
| |
211 check_passed = False |
| |
212 error.append(SpecError('Variable {0} should be checked for type, but was not found in the file.'.format(variable_name))) |
| |
213 |
| |
214 self.check_passed = check_passed |
| |
215 self.error = error |
| |
216 |
| |
217 return check_passed, error |
| |
218 |
| |
219 |
| |
220 class AttributeMandatory(GenericSpecification): |
| |
221 def __init__(self, is_mandatory = True): |
| |
222 self.block_next = True # if true the next checks for this variable will not be performed. |
| |
223 self.is_mandatory = is_mandatory |
| |
224 |
| |
225 def check(self, netcdf_file, attribute_name): |
| |
226 the_attribute = getattr(netcdf_file, attribute_name, None) |
| |
227 error = [] |
| |
228 |
| |
229 if the_attribute: |
| |
230 # If the variable is found in the file |
| |
231 self.attribute_exists = True |
| |
232 check_passed = True |
| |
233 |
| |
234 else: |
| |
235 self.attribute_exists = False |
| |
236 if self.is_mandatory: |
| |
237 # If a mandatory variable is not found in the file |
| |
238 check_passed = False |
| |
239 error.append(SpecError('The attribute {0} is obligatory but was not found in the file.'.format(attribute_name))) |
| |
240 else: |
| |
241 check_passed = True |
| |
242 error.append(SpecNotification('The optional attribute {0} was not found in the file.'.format(attribute_name))) |
| |
243 |
| |
244 self.check_passed = check_passed |
| |
245 self.error = error |
| |
246 |
| |
247 return check_passed, error |
| |
248 |
| |
249 @property |
| |
250 def continue_check(self): |
| |
251 if (not self.attribute_exists) and (self.block_next): |
| |
252 return False |
| |
253 else: |
| |
254 return True |
| |
255 |
| |
256 |
| |
257 class AttributeType(GenericSpecification): |
| |
258 def __init__(self, dtype, block_next = True): |
| |
259 self.block_next = block_next |
| |
260 self.dtype = dtype |
| |
261 |
| |
262 def check(self, netcdf_file, attribute_name): |
| |
263 the_attribute = getattr(netcdf_file, attribute_name, None) |
| |
264 error = [] |
| |
265 |
| |
266 if the_attribute: |
| |
267 # Get the internal python type and not the numpy.dtype. |
| |
268 # The conversions guarantee (?) that a single element is always returned |
| |
269 try: |
| |
270 attribute_type_python = type(np.asscalar(np.asarray(np.asarray(the_attribute[:]).item(0)))) |
| |
271 except: |
| |
272 attribute_type_python = type(np.asscalar(the_attribute)) |
| |
273 |
| |
274 if not (attribute_type_python == self.dtype): |
| |
275 check_passed = False |
| |
276 error.append(SpecError('Attribute {0} is of type {1} while it should be {2}'.format(attribute_name, type(the_attribute).__name__, self.dtype.__name__))) |
| |
277 else: |
| |
278 error = None |
| |
279 check_passed = True |
| |
280 else: |
| |
281 check_passed = False |
| |
282 error.append(SpecError('Attribute {0} should be checked for type, but was not found in the file.'.format(attribute_name))) |
| |
283 |
| |
284 self.check_passed = check_passed |
| |
285 self.error = error |
| |
286 |
| |
287 return check_passed, error |
| |
288 |
| |
289 @property |
| |
290 def continue_check(self): |
| |
291 if (not self.check_passed) and (self.block_next): |
| |
292 return False |
| |
293 else: |
| |
294 return True |
| |
295 |
| |
296 |
| |
297 class AttributeStrLength(GenericSpecification): |
| |
298 |
| |
299 def __init__(self, length): |
| |
300 self.length = length |
| |
301 |
| |
302 |
| |
303 def check(self, netcdf_file, attribute_name): |
| |
304 the_attribute = getattr(netcdf_file, attribute_name, None) |
| |
305 error = [] |
| |
306 |
| |
307 if the_attribute: |
| |
308 if len(the_attribute) != self.length: |
| |
309 check_passed = False |
| |
310 error.append(SpecError('Attribute {0} should be of length {1} while it has length {2}'.format(attribute_name, self.length, len(the_attribute)))) |
| |
311 else: |
| |
312 check_passed = True |
| |
313 else: |
| |
314 check_passed = False |
| |
315 error.append(SpecError('Attribute {0} should be checked for length, but was not found in the file.'.format(attribute_name))) |
| |
316 |
| |
317 self.check_passed = check_passed |
| |
318 self.error = error |
| |
319 |
| |
320 return check_passed, error |
| |
321 |
| |
322 |
| |
323 class FilenameShellPattern(GenericSpecification): |
| |
324 def __init__(self, shell_pattern): |
| |
325 self.pattern = shell_pattern |
| |
326 |
| |
327 def check(self, netcdf_file, filename): |
| |
328 error = [] |
| |
329 |
| |
330 if fnmatch.fnmatch(filename, self.pattern): |
| |
331 check_passed = True |
| |
332 else: |
| |
333 check_passed = False |
| |
334 error.append(SpecError('Filename {0} does not match patter {1}'.format(filename, self.pattern))) |
| |
335 |
| |
336 self.check_passed = check_passed |
| |
337 self.error = error |
| |
338 |
| |
339 return check_passed, error |
| |
340 |
| |
341 |
| |
342 # This is the main class of the script. |
| |
343 class FileChecker: |
| |
344 """ It uses the provided specifications to check the a file. |
| |
345 It can be used with the 'with' statement. For example: |
| |
346 |
| |
347 with FileChecker(filename, specs) as file_checker: |
| |
348 file_checker.run_checks() |
| |
349 file_checker.print_report('error') |
| |
350 |
| |
351 """ |
| |
352 |
| |
353 |
| |
354 def __init__(self, filepath, specs): |
| |
355 self.file = None |
| |
356 self.checks_run = False |
| |
357 self.filepath = filepath |
| |
358 self.filename = os.path.basename(filepath) |
| |
359 self.specs = specs |
| |
360 self.check_results = {} |
| |
361 self.check_results['general'] = [] |
| |
362 |
| |
363 def __enter__(self): |
| |
364 self.open_file() |
| |
365 return self |
| |
366 |
| |
367 def __exit__(self, type, value, traceback): |
| |
368 if self.file: |
| |
369 self.file.close() |
| |
370 |
| |
371 def open_file(self): |
| |
372 try: |
| |
373 self.file = netcdf.Dataset(self.filepath) |
| |
374 except: |
| |
375 self.check_results['general'].append(SpecError('Could not open file {0}.'.format(self.filename))) |
| |
376 |
| |
377 def close_file(self): |
| |
378 self.file.close() |
| |
379 |
| |
380 def run_checks(self): |
| |
381 if self.file: |
| |
382 self.check_file() |
| |
383 self.check_attributes() |
| |
384 self.check_dimensions() |
| |
385 self.check_variables() |
| |
386 self.checks_run = True |
| |
387 |
| |
388 def check_file(self): |
| |
389 self.check_results['file'] = [] |
| |
390 |
| |
391 try: |
| |
392 specs_file = self.specs['file'] |
| |
393 except: |
| |
394 specs_file = [] |
| |
395 |
| |
396 for file_spec in specs_file: |
| |
397 check_passed, error = file_spec.check(self.file, self.filename) |
| |
398 |
| |
399 if error: |
| |
400 self.check_results['file'].extend(list(error)) |
| |
401 |
| |
402 if not file_spec.continue_check: |
| |
403 break |
| |
404 |
| |
405 def check_attributes(self): |
| |
406 """ Check if attributes are according to specs """ |
| |
407 |
| |
408 self.check_results['attributes'] = [] |
| |
409 |
| |
410 try: |
| |
411 spec_attributes = self.specs['attributes'].keys() |
| |
412 except: |
| |
413 spec_attributes = [] |
| |
414 |
| |
415 for attribute_name in spec_attributes: |
| |
416 attribute_specs = self.specs['attributes'][attribute_name] |
| |
417 for attribute_spec in attribute_specs: |
| |
418 check_passed, error = attribute_spec.check(self.file, attribute_name) |
| |
419 |
| |
420 if error: |
| |
421 self.check_results['attributes'].extend(list(error)) |
| |
422 |
| |
423 if not attribute_spec.continue_check: |
| |
424 break # Don't continue checking specifications if a blocking check failed. |
| |
425 |
| |
426 for attribute_name in self.file.ncattrs(): |
| |
427 if attribute_name not in spec_attributes: |
| |
428 self.check_results['attributes'].append(SpecWarning('Attribute {0} found in the file but is not defined in the specifications'.format(attribute_name))) |
| |
429 |
| |
430 def check_dimensions(self): |
| |
431 """ Check if dimension are according to specs """ |
| |
432 self.check_results['dimensions'] = [] |
| |
433 |
| |
434 try: |
| |
435 spec_dimensions = self.specs['dimensions'].keys() |
| |
436 except: |
| |
437 spec_dimensions = [] |
| |
438 |
| |
439 |
| |
440 for dimension_name in spec_dimensions: |
| |
441 dimension_specs = self.specs['dimensions'][dimension_name] |
| |
442 for dimension_spec in dimension_specs: |
| |
443 check_passed, error = dimension_spec.check(self.file, dimension_name) |
| |
444 |
| |
445 if error: |
| |
446 self.check_results['dimensions'].extend(list(error)) |
| |
447 |
| |
448 if not dimension_spec.continue_check: |
| |
449 break # Don't continue checking specifications if a blocking check failed. |
| |
450 |
| |
451 for dimension in self.file.dimensions: |
| |
452 if dimension not in spec_dimensions: |
| |
453 self.check_results['dimensions'].append(SpecWarning('Dimension {0} found in the file but is not defined in the specifications'.format(dimension))) |
| |
454 |
| |
455 def check_variables(self): |
| |
456 """ Check if variables are according to specs """ |
| |
457 |
| |
458 self.check_results['variables'] = [] |
| |
459 |
| |
460 try: |
| |
461 spec_variables = self.specs['variables'].keys() |
| |
462 except: |
| |
463 spec_variables = [] |
| |
464 |
| |
465 for variable_name in spec_variables: |
| |
466 variable_specs = self.specs['variables'][variable_name] |
| |
467 for variable_spec in variable_specs: |
| |
468 check_passed, error = variable_spec.check(self.file, variable_name) |
| |
469 |
| |
470 if error: |
| |
471 self.check_results['variables'].extend(list(error)) |
| |
472 |
| |
473 if not variable_spec.continue_check: |
| |
474 break # Don't continue checking specifications if a blocking check failed. |
| |
475 |
| |
476 for variable_name in self.file.variables: |
| |
477 if variable_name not in spec_variables: |
| |
478 self.check_results['variables'].append(SpecWarning('Variable {0} found in the file but is not defined in the specifications'.format(variable_name))) |
| |
479 |
| |
480 def file_ok(self, level = 'error'): |
| |
481 """ Check if the file checked is ok. What ok means is defined by the level variable """ |
| |
482 |
| |
483 status = None |
| |
484 if self.checks_run: |
| |
485 status = True |
| |
486 for category, result_list in self.check_results.items(): |
| |
487 for result in result_list: |
| |
488 if ERROR_ORDER[result.level] >= ERROR_ORDER[level]: |
| |
489 status = False |
| |
490 |
| |
491 return status |
| |
492 |
| |
493 def results_for_level(self, level): |
| |
494 """ Returns all the results of a specific level """ |
| |
495 results = None |
| |
496 if self.checks_run: |
| |
497 results = [] |
| |
498 for category, result_list in self.check_results.items(): |
| |
499 for result in result_list: |
| |
500 if ERROR_ORDER[result.level] == ERROR_ORDER[level]: |
| |
501 results.append(result) |
| |
502 |
| |
503 return results |
| |
504 |
| |
505 def results_by_level(self): |
| |
506 """ Returns a dictionary with the results by level. """ |
| |
507 |
| |
508 results = {} |
| |
509 for level, order in ERROR_ORDER.items(): |
| |
510 results[level] = self.results_for_level(level) |
| |
511 |
| |
512 return results |
| |
513 |
| |
514 def result_count(self): |
| |
515 """ Returns a dictionary with the number of results per category. """ |
| |
516 |
| |
517 result_number = {} |
| |
518 results = self.results_by_level() |
| |
519 |
| |
520 for category, error_list in results.items(): |
| |
521 if error_list is None: |
| |
522 result_number[category] = 0 |
| |
523 else: |
| |
524 result_number[category] = len(error_list) |
| |
525 return result_number |
| |
526 |
| |
527 def print_report(self, level): |
| |
528 """ Print a report for the given level. """ |
| |
529 |
| |
530 print header_template.format(self, self.result_count()) |
| |
531 |
| |
532 results = self.results_by_level() |
| |
533 |
| |
534 for result_level in ['error', 'warning', 'notification']: |
| |
535 if ERROR_ORDER[result_level] >= ERROR_ORDER[level]: |
| |
536 print "\n{0} details".format(result_level.capitalize()) |
| |
537 print "----------------" |
| |
538 for result in results[result_level]: |
| |
539 print result |
| |
540 |
| |
541 |
| |
542 # Sounding file specifications |
| |
543 sounding_specs = {'file': [FilenameShellPattern('rs_*.nc'),], |
| |
544 'dimensions': {'points': [DimensionMandatory(True), |
| |
545 DimensionUnlimited(False),], |
| |
546 }, |
| |
547 'variables': {'Altitude': [VariableMandatory(True), |
| |
548 VariableDimensions(['points',]), |
| |
549 VariableType(float)], |
| |
550 'Temperature': [VariableMandatory(True), |
| |
551 VariableDimensions(['points',]), |
| |
552 VariableType(float)], |
| |
553 'Pressure': [VariableMandatory(True), |
| |
554 VariableDimensions(['points',]), |
| |
555 VariableType(float)], |
| |
556 'RelativeHumidity': [VariableMandatory(False), |
| |
557 VariableDimensions(['points',]), |
| |
558 VariableType(float)], |
| |
559 }, |
| |
560 'attributes': {'Latitude_degrees_north': [AttributeMandatory(True), |
| |
561 AttributeType(float),], |
| |
562 'Longitude_degrees_east': [AttributeMandatory(True), |
| |
563 AttributeType(float),], |
| |
564 'Altitude_meter_asl': [AttributeMandatory(True), |
| |
565 AttributeType(float),], |
| |
566 'Location': [AttributeMandatory(False), |
| |
567 AttributeType(unicode),], |
| |
568 'Sounding_Station_Name': [AttributeMandatory(False), |
| |
569 AttributeType(unicode),], |
| |
570 'WMO_Station_Number': [AttributeMandatory(False), |
| |
571 AttributeType(unicode),], |
| |
572 'WBAN_Station_Number':[AttributeMandatory(False), |
| |
573 AttributeType(unicode),], |
| |
574 'Sounding_Start_Date':[AttributeMandatory(True), |
| |
575 AttributeType(unicode, block_next = True), |
| |
576 AttributeStrLength(8)], |
| |
577 'Sounding_Start_Time_UT':[AttributeMandatory(True), |
| |
578 AttributeType(unicode, block_next = True), |
| |
579 AttributeStrLength(6)], |
| |
580 'Sounding_Stop_Time_UT':[AttributeMandatory(False), |
| |
581 AttributeType(unicode, block_next = True), |
| |
582 AttributeStrLength(6)], |
| |
583 }, |
| |
584 'name': "SCC Sounding file" |
| |
585 } |
| |
586 |
| |
587 # Lidar ratio file specifications |
| |
588 lidar_ratio_specs = {'file': [FilenameShellPattern('*.nc'),], |
| |
589 'dimensions': {'points': [DimensionMandatory(True), |
| |
590 DimensionUnlimited(False),], |
| |
591 'products': [DimensionMandatory(True), |
| |
592 DimensionUnlimited(False),], |
| |
593 }, |
| |
594 'variables': {'Altitude': [VariableMandatory(True), |
| |
595 VariableDimensions(['points',]), |
| |
596 VariableType(float)], |
| |
597 'Lidar_Ratio': [VariableMandatory(True), |
| |
598 VariableDimensions(['points', 'products']), |
| |
599 VariableType(float)], |
| |
600 'product_ID': [VariableMandatory(True), |
| |
601 VariableDimensions(['products',]), |
| |
602 VariableType(int)], |
| |
603 }, |
| |
604 'attributes': {'Lidar_Station_Name': [AttributeMandatory(True), |
| |
605 AttributeType(unicode),], |
| |
606 }, |
| |
607 'name': "SCC Lidar ratio file" |
| |
608 } |
| |
609 |
| |
610 # Overlap file specifications |
| |
611 overlap_specs = {'file': [FilenameShellPattern('ov_*.nc'),], |
| |
612 'dimensions': {'points': [DimensionMandatory(True), |
| |
613 DimensionUnlimited(False),], |
| |
614 'channels': [DimensionMandatory(True), |
| |
615 DimensionUnlimited(False),], |
| |
616 }, |
| |
617 'variables': {'Altitude': [VariableMandatory(True), |
| |
618 VariableDimensions(['points',]), |
| |
619 VariableType(float)], |
| |
620 'Overlap_Function': [VariableMandatory(True), |
| |
621 VariableDimensions(['points', 'channels']), |
| |
622 VariableType(float)], |
| |
623 'channel_ID': [VariableMandatory(True), |
| |
624 VariableDimensions(['channels',]), |
| |
625 VariableType(int)], |
| |
626 }, |
| |
627 'attributes': {'Lidar_Station_Name': [AttributeMandatory(True), |
| |
628 AttributeType(unicode, block_next = True), |
| |
629 AttributeStrLength(2)], |
| |
630 'Overlap_Measurement_Date': [AttributeMandatory(True), |
| |
631 AttributeType(unicode, block_next = True), |
| |
632 AttributeStrLength(8)], |
| |
633 }, |
| |
634 'name': "SCC Overlap file" |
| |
635 } |
| |
636 |
| |
637 # Raw data file specifications |
| |
638 data_specs = {'file': [FilenameShellPattern('*.nc'),], |
| |
639 'dimensions': {'points': [DimensionMandatory(True), |
| |
640 DimensionUnlimited(False),], |
| |
641 'channels': [DimensionMandatory(True), |
| |
642 DimensionUnlimited(False),], |
| |
643 'nb_of_time_scales': [DimensionMandatory(True), |
| |
644 DimensionUnlimited(False),], |
| |
645 'time': [DimensionMandatory(True), |
| |
646 DimensionUnlimited(True),], |
| |
647 'time_bck': [DimensionMandatory(False), |
| |
648 DimensionUnlimited(False),], |
| |
649 'scan_angles': [DimensionMandatory(True), |
| |
650 DimensionUnlimited(False),], |
| |
651 }, |
| |
652 'variables': {'channel_ID': [VariableMandatory(True), |
| |
653 VariableDimensions(['channels',]), |
| |
654 VariableType(int)], |
| |
655 'Laser_Repetition_Rate': [VariableMandatory(False), |
| |
656 VariableDimensions(['channels',]), |
| |
657 VariableType(int)], |
| |
658 'Laser_Pointing_Angle': [VariableMandatory(True), |
| |
659 VariableDimensions(['scan_angles',]), |
| |
660 VariableType(float)], |
| |
661 'ID_Range': [VariableMandatory(False), |
| |
662 VariableDimensions(['channels',]), |
| |
663 VariableType(int)], |
| |
664 'Scattering_Mechanism': [VariableMandatory(False), |
| |
665 VariableDimensions(['channels',]), |
| |
666 VariableType(int)], |
| |
667 'Emitted_Wavelength': [VariableMandatory(False), |
| |
668 VariableDimensions(['channels',]), |
| |
669 VariableType(float)], |
| |
670 'Detected_Wavelength': [VariableMandatory(False), |
| |
671 VariableDimensions(['channels',]), |
| |
672 VariableType(float)], |
| |
673 'Raw_Data_Range_Resolution': [VariableMandatory(False), |
| |
674 VariableDimensions(['channels',]), |
| |
675 VariableType(float)], |
| |
676 'Background_Mode': [VariableMandatory(False), |
| |
677 VariableDimensions(['channels',]), |
| |
678 VariableType(int)], |
| |
679 'Background_Low': [VariableMandatory(True), |
| |
680 VariableDimensions(['channels',]), |
| |
681 VariableType(float)], |
| |
682 'Background_High': [VariableMandatory(True), |
| |
683 VariableDimensions(['channels',]), |
| |
684 VariableType(float)], |
| |
685 'Molecular_Calc': [VariableMandatory(True), |
| |
686 VariableDimensions([]), |
| |
687 VariableType(int)], |
| |
688 'id_timescale': [VariableMandatory(True), |
| |
689 VariableDimensions(['channels',]), |
| |
690 VariableType(int)], |
| |
691 'Dead_Time_Corr_Type': [VariableMandatory(False), |
| |
692 VariableDimensions(['channels',]), |
| |
693 VariableType(int)], |
| |
694 'Dead_Time': [VariableMandatory(False), |
| |
695 VariableDimensions(['channels',]), |
| |
696 VariableType(float)], |
| |
697 'Acquisition_Mode': [VariableMandatory(False), |
| |
698 VariableDimensions(['channels',]), |
| |
699 VariableType(int)], |
| |
700 'Trigger_Delay': [VariableMandatory(False), |
| |
701 VariableDimensions(['channels',]), |
| |
702 VariableType(float)], |
| |
703 'Laser_Pointing_Angle_of_Profiles': [VariableMandatory(True), |
| |
704 VariableDimensions(['time','nb_of_time_scales',]), |
| |
705 VariableType(int)], |
| |
706 'Raw_Data_Start_Time': [VariableMandatory(True), |
| |
707 VariableDimensions(['time','nb_of_time_scales',]), |
| |
708 VariableType(int)], |
| |
709 'Raw_Data_Stop_Time': [VariableMandatory(True), |
| |
710 VariableDimensions(['time','nb_of_time_scales',]), |
| |
711 VariableType(int)], |
| |
712 'Laser_Shots': [VariableMandatory(True), |
| |
713 VariableDimensions(['time','channels',]), |
| |
714 VariableType(int)], |
| |
715 'Raw_Lidar_Data': [VariableMandatory(False), |
| |
716 VariableDimensions(['time', 'channels', 'points']), |
| |
717 VariableType(float)], |
| |
718 'Depolarization_Factor': [VariableMandatory(False), |
| |
719 VariableDimensions(['channels',]), |
| |
720 VariableType(float)], |
| |
721 'LR_Input': [VariableMandatory(False), |
| |
722 VariableDimensions(['channels',]), |
| |
723 VariableType(int)], |
| |
724 'DAQ_Range': [VariableMandatory(False), |
| |
725 VariableDimensions(['channels',]), |
| |
726 VariableType(float)], |
| |
727 'Pressure_at_Lidar_Station': [VariableMandatory(False), |
| |
728 VariableDimensions([]), |
| |
729 VariableType(float)], |
| |
730 'Temperature_at_Lidar_Station': [VariableMandatory(False), |
| |
731 VariableDimensions([]), |
| |
732 VariableType(float)], |
| |
733 'Background_Profile': [VariableMandatory(False), |
| |
734 VariableDimensions(['time_bck', 'channels', 'points']), |
| |
735 VariableType(float)], |
| |
736 'Raw_Bck_Start_Time': [VariableMandatory(False), |
| |
737 VariableDimensions(['time_bck','nb_of_time_scales',]), |
| |
738 VariableType(int)], |
| |
739 'Raw_Bck_Stop_Time': [VariableMandatory(False), |
| |
740 VariableDimensions(['time_bck','nb_of_time_scales',]), |
| |
741 VariableType(int)], |
| |
742 'Error_On_Raw_Lidar_Data': [VariableMandatory(False), |
| |
743 VariableDimensions(['time','channels', 'points']), |
| |
744 VariableType(float)], |
| |
745 'First_Signal_Rangebin': [VariableMandatory(False), |
| |
746 VariableDimensions(['channels',]), |
| |
747 VariableType(int)], |
| |
748 }, |
| |
749 'attributes': {'Measurement_ID': [AttributeMandatory(True), |
| |
750 AttributeType(unicode, block_next = True), |
| |
751 AttributeStrLength(12)], |
| |
752 'RawData_Start_Date': [AttributeMandatory(True), |
| |
753 AttributeType(unicode, block_next = True), |
| |
754 AttributeStrLength(8)], |
| |
755 'RawData_Start_Time_UT': [AttributeMandatory(True), |
| |
756 AttributeType(unicode, block_next = True), |
| |
757 AttributeStrLength(6)], |
| |
758 'RawData_Stop_Time_UT': [AttributeMandatory(True), |
| |
759 AttributeType(unicode, block_next = True), |
| |
760 AttributeStrLength(6)], |
| |
761 'RawBck_Start_Date': [AttributeMandatory(False), |
| |
762 AttributeType(unicode, block_next = True), |
| |
763 AttributeStrLength(8)], |
| |
764 'RawBck_Start_Time_UT': [AttributeMandatory(False), |
| |
765 AttributeType(unicode, block_next = True), |
| |
766 AttributeStrLength(6)], |
| |
767 'RawBck_Stop_Time_UT': [AttributeMandatory(False), |
| |
768 AttributeType(unicode, block_next = True), |
| |
769 AttributeStrLength(6)], |
| |
770 'Sounding_File_Name': [AttributeMandatory(False), |
| |
771 AttributeType(unicode),], |
| |
772 'LR_File_Name': [AttributeMandatory(False), |
| |
773 AttributeType(unicode),], |
| |
774 'Overlap_File_Name': [AttributeMandatory(False), |
| |
775 AttributeType(unicode),], |
| |
776 'Location': [AttributeMandatory(False), |
| |
777 AttributeType(unicode),], |
| |
778 'System': [AttributeMandatory(False), |
| |
779 AttributeType(unicode),], |
| |
780 'Latitude_degrees_north': [AttributeMandatory(False), |
| |
781 AttributeType(float),], |
| |
782 'Longitude_degrees_east': [AttributeMandatory(False), |
| |
783 AttributeType(float),], |
| |
784 'Altitude_meter_asl': [AttributeMandatory(False), |
| |
785 AttributeType(float),], |
| |
786 }, |
| |
787 'name': "SCC Raw input file" |
| |
788 } |
| |
789 |
| |
790 # Used for the command line arguments |
| |
791 spec_shorthands = {'sounding': sounding_specs, |
| |
792 'lidar_ratio': lidar_ratio_specs, |
| |
793 'overlap': overlap_specs, |
| |
794 'data': data_specs,} |
| |
795 |
| |
796 |
| |
797 if __name__ == "__main__": |
| |
798 |
| |
799 # For use from a terminal |
| |
800 import argparse |
| |
801 |
| |
802 parser = argparse.ArgumentParser() |
| |
803 parser.add_argument("file", help = "The path of the file to be checked") |
| |
804 parser.add_argument("-s", "--specs", default = 'data', |
| |
805 help = "The specificiations to use", |
| |
806 choices = ['data', 'overlap', 'lidar_ratio', 'sounding']) |
| |
807 parser.add_argument("-l", "--level", default = 'warning', |
| |
808 help = "The output level", |
| |
809 choices = ['error', 'warning', 'notification']) |
| |
810 |
| |
811 # Check the arguments |
| |
812 args = parser.parse_args() |
| |
813 |
| |
814 specs = spec_shorthands[args.specs] |
| |
815 |
| |
816 with FileChecker(args.file, specs) as file_checker: |
| |
817 file_checker.run_checks() |
| |
818 file_checker.print_report(args.level) |
| |
819 |