|
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 |