Skip to content

Commit

Permalink
feat: support fmt phased array data (#117)
Browse files Browse the repository at this point in the history
* update: Support FMT PhasedArray Data.

* chore: variable naming & logic

---------

Co-authored-by: CyanideCN <dpy274555447@gmail.com>
  • Loading branch information
pysoer and CyanideCN authored Jul 31, 2024
1 parent 830aeab commit 444e768
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 11 deletions.
4 changes: 3 additions & 1 deletion cinrad/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ def read_auto(filename: str) -> RadarBase:
elif flag[8:12] == b"\x02\x00\x00\x00":
return StandardPUP(filename)
elif flag[8:12] == b"\x10\x00\x00\x00":
return PhasedArrayData(filename)
return StandardData(filename)
else:
raise Exception("Unknown standard radar type")
elif flag[0:2] == b"\x01\x00":
return PhasedArrayData(filename)
elif flag[0:3] == b"MOC":
return MocMosaic(filename)
if flag[50:54] == b"SWAN":
Expand Down
7 changes: 6 additions & 1 deletion cinrad/io/_dtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
'L3_raster','L3_hail','L3_meso','L3_feature','L3_tvs','L3_sti_header','L3_sti_motion',
'L3_sti_position','L3_sti_attribute','L3_sti_component','L3_sti_adaptation',
'L3_vwp_header','L3_vwp','L3_swp','L3_uam','mocm_dtype','mocm_si_dtype','mocm_si_block',
'L3_wer_header'
'L3_wer_header','PA_SDD_site', 'PA_SDD_task', 'PA_SDD_beam', 'PA_SDD_cut', 'PA_SDD_rad_header',
]
# fmt: on
from cinrad.io._radar_struct.CC import (
Expand All @@ -28,6 +28,11 @@
radial_header_dtype as SDD_rad_header,
moment_header_dtype as SDD_mom_header,
product_header_dtype as SDD_pheader,
pa_site_config_dtype as PA_SDD_site,
pa_task_config_dtype as PA_SDD_task,
pa_beam_dtype as PA_SDD_beam,
pa_cut_config_dtype as PA_SDD_cut,
pa_radial_header_dtype as PA_SDD_rad_header,
l3_radial_header_dtype as L3_radial,
l3_radial_block_dtype as L3_rblock,
l3_raster_header_dtype as L3_raster,
Expand Down
129 changes: 129 additions & 0 deletions cinrad/io/_radar_struct/standard_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,135 @@

product_header_dtype = np.dtype(product_header)

pa_site_config = [
("site_code", "S8"),
("site_name", "S32"),
("Latitude", "f4"),
("Longitude", "f4"),
("antenna_height", "i4"),
("ground_height", "i4"),
("frequency", "f4"),
("antenna_type", "i4"),
("tr_number", "i4"),
("RDA_version", "i4"),
("radar_type", "i2"),
("res2", "54c"),
]

pa_site_config_dtype = np.dtype(pa_site_config)

pa_task_config = [
("task_name", "S32"),
("task_dsc", "S128"),
("polar_type", "i4"),
("scan_type", "i4"),
("san_beam_number", "i4"),
("cut_number", "i4"),
("ray_order", "i4"),
("scan_start_time", "i8"),
("res3", "68c"),
]

pa_task_config_dtype = np.dtype(pa_task_config)

pa_beam = [
('beam_index', 'i4'),
('beam_type', 'i4'),
('sub_pulse_number', 'i4'),
('tx_beam_direction', 'f4'),
('tx_beam_width_h', 'f4'),
('tx_beam_width_v', 'f4'),
('tx_beam_gain', 'f4'),
('reserved_00', '100c'),
('sub_pulse_strategy', 'i4'),
('sub_pulse_modulation', 'i4'),
('sub_pulse_frequency', 'f4'),
('sub_pulse_band_width', 'f4'),
('sub_pulse_width', 'i4'),
('reserved_01', '492c'),
]

pa_beam_dtype = np.dtype(pa_beam)

pa_cut_config = [
('cut_index', 'i2'),
('tx_beam_index', 'i2'),
('elev', 'f4'),
('tx_beam_gain', 'f4'),
('rx_beam_width_h', 'f4'),
('rx_beam_width_v', 'f4'),
('rx_beam_gain', 'f4'),
('process_mode', 'i4'),
('wave_form', 'i4'),
('n1_prf_1', 'f4'),
('n1_prf_2', 'f4'),
('n2_prf_1', 'f4'),
('n2_prf_2', 'f4'),
('unfold_mode', 'i4'),
('azimuth', 'f4'),
('start_angle', 'f4'),
('end_angle', 'f4'),
('angle_resolution', 'f4'),
('scan_speed', 'f4'),
('log_reso', 'f4'),
('dop_reso', 'f4'),
('maximum_range', 'i4'),
('maximum_range2', 'i4'),
('start_range', 'i4'),
('sample_1', 'i4'),
('sample_2', 'i4'),
('phase_mode', 'i4'),
('atmospheric_loss', 'f4'),
('nyquist_spd', 'f4'),
('moments_mask', 'i8'),
('moments_size_mask', 'i8'),
('misc_filter_mask', 'i4'),
('sqi_threshold', 'f4'),
('sig_threshold', 'f4'),
('csr_threshold', 'f4'),
('log_threshold', 'f4'),
('cpa_threshold', 'f4'),
('pmi_threshold', 'f4'),
('dplog_threshold', 'f4'),
('thresholds_reserved', '4c'),
('dbt_mask', 'i4'),
('dbz_mask', 'i4'),
('velocity', 'i4'),
('spectrum_width_mask', 'i4'),
('zdr_mask', 'i4'),
('mask_reserved', '12c'),
('scan_sync', '4c'),
('direction', 'i4'),
('ground_clutter_classifier_type', 'i2'),
('ground_clutter_filter_type', 'i2'),
('ground_clutter_filter_notch_width', 'i2'),
('ground_clutter_filter_window', 'i2'),
('reserved', '44c')
]

pa_cut_config_dtype = np.dtype(pa_cut_config)

pa_radial_header = [
("radial_state", "i4"),
("spot_blank", "i4"),
("seq_number", "i4"),
("radial_number", "i4"),
("elevation_number", "i4"),
("azimuth", "f4"),
("elevation", "f4"),
("seconds", "i8"),
("microseconds", "i4"),
("data_length", "i4"),
("moment_number", "i4"),
("scan_beam_index", "i2"),
("hori_est_noise", "i2"),
("vert_est_noise", "i2"),
("zip_type", "i4"), # acctualy it's ref flag,ziptype is reserved
("res6", "70c"),
]

pa_radial_header_dtype = np.dtype(pa_radial_header)

l3_radial_header = [
("dtype", "i4"),
("scale", "i4"),
Expand Down
41 changes: 32 additions & 9 deletions cinrad/io/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
__all__ = ["CinradReader", "StandardData", "PhasedArrayData"]

ScanConfig = namedtuple("ScanConfig", SDD_cut.fields.keys())
ScanConfigPA = namedtuple("ScanConfigPA", PA_SDD_cut.fields.keys())
utc_offset = datetime.timedelta(hours=8)

utc8_tz = datetime.timezone(utc_offset)
Expand Down Expand Up @@ -578,7 +579,21 @@ def _parse(self):
header = np.frombuffer(self.f.read(32), SDD_header)
if header["magic_number"] != 0x4D545352:
raise RadarDecodeError("Invalid standard data")
site_config = np.frombuffer(self.f.read(128), SDD_site)
site_config_dtype = SDD_site
task_dtype = SDD_task
cut_config_dtype = SDD_cut
if header["generic_type"] == 16:
site_config_dtype = PA_SDD_site
task_dtype = PA_SDD_task
cut_config_dtype = PA_SDD_cut
header_length = 128
radial_header_dtype = PA_SDD_rad_header
self._is_phased_array = True
else:
header_length = 64
radial_header_dtype = SDD_rad_header
self._is_phased_array = False
site_config = np.frombuffer(self.f.read(128), site_config_dtype)
self.code = (
site_config["site_code"][0]
.decode("ascii", errors="ignore")
Expand All @@ -591,25 +606,33 @@ def _parse(self):
geo["lon"] = site_config["Longitude"][0]
geo["height"] = site_config["ground_height"][0]
geo["name"] = site_config["site_name"][0].decode("ascii", errors="ignore")
task = np.frombuffer(self.f.read(256), SDD_task)
task = np.frombuffer(self.f.read(256), task_dtype)
self.task_name = (
task["task_name"][0].decode("ascii", errors="ignore").split("\x00")[0]
)
epoch_seconds = datetime.timedelta(
seconds=int(task["scan_start_time"][0])
).total_seconds()
self.scantime = epoch_seconds_to_utc(epoch_seconds)
if self._is_phased_array:
san_beam_number = task["san_beam_number"][0]
self.pa_beam = np.frombuffer(self.f.read(san_beam_number * 640),PA_SDD_beam)
cut_num = task["cut_number"][0]
scan_config = np.frombuffer(self.f.read(256 * cut_num), SDD_cut)
scan_config = np.frombuffer(self.f.read(256 * cut_num), cut_config_dtype)
self.scan_config = list()
scan_config_cls = ScanConfigPA if self._is_phased_array else ScanConfig
for i in scan_config:
_config = ScanConfig(*i)
_config = scan_config_cls(*i)
if _config.dop_reso > 32768: # fine detection scan
true_reso = np.round_((_config.dop_reso - 32768) / 100, 1)
_config_element = list(_config)
_config_element[11] = true_reso
_config_element[12] = true_reso
_config = ScanConfig(*_config_element)
if self._is_phased_array:
_config_element[21] = true_reso
_config_element[22] = true_reso
else:
_config_element[11] = true_reso
_config_element[12] = true_reso
_config = scan_config_cls(*_config_element)
self.scan_config.append(_config)
# TODO: improve repr
data = dict()
Expand All @@ -629,11 +652,11 @@ def _parse(self):
radial_count = 0
while 1:
try:
header_bytes = self.f.read(64)
header_bytes = self.f.read(header_length)
if not header_bytes:
# Fix for single-tilt file
break
radial_header = np.frombuffer(header_bytes, SDD_rad_header)
radial_header = np.frombuffer(header_bytes, radial_header_dtype)
if radial_header["zip_type"][0] == 1: # LZO compression
raise NotImplementedError("LZO compressed file is not supported")
self._time_radial.append(
Expand Down

0 comments on commit 444e768

Please sign in to comment.