@@ -24,7 +24,9 @@ def _stack_data(source: dict, target: dict, fun: Callable):
2424 value = ma .array (value )
2525 if value .ndim > 0 and name in target :
2626 if target [name ].ndim == value .ndim :
27- if (
27+ if name == "covariance_matrix" :
28+ target [name ] = np .array ([target [name ], value ])
29+ elif (
2830 value .ndim > 1
2931 and value .shape [1 ] != target [name ].shape [1 ]
3032 and name in ("irt" , "tb" )
@@ -34,6 +36,10 @@ def _stack_data(source: dict, target: dict, fun: Callable):
3436 )
3537 else :
3638 target [name ] = fun ((target [name ], value ))
39+ elif target [name ].ndim != value .ndim and name == "covariance_matrix" :
40+ target [name ] = np .concatenate (
41+ (target [name ], value [np .newaxis , :, :])
42+ )
3743 elif value .ndim > 0 and name not in target :
3844 target [name ] = value
3945
@@ -72,12 +78,13 @@ def __init__(
7278 self , file_list : list [str ], time_offset : datetime .timedelta | None = None
7379 ):
7480 self .header , self .raw_data = stack_files (file_list )
75- self .raw_data ["time" ] = utils .epoch2unix (
76- self .raw_data ["time" ], self .header ["_time_ref" ], time_offset = time_offset
77- )
81+ if str (file_list [0 ][- 3 :]).lower () not in ("log" , "his" ):
82+ self .raw_data ["time" ] = utils .epoch2unix (
83+ self .raw_data ["time" ], self .header ["_time_ref" ], time_offset = time_offset
84+ )
7885 self .data : dict = {}
7986 self ._init_data ()
80- if str (file_list [0 ][- 3 :]).lower () != " his" :
87+ if str (file_list [0 ][- 3 :]).lower () not in ( "log" , " his") :
8188 try :
8289 self .find_valid_times ()
8390 except ValueError as err :
@@ -389,6 +396,247 @@ def read_met(file_name: str) -> tuple[dict, dict]:
389396 return header , data
390397
391398
399+ def read_log (file_name : str ) -> tuple [dict , dict ]:
400+ """Reads LOG files and returns header and data as dictionary."""
401+ header : dict = {}
402+ data : dict = {}
403+ # Reads covariance matrix
404+ if "covmatrix" in file_name .lower ():
405+ with open (file_name , "r" , encoding = "utf-8" ) as file :
406+ lines = file .readlines ()
407+ data ["cal_date" ] = np .array (
408+ [
409+ datetime .datetime .strptime (
410+ str (lines [0 ].split (" " )[2 ] + lines [0 ].split (" " )[4 ]).strip (),
411+ "%d.%m.%Y%H:%M:%S" ,
412+ ).timestamp ()
413+ ]
414+ )
415+ for lineno , line in enumerate (lines ):
416+ if line .startswith ("Frequencies [GHz]" ):
417+ da = line .split (":" )[1 ].split ()
418+ header ["_f" ] = np .array ([float (f ) for f in da ], dtype = float )
419+ if line .startswith ("Calibrated" ):
420+ da = line .split (":" )[1 ].split ()
421+ data ["status" ] = np .array ([int (f ) for f in da ], dtype = int , ndmin = 2 )
422+ if line .startswith (("Alpha" , "Gain" , "Trec" , "Tnoise" )):
423+ da = line .split (":" )[1 ].split ()
424+ data [line .split ("[" )[0 ].strip ()] = np .array (
425+ [float (f ) for f in da ], dtype = float , ndmin = 2
426+ )
427+ if line .startswith ("Covariance Matrix [K^2]" ):
428+ data ["covariance_matrix" ] = np .ndarray (
429+ (len (header ["_f" ]), len (header ["_f" ])), np .float32
430+ )
431+ for nf in range (len (header ["_f" ])):
432+ line_a = lines [lineno + nf + 1 ].split (":" )[1 ].split ()
433+ data ["covariance_matrix" ][nf , :] = np .array (
434+ [float (f ) for f in line_a ]
435+ )
436+ # Reads absolute calibration log files
437+ elif "abscal" in file_name .lower ():
438+ with open (file_name , "r" , encoding = "utf-8" ) as file :
439+ text = file .read ()
440+ if (
441+ "cancelled" in text
442+ or "failed" in text
443+ or "Rec1" not in text
444+ or "Rec2" not in text
445+ or "New" not in text
446+ ):
447+ return header , data
448+ with open (file_name , "r" , encoding = "utf-8" ) as file :
449+ lines = file .readlines ()
450+ data ["cal_date" ] = np .array (
451+ [
452+ datetime .datetime .strptime (
453+ lines [1 ].split ("_" )[1 ] + lines [1 ].split ("_" )[2 ].split ("." )[0 ],
454+ "%y%m%d%H%M%S" ,
455+ ).timestamp ()
456+ ]
457+ )
458+ for lineno , line in enumerate (lines ):
459+ if line .strip ().startswith ("Number of calibration cycles" ):
460+ header ["n" ] = int (line .split (":" )[1 ].strip ())
461+ if line .strip ().startswith ("New" ):
462+ ll = 1
463+ while lines [lineno + ll ].strip () != "" :
464+ for varo , var in enumerate (("Trec" , "Tnoise" , "Gain" , "Alpha" )):
465+ if var in data :
466+ data [var ] = np .append (
467+ data [var ],
468+ np .array (
469+ [
470+ float (
471+ lines [lineno + ll ].split (" " )[
472+ varo * 4 + 1
473+ ]
474+ )
475+ ],
476+ dtype = float ,
477+ ndmin = 2 ,
478+ ),
479+ axis = 1 ,
480+ )
481+ else :
482+ data [var ] = np .array (
483+ [
484+ float (
485+ lines [lineno + ll ].split (" " )[varo * 4 + 1 ]
486+ )
487+ ],
488+ dtype = float ,
489+ ndmin = 2 ,
490+ )
491+ ll += 1
492+ # Assign frequencies based on number of channels (currently not working for LHATPRO)
493+ header ["_f" ] = (
494+ np .array ([183.91 , 184.81 , 185.81 , 186.81 , 188.31 , 190.81 , 90.0 ])
495+ if len (data ["Trec" ]) == 7
496+ else np .array (
497+ [
498+ 22.24 ,
499+ 23.04 ,
500+ 23.84 ,
501+ 25.44 ,
502+ 26.24 ,
503+ 27.84 ,
504+ 31.4 ,
505+ 51.26 ,
506+ 52.28 ,
507+ 53.86 ,
508+ 54.94 ,
509+ 56.66 ,
510+ 57.3 ,
511+ 58.0 ,
512+ ]
513+ )
514+ )
515+ data ["Gain" ] *= 1e3 # Convert to mV/K
516+ else :
517+ raise InvalidFileError (f"LOG file type not supported" )
518+
519+ return header , data
520+
521+
522+ def read_his (file_name : str ) -> tuple [dict , dict ]:
523+ """This function reads RPG MWR ABSCAL.HIS binary files."""
524+ Fill_Value_Float = - 999.0
525+ Fill_Value_Int = - 99
526+
527+ with open (file_name , "rb" ) as file :
528+ code = np .fromfile (file , np .int32 , 1 )
529+ if code != 39583209 :
530+ raise RuntimeError (["Error: CAL file code " + str (code ) + " not supported" ])
531+
532+ def _get_header ():
533+ """Read header info."""
534+ n = int (np .fromfile (file , np .uint32 , 1 )[0 ])
535+ time_ref = 1
536+ header_names = ["_code" , "n" , "_time_ref" ]
537+ header_values = [code , n , time_ref ]
538+ return dict (zip (header_names , header_values ))
539+
540+ def _create_variables ():
541+ """Initialize data arrays."""
542+ vrs = {
543+ "len" : np .ones (header ["n" ], np .int32 ) * Fill_Value_Int ,
544+ "rad_id" : np .ones (header ["n" ], np .int32 ) * Fill_Value_Int ,
545+ "cal1_t" : np .ones (header ["n" ], np .int32 ) * Fill_Value_Int ,
546+ "cal2_t" : np .ones (header ["n" ], np .int32 ) * Fill_Value_Int ,
547+ "t1" : np .ones (header ["n" ], np .int32 ) * Fill_Value_Int ,
548+ "cal_date" : np .ones (header ["n" ], np .int32 ) * Fill_Value_Int ,
549+ "t2" : np .ones (header ["n" ], np .int32 ) * Fill_Value_Int ,
550+ "a_temp1" : np .ones (header ["n" ], np .float32 ) * Fill_Value_Float ,
551+ "a_temp2" : np .ones (header ["n" ], np .float32 ) * Fill_Value_Float ,
552+ "p1" : np .ones (header ["n" ], np .float32 ) * Fill_Value_Float ,
553+ "p2" : np .ones (header ["n" ], np .float32 ) * Fill_Value_Float ,
554+ "hl_temp1" : np .ones (header ["n" ], np .float32 ) * Fill_Value_Float ,
555+ "hl_temp2" : np .ones (header ["n" ], np .float32 ) * Fill_Value_Float ,
556+ "cl_temp1" : np .ones (header ["n" ], np .float32 ) * Fill_Value_Float ,
557+ "cl_temp2" : np .ones (header ["n" ], np .float32 ) * Fill_Value_Float ,
558+ "spare" : np .ones ([header ["n" ], 5 ], np .float32 ) * Fill_Value_Float ,
559+ "n_ch1" : np .ones (header ["n" ], np .int32 ) * Fill_Value_Int ,
560+ "freq1" : np .ones ([header ["n" ], 7 ], np .float32 ) * Fill_Value_Float ,
561+ "n_ch2" : np .ones (header ["n" ], np .int32 ) * Fill_Value_Int ,
562+ "freq2" : np .ones ([header ["n" ], 7 ], np .float32 ) * Fill_Value_Float ,
563+ "cal_flag" : np .ones ([header ["n" ], 14 ], np .int32 ) * Fill_Value_Int ,
564+ "Gain" : np .ones ([header ["n" ], 14 ], np .float32 ) * Fill_Value_Float ,
565+ "Tnoise" : np .ones ([header ["n" ], 14 ], np .float32 ) * Fill_Value_Float ,
566+ "Trec" : np .ones ([header ["n" ], 14 ], np .float32 ) * Fill_Value_Float ,
567+ "Alpha" : np .ones ([header ["n" ], 14 ], np .float32 ) * Fill_Value_Float ,
568+ }
569+ return vrs
570+
571+ def _get_data ():
572+ """Loop over file to read data."""
573+ data = _create_variables ()
574+ for sample in range (header ["n" ]):
575+ data ["len" ][sample ] = np .fromfile (file , np .int32 , 1 )[0 ]
576+ data ["rad_id" ][sample ] = np .fromfile (file , np .int32 , 1 )[0 ]
577+ data ["cal1_t" ][sample ] = np .fromfile (file , np .int32 , 1 )[0 ]
578+ data ["cal2_t" ][sample ] = np .fromfile (file , np .int32 , 1 )[0 ]
579+ data ["t1" ][sample ] = np .fromfile (file , np .int32 , 1 )[0 ]
580+ data ["cal_date" ][sample ] = utils .epoch2unix (data ["t1" ][sample ], 1 )
581+ data ["t2" ][sample ] = np .fromfile (file , np .int32 , 1 )[0 ]
582+ data ["a_temp1" ][sample ] = np .fromfile (file , np .float32 , 1 )[0 ]
583+ data ["a_temp2" ][sample ] = np .fromfile (file , np .float32 , 1 )[0 ]
584+ data ["p1" ][sample ] = np .fromfile (file , np .float32 , 1 )[0 ]
585+ data ["p2" ][sample ] = np .fromfile (file , np .float32 , 1 )[0 ]
586+ data ["hl_temp1" ][sample ] = np .fromfile (file , np .float32 , 1 )[0 ]
587+ data ["hl_temp2" ][sample ] = np .fromfile (file , np .float32 , 1 )[0 ]
588+ data ["cl_temp1" ][sample ] = np .fromfile (file , np .float32 , 1 )[0 ]
589+ data ["cl_temp2" ][sample ] = np .fromfile (file , np .float32 , 1 )[0 ]
590+ data ["spare" ][sample ,] = np .fromfile (file , np .float32 , 5 )
591+ data ["n_ch1" ][sample ] = np .fromfile (file , np .int32 , 1 )[0 ]
592+ data ["n_ch1" ][sample ] = data ["n_ch1" ][sample ]
593+ data ["freq1" ][sample , 0 : data ["n_ch1" ][sample ]] = np .fromfile (
594+ file , np .float32 , int (data ["n_ch1" ][sample ])
595+ )
596+ data ["n_ch2" ][sample ] = np .fromfile (file , np .int32 , 1 )[0 ]
597+ data ["freq2" ][sample , 0 : int (data ["n_ch2" ][sample ])] = np .fromfile (
598+ file , np .float32 , int (data ["n_ch2" ][sample ])
599+ )
600+ data ["cal_flag" ][
601+ sample , 0 : int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ])
602+ ] = np .fromfile (
603+ file , np .int32 , int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ])
604+ )
605+ data ["Gain" ][
606+ sample , 0 : int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ])
607+ ] = (
608+ np .fromfile (
609+ file ,
610+ np .float32 ,
611+ int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ]),
612+ )
613+ * 1000.0
614+ )
615+ data ["Tnoise" ][
616+ sample , 0 : int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ])
617+ ] = np .fromfile (
618+ file , np .float32 , int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ])
619+ )
620+ data ["Trec" ][
621+ sample , 0 : int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ])
622+ ] = np .fromfile (
623+ file , np .float32 , int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ])
624+ )
625+ data ["Alpha" ][
626+ sample , 0 : int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ])
627+ ] = np .fromfile (
628+ file , np .float32 , int (data ["n_ch1" ][sample ] + data ["n_ch2" ][sample ])
629+ )
630+
631+ file .close ()
632+ return data
633+
634+ header = _get_header ()
635+ data = _get_data ()
636+ header ["_f" ] = np .concatenate ((data ["freq1" ][0 , :], data ["freq2" ][0 , :]))
637+ return header , data
638+
639+
392640def _read (file : BinaryIO , fields : list [Field ], count : int ) -> np .ndarray :
393641 arr = np .fromfile (file , np .dtype (fields ), count )
394642 if (read := len (arr )) != count :
@@ -474,4 +722,6 @@ def _fix_header(header: dict) -> dict:
474722 "hkd" : read_hkd ,
475723 "blb" : read_blb ,
476724 "bls" : read_bls ,
725+ "log" : read_log ,
726+ "his" : read_his ,
477727}
0 commit comments