From c2ec8d9ff40f6c57f5d33e9890de262125e8c8d8 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Tue, 29 Jul 2025 16:42:45 -0500 Subject: [PATCH 01/17] fix ale --- src/multirtc/multimetric/ale.py | 13 ++++++++++--- src/multirtc/multimetric/corner_reflector.py | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/multirtc/multimetric/ale.py b/src/multirtc/multimetric/ale.py index 7f3e310..65b5552 100644 --- a/src/multirtc/multimetric/ale.py +++ b/src/multirtc/multimetric/ale.py @@ -17,6 +17,10 @@ gdal.UseExceptions() +def db(data): + return 10 * np.log10(data) + + def gaussfit(x, y, A, x0, y0, sigma_x, sigma_y, theta): theta = np.radians(theta) sigx2 = sigma_x**2 @@ -52,7 +56,10 @@ def plot_crs_on_image(cr_df, data, project, outdir): max_y = cr_df['yloc'].max() + buffer fig, ax = plt.subplots(figsize=(15, 7)) - ax.imshow(data, cmap='gray', interpolation='bilinear', vmin=0.3, vmax=1.7, origin='upper') + data_db = db(data) + vmin = np.nanpercentile(data_db, 2) + vmax = np.nanpercentile(data_db, 98) + ax.imshow(data_db, cmap='gray', interpolation='bilinear', vmin=vmin, vmax=vmax, origin='upper') ax.set_xlim(min_x, max_x) ax.set_ylim(min_y, max_y) ax.axis('off') @@ -130,7 +137,7 @@ def calculate_ale_for_cr(point, data, project, outdir, search_window=100, oversa plt.rcParams.update({'font.size': 14}) fig, ax = plt.subplots(1, 3, figsize=(15, 7)) - ax[0].imshow(centered_data, cmap='gray', interpolation=None, origin='upper') + ax[0].imshow(db(centered_data), cmap='gray', interpolation=None, origin='upper') ax[0].plot(xpeak_centered, ypeak_centered, 'r+', label='Return Peak') ax[0].plot(xreal_centered, yreal_centered, 'b+', label='CR Location') ax[0].legend() @@ -144,7 +151,7 @@ def calculate_ale_for_cr(point, data, project, outdir, search_window=100, oversa ax[2].set_title(f'Gaussian Fit Corner Reflector (ID {int(point["ID"])})') [axi.axis('off') for axi in ax] fig.tight_layout() - fig.savefig(outdir / f'{project}_CR_{point["ID"]}.png', dpi=300, bbox_inches='tight') + fig.savefig(outdir / f'{project}_CR_{int(point["ID"])}.png', dpi=300, bbox_inches='tight') return point diff --git a/src/multirtc/multimetric/corner_reflector.py b/src/multirtc/multimetric/corner_reflector.py index 051c27b..de2b5af 100644 --- a/src/multirtc/multimetric/corner_reflector.py +++ b/src/multirtc/multimetric/corner_reflector.py @@ -83,7 +83,7 @@ def get_cr_df(bounds, date, azmangle, outdir): def add_geo_image_location(cr_df, geotransform, shape, epsg): bounds = get_bounds(geotransform, shape) x_start = bounds.bounds[0] - y_start = bounds.bounds[1] + y_start = bounds.bounds[3] x_spacing = geotransform[1] y_spacing = geotransform[5] blank = [np.nan] * cr_df.shape[0] From cd17164e9acc4a2035ebacef91fc59ab7f1f239b Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Thu, 31 Jul 2025 08:44:30 -0500 Subject: [PATCH 02/17] full run fixes --- src/multirtc/multimetric/corner_reflector.py | 4 ++++ src/multirtc/multimetric/point_target.py | 5 +++-- src/multirtc/multimetric/rle.py | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/multirtc/multimetric/corner_reflector.py b/src/multirtc/multimetric/corner_reflector.py index de2b5af..af05ced 100644 --- a/src/multirtc/multimetric/corner_reflector.py +++ b/src/multirtc/multimetric/corner_reflector.py @@ -124,6 +124,10 @@ def add_rdr_image_location(slc, cr_df, search_radius): row_guess, col_guess = int(round(row_guess)), int(round(col_guess)) row_range = (row_guess - search_radius, row_guess + search_radius) col_range = (col_guess - search_radius, col_guess + search_radius) + if row_range[0] < 0 or row_range[1] >= slc.shape[0] or col_range[0] < 0 or col_range[1] >= slc.shape[1]: + no_peak.append(idx) + print(f'CR {int(row["ID"])} outside of SLC bounds, skipping.') + continue data = slc.load_data(row_range, col_range) in_db = 10 * np.log10(np.abs(data)) row_peak, col_peak = np.unravel_index(np.argmax(in_db, axis=None), in_db.shape) diff --git a/src/multirtc/multimetric/point_target.py b/src/multirtc/multimetric/point_target.py index b17c161..a2235e1 100644 --- a/src/multirtc/multimetric/point_target.py +++ b/src/multirtc/multimetric/point_target.py @@ -39,8 +39,8 @@ def plot_profile(ax, n, magnitude, phase, title=None): def analyze_point_targets(platform, filepath, project, basedir, width=64): outdir = Path(basedir).expanduser() / project outdir.mkdir(exist_ok=True, parents=True) - slc = get_slc('UMBRA', filepath.name, filepath.parent.expanduser()) - cr_df = corner_reflector.get_cr_df(box(*slc.footprint.bounds), 4326, slc.reference_time, slc.look_angle, outdir) + slc = get_slc(platform, filepath.name, filepath.parent.expanduser()) + cr_df = corner_reflector.get_cr_df(box(*slc.footprint.bounds), slc.reference_time, slc.look_angle, outdir) cr_df = corner_reflector.add_rdr_image_location(slc, cr_df, search_radius=50) blank = [np.nan] * cr_df.shape[0] cr_df = cr_df.assign(az_res=blank, az_pslr=blank, az_islr=blank, rng_res=blank, rng_pslr=blank, rng_islr=blank) @@ -88,6 +88,7 @@ def analyze_point_targets(platform, filepath, project, basedir, width=64): plt.tight_layout() name_base = f'{project}_CR_{int(row["ID"])}' figure.savefig(outdir / f'{name_base}_point_target.png', dpi=300) + plt.close(figure) cr_df['az_res'] = cr_df['az_res'] * slc.source.Grid.Row.SS cr_df['rng_res'] = cr_df['rng_res'] * slc.source.Grid.Col.SS diff --git a/src/multirtc/multimetric/rle.py b/src/multirtc/multimetric/rle.py index e2ed4d5..01633fc 100644 --- a/src/multirtc/multimetric/rle.py +++ b/src/multirtc/multimetric/rle.py @@ -143,6 +143,7 @@ def plot_offsets(df: pd.DataFrame, output_path: Path): plt.colorbar(im2, ax=ax2) plt.tight_layout() plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close(fig) def rle(reference_path: Path, secondary_path: Path, project: str, basedir: Path, max_nan_ratio=0.1, max_error=0.01): @@ -163,8 +164,10 @@ def rle(reference_path: Path, secondary_path: Path, project: str, basedir: Path, shift_y, shift_x = shift * pixel_size rows.append(pd.Series({'id': tile.id, 'shift_x': shift_x, 'shift_y': shift_y, 'error': error})) base_name = f'{reference_path.stem}_x_{secondary_path.stem}' + if len(rows) == 0: + print('No valid tiles found. Skipping RLE analysis.') + return df = pd.DataFrame(rows) - df['shift_x_mdz'] = 0.6745 * np.abs(df['shift_x'] - np.median(df['shift_x'])) / median_abs_deviation(df['shift_x']) df['shift_y_mdz'] = 0.6745 * np.abs(df['shift_y'] - np.median(df['shift_y'])) / median_abs_deviation(df['shift_y']) df['valid'] = (df['shift_x_mdz'] <= 3.5) & (df['shift_y_mdz'] <= 3.5) From dc004739a7389ceeba6e4bc9b39ca5a5a8c97924 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sat, 9 Aug 2025 19:45:33 -0500 Subject: [PATCH 03/17] update readme --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0d6f416..481b760 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,33 @@ A python library for creating ISCE3-based RTCs for multiple SAR data sources > [!IMPORTANT] > All credit for this library's RTC algorithm goes to Gustavo Shiroma and the JPL [OPERA](https://www.jpl.nasa.gov/go/opera/about-opera/) and [ISCE3](https://github.com/isce-framework/isce3) teams. This package merely allows others to use their algorithm with a wider set of SAR data sources. The RTC algorithm utilized by this package is described in [Shiroma et al., 2023](https://doi.org/10.1109/TGRS.2022.3147472). -## Usage +## Dataset Support MultiRTC allows users to create RTC products from SLC data for multiple SAR sensor platforms, and provides utilities for assessing the resulting products. All utilities can be accessed via CLI pattern `multirtc SUBCOMMAND ARGS`, with the primary subcommand `multirtc rtc`. -Currently the list of supported datasets includes: - -Full RTC: -- [Sentinel-1 Burst SLCs](https://www.earthdata.nasa.gov/data/catalog/alaska-satellite-facility-distributed-active-archive-center-sentinel-1-bursts-version) -- [Capella SICD SLCs](https://www.capellaspace.com/earth-observation/data) -- [ICEYE SICD SLCs](https://sar.iceye.com/5.0/productFormats/slc/) +Below is a list of relevant SAR data sources and their support status: + +| Mission | File Format | Image Mode | Image Grid Type | Status | +|------------|-------------|-------------------------------------|-------------| +| Sentinel-1 | SAFE | Burst IW | Range Doppler | Supported | +| Sentinel-1 | SAFE | Full-frame IW | Range Doppler | Unsupported | +| Sentinel-1 | SAFE | Burst EW | Range Doppler | Unsupported | +| Sentinel-1 | SAFE | Full-frame EW | Range Doppler | Unsupported | +| Capella | SICD | Spotlight | Polar | Unsupported | +| Capella | SICD | Sliding Spotlight | Range Doppler | Supported | +| Capella | SICD | Stripmap | Range Doppler | Supported | +| Iceye | SICD | Dwell | Range Doppler | Supported | +| Iceye | SICD | Spotlight | Range Doppler | Supported | +| Iceye | SICD | Sliding Spotlight | Range Doppler | Supported | +| Iceye | SICD | Stripmap | Range Doppler | Supported | +| Iceye | SICD | Scan | Range Doppler | Supported | +| Umbra | SICD | Dwell | Polar | Supported^* | +| Umbra | SICD | Spotlight | Polar | Supported^* | + +I have done my best to accurately reflect the support status of each SAR image type, but please let me know if I have made any mistakes. Note that some commercial datasets used to use polar instead of range doppler image grids for specific images modes. This table is based on the image grid types currently being used. + +^*Polar image grid support is implemented via the [approach detailed by Piyush Agram](https://arxiv.org/abs/2503.07889v1) in his recent technical note. I have implemented his method in a fork of the main ISCE3 repo, which you can view [here](https://github.com/forrestfwilliams/isce3/tree/pfa). The long-term plan is to merge this into the main ISCE3 repo but until that is complete, polar grid support is only available via this project's `pfa`-suffixed docker containers. See the running via docker section for more details. -Geocode Only: -- [UMBRA SICD SLCs](https://help.umbra.space/product-guide/umbra-products/umbra-product-specifications) +## Usage To create an RTC, use the `multirtc` CLI entrypoint using the following pattern: @@ -30,8 +45,24 @@ Where `PLATFORM` is the name of the satellite platform (currently `S1`, `CAPELLA Output RTC pixel values represent gamma0 power. -### Current Umbra Implementation -Currently, the Umbra processor only supports basic geocoding and not full RTC processing. ISCE3's RTC algorithm is only designed to work with Range Migration Algorithm (RMA) focused SLC products, but Umbra creates their data using the Polar Format Algorithm (PFA). Using an [approach detailed by Piyush Agram](https://arxiv.org/abs/2503.07889v1) to adapt RMA approaches to the PFA image geometry, we have developed a workflow to geocode an Umbra SLC but there is more work to be done to implement full RTC processing. Since full RTC is not yet implemented, Umbra geocoded pixel values represent sigma0 power. +### Running via Docker +In addition to the main python interface, I've also provided an experimental docker container that contains full support for polar grid format SICD data. Encapsulating this functionality in a docker container is ncessary for now because it requires re-compiling a development version of ISCE3. The docker container can be run using a similar interface, with exception of needing to pass your EarthData credentials and the need to pass a mounted volume with an `input` and `output` directory inside: + +```bash +docker run -it --rm \ + -e EARTHDATA_USERNAME=[YOUR_USERNAME_HERE] \ + -e EARTHDATA_PASSWORD=[YOUR_PASSWORD_HERE] \ + -v ~/local_path/project1:/home/conda/project1 \ + ghcr.io/forrestfwilliams/multirtc:0.4.0.pfa \ + rtc PLATFORM SLC-GRANULE --resolution RESOLUTION +``` +The local `project1` directory can be a name of your choosing and should have the structure: +``` +project1/ + |--input/ + |--input.slc (if needed) + |--output/ +``` ### DEM options Currently, only the OPERA DEM is supported. This is a global Height Above Ellipsoid DEM sourced from the [COP-30 DEM](https://portal.opentopography.org/raster?opentopoID=OTSDEM.032021.4326.3). In the future, we hope to support a wider variety of automatically retrieved and user provided DEMs. From 06d1a3313361ddd7929983854e889e886083855f Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sat, 9 Aug 2025 19:48:39 -0500 Subject: [PATCH 04/17] reformat table --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 481b760..ce37685 100644 --- a/README.md +++ b/README.md @@ -13,26 +13,26 @@ MultiRTC allows users to create RTC products from SLC data for multiple SAR sens Below is a list of relevant SAR data sources and their support status: -| Mission | File Format | Image Mode | Image Grid Type | Status | -|------------|-------------|-------------------------------------|-------------| -| Sentinel-1 | SAFE | Burst IW | Range Doppler | Supported | -| Sentinel-1 | SAFE | Full-frame IW | Range Doppler | Unsupported | -| Sentinel-1 | SAFE | Burst EW | Range Doppler | Unsupported | -| Sentinel-1 | SAFE | Full-frame EW | Range Doppler | Unsupported | -| Capella | SICD | Spotlight | Polar | Unsupported | -| Capella | SICD | Sliding Spotlight | Range Doppler | Supported | -| Capella | SICD | Stripmap | Range Doppler | Supported | -| Iceye | SICD | Dwell | Range Doppler | Supported | -| Iceye | SICD | Spotlight | Range Doppler | Supported | -| Iceye | SICD | Sliding Spotlight | Range Doppler | Supported | -| Iceye | SICD | Stripmap | Range Doppler | Supported | -| Iceye | SICD | Scan | Range Doppler | Supported | -| Umbra | SICD | Dwell | Polar | Supported^* | -| Umbra | SICD | Spotlight | Polar | Supported^* | +| Mission | File Format | Image Mode | Image Grid Type | Status | +|------------|-------------|-------------------------------------|---------------| +| Sentinel-1 | SAFE | Burst IW | Range Doppler | Supported | +| Sentinel-1 | SAFE | Full-frame IW | Range Doppler | Unsupported | +| Sentinel-1 | SAFE | Burst EW | Range Doppler | Unsupported | +| Sentinel-1 | SAFE | Full-frame EW | Range Doppler | Unsupported | +| Capella | SICD | Spotlight | Polar | Supported(\*) | +| Capella | SICD | Sliding Spotlight | Range Doppler | Supported | +| Capella | SICD | Stripmap | Range Doppler | Supported | +| Iceye | SICD | Dwell | Range Doppler | Supported | +| Iceye | SICD | Spotlight | Range Doppler | Supported | +| Iceye | SICD | Sliding Spotlight | Range Doppler | Supported | +| Iceye | SICD | Stripmap | Range Doppler | Supported | +| Iceye | SICD | Scan | Range Doppler | Supported | +| Umbra | SICD | Dwell | Polar | Supported(\*) | +| Umbra | SICD | Spotlight | Polar | Supported(\*) | I have done my best to accurately reflect the support status of each SAR image type, but please let me know if I have made any mistakes. Note that some commercial datasets used to use polar instead of range doppler image grids for specific images modes. This table is based on the image grid types currently being used. -^*Polar image grid support is implemented via the [approach detailed by Piyush Agram](https://arxiv.org/abs/2503.07889v1) in his recent technical note. I have implemented his method in a fork of the main ISCE3 repo, which you can view [here](https://github.com/forrestfwilliams/isce3/tree/pfa). The long-term plan is to merge this into the main ISCE3 repo but until that is complete, polar grid support is only available via this project's `pfa`-suffixed docker containers. See the running via docker section for more details. +(*) Polar image grid support is implemented via the [approach detailed by Piyush Agram](https://arxiv.org/abs/2503.07889v1) in his recent technical note. I have implemented his method in a fork of the main ISCE3 repo, which you can view [here](https://github.com/forrestfwilliams/isce3/tree/pfa). The long-term plan is to merge this into the main ISCE3 repo but until that is complete, polar grid support is only available via this project's `pfa`-suffixed docker containers. See the running via docker section for more details. ## Usage From 89b1630273bd4d2ea8aa11733465393235f61779 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sat, 9 Aug 2025 19:50:25 -0500 Subject: [PATCH 05/17] fix table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce37685..668b2b9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ MultiRTC allows users to create RTC products from SLC data for multiple SAR sens Below is a list of relevant SAR data sources and their support status: | Mission | File Format | Image Mode | Image Grid Type | Status | -|------------|-------------|-------------------------------------|---------------| +|------------|-------------|-------------------|-----------------|---------------| | Sentinel-1 | SAFE | Burst IW | Range Doppler | Supported | | Sentinel-1 | SAFE | Full-frame IW | Range Doppler | Unsupported | | Sentinel-1 | SAFE | Burst EW | Range Doppler | Unsupported | From 89127d36ba0343d1927b40e90744489ed6cd054d Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sat, 9 Aug 2025 19:52:50 -0500 Subject: [PATCH 06/17] fix asterisks --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 668b2b9..5f45042 100644 --- a/README.md +++ b/README.md @@ -13,26 +13,26 @@ MultiRTC allows users to create RTC products from SLC data for multiple SAR sens Below is a list of relevant SAR data sources and their support status: -| Mission | File Format | Image Mode | Image Grid Type | Status | -|------------|-------------|-------------------|-----------------|---------------| -| Sentinel-1 | SAFE | Burst IW | Range Doppler | Supported | -| Sentinel-1 | SAFE | Full-frame IW | Range Doppler | Unsupported | -| Sentinel-1 | SAFE | Burst EW | Range Doppler | Unsupported | -| Sentinel-1 | SAFE | Full-frame EW | Range Doppler | Unsupported | -| Capella | SICD | Spotlight | Polar | Supported(\*) | -| Capella | SICD | Sliding Spotlight | Range Doppler | Supported | -| Capella | SICD | Stripmap | Range Doppler | Supported | -| Iceye | SICD | Dwell | Range Doppler | Supported | -| Iceye | SICD | Spotlight | Range Doppler | Supported | -| Iceye | SICD | Sliding Spotlight | Range Doppler | Supported | -| Iceye | SICD | Stripmap | Range Doppler | Supported | -| Iceye | SICD | Scan | Range Doppler | Supported | -| Umbra | SICD | Dwell | Polar | Supported(\*) | -| Umbra | SICD | Spotlight | Polar | Supported(\*) | +| Mission | File Format | Image Mode | Image Grid Type | Status | +|------------|-------------|-------------------|-----------------|-------------| +| Sentinel-1 | SAFE | Burst IW | Range Doppler | Supported | +| Sentinel-1 | SAFE | Full-frame IW | Range Doppler | Unsupported | +| Sentinel-1 | SAFE | Burst EW | Range Doppler | Unsupported | +| Sentinel-1 | SAFE | Full-frame EW | Range Doppler | Unsupported | +| Capella | SICD | Spotlight | Polar | Supported\* | +| Capella | SICD | Sliding Spotlight | Range Doppler | Supported | +| Capella | SICD | Stripmap | Range Doppler | Supported | +| Iceye | SICD | Dwell | Range Doppler | Supported | +| Iceye | SICD | Spotlight | Range Doppler | Supported | +| Iceye | SICD | Sliding Spotlight | Range Doppler | Supported | +| Iceye | SICD | Stripmap | Range Doppler | Supported | +| Iceye | SICD | Scan | Range Doppler | Supported | +| Umbra | SICD | Dwell | Polar | Supported\* | +| Umbra | SICD | Spotlight | Polar | Supported\* | I have done my best to accurately reflect the support status of each SAR image type, but please let me know if I have made any mistakes. Note that some commercial datasets used to use polar instead of range doppler image grids for specific images modes. This table is based on the image grid types currently being used. -(*) Polar image grid support is implemented via the [approach detailed by Piyush Agram](https://arxiv.org/abs/2503.07889v1) in his recent technical note. I have implemented his method in a fork of the main ISCE3 repo, which you can view [here](https://github.com/forrestfwilliams/isce3/tree/pfa). The long-term plan is to merge this into the main ISCE3 repo but until that is complete, polar grid support is only available via this project's `pfa`-suffixed docker containers. See the running via docker section for more details. +\*Polar image grid support is implemented via the [approach detailed by Piyush Agram](https://arxiv.org/abs/2503.07889v1) in his recent technical note. I have implemented his method in a fork of the main ISCE3 repo, which you can view [here](https://github.com/forrestfwilliams/isce3/tree/pfa). The long-term plan is to merge this into the main ISCE3 repo but until that is complete, polar grid support is only available via this project's `pfa`-suffixed docker containers. See the running via docker section for more details. ## Usage From 50448b8108d6f63c25dc1843b6c587ca6e35d4b2 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sat, 9 Aug 2025 20:52:52 -0500 Subject: [PATCH 07/17] add layer info --- README.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5f45042..60bda41 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,33 @@ In addition to the main python interface, I've also provided an experimental doc docker run -it --rm \ -e EARTHDATA_USERNAME=[YOUR_USERNAME_HERE] \ -e EARTHDATA_PASSWORD=[YOUR_PASSWORD_HERE] \ - -v ~/local_path/project1:/home/conda/project1 \ + -v ~/LOCAL_PATH/PROJECT:/home/conda/PROJECT \ ghcr.io/forrestfwilliams/multirtc:0.4.0.pfa \ - rtc PLATFORM SLC-GRANULE --resolution RESOLUTION + rtc PLATFORM SLC-GRANULE --resolution RESOLUTION --work-dir PROJECT ``` The local `project1` directory can be a name of your choosing and should have the structure: ``` -project1/ +PROJECT/ |--input/ |--input.slc (if needed) |--output/ ``` +If you're encountering `permission denied` errors when running the container, make sure other users are allowed to read/write to your project directory (`chmod -R a+rwX ~/LOCAL_PATH/PROJECT`). + +### Output Layers +MultiRTC outputs one main RTC image and nine metadata images as GeoTIFFs. All layers follow the naming schema `{FILEID}_{DATASET}.tif`, with the main RTC image omiting the `_{DATASET}` component. The layers are as follows +`FILEID.tif`: The radiometric and terrain corrected backscatter data in gamma0 radiometry. +`FILEID_incidence_angle.tif`: The angle between the LOS vector and the ellipsoid normal at the target. +`FILEID_interpolated_dem.tif`: The DEM used of calculating layover/shadow. +`FILEID_local_incidence_angle.tif`: The angle between LOS vector and terrain normal vector at the target. +`FILEID_mask.tif`: The layover/shadow mask. `0` is no shadow or shadow, `1` is shadow, `2` is layover and `3` is layover and shadow. +`FILEID_number_of_looks.tif`: The number of radar samples used to compute each output image pixel. +`FILEID_projection_angle.tif`: TODO. +`FILEID_rtc_anf_gamma0_to_beta0.tif`: The conversion values needed to normalize the gamma0 backscatter to beta0. +`FILEID_rtc_anf_gamma0_to_sigma0.tif`: The conversion values needed to normalize the gamma0 backscatter to sigma0. +`FILEID_rtc_anf_projection_angle.tif`: TODO. + +More information on the metadata images can be found in the OPERA RTC Static Product guide on the [OPERA RTC Product website](https://www.jpl.nasa.gov/go/opera/products/rtc-product/). ### DEM options Currently, only the OPERA DEM is supported. This is a global Height Above Ellipsoid DEM sourced from the [COP-30 DEM](https://portal.opentopography.org/raster?opentopoID=OTSDEM.032021.4326.3). In the future, we hope to support a wider variety of automatically retrieved and user provided DEMs. From a9fb13063cc2478cdd0eb4e7128bb4c18abf951e Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sat, 9 Aug 2025 21:38:58 -0500 Subject: [PATCH 08/17] don't output area projection meatadata --- README.md | 6 ++---- src/multirtc/create_rtc.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 60bda41..eddeb71 100644 --- a/README.md +++ b/README.md @@ -66,17 +66,15 @@ PROJECT/ If you're encountering `permission denied` errors when running the container, make sure other users are allowed to read/write to your project directory (`chmod -R a+rwX ~/LOCAL_PATH/PROJECT`). ### Output Layers -MultiRTC outputs one main RTC image and nine metadata images as GeoTIFFs. All layers follow the naming schema `{FILEID}_{DATASET}.tif`, with the main RTC image omiting the `_{DATASET}` component. The layers are as follows +MultiRTC outputs one main RTC image and seven metadata images as GeoTIFFs. All layers follow the naming schema `{FILEID}_{DATASET}.tif`, with the main RTC image omiting the `_{DATASET}` component. The layers are as follows `FILEID.tif`: The radiometric and terrain corrected backscatter data in gamma0 radiometry. `FILEID_incidence_angle.tif`: The angle between the LOS vector and the ellipsoid normal at the target. `FILEID_interpolated_dem.tif`: The DEM used of calculating layover/shadow. -`FILEID_local_incidence_angle.tif`: The angle between LOS vector and terrain normal vector at the target. +`FILEID_local_incidence_angle.tif`: The angle between the LOS vector and terrain normal vector at the target. `FILEID_mask.tif`: The layover/shadow mask. `0` is no shadow or shadow, `1` is shadow, `2` is layover and `3` is layover and shadow. `FILEID_number_of_looks.tif`: The number of radar samples used to compute each output image pixel. -`FILEID_projection_angle.tif`: TODO. `FILEID_rtc_anf_gamma0_to_beta0.tif`: The conversion values needed to normalize the gamma0 backscatter to beta0. `FILEID_rtc_anf_gamma0_to_sigma0.tif`: The conversion values needed to normalize the gamma0 backscatter to sigma0. -`FILEID_rtc_anf_projection_angle.tif`: TODO. More information on the metadata images can be found in the OPERA RTC Static Product guide on the [OPERA RTC Product website](https://www.jpl.nasa.gov/go/opera/products/rtc-product/). diff --git a/src/multirtc/create_rtc.py b/src/multirtc/create_rtc.py index 0b5667d..1f91c1f 100755 --- a/src/multirtc/create_rtc.py +++ b/src/multirtc/create_rtc.py @@ -388,10 +388,10 @@ def save_intermediate_geocode_files( names = [ LAYER_NAME_LOCAL_INCIDENCE_ANGLE, LAYER_NAME_INCIDENCE_ANGLE, - LAYER_NAME_PROJECTION_ANGLE, - LAYER_NAME_RTC_ANF_PROJECTION_ANGLE, - # LAYER_NAME_RANGE_SLOPE, # FIXME LAYER_NAME_DEM, + # LAYER_NAME_PROJECTION_ANGLE, + # LAYER_NAME_RTC_ANF_PROJECTION_ANGLE, + # LAYER_NAME_RANGE_SLOPE, # FIXME ] raster_objs = [] for name in names: @@ -408,10 +408,10 @@ def save_intermediate_geocode_files( ( local_incidence_angle_raster, incidence_angle_raster, - projection_angle_raster, - rtc_anf_projection_angle_raster, - # range_slope_raster, # FIXME interpolated_dem_raster, + # projection_angle_raster, + # rtc_anf_projection_angle_raster, + # range_slope_raster, # FIXME ) = raster_objs # TODO review this (Doppler)!!! @@ -432,9 +432,9 @@ def save_intermediate_geocode_files( dem_interp_method_enum, incidence_angle_raster=incidence_angle_raster, local_incidence_angle_raster=local_incidence_angle_raster, - projection_angle_raster=projection_angle_raster, - simulated_radar_brightness_raster=rtc_anf_projection_angle_raster, interpolated_dem_raster=interpolated_dem_raster, + # projection_angle_raster=projection_angle_raster, + # simulated_radar_brightness_raster=rtc_anf_projection_angle_raster, # range_slope_angle_raster=range_slope_raster, # FIXME ) for obj in output_obj_list: From c60cf569f52c1a7d4b7a7063166d9a80a1005153 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sat, 9 Aug 2025 21:40:38 -0500 Subject: [PATCH 09/17] update changelog --- CHANGELOG.md | 8 ++++++++ README.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b9c52..c5a9d01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [PEP 440](https://www.python.org/dev/peps/pep-0440/) and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.1] + +### Changed +* Update readme with docker instructions and layer info. + +### Removed +* Output of area projection metadata. + ## [0.4.0] ### Changed diff --git a/README.md b/README.md index eddeb71..b2458d5 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ docker run -it --rm \ -e EARTHDATA_USERNAME=[YOUR_USERNAME_HERE] \ -e EARTHDATA_PASSWORD=[YOUR_PASSWORD_HERE] \ -v ~/LOCAL_PATH/PROJECT:/home/conda/PROJECT \ - ghcr.io/forrestfwilliams/multirtc:0.4.0.pfa \ + ghcr.io/forrestfwilliams/multirtc:VERSION.pfa \ rtc PLATFORM SLC-GRANULE --resolution RESOLUTION --work-dir PROJECT ``` The local `project1` directory can be a name of your choosing and should have the structure: From 7ffbf5b7512fcf86bbf78fd05972dfa0f74e2c10 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sun, 10 Aug 2025 16:45:47 -0500 Subject: [PATCH 10/17] fix formatting --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b2458d5..928762e 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,8 @@ In addition to the main python interface, I've also provided an experimental doc ```bash docker run -it --rm \ - -e EARTHDATA_USERNAME=[YOUR_USERNAME_HERE] \ - -e EARTHDATA_PASSWORD=[YOUR_PASSWORD_HERE] \ + -e EARTHDATA_USERNAME=YOUR_USERNAME_HERE \ + -e EARTHDATA_PASSWORD=YOUR_PASSWORD_HERE \ -v ~/LOCAL_PATH/PROJECT:/home/conda/PROJECT \ ghcr.io/forrestfwilliams/multirtc:VERSION.pfa \ rtc PLATFORM SLC-GRANULE --resolution RESOLUTION --work-dir PROJECT @@ -66,15 +66,15 @@ PROJECT/ If you're encountering `permission denied` errors when running the container, make sure other users are allowed to read/write to your project directory (`chmod -R a+rwX ~/LOCAL_PATH/PROJECT`). ### Output Layers -MultiRTC outputs one main RTC image and seven metadata images as GeoTIFFs. All layers follow the naming schema `{FILEID}_{DATASET}.tif`, with the main RTC image omiting the `_{DATASET}` component. The layers are as follows -`FILEID.tif`: The radiometric and terrain corrected backscatter data in gamma0 radiometry. -`FILEID_incidence_angle.tif`: The angle between the LOS vector and the ellipsoid normal at the target. -`FILEID_interpolated_dem.tif`: The DEM used of calculating layover/shadow. -`FILEID_local_incidence_angle.tif`: The angle between the LOS vector and terrain normal vector at the target. -`FILEID_mask.tif`: The layover/shadow mask. `0` is no shadow or shadow, `1` is shadow, `2` is layover and `3` is layover and shadow. -`FILEID_number_of_looks.tif`: The number of radar samples used to compute each output image pixel. -`FILEID_rtc_anf_gamma0_to_beta0.tif`: The conversion values needed to normalize the gamma0 backscatter to beta0. -`FILEID_rtc_anf_gamma0_to_sigma0.tif`: The conversion values needed to normalize the gamma0 backscatter to sigma0. +MultiRTC outputs one main RTC image and seven metadata images as GeoTIFFs. All layers follow the naming schema `{FILEID}_{DATASET}.tif`, with the main RTC image omiting the `_{DATASET}` component. The layers are as follows: +- `FILEID.tif`: The radiometric and terrain corrected backscatter data in gamma0 radiometry. +- `FILEID_incidence_angle.tif`: The angle between the LOS vector and the ellipsoid normal at the target. +- `FILEID_interpolated_dem.tif`: The DEM used of calculating layover/shadow. +- `FILEID_local_incidence_angle.tif`: The angle between the LOS vector and terrain normal vector at the target. +- `FILEID_mask.tif`: The layover/shadow mask. `0` is no shadow or shadow, `1` is shadow, `2` is layover and `3` is layover and shadow. +- `FILEID_number_of_looks.tif`: The number of radar samples used to compute each output image pixel. +- `FILEID_rtc_anf_gamma0_to_beta0.tif`: The conversion values needed to normalize the gamma0 backscatter to beta0. +- `FILEID_rtc_anf_gamma0_to_sigma0.tif`: The conversion values needed to normalize the gamma0 backscatter to sigma0. More information on the metadata images can be found in the OPERA RTC Static Product guide on the [OPERA RTC Product website](https://www.jpl.nasa.gov/go/opera/products/rtc-product/). From 1954f466c42888ec800816e5a491a714a718173f Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sun, 10 Aug 2025 17:08:59 -0500 Subject: [PATCH 11/17] smal fixes --- CHANGELOG.md | 3 ++- src/multirtc/create_rtc.py | 4 +++- src/multirtc/multirtc.py | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a9d01..78bec13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [PEP 440](https://www.python.org/dev/peps/pep-0440/) and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.4.1] +## [0.5.0] ### Changed * Update readme with docker instructions and layer info. ### Removed * Output of area projection metadata. +* Prototype polar grid support in favor of full support via the new docker image. ## [0.4.0] diff --git a/src/multirtc/create_rtc.py b/src/multirtc/create_rtc.py index 1f91c1f..2e768cf 100755 --- a/src/multirtc/create_rtc.py +++ b/src/multirtc/create_rtc.py @@ -2,6 +2,7 @@ import logging import os import time +from pathlib import Path import isce3 import numpy as np @@ -143,7 +144,8 @@ def compute_correction_lut( rg_lut = isce3.core.LUT2d( bistatic_delay.x_start, bistatic_delay.y_start, bistatic_delay.x_spacing, bistatic_delay.y_spacing, tropo ) - + [x.unlink() for x in Path(scratch_path).glob('*.hdr') if x.is_file()] + [x.unlink() for x in Path(scratch_path).glob('*.rdr') if x.is_file()] return rg_lut, az_lut diff --git a/src/multirtc/multirtc.py b/src/multirtc/multirtc.py index 9677e7a..e3f5537 100644 --- a/src/multirtc/multirtc.py +++ b/src/multirtc/multirtc.py @@ -85,7 +85,10 @@ def run_multirtc(platform: str, granule: str, resolution: int, work_dir: Path) - ) rtc(slc, geogrid, opts) else: - pfa_prototype_geocode(slc, geogrid, dem_path, output_dir) + raise NotImplementedError( + 'RTC creation is not supported for this input. For polar grid support, use the multirtc docker image:\n' + 'https://github.com/forrestfwilliams/MultiRTC/pkgs/container/multirtc' + ) def create_parser(parser): From 6836468a7cef6ecf80d3205cdcc03312595f7cc0 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sun, 10 Aug 2025 17:10:48 -0500 Subject: [PATCH 12/17] fix ruff --- src/multirtc/multirtc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multirtc/multirtc.py b/src/multirtc/multirtc.py index e3f5537..b341745 100644 --- a/src/multirtc/multirtc.py +++ b/src/multirtc/multirtc.py @@ -8,7 +8,7 @@ from multirtc import dem from multirtc.base import Slc -from multirtc.create_rtc import pfa_prototype_geocode, rtc +from multirtc.create_rtc import rtc from multirtc.rtc_options import RtcOptions from multirtc.sentinel1 import S1BurstSlc from multirtc.sicd import SicdPfaSlc, SicdRzdSlc From f4f875f83e0185e5216f97262e59eec84aec7ac2 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Sun, 10 Aug 2025 17:20:30 -0500 Subject: [PATCH 13/17] update for pr --- CHANGELOG.md | 3 +++ README.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78bec13..c88db45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * Output of area projection metadata. * Prototype polar grid support in favor of full support via the new docker image. +### Fixed +* Plotting issues with the cal/val submodule. + ## [0.4.0] ### Changed diff --git a/README.md b/README.md index 928762e..97a9bb9 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,10 @@ More information on the metadata images can be found in the OPERA RTC Static Pro Currently, only the OPERA DEM is supported. This is a global Height Above Ellipsoid DEM sourced from the [COP-30 DEM](https://portal.opentopography.org/raster?opentopoID=OTSDEM.032021.4326.3). In the future, we hope to support a wider variety of automatically retrieved and user provided DEMs. ## Calibration & Validation Subcommands + +> [!WARNING] +> This submodule currently only support Umbra SICD data! Reach out if you would like to see this submodule expanded to other datasets. + MultiRTC includes three calibration and validation (cal/val) subcommands for assessing the geometric and radiometric quality of SAR products. These tools are useful for analyzing geolocation, co-registration, and impulse response performance. ### `ale` Absolute Location Error From 3eb1f99e8deebbcf33facbc0e5ec1449395298f8 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Mon, 11 Aug 2025 11:05:41 -0500 Subject: [PATCH 14/17] add geocode entrypoint --- README.md | 12 ++++++++- src/multirtc/__main__.py | 9 ++++--- src/multirtc/create_rtc.py | 52 +++++++++++++++++++++---------------- src/multirtc/geocode.py | 19 ++++++++++++++ src/multirtc/multirtc.py | 10 ++++--- src/multirtc/rtc_options.py | 28 +++++--------------- 6 files changed, 78 insertions(+), 52 deletions(-) create mode 100644 src/multirtc/geocode.py diff --git a/README.md b/README.md index 97a9bb9..5ed8c16 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,14 @@ Where `PLATFORM` is the name of the satellite platform (currently `S1`, `CAPELLA Output RTC pixel values represent gamma0 power. +To create an image that is geocoded but not radiometricly corrected, use the `geocoded` flag instead: + +```bash +multirtc geocode PLATFORM SLC-GRANULE --resolution RESOLUTION --work-dir WORK-DIR +``` + +Output geocoded pixel values represent sigma0 power. + ### Running via Docker In addition to the main python interface, I've also provided an experimental docker container that contains full support for polar grid format SICD data. Encapsulating this functionality in a docker container is ncessary for now because it requires re-compiling a development version of ISCE3. The docker container can be run using a similar interface, with exception of needing to pass your EarthData credentials and the need to pass a mounted volume with an `input` and `output` directory inside: @@ -78,8 +86,10 @@ MultiRTC outputs one main RTC image and seven metadata images as GeoTIFFs. All l More information on the metadata images can be found in the OPERA RTC Static Product guide on the [OPERA RTC Product website](https://www.jpl.nasa.gov/go/opera/products/rtc-product/). +All metadata images other than `FILEID_mask.tif`, and `FILEID_number_of_looks.tif` are omitted for geocode-only products. + ### DEM options -Currently, only the OPERA DEM is supported. This is a global Height Above Ellipsoid DEM sourced from the [COP-30 DEM](https://portal.opentopography.org/raster?opentopoID=OTSDEM.032021.4326.3). In the future, we hope to support a wider variety of automatically retrieved and user provided DEMs. +Currently, only the OPERA DEM is supported. This is a global Height Above Ellipsoid DEM sourced from the [COP-30 DEM](https://portal.opentopography.org/raster?opentopoID=OTSDEM.032021.4326.3). In the future, we hope to support a wider variety of automatically retrieved and user provided DEMs. If the low resolution of the default DEM is causing radiometry issues, try using the `geocode` instead of `rtc` workflow. ## Calibration & Validation Subcommands diff --git a/src/multirtc/__main__.py b/src/multirtc/__main__.py index e6adad7..efbbdd4 100644 --- a/src/multirtc/__main__.py +++ b/src/multirtc/__main__.py @@ -1,6 +1,6 @@ import argparse -from multirtc import multirtc +from multirtc import geocode, multirtc from multirtc.multimetric import ale, point_target, rle @@ -12,8 +12,11 @@ def main(): ) subparsers = global_parser.add_subparsers(title='command', help='MultiRTC sub-commands') - multirtc_parser = multirtc.create_parser(subparsers.add_parser('rtc', help=multirtc.__doc__)) - multirtc_parser.set_defaults(func=multirtc.run) + rtc_parser = multirtc.create_parser(subparsers.add_parser('rtc', help=multirtc.__doc__)) + rtc_parser.set_defaults(func=multirtc.run) + + geocode_parser = multirtc.create_parser(subparsers.add_parser('geocode', help=geocode.__doc__)) + geocode_parser.set_defaults(func=geocode.run) ale_parser = ale.create_parser(subparsers.add_parser('ale', help=ale.__doc__)) ale_parser.set_defaults(func=ale.run) diff --git a/src/multirtc/create_rtc.py b/src/multirtc/create_rtc.py index 2e768cf..dfc6982 100755 --- a/src/multirtc/create_rtc.py +++ b/src/multirtc/create_rtc.py @@ -518,11 +518,16 @@ def rtc(slc, geogrid, opts): geocode_kwargs['input_layover_shadow_mask_raster'] = slantrange_layover_shadow_mask_raster out_geo_nlooks_obj = isce3.io.Raster(nlooks_file, geogrid.width, geogrid.length, 1, gdal.GDT_Float32, raster_format) - out_geo_rtc_obj = isce3.io.Raster(rtc_anf_file, geogrid.width, geogrid.length, 1, gdal.GDT_Float32, raster_format) - out_geo_rtc_gamma0_to_sigma0_obj = isce3.io.Raster( - rtc_anf_gamma0_to_sigma0_file, geogrid.width, geogrid.length, 1, gdal.GDT_Float32, raster_format - ) - geocode_kwargs['out_geo_rtc_gamma0_to_sigma0'] = out_geo_rtc_gamma0_to_sigma0_obj + out_geo_rtc_obj = None + if opts.apply_rtc: + out_geo_rtc_obj = isce3.io.Raster( + rtc_anf_file, geogrid.width, geogrid.length, 1, gdal.GDT_Float32, raster_format + ) + out_geo_rtc_gamma0_to_sigma0_obj = isce3.io.Raster( + rtc_anf_gamma0_to_sigma0_file, geogrid.width, geogrid.length, 1, gdal.GDT_Float32, raster_format + ) + geocode_kwargs['out_geo_rtc_gamma0_to_sigma0'] = out_geo_rtc_gamma0_to_sigma0_obj + if opts.apply_bistatic_delay or opts.apply_static_tropo: rg_lut, az_lut = compute_correction_lut( slc.source, @@ -611,26 +616,27 @@ def rtc(slc, geogrid, opts): out_geo_nlooks_obj.close_dataset() del out_geo_nlooks_obj - out_geo_rtc_obj.close_dataset() - del out_geo_rtc_obj + if opts.apply_rtc: + out_geo_rtc_gamma0_to_sigma0_obj.close_dataset() + del out_geo_rtc_gamma0_to_sigma0_obj - out_geo_rtc_gamma0_to_sigma0_obj.close_dataset() - del out_geo_rtc_gamma0_to_sigma0_obj + out_geo_rtc_obj.close_dataset() + del out_geo_rtc_obj - radar_grid_file_dict = {} - save_intermediate_geocode_files( - geogrid, - opts.dem_interpolation_method_isce3, - product_id, - output_dir, - raster_extension, - dem_raster, - radar_grid_file_dict, - lookside, - wavelength, - orbit, - doppler=doppler, - ) + radar_grid_file_dict = {} + save_intermediate_geocode_files( + geogrid, + opts.dem_interpolation_method_isce3, + product_id, + output_dir, + raster_extension, + dem_raster, + radar_grid_file_dict, + lookside, + wavelength, + orbit, + doppler=doppler, + ) t_end = time.time() logger.info(f'elapsed time: {t_end - t_start}') diff --git a/src/multirtc/geocode.py b/src/multirtc/geocode.py new file mode 100644 index 0000000..f12daab --- /dev/null +++ b/src/multirtc/geocode.py @@ -0,0 +1,19 @@ +"""Create a geocoded dataset for a multiple satellite platforms""" + +from pathlib import Path + +from multirtc.multirtc import SUPPORTED, run_multirtc + + +def create_parser(parser): + parser.add_argument('platform', choices=SUPPORTED, help='Platform to create geocoded dataset for') + parser.add_argument('granule', help='Data granule to create geocoded dataset for.') + parser.add_argument('--resolution', type=float, help='Resolution of the output dataset (m)') + parser.add_argument('--work-dir', type=Path, default=None, help='Working directory for processing') + return parser + + +def run(args): + if args.work_dir is None: + args.work_dir = Path.cwd() + run_multirtc(args.platform, args.granule, args.resolution, args.work_dir, apply_rtc=False) diff --git a/src/multirtc/multirtc.py b/src/multirtc/multirtc.py index b341745..b170ceb 100644 --- a/src/multirtc/multirtc.py +++ b/src/multirtc/multirtc.py @@ -61,7 +61,7 @@ def get_slc(platform: str, granule: str, input_dir: Path) -> Slc: return slc -def run_multirtc(platform: str, granule: str, resolution: int, work_dir: Path) -> None: +def run_multirtc(platform: str, granule: str, resolution: int, work_dir: Path, apply_rtc=True) -> None: """Create an RTC or Geocoded dataset using the OPERA algorithm. Args: @@ -69,6 +69,7 @@ def run_multirtc(platform: str, granule: str, resolution: int, work_dir: Path) - granule: Granule name if data is available in ASF archive, or filename if granule is already downloaded. resolution: Resolution of the output RTC (in meters). work_dir: Working directory for processing. + apply_rtc: If True perform radiometric correction; if False, only geocode. """ input_dir, output_dir = prep_dirs(work_dir) slc = get_slc(platform, granule, input_dir) @@ -79,6 +80,7 @@ def run_multirtc(platform: str, granule: str, resolution: int, work_dir: Path) - opts = RtcOptions( dem_path=str(dem_path), output_dir=str(output_dir), + apply_rtc=apply_rtc, resolution=resolution, apply_bistatic_delay=slc.supports_bistatic_delay, apply_static_tropo=slc.supports_static_tropo, @@ -102,14 +104,14 @@ def create_parser(parser): def run(args): if args.work_dir is None: args.work_dir = Path.cwd() - run_multirtc(args.platform, args.granule, args.resolution, args.work_dir) + run_multirtc(args.platform, args.granule, args.resolution, args.work_dir, apply_rtc=True) def main(): - """Create a RTC or geocoded dataset for a multiple satellite platforms + """Create a RTC dataset for a multiple satellite platforms Example command: - multirtc UMBRA umbra_image.ntif --resolution 40 + multirtc rtc UMBRA umbra_image.ntif --resolution 40 """ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser = create_parser(parser) diff --git a/src/multirtc/rtc_options.py b/src/multirtc/rtc_options.py index 28b7f86..d850603 100644 --- a/src/multirtc/rtc_options.py +++ b/src/multirtc/rtc_options.py @@ -10,11 +10,13 @@ class RtcOptions: output_dir: str dem_path: str - apply_rtc: bool = True + apply_rtc: bool + resolution: float apply_thermal_noise: bool = True apply_abs_rad: bool = True apply_bistatic_delay: bool = True apply_static_tropo: bool = True + terrain_radiometry: str = 'gamma0' # 'gamma0' or 'sigma0' apply_valid_samples_sub_swath_masking: bool = True apply_shadow_masking: bool = True dem_interpolation_method: str = 'biquintic' @@ -28,33 +30,22 @@ class RtcOptions: clip_min: float = np.nan clip_max: float = np.nan upsample_radar_grid: bool = False - terrain_radiometry: str = 'gamma0' # 'gamma0' or 'sigma0' rtc_algorithm_type: str = 'area_projection' # 'area_projection' or 'bilinear_distribution' input_terrain_radiometry: str = 'beta0' - rtc_min_value_db: int = -30.0 + rtc_min_value_db: float = -30.0 rtc_upsampling: int = 2 rtc_area_beta_mode: str = 'auto' geo2rdr_threshold: float = 1.0e-7 geo2rdr_numiter: int = 50 rdr2geo_threshold: float = 1.0e-7 rdr2geo_numiter: int = 25 - output_epsg: int = None - resolution: int = 30 + output_epsg: int | None = None def __post_init__(self): if not self.apply_rtc: - if self.save_rtc_anf: - raise ValueError('RTC ANF flags are only available with RTC enabled') - if self.save_rtc_anf_gamma0_to_sigma0: - raise ValueError('RTC ANF gamma0 to sigma0 flags are only available with RTC enabled') - - if self.terrain_radiometry == 'sigma0' and self.save_rtc_anf_gamma0_to_sigma0: - raise ValueError('RTC ANF gamma0 to sigma0 flags are only available with output type set to gamma0') + self.terrain_radiometry: str = 'sigma0' - if self.apply_rtc: - self.layer_name_rtc_anf = f'rtc_anf_{self.terrain_radiometry}_to_{self.input_terrain_radiometry}' - else: - self.layer_name_rtc_anf = '' + self.layer_name_rtc_anf = f'rtc_anf_{self.terrain_radiometry}_to_{self.input_terrain_radiometry}' if self.dem_interpolation_method == 'biquintic': self.dem_interpolation_method_isce3 = isce3.core.DataInterpMethod.BIQUINTIC @@ -94,11 +85,6 @@ def __post_init__(self): else: raise ValueError(f'Invalid terrain radiometry: {self.terrain_radiometry}') - if self.apply_rtc: - self.layer_name_rtc_anf = f'rtc_anf_{self.terrain_radiometry}_to_{self.input_terrain_radiometry}' - else: - self.layer_name_rtc_anf = '' - if self.rtc_area_beta_mode == 'pixel_area': self.rtc_area_beta_mode_isce3 = isce3.geometry.RtcAreaBetaMode.PIXEL_AREA elif self.rtc_area_beta_mode == 'projection_angle': From 64cd7aa36c1f5601cf21af796761188e68532a06 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Mon, 11 Aug 2025 11:06:11 -0500 Subject: [PATCH 15/17] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c88db45..0bc3629 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.5.0] +### Added +* Geocode-only entrypoint. + ### Changed * Update readme with docker instructions and layer info. From b572adfded6d6620ae18f28caba25936b378ea35 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Mon, 11 Aug 2025 11:12:21 -0500 Subject: [PATCH 16/17] remove old entrypoints --- CHANGELOG.md | 1 + src/multirtc/multimetric/ale.py | 11 ----------- src/multirtc/multimetric/point_target.py | 11 ----------- src/multirtc/multimetric/rle.py | 11 ----------- src/multirtc/multirtc.py | 16 ---------------- 5 files changed, 1 insertion(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc3629..8ae14a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Removed * Output of area projection metadata. * Prototype polar grid support in favor of full support via the new docker image. +* Old per-file entrypoints. ### Fixed * Plotting issues with the cal/val submodule. diff --git a/src/multirtc/multimetric/ale.py b/src/multirtc/multimetric/ale.py index 65b5552..6b4b81b 100644 --- a/src/multirtc/multimetric/ale.py +++ b/src/multirtc/multimetric/ale.py @@ -243,14 +243,3 @@ def run(args): assert args.filepath.exists(), f'File {args.filepath} does not exist.' ale(args.filepath, args.date, args.azmangle, args.project, basedir=args.basedir) - - -def main(): - parser = ArgumentParser(description=__doc__) - parser = create_parser(parser) - args = parser.parse_args() - run(args) - - -if __name__ == '__main__': - main() diff --git a/src/multirtc/multimetric/point_target.py b/src/multirtc/multimetric/point_target.py index a2235e1..3d706fa 100644 --- a/src/multirtc/multimetric/point_target.py +++ b/src/multirtc/multimetric/point_target.py @@ -111,14 +111,3 @@ def run(args): assert args.filepath.exists() args.basedir = Path(args.basedir).expanduser() analyze_point_targets(args.platform, args.filepath, args.project, basedir=args.basedir) - - -def main(): - parser = ArgumentParser(description='Point target (resolution, PSLR, and ISLR) analysis') - parser = create_parser(parser) - args = parser.parse_args() - run(args) - - -if __name__ == '__main__': - main() diff --git a/src/multirtc/multimetric/rle.py b/src/multirtc/multimetric/rle.py index 01633fc..5d5d20d 100644 --- a/src/multirtc/multimetric/rle.py +++ b/src/multirtc/multimetric/rle.py @@ -196,14 +196,3 @@ def run(args): args.basedir = Path(args.basedir) assert args.basedir.exists(), f'Base directory {args.basedir} does not exist' rle(args.reference, args.secondary, project=args.project, basedir=args.basedir) - - -def main(): - parser = ArgumentParser(description=__doc__) - parser = create_parser(parser) - args = parser.parse_args() - run(args) - - -if __name__ == '__main__': - main() diff --git a/src/multirtc/multirtc.py b/src/multirtc/multirtc.py index b170ceb..ad3c8f2 100644 --- a/src/multirtc/multirtc.py +++ b/src/multirtc/multirtc.py @@ -105,19 +105,3 @@ def run(args): if args.work_dir is None: args.work_dir = Path.cwd() run_multirtc(args.platform, args.granule, args.resolution, args.work_dir, apply_rtc=True) - - -def main(): - """Create a RTC dataset for a multiple satellite platforms - - Example command: - multirtc rtc UMBRA umbra_image.ntif --resolution 40 - """ - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser = create_parser(parser) - args = parser.parse_args() - run(args) - - -if __name__ == '__main__': - main() From 96f2b9e972b397b612934ccf9009c00002a5bc96 Mon Sep 17 00:00:00 2001 From: Forrest Williams Date: Mon, 11 Aug 2025 11:14:43 -0500 Subject: [PATCH 17/17] fix ruff --- src/multirtc/multimetric/ale.py | 1 - src/multirtc/multimetric/point_target.py | 1 - src/multirtc/multimetric/rle.py | 1 - src/multirtc/multirtc.py | 1 - 4 files changed, 4 deletions(-) diff --git a/src/multirtc/multimetric/ale.py b/src/multirtc/multimetric/ale.py index 6b4b81b..78d9ad5 100644 --- a/src/multirtc/multimetric/ale.py +++ b/src/multirtc/multimetric/ale.py @@ -1,6 +1,5 @@ """Absolute Location Error (ALE) analysis""" -from argparse import ArgumentParser from datetime import datetime from pathlib import Path diff --git a/src/multirtc/multimetric/point_target.py b/src/multirtc/multimetric/point_target.py index 3d706fa..2f1c579 100644 --- a/src/multirtc/multimetric/point_target.py +++ b/src/multirtc/multimetric/point_target.py @@ -1,6 +1,5 @@ """Point target (resolution, PSLR, and ISLR) analysis""" -from argparse import ArgumentParser from pathlib import Path import matplotlib.pyplot as plt diff --git a/src/multirtc/multimetric/rle.py b/src/multirtc/multimetric/rle.py index 5d5d20d..998e8a6 100644 --- a/src/multirtc/multimetric/rle.py +++ b/src/multirtc/multimetric/rle.py @@ -1,6 +1,5 @@ """Relative Location Error (RLE) analysis""" -from argparse import ArgumentParser from dataclasses import dataclass from pathlib import Path from tempfile import NamedTemporaryFile diff --git a/src/multirtc/multirtc.py b/src/multirtc/multirtc.py index ad3c8f2..a8bc71c 100644 --- a/src/multirtc/multirtc.py +++ b/src/multirtc/multirtc.py @@ -1,6 +1,5 @@ """Create an RTC dataset for a multiple satellite platforms""" -import argparse from pathlib import Path from burst2safe.burst2safe import burst2safe