6 from netCDF4
import Dataset
as NC
7 from datetime
import datetime, timedelta, timezone
11 __version__ =
"1.1 2024-09-12"
35 "Scene Center Latitude",
36 "Scene Center Longitude",
37 "Scene Center Solar Zenith",
38 "Upper Left Latitude",
39 "Upper Left Longitude",
40 "Upper Right Latitude",
41 "Upper Right Longitude",
42 "Lower Left Latitude",
43 "Lower Left Longitude",
44 "Lower Right Latitude",
45 "Lower Right Longitude",
46 "Northernmost Latitude",
47 "Southernmost Latitude",
48 "Westernmost Longitude",
49 "Easternmost Longitude",
50 "Start Center Latitude",
51 "Start Center Longitude",
52 "End Center Latitude",
53 "End Center Longitude",
56 "Pixels per Scan Line",
57 "Number of Scan Lines",
58 "Scene Center Scan Line",
60 "Gain 1 Saturated Pixels",
61 "Gain 2 Saturated Pixels",
62 "Gain 1 Non-Saturated Pixels",
63 "Gain 2 Non-Saturated Pixels",
65 "Mean Gain 1 Radiance",
66 "Mean Gain 2 Radiance",
69 "SDPS Missing Frames",
81 SDC.FLOAT32: np.float32,
82 SDC.FLOAT64: np.float64,
84 SDC.UINT16: np.uint16,
85 SDC.UINT32: np.uint32,
97 ncname = hdfname.lower().replace(
" ",
"_")
98 if ncname ==
"number_of_scan_lines":
return "scans"
99 elif ncname ==
"pixels_per_scan_line":
return "pixels"
100 elif ncname ==
"number_of_knees":
return "knees"
101 elif ncname ==
"number_of_bands":
return "bands"
102 elif ncname ==
"number_of_sides":
return "mirror_sides"
103 elif ncname ==
"number_of_tilts":
return "tilts"
104 elif ncname ==
"number_of_gains":
return "gains"
105 elif ncname ==
"start_node":
return "startDirection"
106 elif ncname ==
"end_node":
return "endDirection"
107 elif ncname ==
"msec":
return "scan_time"
108 elif ncname.startswith(
'lac'):
return ncname.replace(
'lac',
'LAC')
115 if (dim ==
"1"):
return None
117 if (sds ==
"tilt_ranges"):
return "scan_index"
118 if (sds ==
"sc_id"):
return "sc_ids"
119 elif (dim ==
"3"):
return "vector_elements"
121 if (sds ==
"eng_qual")
or (sds ==
"s_flags"):
return "quality_flags"
122 if (sds ==
"sc_ttag"):
return "sc_time_tags"
123 elif (dim ==
"6"):
return "scan_track_coefficients"
124 elif (dim ==
"8"):
return "navigation_flags"
125 elif (dim ==
"20"):
return "tilts"
126 elif (dim ==
"32"):
return "inst_discrete_telemetries"
128 if (sds ==
"inst_ana"):
return "inst_analog_telemetries"
129 if (sds ==
"sc_ana"):
return "sc_analog_telemetries"
130 if (sds ==
"sc_dis"):
return "sc_discrete_telemetries"
131 elif (dim ==
"44"):
return "inst_telemetries"
132 elif (dim ==
"775"):
return "sc_sohs"
134 ncdim = dim.lower().replace(
" ",
"_")
135 if ncdim ==
"number_of_scan_lines":
return "scans"
136 elif ncdim ==
"pixels_per_scan_line":
return "pixels"
137 elif ncdim ==
"number_of_knees":
return "knees"
138 elif ncdim ==
"number_of_bands":
return "bands"
139 elif ncdim ==
"number_of_sides":
return "mirror_sides"
140 elif ncdim ==
"number_of_tilts":
return "tilts"
141 elif ncdim ==
"number_of_gains":
return "gains"
151 print(
"Copying global attributes")
152 globalAttr = hdfFile.attributes()
154 for name, val
in globalAttr.items():
158 if (name
in SKIP_ATTRIBUTES):
continue
164 if (isinstance(val, str)):
168 val = np.float32(val)
if isinstance(val, float)
else np.int32(val)
176 errorAt =
"node_crossing_time"
177 node_crossing_str = globalAttr.get(
"Node Crossing Time")
178 nct = node_crossing_str[:13]
179 fsec = node_crossing_str[13:17]
180 node_crossing = datetime.strptime(nct,
"%Y%j%H%M%S")
181 ncFile.setncattr(
"node_crossing_time",
str(node_crossing.strftime(
'%Y-%m-%dT%H:%M:%S.') + fsec[:3] +
'Z'))
184 errorAt =
"scene_center_time"
185 scene_center_str = globalAttr.get(
"Scene Center Time")
186 sct = scene_center_str[:13]
187 fsec = scene_center_str[13:17]
188 scene_center = datetime.strptime(sct,
"%Y%j%H%M%S")
189 ncFile.setncattr(
"scene_center_time",
str(scene_center.strftime(
'%Y-%m-%dT%H:%M:%S.') + fsec[:3] +
'Z'))
192 errorAt =
"time_coverage_start"
193 syear = globalAttr.get(
"Start Year")
194 sday = globalAttr.get(
"Start Day")
195 ssec =
float(
"{:.3f}".format(globalAttr.get(
"Start Millisec")/1000))
196 fssec =
str(ssec).split(
'.')[1]
198 start_of_year = datetime(syear, 1, 1,tzinfo=timezone.utc)
199 stime = start_of_year + timedelta(days=(sday-1), seconds=ssec)
201 ncFile.setncattr(
"time_coverage_start",
str(stime.strftime(
'%Y-%m-%dT%H:%M:%S.') + fssec +
'Z'))
203 errorAt =
"time_coverage_end"
204 eyear = globalAttr.get(
"End Year")
205 eday = globalAttr.get(
"End Day") - 1
206 esec =
float(
"{:.3f}".format(globalAttr.get(
"End Millisec")/1000))
207 fesec =
str(esec).split(
'.')[1]
209 end_of_year = datetime(eyear, 1, 1, tzinfo=timezone.utc)
211 etime = end_of_year + timedelta(days=eday, seconds=esec)
213 ncFile.setncattr(
"time_coverage_end",
str(etime.strftime(
'%Y-%m-%dT%H:%M:%S.') + fesec +
'Z'))
216 ncFile.setncattr(
"l1aconvert_seawifs_version", __version__)
220 ncFile.setncattr(
"history",
"{}; python3 l1acovert_seawifs {} {}".format(globalAttr.get(
"Processing Control")[:-1], iFile, oFile))
223 errorAt =
"date_created"
224 create_date = datetime.now(tz=timezone.utc)
226 ncFile.setncattr(
"date_created",
str(create_date.strftime(
'%Y-%m-%dT%H:%M:%SZ')))
229 ncFile.setncattr(
"cdm_data_type",
"swath")
230 ncFile.setncattr(
"Conventions",
"CF-1.8, ACDD-1.3")
231 ncFile.setncattr(
"creator_email",
"data@oceancolor.gsfc.nasa.gov")
232 ncFile.setncattr(
"creator_name",
"NASA/GSFC/OBPG")
233 ncFile.setncattr(
"creator_url",
"https://oceancolor.gsfc.nasa.gov")
234 ncFile.setncattr(
"institution",
"NASA Goddard Space Flight Center, Ocean Biology Processing Group")
235 ncFile.setncattr(
"instrument",
"SeaWiFS")
236 ncFile.setncattr(
"keywords_vocabulary",
"NASA Global Change Master Directory (GCMD) Science Keywords")
237 ncFile.setncattr(
"license",
"https://www.earthdata.nasa.gov/engage/open-data-services-and-software/data-and-information-policy")
238 ncFile.setncattr(
"naming_authority",
"gov.nasa.gsfc.oceancolor")
239 ncFile.setncattr(
"platform",
"Orbview-2")
240 ncFile.setncattr(
"processing_level",
"L1A")
241 ncFile.setncattr(
"processing_version",
"V2")
242 ncFile.setncattr(
"product_name",
"{}".format(oFile.name))
243 ncFile.setncattr(
"project",
"Ocean Biology Processing Group")
244 ncFile.setncattr(
"publisher_email",
"data@oceancolor.gsfc.nasa.gov")
245 ncFile.setncattr(
"publisher_name",
"NASA/GSFC/OB.DAAC")
246 ncFile.setncattr(
"publisher_url",
"https://oceancolor.gsfc.nasa.gov")
247 ncFile.setncattr(
"standard_name_vocabulary",
"CF Standard Name Table v79")
250 print(f
"-E- Error copying global attributes. Was processing <{errorAt}> from HDF4 when error was caught.")
258 for attr
in attrDict.keys():
259 if (attr ==
"long_name" or attr ==
"long name"):
260 ncVar.long_name = attrDict.get(attr)
261 if (attr ==
"units"):
262 if ncVar.name !=
'l1a_data':
263 ncVar.units = attrDict.get(attr)
266 if (attr ==
"valid_range" or attr ==
"valid range"):
269 validRange = attrDict.get(attr)
274 if isinstance(validRange, str):
275 validRange = attrDict.get(attr)[1:-2].split(
",")
280 if (dataType == np.float32):
281 npValidMin = np.float32(validRange[0])
282 npValidMax = np.float32(validRange[1])
284 elif (dataType == np.float64):
285 npValidMin = np.float64(validRange[0])
286 npValidMax = np.float64(validRange[1])
288 elif (dataType == np.uint16):
289 npValidMin = np.uint16(validRange[0])
290 npValidMax = np.uint16(validRange[1])
292 elif (dataType == np.uint32):
293 npValidMin = np.uint32(validRange[0])
294 npValidMax = np.uint32(validRange[1])
296 elif (dataType == np.int16):
297 npValidMin = np.int16(validRange[0])
298 npValidMax = np.int16(validRange[1])
301 npValidMin = np.int32(validRange[0])
302 npValidMax = np.int32(validRange[1])
305 ncVar.valid_min = npValidMin
306 ncVar.valid_max = npValidMax
316 'inst_analog_telemetries': 4,
318 'inst_discrete_telemetries': 2,
322 'sc_discrete_telemetries': 4,
323 'inst_telemetries': 4,
324 'navigation_flags': 8,
325 'scan_track_coefficients': 6,
329 'sc_analog_telemetries': 4,
331 'vector_elements': 3,
332 'matrix_column_elements': 3,
333 'matrix_row_elements': 3,
337 dimsize = len(ncFile.dimensions[dim])
338 if sizes[dim] <= dimsize:
339 chunks.append(sizes[dim])
359 print(
"Copying datasets/variables...")
361 datasetNames = hdfFile.datasets().keys()
362 for name
in datasetNames:
368 currSet = hdfFile.select(name)
369 hdfDataType = currSet.info()[3];
370 hdfDims = currSet.dimensions().keys()
372 hdfDatasetAttr = currSet.attributes()
376 ncDatatype = GET_NC_TYPES.get(hdfDataType)
380 if name ==
"tilt_lats" or name ==
"tilt_lons":
381 ncDims = (
'tilts',
'scan_index',
'pixel_index')
382 if name ==
"sen_mat":
383 ncDims = (
'scans',
'matrix_column_elements',
'matrix_row_elements')
387 newVariable = ncFile.createVariable(
convertToNetcdfName(name), ncDatatype, ncDims, chunksizes=chunks, zlib=
True)
388 newVariable[:] = data
394 newVariable.setncattr(
"units",
"milliseconds")
395 newVariable.setncattr(
"comment",
"milliseconds since start of day, day boundary crossing indicated if value is smaller than previous scan")
397 except Exception
as e:
398 print(f
"-E- Error copying datasets/variables. Error occurred with HDF4 dataset named <{errorAt}>")
399 print(f
"Reason: {e}")
406 print(
"adding CF-compliant time variable...")
408 msecs = ncFile[
'scan_time'][:]
409 timevar = np.zeros(len(msecs),np.float64)
410 time_coverage_start = datetime.fromisoformat(ncFile.time_coverage_start[:23])
411 day_start = datetime.strptime(time_coverage_start.strftime(
'%Y%m%d'),
'%Y%m%d')
412 for i,msec
in enumerate(msecs):
413 seconds = msec / 1000.
414 scantime = day_start + timedelta(seconds=seconds)
415 if scantime < time_coverage_start:
416 scantime = scantime+ timedelta(days=1)
417 timevar[i] = scantime.strftime(
'%s.%f')
419 if len(ncFile.dimensions[
'scans']) < chunksize[0]:
421 timeVariable = ncFile.createVariable(
'time', np.float64,
'scans', chunksizes=chunksize, zlib=
True)
422 timeVariable[:] = timevar
423 timeVariable.setncattr(
"long_name",
"time")
424 timeVariable.setncattr(
"units",
"seconds since 1970-1-1")
426 except Exception
as e:
427 print(f
"-E- Error copying datasets/variables. Error occurred with adding time dataset...")
428 print(f
"Reason: {e}")
436 print(
"Closing HDF4 File...")
438 print(
"Closing NetCDF File...")
450 errorAt =
"Setting Dimensions"
454 dimSet.add((
"scan_index",2))
455 dimSet.add((
"pixel_index",2))
456 dimSet.add((
"sc_ids", 2))
457 dimSet.add((
"vector_elements", 3))
458 dimSet.add((
"matrix_column_elements", 3))
459 dimSet.add((
"matrix_row_elements", 3))
460 dimSet.add((
"quality_flags",4))
461 dimSet.add((
"sc_time_tags", 4))
462 dimSet.add((
"scan_track_coefficients", 6))
463 dimSet.add((
"navigation_flags", 8))
464 dimSet.add((
"tilts", 20))
465 dimSet.add((
"inst_discrete_telemetries", 32))
466 dimSet.add((
"inst_analog_telemetries", 40))
467 dimSet.add((
"sc_analog_telemetries", 40))
468 dimSet.add((
"sc_discrete_telemetries", 40))
469 dimSet.add((
"inst_telemetries", 44))
470 dimSet.add((
"sc_sohs", 775))
471 dimSet.add((
"bands", 8))
472 dimSet.add((
"mirror_sides", 2))
473 dimSet.add((
"gains", 4))
474 dimSet.add((
"knees", 5))
476 errorAt =
"extract dimension details from HDF file"
478 hdfDatasets = hdfFile.datasets()
480 errorAt = f
"extract {var} from HDF file"
481 varInfo = hdfDatasets[var]
482 dimNames = varInfo[0]
486 for i
in range(len(dimNames)):
495 errorAt = f
"set {dim} in the NetCDF file"
498 ncFile.createDimension(name, val)
501 print(f
"-E- Error copying dimensions. Was trying to {errorAt} when error was caught.")
505 print(f
"l1aconvert_seawifs {__version__}")
511 parser = argparse.ArgumentParser(description=
'''
512 Given an HDF4 Scientific Dataset (SD), convert it
513 into a NetCDF (NC) file format with the same name.
514 If the optional argument [oFile] name is given,
515 output as the oFile name.
518 parser.add_argument(
"ifile", type=str, help=
"input HDF4 File")
519 parser.add_argument(
"ofile", nargs=
'?', type=str, help=
"Optional output file name; default = input + '.nc'")
520 parser.add_argument(
"--doi",
"-d",type=str,default=
None,help=
"DOI string for metadata")
524 args = parser.parse_args()
525 fileName = args.ifile
526 oFileName = fileName +
".nc" if args.ofile
is None else args.ofile
529 print(f
"Input File:\t{fileName}")
530 print(f
"Output File:\t{oFileName}\n")
534 hdfFile = HDF(fileName, HDF_READ)
535 print(f
"Opening file:\t{fileName}")
537 print(f
"\n-E- Error opening file named: {fileName}.\n Make sure the filetype is hdf4.\n")
541 ncFile =
NC(oFileName, NC_WRITE, NC_FORMAT)
545 ncFile.setncattr(
"doi",args.doi)
549 print(
"Conversion of L1A HDF4 to NETCDF successful")
551 if __name__ ==
'__main__':