|
3 | 3 | import os |
4 | 4 | from typing import Dict, Any |
5 | 5 |
|
| 6 | +import pandas as pd |
6 | 7 | import xarray as xr |
7 | 8 | import numpy as np |
8 | 9 | from osgeo import gdal |
9 | 10 | from xarray.backends import AbstractDataStore |
10 | 11 | from xarray.core.types import ReadBuffer |
11 | | -from asar_xarray import reader, utils |
| 12 | +from asar_xarray import reader, utils, envisat_direct |
12 | 13 | from loguru import logger |
13 | 14 |
|
14 | 15 | from asar_xarray.derived_subdatasets_metadata import process_derived_subdatasets_metadata |
15 | 16 | from asar_xarray.general_metadata import process_general_metadata |
16 | 17 | from asar_xarray.records_metadata import process_records_metadata |
17 | 18 |
|
18 | 19 |
|
19 | | -def get_attributes(gdal_dataset: gdal.Dataset) -> Dict[str, Any]: |
| 20 | +def get_metadata(gdal_dataset: gdal.Dataset) -> Dict[str, Any]: |
20 | 21 | """ |
21 | 22 | Build xarray attributes from gdal dataset to be used in xarray. |
22 | 23 |
|
@@ -54,24 +55,110 @@ def open_asar_dataset(filepath: str | os.PathLike[Any] | ReadBuffer[Any] | Abstr |
54 | 55 | gdal_dataset: gdal.Dataset = reader.get_gdal_dataset(filepath) |
55 | 56 |
|
56 | 57 | # Extract metadata attributes |
57 | | - attributes = get_attributes(gdal_dataset) |
| 58 | + metadata = get_metadata(gdal_dataset) |
58 | 59 |
|
59 | | - # Read pixel data from the dataset |
60 | | - data = gdal_dataset.ReadAsArray() |
| 60 | + # Duplicate, read directly from file, as gdal does not parse some necessary metadata |
| 61 | + metadata["direct_parse"] = envisat_direct.parse_direct(filepath) |
61 | 62 |
|
62 | 63 | # Create an xarray Dataset with pixel data and metadata attributes |
63 | | - dataset: xr.Dataset = xr.Dataset( |
64 | | - data_vars={'pixel_values': (('y', 'x'), data)}, |
65 | | - coords={ |
66 | | - 'x': np.arange(data.shape[1]), |
67 | | - 'y': np.arange(data.shape[0]) |
68 | | - }, |
69 | | - attrs=attributes |
70 | | - ) |
| 64 | + dataset: xr.Dataset = create_dataset(metadata, filepath) |
71 | 65 |
|
72 | 66 | return dataset |
73 | 67 |
|
74 | 68 |
|
| 69 | +def create_dataset(metadata: dict[str, Any], filepath: str) -> xr.Dataset: |
| 70 | + """ |
| 71 | + Create an xarray Dataset from ASAR metadata and file path. |
| 72 | +
|
| 73 | + This function constructs the coordinates, attributes, and data variables |
| 74 | + for an ASAR product, using the provided metadata and file path. |
| 75 | +
|
| 76 | + :param metadata: Dictionary containing ASAR product metadata. |
| 77 | + :param filepath: Path to the ASAR dataset file. |
| 78 | + :return: An xarray Dataset with pixel data, coordinates, and attributes. |
| 79 | + """ |
| 80 | + number_of_samples = metadata["line_length"] |
| 81 | + product_first_line_utc_time = metadata["first_line_time"] |
| 82 | + product_last_line_utc_time = metadata["last_line_time"] |
| 83 | + print(product_first_line_utc_time) |
| 84 | + |
| 85 | + number_of_lines = metadata["records"]["main_processing_params"]["num_output_lines"] |
| 86 | + azimuth_time_interval = 1 / metadata["records"]["main_processing_params"]["image_parameters"]["prf_value"][0] |
| 87 | + range_sampling_rate = metadata["records"]["main_processing_params"]["range_samp_rate"] |
| 88 | + image_slant_range_time = metadata["direct_parse"]["slant_time_first"] * 1e-9 |
| 89 | + |
| 90 | + number_of_bursts = 0 |
| 91 | + |
| 92 | + attrs = { |
| 93 | + "family_name": "Envisat", |
| 94 | + "number": 1, |
| 95 | + "mode": 1, |
| 96 | + "swaths": metadata["swath"], |
| 97 | + "orbit_number": metadata["abs_orbit"], |
| 98 | + "relative_orbit_number": metadata["rel_orbit"], |
| 99 | + "pass": metadata["pass"], |
| 100 | + "transmitter_receiver_polarisations": metadata["mds1_tx_rx_polar"], |
| 101 | + "product_type": "SLC", |
| 102 | + "start_time": product_first_line_utc_time, |
| 103 | + "stop_time": product_last_line_utc_time, |
| 104 | + |
| 105 | + "radar_frequency": metadata["records"]["main_processing_params"]["radar_freq"] / 1e9, |
| 106 | + "ascending_node_time": "", |
| 107 | + "azimuth_pixel_spacing": metadata["records"]["main_processing_params"]["azimuth_spacing"], |
| 108 | + "range_pixel_spacing": metadata["records"]["main_processing_params"]["range_samp_rate"], |
| 109 | + "product_first_line_utc_time": product_first_line_utc_time, |
| 110 | + "product_last_line_utc_time": product_last_line_utc_time, |
| 111 | + "azimuth_time_interval": azimuth_time_interval, |
| 112 | + "image_slant_range_time": image_slant_range_time, |
| 113 | + "range_sampling_rate": range_sampling_rate, |
| 114 | + "incidence_angle_mid_swath": metadata["direct_parse"]["incidence_angle_center"], |
| 115 | + "metadata": metadata |
| 116 | + } |
| 117 | + |
| 118 | + azimuth_time = compute_azimuth_time( |
| 119 | + product_first_line_utc_time, product_last_line_utc_time, number_of_lines |
| 120 | + ) |
| 121 | + |
| 122 | + if number_of_bursts == 0: |
| 123 | + swap_dims = {"line": "azimuth_time", "pixel": "slant_range_time"} |
| 124 | + else: |
| 125 | + raise NotImplementedError( |
| 126 | + "Burst processing is not implemented yet." |
| 127 | + ) |
| 128 | + |
| 129 | + coords: dict[str, Any] = { |
| 130 | + "pixel": np.arange(0, number_of_samples, dtype=int), |
| 131 | + "line": np.arange(0, number_of_lines, dtype=int), |
| 132 | + # set "units" explicitly as CF conventions don't support "nanoseconds". |
| 133 | + # See: https://github.com/pydata/xarray/issues/4183#issuecomment-685200043 |
| 134 | + "azimuth_time": ( |
| 135 | + "line", |
| 136 | + azimuth_time, |
| 137 | + {}, |
| 138 | + {"units": f"microseconds since {azimuth_time[0]}"}, |
| 139 | + ), |
| 140 | + } |
| 141 | + |
| 142 | + slant_range_time = np.linspace( |
| 143 | + image_slant_range_time, |
| 144 | + image_slant_range_time + (number_of_samples - 1) / range_sampling_rate, |
| 145 | + number_of_samples, |
| 146 | + ) |
| 147 | + coords["slant_range_time"] = ("pixel", slant_range_time) |
| 148 | + |
| 149 | + data = xr.open_dataarray(filepath, engine='rasterio') |
| 150 | + data.encoding.clear() |
| 151 | + data = data.squeeze("band").drop_vars(["band", "spatial_ref"]) |
| 152 | + data = data.rename({"y": "line", "x": "pixel"}) |
| 153 | + data = data.assign_coords(coords) |
| 154 | + data = data.swap_dims(swap_dims) |
| 155 | + |
| 156 | + data.attrs.update(attrs) |
| 157 | + data.encoding.update({}) |
| 158 | + |
| 159 | + return xr.Dataset(attrs=attrs, data_vars={"measurements": data}) |
| 160 | + |
| 161 | + |
75 | 162 | def get_chirp_parameters(dataset: gdal.Dataset) -> dict[str, Any]: |
76 | 163 | """ |
77 | 164 | Parse dataset metadata for chirp parameters. |
@@ -134,3 +221,27 @@ def get_chirp_cal_pulse_info(metadata: dict[str, str]) -> list[dict[str, Any]]: |
134 | 221 |
|
135 | 222 | # Convert the dictionary of dictionaries to the list of dictionaries |
136 | 223 | return list(cal_info_dict.values()) |
| 224 | + |
| 225 | + |
| 226 | +def compute_azimuth_time( |
| 227 | + product_first_line_utc_time: np.datetime64, |
| 228 | + product_last_line_utc_time: np.datetime64, |
| 229 | + number_of_lines: int |
| 230 | +) -> np.ndarray: |
| 231 | + """ |
| 232 | + Compute an array of azimuth times for each line in the ASAR product. |
| 233 | +
|
| 234 | + This function generates a sequence of evenly spaced time values between the |
| 235 | + first and last line UTC times, corresponding to the number of lines in the product. |
| 236 | +
|
| 237 | + :param product_first_line_utc_time: UTC time of the first line (as np.datetime64). |
| 238 | + :param product_last_line_utc_time: UTC time of the last line (as np.datetime64). |
| 239 | + :param number_of_lines: Total number of lines in the product. |
| 240 | + :return: Numpy array of azimuth times for each line. |
| 241 | + """ |
| 242 | + azimuth_time = pd.date_range( |
| 243 | + start=product_first_line_utc_time, |
| 244 | + end=product_last_line_utc_time, |
| 245 | + periods=number_of_lines |
| 246 | + ) |
| 247 | + return np.asarray(azimuth_time.values, dtype='datetime64[ns]') |
0 commit comments