From 3686ba421f86f86bd153c04de959caa7f7ef5642 Mon Sep 17 00:00:00 2001 From: thudson Date: Mon, 26 Feb 2024 23:06:50 +0000 Subject: [PATCH 01/35] Carrier phase functons & pybinds --- cxx/isce3/image/Resample.cpp | 121 ++++++++++++++++++ cxx/isce3/image/Resample.h | 57 ++++++++- .../pybind_isce3/image/Resample.cpp | 76 +++++++++++ 3 files changed, 253 insertions(+), 1 deletion(-) diff --git a/cxx/isce3/image/Resample.cpp b/cxx/isce3/image/Resample.cpp index 0beacc7de..af93d88a5 100644 --- a/cxx/isce3/image/Resample.cpp +++ b/cxx/isce3/image/Resample.cpp @@ -3,10 +3,112 @@ #include #include #include +#include namespace isce3::image::v2 { +template +std::complex getPixelCarrierPhase( + const float azimuth, + const float range, + const AzRgFunc& carrier_phase +) { + // Evaluate the pixel's carrier phase + // unit: phase (radians) + const float phase = carrier_phase.eval(azimuth, range); + + // Convert the carrier phase into a unit phasor (i.e. an angle on the unit + // circle in the complex plane). + // unit: phase (complex) + const std::complex phase_complex(std::cos(phase), std::sin(phase)); + + // return carrier at the radar grid coordinates + return phase_complex; +} + + +template +void getModulationPhase( + ArrayRef2D> phase_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const size_t input_azimuth_first_line, + const size_t input_range_first_pixel +) +{ + // unit: azimuth row indices (int) + const size_t phase_block_length = phase_data_block.rows(); + // unit: range column indices (int) + const size_t phase_block_width = phase_data_block.cols(); + + // remove carrier from radar data +#pragma omp parallel for collapse(2) + for ( size_t az_index = 0; az_index < phase_block_length; ++az_index) { + for (size_t rg_index = 0; rg_index < phase_block_width; ++rg_index) { + // Offset for block starting line + // unit: azimuth row indices (int) + const auto az_carrier_index = az_index + input_azimuth_first_line; + // unit: time (seconds) + const double azimuth = + radar_grid.sensingStart() + az_carrier_index / radar_grid.prf(); + + // Offset for block starting pixel + // unit: range column indices (int) + const auto rg_carrier_index = rg_index + input_range_first_pixel; + // unit: distance (meters) + const double range = radar_grid.startingRange() + + rg_carrier_index * radar_grid.rangePixelSpacing(); + + // Get the carrier phase for this pixel + const auto phase = getPixelCarrierPhase(azimuth, range, carrier_phase); + + // Write the phase into the output data block + phase_data_block(az_index, rg_index) = phase; + } + } // end multithreaded block +} + + +template +void getModulationPhaseAtCoords( + ArrayRef2D> phase_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const ConstArrayRef2D azimuth_indices, + const ConstArrayRef2D range_indices +) +{ + const size_t outWidth = phase_data_block.cols(); + const size_t outLength = phase_data_block.rows(); + +#pragma omp parallel for collapse(2) + for (size_t az_index = 0; az_index < outLength; ++az_index){ + for (size_t rg_index = 0; rg_index < outWidth; ++rg_index){ + + // Get the indices on the output grid corresponding to these indices + // on the input grid overall. + const double az_carrier_index = azimuth_indices(az_index, rg_index); + const double rg_carrier_index = range_indices(az_index, rg_index); + + // Azimuth time at the current output pixel + const double azimuth = radar_grid.sensingStart() + az_carrier_index / + radar_grid.prf(); + + // Slant Range at the current output pixel + const double range = radar_grid.startingRange() + rg_carrier_index * + radar_grid.rangePixelSpacing(); + + // Get the carrier phase for this pixel + const auto phase = getPixelCarrierPhase(azimuth, range, carrier_phase); + + // Write the phase into the output data block + phase_data_block(az_index, rg_index) = phase; + } + } // end multithreaded block +} + + void resampleToCoords( ArrayRef2D> resampled_data_block, const ConstArrayRef2D> input_data_block, @@ -174,4 +276,23 @@ void resampleToCoords( } // end omp parallel } // end resampleToCoords + +#define EXPLICIT_INSTANTIATION(AzRgFunc) \ +template void getModulationPhase( \ + ArrayRef2D> phase_data_block, \ + const AzRgFunc& carrier_phase, \ + const isce3::product::RadarGridParameters& radar_grid, \ + const size_t input_azimuth_first_line, \ + const size_t input_range_first_pixel \ +); \ +template void getModulationPhaseAtCoords( \ + ArrayRef2D> phase_data_block, \ + const AzRgFunc& carrier_phase, \ + const isce3::product::RadarGridParameters& radar_grid, \ + const ConstArrayRef2D azimuth_indices, \ + const ConstArrayRef2D range_indices \ +) +EXPLICIT_INSTANTIATION(isce3::core::LUT2d); +EXPLICIT_INSTANTIATION(isce3::core::Poly2d); + } // end namespace isce3::image::v2 diff --git a/cxx/isce3/image/Resample.h b/cxx/isce3/image/Resample.h index f05ff5fee..57b60bd4f 100644 --- a/cxx/isce3/image/Resample.h +++ b/cxx/isce3/image/Resample.h @@ -3,6 +3,7 @@ #include #include +#include #include namespace isce3::image::v2 { @@ -16,6 +17,60 @@ using ArrayRef2D = Eigen::Ref>; template using ConstArrayRef2D = Eigen::Ref>; +/** + * Remove range and azimuth phase carrier from a block of input radar SLC data + * + * @param[out] phase_data_block + * Block of data to be written to. All data in block will be overwritten. + * unit: phase (complex) array2D + * @tparam[in] carrier_phase + * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. + * This phase will be removed from the resampled image. + * @param[in] radar_grid + * parameters for the given radar grid + * @param[in] input_azimuth_first_line + * line index of the first sample of the block of input data with respect to the origin + * of the full SLC scene + * unit: azimuth row indices (int) + * @param[in] input_range_first_pixel + * pixel index of the first sample of the block of input data with respect to the origin + * of the full SLC scene + * unit: range column indices (int) + */ +template +void getModulationPhase( + ArrayRef2D> phase_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const size_t input_azimuth_first_line, + const size_t input_range_first_pixel +); + + +/** + * Add back range and azimuth phase carrier and simultaneously flatten the block of + * resampled SLC + * + * @param[out] phase_data_block Block of data to be written to. All data in block will + * be overwritten. + * @tparam[in] carrier_phase carrier phase of the SLC data, in radian, as a + * function of azimuth and range + * @param[in] radar_grid parameters for the given radar grid + * @param[in] azimuth_indices azimuth index of each output coordinate pixel in the + * resampling coordinate system + * @param[in] range_indices range index of each output coordinate pixel in the + * resampling coordinate system + */ +template +void getModulationPhaseAtCoords( + ArrayRef2D> phase_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const ConstArrayRef2D azimuth_indices, + const ConstArrayRef2D range_indices +); + + /** Interpolate input SLC block into the index values of the output block. * * @param[out] resampled_data_block @@ -27,7 +82,7 @@ using ConstArrayRef2D = Eigen::Ref>; * @param[in] azimuth_input_indices * azimuth (radar-coordinates y) index of the pixels in input grid * @param[in] radar_grid - * RadarGridParameters of radar data + * parameters for the given radar grid * @param[in] native_doppler_lut * native doppler of SLC image * @param[in] fill_value diff --git a/python/extensions/pybind_isce3/image/Resample.cpp b/python/extensions/pybind_isce3/image/Resample.cpp index edd2f9bfe..020b40d31 100644 --- a/python/extensions/pybind_isce3/image/Resample.cpp +++ b/python/extensions/pybind_isce3/image/Resample.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -11,6 +12,7 @@ namespace py = pybind11; +template void addbindings_resamp(py::module & m) { // Write _resample_to_coords as a private function. This will be used by a wrapper @@ -49,4 +51,78 @@ void addbindings_resamp(py::module & m) The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. )" ); + + + m.def( + "get_modulation_phase", + py::overload_cast< + isce3::image::v2::ArrayRef2D>, + const AzRgFunc&, + const isce3::product::RadarGridParameters&, + const size_t, + const size_t + >(&isce3::image::v2::getModulationPhase), + py::arg("phase_data_block"), + py::arg("carrier_phase"), + py::arg("radar_grid"), + py::arg("input_azimuth_first_line"), + py::arg("input_range_first_pixel"), + R"( + Acquire the phase of the given carrier at each given index of a radar scene. + + Parameters + ---------- + phase_data_block: numpy.ndarray (complex64) + The output phase array to modify. Anything in this array will be + overwritten. + carrier_phase: isce3.core.LUT2d + An LUT2d describing the carrier frequency of the radar data over azimuth + and range. + radar_grid: isce3.product.RadarGridParameters + Radar grid parameters of the radar swath. + azimuth_indices: numpy.ndarray (float64) + azimuth index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + range_indices: numpy.ndarray (float64) + range index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + )" + ); + + + m.def( + "get_modulation_phase_at_coords", + py::overload_cast< + isce3::image::v2::ArrayRef2D>, + const AzRgFunc&, + const isce3::product::RadarGridParameters&, + const isce3::image::v2::ArrayRef2D, + const isce3::image::v2::ArrayRef2D + >(&isce3::image::v2::getModulationPhaseAtCoords), + py::arg("phase_data_block"), + py::arg("carrier_phase"), + py::arg("radar_grid"), + py::arg("azimuth_indices"), + py::arg("range_indices"), + R"( + Acquire the phase of the given carrier at each given index of a radar scene. + + Parameters + ---------- + phase_data_block: numpy.ndarray (complex64) + The output phase array to modify. Anything in this array will be + overwritten. + carrier_phase: isce3.core.LUT2d + An LUT2d describing the carrier frequency of the radar data over azimuth + and range. + radar_grid: isce3.product.RadarGridParameters + Radar grid parameters of the radar swath. + azimuth_indices: numpy.ndarray (float64) + azimuth index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + range_indices: numpy.ndarray (float64) + range index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + )" + ); } From 23e741a1e6272b49373ee357598dfceea0436650 Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 27 Feb 2024 19:54:42 +0000 Subject: [PATCH 02/35] Addbindings templates --- python/extensions/pybind_isce3/image/Resample.cpp | 3 +++ python/extensions/pybind_isce3/image/image.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/python/extensions/pybind_isce3/image/Resample.cpp b/python/extensions/pybind_isce3/image/Resample.cpp index 020b40d31..355e98ed8 100644 --- a/python/extensions/pybind_isce3/image/Resample.cpp +++ b/python/extensions/pybind_isce3/image/Resample.cpp @@ -126,3 +126,6 @@ void addbindings_resamp(py::module & m) )" ); } + +template void addbindings_resamp>(py::module & m); +template void addbindings_resamp(py::module & m); diff --git a/python/extensions/pybind_isce3/image/image.cpp b/python/extensions/pybind_isce3/image/image.cpp index c71d72910..7b7d7834a 100644 --- a/python/extensions/pybind_isce3/image/image.cpp +++ b/python/extensions/pybind_isce3/image/image.cpp @@ -11,7 +11,7 @@ void addsubmodule_image(py::module & m) py::module m_image_v2 = m_image.def_submodule("v2"); // Add the resample v2 functionality to the v2 module. - addbindings_resamp(m_image_v2); + addbindings_resamp(m_image_v2); // forward declare bound classes for v1 py::class_ pyResampSlc(m_image, "ResampSlc"); From e3d32ba6cb9dfaa12e3c4c74f835f3290f95965e Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 27 Feb 2024 19:58:17 +0000 Subject: [PATCH 03/35] call addbindings temlates in image.cpp pybind --- python/extensions/pybind_isce3/image/image.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/extensions/pybind_isce3/image/image.cpp b/python/extensions/pybind_isce3/image/image.cpp index 7b7d7834a..ecdf0a7ed 100644 --- a/python/extensions/pybind_isce3/image/image.cpp +++ b/python/extensions/pybind_isce3/image/image.cpp @@ -11,7 +11,8 @@ void addsubmodule_image(py::module & m) py::module m_image_v2 = m_image.def_submodule("v2"); // Add the resample v2 functionality to the v2 module. - addbindings_resamp(m_image_v2); + addbindings_resamp>(m_image_v2); + addbindings_resamp(m_image_v2); // forward declare bound classes for v1 py::class_ pyResampSlc(m_image, "ResampSlc"); From b7d9f5414c5a22024f56a7715f3337324cb5c1eb Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 27 Feb 2024 20:00:03 +0000 Subject: [PATCH 04/35] template in resample.h pybind --- python/extensions/pybind_isce3/image/Resample.h | 1 + 1 file changed, 1 insertion(+) diff --git a/python/extensions/pybind_isce3/image/Resample.h b/python/extensions/pybind_isce3/image/Resample.h index 86585ab31..2d71907aa 100644 --- a/python/extensions/pybind_isce3/image/Resample.h +++ b/python/extensions/pybind_isce3/image/Resample.h @@ -1,4 +1,5 @@ #pragma once #include +template void addbindings_resamp(pybind11::module&); From f6f4ae6db16ac10471cce6838056265686a3396b Mon Sep 17 00:00:00 2001 From: thudson Date: Thu, 29 Feb 2024 00:48:58 +0000 Subject: [PATCH 05/35] Added modulate/demodulate functions for benchmarking --- cxx/isce3/image/Resample.cpp | 135 ++++++++++++++++-- cxx/isce3/image/Resample.h | 67 ++++++++- .../pybind_isce3/image/Resample.cpp | 90 +++++++++++- 3 files changed, 277 insertions(+), 15 deletions(-) diff --git a/cxx/isce3/image/Resample.cpp b/cxx/isce3/image/Resample.cpp index af93d88a5..9a356012c 100644 --- a/cxx/isce3/image/Resample.cpp +++ b/cxx/isce3/image/Resample.cpp @@ -12,7 +12,8 @@ template std::complex getPixelCarrierPhase( const float azimuth, const float range, - const AzRgFunc& carrier_phase + const AzRgFunc& carrier_phase, + bool conjugate = false ) { // Evaluate the pixel's carrier phase // unit: phase (radians) @@ -21,10 +22,11 @@ std::complex getPixelCarrierPhase( // Convert the carrier phase into a unit phasor (i.e. an angle on the unit // circle in the complex plane). // unit: phase (complex) - const std::complex phase_complex(std::cos(phase), std::sin(phase)); - - // return carrier at the radar grid coordinates - return phase_complex; + if(conjugate){ + return std::complex(std::cos(phase), -std::sin(phase)); + } else { + return std::complex(std::cos(phase), std::sin(phase)); + } } @@ -34,7 +36,8 @@ void getModulationPhase( const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const size_t input_azimuth_first_line, - const size_t input_range_first_pixel + const size_t input_range_first_pixel, + const bool conjugate ) { // unit: azimuth row indices (int) @@ -61,7 +64,9 @@ void getModulationPhase( rg_carrier_index * radar_grid.rangePixelSpacing(); // Get the carrier phase for this pixel - const auto phase = getPixelCarrierPhase(azimuth, range, carrier_phase); + const auto phase = getPixelCarrierPhase( + azimuth, range, carrier_phase, conjugate + ); // Write the phase into the output data block phase_data_block(az_index, rg_index) = phase; @@ -76,7 +81,8 @@ void getModulationPhaseAtCoords( const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, - const ConstArrayRef2D range_indices + const ConstArrayRef2D range_indices, + const bool conjugate ) { const size_t outWidth = phase_data_block.cols(); @@ -100,7 +106,9 @@ void getModulationPhaseAtCoords( radar_grid.rangePixelSpacing(); // Get the carrier phase for this pixel - const auto phase = getPixelCarrierPhase(azimuth, range, carrier_phase); + const auto phase = getPixelCarrierPhase( + azimuth, range, carrier_phase, conjugate + ); // Write the phase into the output data block phase_data_block(az_index, rg_index) = phase; @@ -109,6 +117,93 @@ void getModulationPhaseAtCoords( } +template +void modulate( + ArrayRef2D> slc_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const size_t input_azimuth_first_line, + const size_t input_range_first_pixel, + const bool conjugate +) +{ + // unit: azimuth row indices (int) + const size_t phase_block_length = slc_data_block.rows(); + // unit: range column indices (int) + const size_t phase_block_width = slc_data_block.cols(); + + // remove carrier from radar data +#pragma omp parallel for collapse(2) + for ( size_t az_index = 0; az_index < phase_block_length; ++az_index) { + for (size_t rg_index = 0; rg_index < phase_block_width; ++rg_index) { + // Offset for block starting line + // unit: azimuth row indices (int) + const auto az_carrier_index = az_index + input_azimuth_first_line; + // unit: time (seconds) + const double azimuth = + radar_grid.sensingStart() + az_carrier_index / radar_grid.prf(); + + // Offset for block starting pixel + // unit: range column indices (int) + const auto rg_carrier_index = rg_index + input_range_first_pixel; + // unit: distance (meters) + const double range = radar_grid.startingRange() + + rg_carrier_index * radar_grid.rangePixelSpacing(); + + // Get the carrier phase for this pixel + const auto phase = getPixelCarrierPhase( + azimuth, range, carrier_phase, conjugate + ); + + // Modulate the phase into the output data block + slc_data_block(az_index, rg_index) *= phase; + } + } // end multithreaded block +} + + +template +void modulateAtCoords( + ArrayRef2D> slc_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const ConstArrayRef2D azimuth_indices, + const ConstArrayRef2D range_indices, + const bool conjugate +) +{ + const size_t outWidth = slc_data_block.cols(); + const size_t outLength = slc_data_block.rows(); + +#pragma omp parallel for collapse(2) + for (size_t az_index = 0; az_index < outLength; ++az_index){ + for (size_t rg_index = 0; rg_index < outWidth; ++rg_index){ + + // Get the indices on the output grid corresponding to these indices + // on the input grid overall. + const double az_carrier_index = azimuth_indices(az_index, rg_index); + const double rg_carrier_index = range_indices(az_index, rg_index); + + // Azimuth time at the current output pixel + const double azimuth = radar_grid.sensingStart() + az_carrier_index / + radar_grid.prf(); + + // Slant Range at the current output pixel + const double range = radar_grid.startingRange() + rg_carrier_index * + radar_grid.rangePixelSpacing(); + + // Get the carrier phase for this pixel + const auto phase = getPixelCarrierPhase( + azimuth, range, carrier_phase, conjugate + ); + + // Modulate the phase into the output data block + slc_data_block(az_index, rg_index) *= phase; + } + } // end multithreaded block +} + + void resampleToCoords( ArrayRef2D> resampled_data_block, const ConstArrayRef2D> input_data_block, @@ -283,14 +378,32 @@ template void getModulationPhase( \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ const size_t input_azimuth_first_line, \ - const size_t input_range_first_pixel \ + const size_t input_range_first_pixel, \ + const bool conjugate \ ); \ template void getModulationPhaseAtCoords( \ ArrayRef2D> phase_data_block, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ const ConstArrayRef2D azimuth_indices, \ - const ConstArrayRef2D range_indices \ + const ConstArrayRef2D range_indices, \ + const bool conjugate \ +); \ +template void modulate( \ + ArrayRef2D> slc_data_block, \ + const AzRgFunc& carrier_phase, \ + const isce3::product::RadarGridParameters& radar_grid, \ + const size_t input_azimuth_first_line, \ + const size_t input_range_first_pixel, \ + const bool conjugate \ +); \ +template void modulateAtCoords( \ + ArrayRef2D> slc_data_block, \ + const AzRgFunc& carrier_phase, \ + const isce3::product::RadarGridParameters& radar_grid, \ + const ConstArrayRef2D azimuth_indices, \ + const ConstArrayRef2D range_indices, \ + const bool conjugate \ ) EXPLICIT_INSTANTIATION(isce3::core::LUT2d); EXPLICIT_INSTANTIATION(isce3::core::Poly2d); diff --git a/cxx/isce3/image/Resample.h b/cxx/isce3/image/Resample.h index 57b60bd4f..363a73f4b 100644 --- a/cxx/isce3/image/Resample.h +++ b/cxx/isce3/image/Resample.h @@ -36,6 +36,8 @@ using ConstArrayRef2D = Eigen::Ref>; * pixel index of the first sample of the block of input data with respect to the origin * of the full SLC scene * unit: range column indices (int) + * @param[in] conjugate + * if true, modulate the conjugate of the phase. */ template void getModulationPhase( @@ -43,7 +45,8 @@ void getModulationPhase( const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const size_t input_azimuth_first_line, - const size_t input_range_first_pixel + const size_t input_range_first_pixel, + const bool conjugate ); @@ -60,6 +63,7 @@ void getModulationPhase( * resampling coordinate system * @param[in] range_indices range index of each output coordinate pixel in the * resampling coordinate system + * @param[in] conjugate if true, modulate the conjugate of the phase. */ template void getModulationPhaseAtCoords( @@ -67,7 +71,66 @@ void getModulationPhaseAtCoords( const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, - const ConstArrayRef2D range_indices + const ConstArrayRef2D range_indices, + const bool conjugate +); + + +/** + * Remove range and azimuth phase carrier from a block of input radar SLC data + * + * @param[out] slc_data_block + * Block of data to be modulated. + * unit: phase (complex) array2D + * @tparam[in] carrier_phase + * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. + * This phase will be removed from the resampled image. + * @param[in] radar_grid + * parameters for the given radar grid + * @param[in] input_azimuth_first_line + * line index of the first sample of the block of input data with respect to the origin + * of the full SLC scene + * unit: azimuth row indices (int) + * @param[in] input_range_first_pixel + * pixel index of the first sample of the block of input data with respect to the origin + * of the full SLC scene + * unit: range column indices (int) + * @param[in] conjugate + * if true, modulate the conjugate of the phase. + */ +template +void modulate( + ArrayRef2D> slc_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const size_t input_azimuth_first_line, + const size_t input_range_first_pixel, + const bool conjugate +); + + +/** + * Add back range and azimuth phase carrier and simultaneously flatten the block of + * resampled SLC + * + * @param[out] slc_data_block Block of data to be modulated. + * @tparam[in] carrier_phase carrier phase of the SLC data, in radian, as a + * function of azimuth and range + * @param[in] radar_grid parameters for the given radar grid + * @param[in] azimuth_indices azimuth index of each output coordinate pixel in the + * resampling coordinate system + * @param[in] range_indices range index of each output coordinate pixel in the + * resampling coordinate system + * @param[in] conjugate if true, modulate the conjugate of the phase. + */ +template +void modulateAtCoords( + ArrayRef2D> slc_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const ConstArrayRef2D azimuth_indices, + const ConstArrayRef2D range_indices, + const bool conjugate ); diff --git a/python/extensions/pybind_isce3/image/Resample.cpp b/python/extensions/pybind_isce3/image/Resample.cpp index 355e98ed8..1548cc24f 100644 --- a/python/extensions/pybind_isce3/image/Resample.cpp +++ b/python/extensions/pybind_isce3/image/Resample.cpp @@ -60,13 +60,15 @@ void addbindings_resamp(py::module & m) const AzRgFunc&, const isce3::product::RadarGridParameters&, const size_t, - const size_t + const size_t, + const bool >(&isce3::image::v2::getModulationPhase), py::arg("phase_data_block"), py::arg("carrier_phase"), py::arg("radar_grid"), py::arg("input_azimuth_first_line"), py::arg("input_range_first_pixel"), + py::arg("conjugate"), R"( Acquire the phase of the given carrier at each given index of a radar scene. @@ -97,13 +99,95 @@ void addbindings_resamp(py::module & m) const AzRgFunc&, const isce3::product::RadarGridParameters&, const isce3::image::v2::ArrayRef2D, - const isce3::image::v2::ArrayRef2D + const isce3::image::v2::ArrayRef2D, + const bool >(&isce3::image::v2::getModulationPhaseAtCoords), py::arg("phase_data_block"), py::arg("carrier_phase"), py::arg("radar_grid"), py::arg("azimuth_indices"), py::arg("range_indices"), + py::arg("conjugate"), + R"( + Acquire the phase of the given carrier at each given index of a radar scene. + + Parameters + ---------- + phase_data_block: numpy.ndarray (complex64) + The output phase array to modify. Anything in this array will be + overwritten. + carrier_phase: isce3.core.LUT2d + An LUT2d describing the carrier frequency of the radar data over azimuth + and range. + radar_grid: isce3.product.RadarGridParameters + Radar grid parameters of the radar swath. + azimuth_indices: numpy.ndarray (float64) + azimuth index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + range_indices: numpy.ndarray (float64) + range index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + )" + ); + + + m.def( + "modulate", + py::overload_cast< + isce3::image::v2::ArrayRef2D>, + const AzRgFunc&, + const isce3::product::RadarGridParameters&, + const size_t, + const size_t, + const bool + >(&isce3::image::v2::modulate), + py::arg("phase_data_block"), + py::arg("carrier_phase"), + py::arg("radar_grid"), + py::arg("input_azimuth_first_line"), + py::arg("input_range_first_pixel"), + py::arg("conjugate"), + R"( + Acquire the phase of the given carrier at each given index of a radar scene. + + Parameters + ---------- + phase_data_block: numpy.ndarray (complex64) + The output phase array to modify. Anything in this array will be + overwritten. + carrier_phase: isce3.core.LUT2d + An LUT2d describing the carrier frequency of the radar data over azimuth + and range. + radar_grid: isce3.product.RadarGridParameters + Radar grid parameters of the radar swath. + azimuth_indices: numpy.ndarray (float64) + azimuth index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + range_indices: numpy.ndarray (float64) + range index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + conjugate: bool, optional + If True, modulate the conjugate of the phase. + )" + ); + + + m.def( + "modulate_at_coords", + py::overload_cast< + isce3::image::v2::ArrayRef2D>, + const AzRgFunc&, + const isce3::product::RadarGridParameters&, + const isce3::image::v2::ArrayRef2D, + const isce3::image::v2::ArrayRef2D, + const bool + >(&isce3::image::v2::modulateAtCoords), + py::arg("phase_data_block"), + py::arg("carrier_phase"), + py::arg("radar_grid"), + py::arg("azimuth_indices"), + py::arg("range_indices"), + py::arg("conjugate"), R"( Acquire the phase of the given carrier at each given index of a radar scene. @@ -123,6 +207,8 @@ void addbindings_resamp(py::module & m) range_indices: numpy.ndarray (float64) range index of each output coordinate pixel in the given radar coordinate system. Must be the same shape as phase_data_block. + conjugate: bool, optional + If True, modulate the conjugate of the phase. )" ); } From bb5b02d51af013c4e5f9818d92d9468b32e50019 Mon Sep 17 00:00:00 2001 From: thudson Date: Thu, 29 Feb 2024 01:07:08 +0000 Subject: [PATCH 06/35] Parameter name fix --- python/extensions/pybind_isce3/image/Resample.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/python/extensions/pybind_isce3/image/Resample.cpp b/python/extensions/pybind_isce3/image/Resample.cpp index 1548cc24f..de650daac 100644 --- a/python/extensions/pybind_isce3/image/Resample.cpp +++ b/python/extensions/pybind_isce3/image/Resample.cpp @@ -141,7 +141,7 @@ void addbindings_resamp(py::module & m) const size_t, const bool >(&isce3::image::v2::modulate), - py::arg("phase_data_block"), + py::arg("slc_data_block"), py::arg("carrier_phase"), py::arg("radar_grid"), py::arg("input_azimuth_first_line"), @@ -152,9 +152,8 @@ void addbindings_resamp(py::module & m) Parameters ---------- - phase_data_block: numpy.ndarray (complex64) - The output phase array to modify. Anything in this array will be - overwritten. + slc_data_block: numpy.ndarray (complex64) + The output phase array to modulate. carrier_phase: isce3.core.LUT2d An LUT2d describing the carrier frequency of the radar data over azimuth and range. @@ -182,7 +181,7 @@ void addbindings_resamp(py::module & m) const isce3::image::v2::ArrayRef2D, const bool >(&isce3::image::v2::modulateAtCoords), - py::arg("phase_data_block"), + py::arg("slc_data_block"), py::arg("carrier_phase"), py::arg("radar_grid"), py::arg("azimuth_indices"), @@ -193,9 +192,8 @@ void addbindings_resamp(py::module & m) Parameters ---------- - phase_data_block: numpy.ndarray (complex64) - The output phase array to modify. Anything in this array will be - overwritten. + slc_data_block: numpy.ndarray (complex64) + The output phase array to modulate. carrier_phase: isce3.core.LUT2d An LUT2d describing the carrier frequency of the radar data over azimuth and range. From 45a5bf3bbc0d7c520e2520317441d46fe2bd1dfc Mon Sep 17 00:00:00 2001 From: thudson Date: Thu, 29 Feb 2024 01:07:24 +0000 Subject: [PATCH 07/35] Incorporated into resample_slc_blocks --- .../packages/isce3/image/v2/resample_slc.py | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index dd3159ce1..83b2a134b 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -1,13 +1,13 @@ from __future__ import annotations -from collections.abc import Sequence +from collections.abc import Iterable, Sequence from time import perf_counter import journal import numpy as np from isce3.core import SINC_HALF, LUT2d from isce3.core.resample_block_generators import get_blocks, get_blocks_by_offsets -from isce3.ext.isce3.image.v2 import _resample_to_coords +from isce3.ext.isce3.image.v2 import _resample_to_coords, modulate, modulate_at_coords from isce3.io.dataset import DatasetReader, DatasetWriter from isce3.product import RadarGridParameters @@ -24,6 +24,9 @@ def resample_slc_blocks( quiet: bool = False, fill_value: np.complex64 = np.nan + 1.0j * np.nan, with_gpu: bool = False, + *, + modulation_carriers: Iterable[LUT2d] | None = None, + demodulation_carriers: Iterable[LUT2d] | None = None, ) -> None: """ Resamples one or more SLCs onto a geometry described by given offsets datasets. @@ -212,6 +215,27 @@ def resample_slc_blocks( # Run the resampling algorithm on the given blocks. for i in range(len(input_blocks)): input_block = input_blocks[i] + + if demodulation_carriers is not None: + if not quiet: + info_channel.log( + f"demodulating input SLC for block {out_block_slice}..." + ) + + in_az_slice, in_rg_slice = in_slices + az_first_line = in_az_slice.start + rg_first_pixel = in_rg_slice.start + + for carrier in demodulation_carriers: + modulate( + slc_data_block=input_block, + carrier_phase=carrier, + radar_grid=in_grid, + input_azimuth_first_line=az_first_line, + input_range_first_pixel=rg_first_pixel, + conjugate=True, + ) + if not quiet: info_channel.log( f"interpolating to output SLC for block {out_block_slice}..." @@ -228,6 +252,25 @@ def resample_slc_blocks( fill_value, ) + + if modulation_carriers is not None: + if not quiet: + info_channel.log( + f"remodulating output SLC for block {out_block_slice}..." + ) + + for carrier in modulation_carriers: + modulate_at_coords( + slc_data_block=output_block, + carrier_phase=carrier, + radar_grid=in_grid, + azimuth_indices=azimuth_index_grid, + range_indices=range_index_grid, + conjugate=False, + ) + + output_blocks[i] = output_block + block_processing_timer += perf_counter() # The resampling blocks have now been filled. For each output dataset, write From 1d36c6a8fdc14ea1609fd7a616ca8b7b0ced431a Mon Sep 17 00:00:00 2001 From: thudson Date: Thu, 29 Feb 2024 18:40:13 +0000 Subject: [PATCH 08/35] clearer input parameters for modulate() on resample_to_coords --- python/packages/isce3/image/v2/resample_slc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index 83b2a134b..5e68ad13e 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -223,16 +223,16 @@ def resample_slc_blocks( ) in_az_slice, in_rg_slice = in_slices - az_first_line = in_az_slice.start - rg_first_pixel = in_rg_slice.start + in_az_first_line = in_az_slice.start + in_rg_first_pixel = in_rg_slice.start for carrier in demodulation_carriers: modulate( slc_data_block=input_block, carrier_phase=carrier, radar_grid=in_grid, - input_azimuth_first_line=az_first_line, - input_range_first_pixel=rg_first_pixel, + input_azimuth_first_line=in_az_first_line, + input_range_first_pixel=in_rg_first_pixel, conjugate=True, ) From 524a843ae45103bafb5c83e5741593fe2044068a Mon Sep 17 00:00:00 2001 From: thudson Date: Fri, 1 Mar 2024 21:25:22 +0000 Subject: [PATCH 09/35] Docstring update --- python/packages/isce3/image/v2/resample_slc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index 5e68ad13e..7099fe67a 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -66,6 +66,12 @@ def resample_slc_blocks( with_gpu : bool, optional If True, run the GPU resample workflow. If False, run the CPU resample workflow. Defaults to False. + modulation_carriers: Iterable[LUT2d] or None, optional + Carrier phase LUT's to modulate into the output product. If none, + no output modulation will occur. Defaults to None. + demodulation_carriers: Iterable[LUT2d] or None, optional + Carrier phase LUT's to demodulate from the input prior to resampling. If none, + no output demodulation will occur. Defaults to None. """ info_channel = journal.info("resample_slc.resample_slc_blocks") warning_channel = journal.warning("resample_slc.resample_slc_blocks") From 7730f934f834e0aa39b0793494c5b998ea32b4bb Mon Sep 17 00:00:00 2001 From: thudson Date: Mon, 11 Mar 2024 17:12:43 +0000 Subject: [PATCH 10/35] Changed parameter and function names + Added wrapper functions in python --- cxx/isce3/image/Resample.cpp | 64 ++++---- cxx/isce3/image/Resample.h | 12 +- .../pybind_isce3/image/Resample.cpp | 16 +- python/packages/isce3/image/v2/modulate.py | 145 ++++++++++++++++++ .../packages/isce3/image/v2/resample_slc.py | 37 +++-- 5 files changed, 213 insertions(+), 61 deletions(-) create mode 100644 python/packages/isce3/image/v2/modulate.py diff --git a/cxx/isce3/image/Resample.cpp b/cxx/isce3/image/Resample.cpp index 9a356012c..b217070e9 100644 --- a/cxx/isce3/image/Resample.cpp +++ b/cxx/isce3/image/Resample.cpp @@ -9,30 +9,30 @@ namespace isce3::image::v2 { template -std::complex getPixelCarrierPhase( - const float azimuth, - const float range, +std::complex _getPixelCarrierPhase( + const double azimuth, + const double range, const AzRgFunc& carrier_phase, bool conjugate = false ) { // Evaluate the pixel's carrier phase // unit: phase (radians) - const float phase = carrier_phase.eval(azimuth, range); + const double phase = carrier_phase.eval(azimuth, range); // Convert the carrier phase into a unit phasor (i.e. an angle on the unit // circle in the complex plane). - // unit: phase (complex) + // unit: unitless (complex) if(conjugate){ - return std::complex(std::cos(phase), -std::sin(phase)); + return std::complex(std::cos(phase), -std::sin(phase)); } else { - return std::complex(std::cos(phase), std::sin(phase)); + return std::complex(std::cos(phase), std::sin(phase)); } } template void getModulationPhase( - ArrayRef2D> phase_data_block, + ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const size_t input_azimuth_first_line, @@ -41,13 +41,13 @@ void getModulationPhase( ) { // unit: azimuth row indices (int) - const size_t phase_block_length = phase_data_block.rows(); + const size_t phase_block_length = out.rows(); // unit: range column indices (int) - const size_t phase_block_width = phase_data_block.cols(); + const size_t phase_block_width = out.cols(); // remove carrier from radar data #pragma omp parallel for collapse(2) - for ( size_t az_index = 0; az_index < phase_block_length; ++az_index) { + for (size_t az_index = 0; az_index < phase_block_length; ++az_index) { for (size_t rg_index = 0; rg_index < phase_block_width; ++rg_index) { // Offset for block starting line // unit: azimuth row indices (int) @@ -63,13 +63,13 @@ void getModulationPhase( const double range = radar_grid.startingRange() + rg_carrier_index * radar_grid.rangePixelSpacing(); - // Get the carrier phase for this pixel - const auto phase = getPixelCarrierPhase( + // Get the carrier unit phasor for this pixel + const auto phasor = _getPixelCarrierPhase( azimuth, range, carrier_phase, conjugate ); - // Write the phase into the output data block - phase_data_block(az_index, rg_index) = phase; + // Write the phasor into the output data block + out(az_index, rg_index) = phasor; } } // end multithreaded block } @@ -77,7 +77,7 @@ void getModulationPhase( template void getModulationPhaseAtCoords( - ArrayRef2D> phase_data_block, + ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, @@ -85,8 +85,8 @@ void getModulationPhaseAtCoords( const bool conjugate ) { - const size_t outWidth = phase_data_block.cols(); - const size_t outLength = phase_data_block.rows(); + const size_t outWidth = out.cols(); + const size_t outLength = out.rows(); #pragma omp parallel for collapse(2) for (size_t az_index = 0; az_index < outLength; ++az_index){ @@ -105,13 +105,13 @@ void getModulationPhaseAtCoords( const double range = radar_grid.startingRange() + rg_carrier_index * radar_grid.rangePixelSpacing(); - // Get the carrier phase for this pixel - const auto phase = getPixelCarrierPhase( + // Get the carrier phasor for this pixel + const auto phasor = _getPixelCarrierPhase( azimuth, range, carrier_phase, conjugate ); - // Write the phase into the output data block - phase_data_block(az_index, rg_index) = phase; + // Write the phasor into the output data block + out(az_index, rg_index) = phasor; } } // end multithreaded block } @@ -150,13 +150,13 @@ void modulate( const double range = radar_grid.startingRange() + rg_carrier_index * radar_grid.rangePixelSpacing(); - // Get the carrier phase for this pixel - const auto phase = getPixelCarrierPhase( + // Get the carrier phasor for this pixel + const auto phasor = _getPixelCarrierPhase( azimuth, range, carrier_phase, conjugate ); - // Modulate the phase into the output data block - slc_data_block(az_index, rg_index) *= phase; + // Modulate the phasor into the output data block + slc_data_block(az_index, rg_index) *= phasor; } } // end multithreaded block } @@ -192,13 +192,13 @@ void modulateAtCoords( const double range = radar_grid.startingRange() + rg_carrier_index * radar_grid.rangePixelSpacing(); - // Get the carrier phase for this pixel - const auto phase = getPixelCarrierPhase( + // Get the carrier phasor for this pixel + const auto phasor = _getPixelCarrierPhase( azimuth, range, carrier_phase, conjugate ); - // Modulate the phase into the output data block - slc_data_block(az_index, rg_index) *= phase; + // Modulate the phasor into the output data block + slc_data_block(az_index, rg_index) *= phasor; } } // end multithreaded block } @@ -374,7 +374,7 @@ void resampleToCoords( #define EXPLICIT_INSTANTIATION(AzRgFunc) \ template void getModulationPhase( \ - ArrayRef2D> phase_data_block, \ + ArrayRef2D> out, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ const size_t input_azimuth_first_line, \ @@ -382,7 +382,7 @@ template void getModulationPhase( \ const bool conjugate \ ); \ template void getModulationPhaseAtCoords( \ - ArrayRef2D> phase_data_block, \ + ArrayRef2D> out, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ const ConstArrayRef2D azimuth_indices, \ diff --git a/cxx/isce3/image/Resample.h b/cxx/isce3/image/Resample.h index 363a73f4b..4bb406797 100644 --- a/cxx/isce3/image/Resample.h +++ b/cxx/isce3/image/Resample.h @@ -20,7 +20,7 @@ using ConstArrayRef2D = Eigen::Ref>; /** * Remove range and azimuth phase carrier from a block of input radar SLC data * - * @param[out] phase_data_block + * @param[out] out * Block of data to be written to. All data in block will be overwritten. * unit: phase (complex) array2D * @tparam[in] carrier_phase @@ -41,7 +41,7 @@ using ConstArrayRef2D = Eigen::Ref>; */ template void getModulationPhase( - ArrayRef2D> phase_data_block, + ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const size_t input_azimuth_first_line, @@ -54,7 +54,7 @@ void getModulationPhase( * Add back range and azimuth phase carrier and simultaneously flatten the block of * resampled SLC * - * @param[out] phase_data_block Block of data to be written to. All data in block will + * @param[out] out Block of data to be written to. All data in block will * be overwritten. * @tparam[in] carrier_phase carrier phase of the SLC data, in radian, as a * function of azimuth and range @@ -67,7 +67,7 @@ void getModulationPhase( */ template void getModulationPhaseAtCoords( - ArrayRef2D> phase_data_block, + ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, @@ -100,7 +100,7 @@ void getModulationPhaseAtCoords( */ template void modulate( - ArrayRef2D> slc_data_block, + ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const size_t input_azimuth_first_line, @@ -125,7 +125,7 @@ void modulate( */ template void modulateAtCoords( - ArrayRef2D> slc_data_block, + ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, diff --git a/python/extensions/pybind_isce3/image/Resample.cpp b/python/extensions/pybind_isce3/image/Resample.cpp index de650daac..2c723ac81 100644 --- a/python/extensions/pybind_isce3/image/Resample.cpp +++ b/python/extensions/pybind_isce3/image/Resample.cpp @@ -54,7 +54,7 @@ void addbindings_resamp(py::module & m) m.def( - "get_modulation_phase", + "_get_modulation_phase", py::overload_cast< isce3::image::v2::ArrayRef2D>, const AzRgFunc&, @@ -63,7 +63,7 @@ void addbindings_resamp(py::module & m) const size_t, const bool >(&isce3::image::v2::getModulationPhase), - py::arg("phase_data_block"), + py::arg("out"), py::arg("carrier_phase"), py::arg("radar_grid"), py::arg("input_azimuth_first_line"), @@ -74,7 +74,7 @@ void addbindings_resamp(py::module & m) Parameters ---------- - phase_data_block: numpy.ndarray (complex64) + out: numpy.ndarray (complex64) The output phase array to modify. Anything in this array will be overwritten. carrier_phase: isce3.core.LUT2d @@ -93,7 +93,7 @@ void addbindings_resamp(py::module & m) m.def( - "get_modulation_phase_at_coords", + "_get_modulation_phase_at_coords", py::overload_cast< isce3::image::v2::ArrayRef2D>, const AzRgFunc&, @@ -102,7 +102,7 @@ void addbindings_resamp(py::module & m) const isce3::image::v2::ArrayRef2D, const bool >(&isce3::image::v2::getModulationPhaseAtCoords), - py::arg("phase_data_block"), + py::arg("out"), py::arg("carrier_phase"), py::arg("radar_grid"), py::arg("azimuth_indices"), @@ -113,7 +113,7 @@ void addbindings_resamp(py::module & m) Parameters ---------- - phase_data_block: numpy.ndarray (complex64) + out: numpy.ndarray (complex64) The output phase array to modify. Anything in this array will be overwritten. carrier_phase: isce3.core.LUT2d @@ -132,7 +132,7 @@ void addbindings_resamp(py::module & m) m.def( - "modulate", + "_modulate", py::overload_cast< isce3::image::v2::ArrayRef2D>, const AzRgFunc&, @@ -172,7 +172,7 @@ void addbindings_resamp(py::module & m) m.def( - "modulate_at_coords", + "_modulate_at_coords", py::overload_cast< isce3::image::v2::ArrayRef2D>, const AzRgFunc&, diff --git a/python/packages/isce3/image/v2/modulate.py b/python/packages/isce3/image/v2/modulate.py new file mode 100644 index 000000000..6bbd9eb60 --- /dev/null +++ b/python/packages/isce3/image/v2/modulate.py @@ -0,0 +1,145 @@ +from __future__ import annotations + +import journal +import numpy as np + +from isce3.core import LUT2d +from isce3.ext.isce3.image.v2 import ( + _get_modulation_phase, + _get_modulation_phase_at_coords, + _modulate, + _modulate_at_coords, +) +from isce3.product import RadarGridParameters + + +def modulate( + slc_data_block: np.ndarray[np.complex64], + carrier_phase: LUT2d, + radar_grid: RadarGridParameters, + input_azimuth_first_line: int, + input_range_first_pixel: int, + conjugate: bool = False, + out: np.ndarray[np.complex64] | None = None, +) -> np.ndarray[np.complex64]: + out_array = out if out is not None else np.copy(slc_data_block) + + _modulate( + slc_data_block=out_array, + carrier_phase=carrier_phase, + radar_grid=radar_grid, + input_azimuth_first_line=input_azimuth_first_line, + input_range_first_pixel=input_range_first_pixel, + conjugate=conjugate, + ) + + return out_array + + +def modulate_at_coords( + slc_data_block: np.ndarray[np.complex64], + carrier_phase: LUT2d, + radar_grid: RadarGridParameters, + azimuth_indices: np.ndarray[np.float64], + range_indices: np.ndarray[np.float64], + conjugate: bool = False, + out: np.ndarray[np.complex64] | None = None, +) -> np.ndarray[np.complex64]: + error_channel = journal.error("modulate.modulate_at_coords") + out_array = out if out is not None else np.copy(slc_data_block) + + if out_array.shape != azimuth_indices.shape: + err_log = ( + f"Output block shape {out_array.shape} and azimuth indices block shape " + f"{azimuth_indices.shape} are unequal." + ) + error_channel.log(err_log) + raise ValueError(err_log) + + if out_array.shape != range_indices.shape: + err_log = ( + f"Output block shape {out_array.shape} and range indices block shape " + f"{azimuth_indices.shape} are unequal." + ) + error_channel.log(err_log) + raise ValueError(err_log) + + _modulate_at_coords( + slc_data_block=out_array, + carrier_phase=carrier_phase, + radar_grid=radar_grid, + azimuth_indices=azimuth_indices, + range_indices=range_indices, + conjugate=conjugate, + ) + + return out_array + + +def get_modulation_phase( + carrier_phase: LUT2d, + radar_grid: RadarGridParameters, + input_azimuth_first_line: int, + input_range_first_pixel: int, + conjugate: bool = False, + out: np.ndarray[np.complex64] | None = None, +) -> np.ndarray[np.complex64]: + out_array = out if out is not None else np.full( + (radar_grid.length, radar_grid.width), + fill_value=np.nan + 1.0j * np.nan, + dtype=np.complex64, + ) + + _get_modulation_phase( + out=out_array, + carrier_phase=carrier_phase, + radar_grid=radar_grid, + input_azimuth_first_line=input_azimuth_first_line, + input_range_first_pixel=input_range_first_pixel, + conjugate=conjugate, + ) + + return out_array + + +def get_modulation_phase_at_coords( + carrier_phase: LUT2d, + radar_grid: RadarGridParameters, + azimuth_indices: np.ndarray[np.float64], + range_indices: np.ndarray[np.float64], + conjugate: bool = False, + out: np.ndarray[np.complex64] | None = None, +) -> np.ndarray[np.complex64]: + error_channel = journal.error("modulate.get_modulation_phase_at_coords") + out_array = out if out is not None else np.full( + azimuth_indices.shape, + fill_value=np.nan + 1.0j * np.nan, + dtype=np.complex64, + ) + + if out_array.shape != azimuth_indices.shape: + err_log = ( + f"Output block shape {out_array.shape} and azimuth indices block shape " + f"{azimuth_indices.shape} are unequal." + ) + error_channel.log(err_log) + raise ValueError(err_log) + + if out_array.shape != range_indices.shape: + err_log = ( + f"Output block shape {out_array.shape} and range indices block shape " + f"{azimuth_indices.shape} are unequal." + ) + error_channel.log(err_log) + raise ValueError(err_log) + + _get_modulation_phase_at_coords( + out=out_array, + carrier_phase=carrier_phase, + radar_grid=radar_grid, + azimuth_indices=azimuth_indices, + range_indices=range_indices, + conjugate=conjugate, + ) + + return out_array diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index 7099fe67a..feacecf82 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -7,7 +7,8 @@ import numpy as np from isce3.core import SINC_HALF, LUT2d from isce3.core.resample_block_generators import get_blocks, get_blocks_by_offsets -from isce3.ext.isce3.image.v2 import _resample_to_coords, modulate, modulate_at_coords +from isce3.ext.isce3.image.v2 import _resample_to_coords +from isce3.image.v2.modulate import modulate, modulate_at_coords from isce3.io.dataset import DatasetReader, DatasetWriter from isce3.product import RadarGridParameters @@ -25,8 +26,8 @@ def resample_slc_blocks( fill_value: np.complex64 = np.nan + 1.0j * np.nan, with_gpu: bool = False, *, - modulation_carriers: Iterable[LUT2d] | None = None, - demodulation_carriers: Iterable[LUT2d] | None = None, + carrier_luts: Iterable[LUT2d] | None = None, + remodulate: bool = False, ) -> None: """ Resamples one or more SLCs onto a geometry described by given offsets datasets. @@ -66,12 +67,12 @@ def resample_slc_blocks( with_gpu : bool, optional If True, run the GPU resample workflow. If False, run the CPU resample workflow. Defaults to False. - modulation_carriers: Iterable[LUT2d] or None, optional - Carrier phase LUT's to modulate into the output product. If none, - no output modulation will occur. Defaults to None. - demodulation_carriers: Iterable[LUT2d] or None, optional - Carrier phase LUT's to demodulate from the input prior to resampling. If none, - no output demodulation will occur. Defaults to None. + carrier_luts: Iterable[LUT2d] or None, optional + Carrier phase LUT's to demodulate from the input product. If none, + no input demodulation will occur. Defaults to None. + remodulate: bool, optional + If True, remodulate the carrier_luts phase into the output data. Use only if + carrier_luts is also given. Defaults to False. """ info_channel = journal.info("resample_slc.resample_slc_blocks") warning_channel = journal.warning("resample_slc.resample_slc_blocks") @@ -90,6 +91,11 @@ def resample_slc_blocks( error_channel.log(err_log) raise ValueError(err_log) + if remodulate and (carrier_luts is None): + err_log = "If remodulate is True, carrier_luts must also be given." + error_channel.log(err_log) + raise ValueError(err_log) + for in_dataset in input_slcs: in_dataset_dtype = in_dataset.dtype if in_dataset_dtype not in [np.complex64, np.complex128]: @@ -222,7 +228,7 @@ def resample_slc_blocks( for i in range(len(input_blocks)): input_block = input_blocks[i] - if demodulation_carriers is not None: + if carrier_luts is not None: if not quiet: info_channel.log( f"demodulating input SLC for block {out_block_slice}..." @@ -232,11 +238,12 @@ def resample_slc_blocks( in_az_first_line = in_az_slice.start in_rg_first_pixel = in_rg_slice.start - for carrier in demodulation_carriers: + for carrier in carrier_luts: modulate( slc_data_block=input_block, + out=input_block, carrier_phase=carrier, - radar_grid=in_grid, + radar_grid=input_radar_grid, input_azimuth_first_line=in_az_first_line, input_range_first_pixel=in_rg_first_pixel, conjugate=True, @@ -259,17 +266,17 @@ def resample_slc_blocks( ) - if modulation_carriers is not None: + if remodulate and (carrier_luts is not None): if not quiet: info_channel.log( f"remodulating output SLC for block {out_block_slice}..." ) - for carrier in modulation_carriers: + for carrier in carrier_luts: modulate_at_coords( slc_data_block=output_block, carrier_phase=carrier, - radar_grid=in_grid, + radar_grid=input_radar_grid, azimuth_indices=azimuth_index_grid, range_indices=range_index_grid, conjugate=False, From 3bc4bebb9f0d3e67d80ea9ecbc78eea10e7c09e4 Mon Sep 17 00:00:00 2001 From: thudson Date: Thu, 21 Mar 2024 16:00:55 +0000 Subject: [PATCH 11/35] Made input types consistent --- cxx/isce3/image/Resample.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cxx/isce3/image/Resample.h b/cxx/isce3/image/Resample.h index 4bb406797..d269edaf7 100644 --- a/cxx/isce3/image/Resample.h +++ b/cxx/isce3/image/Resample.h @@ -41,7 +41,7 @@ using ConstArrayRef2D = Eigen::Ref>; */ template void getModulationPhase( - ArrayRef2D> out, + ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const size_t input_azimuth_first_line, @@ -67,7 +67,7 @@ void getModulationPhase( */ template void getModulationPhaseAtCoords( - ArrayRef2D> out, + ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, @@ -100,7 +100,7 @@ void getModulationPhaseAtCoords( */ template void modulate( - ArrayRef2D> slc_data_block, + ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const size_t input_azimuth_first_line, @@ -125,7 +125,7 @@ void modulate( */ template void modulateAtCoords( - ArrayRef2D> slc_data_block, + ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, From 6656baf8b0bf4ed6a05ad9283c471bb05f864f66 Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 27 Mar 2024 20:08:36 +0000 Subject: [PATCH 12/35] Name changes in resample_slc --- .../packages/isce3/image/v2/resample_slc.py | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index feacecf82..119e7497f 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -6,6 +6,7 @@ import journal import numpy as np from isce3.core import SINC_HALF, LUT2d +from isce3.core.poly2d import Poly2d from isce3.core.resample_block_generators import get_blocks, get_blocks_by_offsets from isce3.ext.isce3.image.v2 import _resample_to_coords from isce3.image.v2.modulate import modulate, modulate_at_coords @@ -26,7 +27,7 @@ def resample_slc_blocks( fill_value: np.complex64 = np.nan + 1.0j * np.nan, with_gpu: bool = False, *, - carrier_luts: Iterable[LUT2d] | None = None, + phase_carriers: Iterable[LUT2d | Poly2d] | None = None, remodulate: bool = False, ) -> None: """ @@ -67,12 +68,12 @@ def resample_slc_blocks( with_gpu : bool, optional If True, run the GPU resample workflow. If False, run the CPU resample workflow. Defaults to False. - carrier_luts: Iterable[LUT2d] or None, optional - Carrier phase LUT's to demodulate from the input product. If none, - no input demodulation will occur. Defaults to None. + phase_carriers: Iterable of LUT2d or Poly2d or None, optional + Carrier phase, in radians, to remove prior to resampling. If None, the carrier + phase is assumed to be zero. Defaults to None. remodulate: bool, optional - If True, remodulate the carrier_luts phase into the output data. Use only if - carrier_luts is also given. Defaults to False. + If True, remodulate the phase_carriers phase into the output data. Use only if + phase_carriers is also given. Defaults to False. """ info_channel = journal.info("resample_slc.resample_slc_blocks") warning_channel = journal.warning("resample_slc.resample_slc_blocks") @@ -91,8 +92,8 @@ def resample_slc_blocks( error_channel.log(err_log) raise ValueError(err_log) - if remodulate and (carrier_luts is None): - err_log = "If remodulate is True, carrier_luts must also be given." + if remodulate and (phase_carriers is None): + err_log = "If remodulate is True, phase_carriers must also be given." error_channel.log(err_log) raise ValueError(err_log) @@ -228,7 +229,7 @@ def resample_slc_blocks( for i in range(len(input_blocks)): input_block = input_blocks[i] - if carrier_luts is not None: + if phase_carriers is not None: if not quiet: info_channel.log( f"demodulating input SLC for block {out_block_slice}..." @@ -238,7 +239,7 @@ def resample_slc_blocks( in_az_first_line = in_az_slice.start in_rg_first_pixel = in_rg_slice.start - for carrier in carrier_luts: + for carrier in phase_carriers: modulate( slc_data_block=input_block, out=input_block, @@ -266,13 +267,13 @@ def resample_slc_blocks( ) - if remodulate and (carrier_luts is not None): + if remodulate and (phase_carriers is not None): if not quiet: info_channel.log( f"remodulating output SLC for block {out_block_slice}..." ) - for carrier in carrier_luts: + for carrier in phase_carriers: modulate_at_coords( slc_data_block=output_block, carrier_phase=carrier, From ecb4356acad2a4ef900f1da000f3929108f50be2 Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 27 Mar 2024 20:58:31 +0000 Subject: [PATCH 13/35] docstrings and fixes for modulate.py --- python/packages/isce3/image/v2/modulate.py | 141 ++++++++++++++++++++- 1 file changed, 134 insertions(+), 7 deletions(-) diff --git a/python/packages/isce3/image/v2/modulate.py b/python/packages/isce3/image/v2/modulate.py index 6bbd9eb60..13832017f 100644 --- a/python/packages/isce3/image/v2/modulate.py +++ b/python/packages/isce3/image/v2/modulate.py @@ -4,6 +4,7 @@ import numpy as np from isce3.core import LUT2d +from isce3.core.poly2d import Poly2d from isce3.ext.isce3.image.v2 import ( _get_modulation_phase, _get_modulation_phase_at_coords, @@ -15,17 +16,52 @@ def modulate( slc_data_block: np.ndarray[np.complex64], - carrier_phase: LUT2d, + carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, input_azimuth_first_line: int, input_range_first_pixel: int, conjugate: bool = False, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: - out_array = out if out is not None else np.copy(slc_data_block) + """ + Evaluate and modulate or demodulate the phase carrier onto the given SLC data block. + + Parameters + ---------- + slc_data_block : np.ndarray of np.complex64 + The block of SLC data to modulate. + carrier_phase : LUT2d or Poly2d + Carrier phase of the SLC data, in radian, as a function of azimuth and range. + This phase will be modulated to or demodulated from the image. + radar_grid : RadarGridParameters + Parameters for the given radar grid. + input_azimuth_first_line : int + Line index of the first sample of the block of input data with respect to the + origin of the full SLC scene + input_range_first_pixel : int + Pixel index of the first sample of the block of input data with respect to the + origin of the full SLC scene + conjugate : bool, optional + If True, modulate the conjugate of the phase, by default False + out : np.ndarray of np.complex64 | None, optional + The array to output data to, or None. If given, must be the same size as + slc_data_block. Any contents of this array will he overwritten, by default None + + Returns + ------- + np.ndarray of np.complex64 + The modulated SLC block. If `out` was given, this will be the same array as + the `out` array. + """ + out_array = out if out is not None else np.full( + (radar_grid.length, radar_grid.width), + fill_value=np.nan + 1.0j * np.nan, + dtype=np.complex64, + ) + np.copyto(out_array, slc_data_block) _modulate( - slc_data_block=out_array, + slc_data_block=out, carrier_phase=carrier_phase, radar_grid=radar_grid, input_azimuth_first_line=input_azimuth_first_line, @@ -38,15 +74,52 @@ def modulate( def modulate_at_coords( slc_data_block: np.ndarray[np.complex64], - carrier_phase: LUT2d, + carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, azimuth_indices: np.ndarray[np.float64], range_indices: np.ndarray[np.float64], conjugate: bool = False, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: + """ + Evaluate and modulate or demodulate the phase carrier onto the given SLC data block + at the given indices. + + Parameters + ---------- + slc_data_block : np.ndarray of np.complex64 + The block of SLC data to modulate. + carrier_phase : LUT2d or Poly2d + Carrier phase of the SLC data, in radian, as a function of azimuth and range. + This phase will be modulated to or demodulated from the image. + radar_grid : RadarGridParameters + Parameters for the given radar grid. + azimuth_indices : np.ndarray of np.float64 + Azimuth index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + range_indices : np.ndarray of np.float64 + Range index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + conjugate : bool, optional + If True, modulate the conjugate of the phase, by default False + out : np.ndarray of np.complex64 | None, optional + The array to output data to, or None. If given, must be the same size as + slc_data_block. Any contents of this array will he overwritten, by default None + + Returns + ------- + np.ndarray of np.complex64 + The modulated SLC block. If `out` was given, this will be the same array as + the `out` array. + """ error_channel = journal.error("modulate.modulate_at_coords") - out_array = out if out is not None else np.copy(slc_data_block) + + out_array = out if out is not None else np.full( + (radar_grid.length, radar_grid.width), + fill_value=np.nan + 1.0j * np.nan, + dtype=np.complex64, + ) + np.copyto(out_array, slc_data_block) if out_array.shape != azimuth_indices.shape: err_log = ( @@ -77,13 +150,40 @@ def modulate_at_coords( def get_modulation_phase( - carrier_phase: LUT2d, + carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, input_azimuth_first_line: int, input_range_first_pixel: int, conjugate: bool = False, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: + """ + Acquire the phase of the given carrier of a radar scene. + + Parameters + ---------- + carrier_phase : LUT2d or Poly2d + Carrier phase, in radian, as a function of azimuth and range. + radar_grid : RadarGridParameters + Parameters for the given radar grid. + input_azimuth_first_line : int + Line index of the first sample of the block of input data with respect to the + origin of the full SLC scene + input_range_first_pixel : int + Pixel index of the first sample of the block of input data with respect to the + origin of the full SLC scene + conjugate : bool, optional + If True, get the conjugate of the phase, by default False + out : np.ndarray[np.complex64] | None, optional + The output phase array to modify. Anything in this array will be overwritten. + Defaults to None + + Returns + ------- + np.ndarray of np.complex64 + The carrier phase, in the form of complex unit vectors. If `out` was given, this + will be the same array as the `out` array. + """ out_array = out if out is not None else np.full( (radar_grid.length, radar_grid.width), fill_value=np.nan + 1.0j * np.nan, @@ -103,13 +203,40 @@ def get_modulation_phase( def get_modulation_phase_at_coords( - carrier_phase: LUT2d, + carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, azimuth_indices: np.ndarray[np.float64], range_indices: np.ndarray[np.float64], conjugate: bool = False, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: + """ + Acquire the phase of the given carrier at each given index of a radar scene. + + Parameters + ---------- + carrier_phase : LUT2d or Poly2d + Carrier phase, in radian, as a function of azimuth and range. + radar_grid : RadarGridParameters + Parameters for the given radar grid. + azimuth_indices : np.ndarray of np.float64 + Azimuth index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + range_indices : np.ndarray of np.float64 + Range index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + conjugate : bool, optional + If True, get the conjugate of the phase, by default False + out : np.ndarray[np.complex64] | None, optional + The output phase array to modify. Anything in this array will be overwritten. + Defaults to None + + Returns + ------- + np.ndarray of np.complex64 + The carrier phase, in the form of complex unit vectors. If `out` was given, this + will be the same array as the `out` array. + """ error_channel = journal.error("modulate.get_modulation_phase_at_coords") out_array = out if out is not None else np.full( azimuth_indices.shape, From 36623e2172c02eeb78d4b776629647c845909c95 Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 27 Mar 2024 20:59:39 +0000 Subject: [PATCH 14/35] C++ documentation changes --- cxx/isce3/image/Resample.h | 71 +++++++++-------- .../pybind_isce3/image/Resample.cpp | 76 ++++++++++--------- 2 files changed, 80 insertions(+), 67 deletions(-) diff --git a/cxx/isce3/image/Resample.h b/cxx/isce3/image/Resample.h index d269edaf7..b35d0c6b2 100644 --- a/cxx/isce3/image/Resample.h +++ b/cxx/isce3/image/Resample.h @@ -18,14 +18,13 @@ template using ConstArrayRef2D = Eigen::Ref>; /** - * Remove range and azimuth phase carrier from a block of input radar SLC data + * Acquire the phase of the given carrier of a radar scene. * * @param[out] out * Block of data to be written to. All data in block will be overwritten. - * unit: phase (complex) array2D + * unit: array2D of complex * @tparam[in] carrier_phase * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. - * This phase will be removed from the resampled image. * @param[in] radar_grid * parameters for the given radar grid * @param[in] input_azimuth_first_line @@ -37,7 +36,7 @@ using ConstArrayRef2D = Eigen::Ref>; * of the full SLC scene * unit: range column indices (int) * @param[in] conjugate - * if true, modulate the conjugate of the phase. + * if true, get the conjugate of the phase. */ template void getModulationPhase( @@ -51,19 +50,23 @@ void getModulationPhase( /** - * Add back range and azimuth phase carrier and simultaneously flatten the block of - * resampled SLC + * Acquire the phase of the given carrier at each given index of a radar scene. * - * @param[out] out Block of data to be written to. All data in block will - * be overwritten. - * @tparam[in] carrier_phase carrier phase of the SLC data, in radian, as a - * function of azimuth and range - * @param[in] radar_grid parameters for the given radar grid - * @param[in] azimuth_indices azimuth index of each output coordinate pixel in the - * resampling coordinate system - * @param[in] range_indices range index of each output coordinate pixel in the - * resampling coordinate system - * @param[in] conjugate if true, modulate the conjugate of the phase. + * @param[out] out + * Block of data to be written to. All data in block will be overwritten. + * unit: array2D of complex + * @tparam[in] carrier_phase + * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. + * @param[in] radar_grid + * parameters for the given radar grid + * @param[in] azimuth_indices + * azimuth index of each output coordinate pixel in the resampling coordinate system + * unit: azimuth row indices (int) + * @param[in] range_indices + * range index of each output coordinate pixel in the resampling coordinate system + * unit: range column indices (int) + * @param[in] conjugate + * if true, modulate the conjugate of the phase. */ template void getModulationPhaseAtCoords( @@ -80,11 +83,11 @@ void getModulationPhaseAtCoords( * Remove range and azimuth phase carrier from a block of input radar SLC data * * @param[out] slc_data_block - * Block of data to be modulated. - * unit: phase (complex) array2D + * the block of data SLC to be modulated. + * unit: array2D of complex * @tparam[in] carrier_phase - * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. - * This phase will be removed from the resampled image. + * carrier phase of the SLC data, in radian, as a function of azimuth and range. + * This phase will be modulated to or demodulated from the image. * @param[in] radar_grid * parameters for the given radar grid * @param[in] input_azimuth_first_line @@ -110,18 +113,24 @@ void modulate( /** - * Add back range and azimuth phase carrier and simultaneously flatten the block of - * resampled SLC + * Evaluate and modulate or demodulate the phase carrier onto the given SLC data block. * - * @param[out] slc_data_block Block of data to be modulated. - * @tparam[in] carrier_phase carrier phase of the SLC data, in radian, as a - * function of azimuth and range - * @param[in] radar_grid parameters for the given radar grid - * @param[in] azimuth_indices azimuth index of each output coordinate pixel in the - * resampling coordinate system - * @param[in] range_indices range index of each output coordinate pixel in the - * resampling coordinate system - * @param[in] conjugate if true, modulate the conjugate of the phase. + * @param[out] slc_data_block + * the block of data SLC to be modulated. + * unit: array2D of complex + * @tparam[in] carrier_phase + * carrier phase of the SLC data, in radian, as a function of azimuth and range. + * This phase will be modulated to or demodulated from the image. + * @param[in] radar_grid + * parameters for the given radar grid + * @param[in] azimuth_indices + * azimuth index of each output coordinate pixel in the resampling coordinate system + * unit: azimuth row indices (int) + * @param[in] range_indices + * range index of each output coordinate pixel in the resampling coordinate system + * unit: range column indices (int) + * @param[in] conjugate + * if true, modulate the conjugate of the phase. */ template void modulateAtCoords( diff --git a/python/extensions/pybind_isce3/image/Resample.cpp b/python/extensions/pybind_isce3/image/Resample.cpp index 2c723ac81..fcc603700 100644 --- a/python/extensions/pybind_isce3/image/Resample.cpp +++ b/python/extensions/pybind_isce3/image/Resample.cpp @@ -70,24 +70,25 @@ void addbindings_resamp(py::module & m) py::arg("input_range_first_pixel"), py::arg("conjugate"), R"( - Acquire the phase of the given carrier at each given index of a radar scene. + Acquire the phase of the given carrier of a radar scene. Parameters ---------- out: numpy.ndarray (complex64) The output phase array to modify. Anything in this array will be overwritten. - carrier_phase: isce3.core.LUT2d - An LUT2d describing the carrier frequency of the radar data over azimuth - and range. + carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d + Carrier phase, in radian, as a function of azimuth and range. radar_grid: isce3.product.RadarGridParameters - Radar grid parameters of the radar swath. - azimuth_indices: numpy.ndarray (float64) - azimuth index of each output coordinate pixel in the given radar coordinate - system. Must be the same shape as phase_data_block. - range_indices: numpy.ndarray (float64) - range index of each output coordinate pixel in the given radar coordinate - system. Must be the same shape as phase_data_block. + Parameters for the given radar grid. + input_azimuth_first_line: numpy.ndarray (float64) + Line index of the first sample of the block of input data with respect to + the origin of the full SLC scene + input_range_first_pixel: numpy.ndarray (float64) + Pixel index of the first sample of the block of input data with respect to + the origin of the full SLC scene + conjugate: bool + If True, get the conjugate of the phase. )" ); @@ -116,17 +117,18 @@ void addbindings_resamp(py::module & m) out: numpy.ndarray (complex64) The output phase array to modify. Anything in this array will be overwritten. - carrier_phase: isce3.core.LUT2d - An LUT2d describing the carrier frequency of the radar data over azimuth - and range. + carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d + Carrier phase, in radian, as a function of azimuth and range. radar_grid: isce3.product.RadarGridParameters - Radar grid parameters of the radar swath. + Parameters for the given radar grid. azimuth_indices: numpy.ndarray (float64) - azimuth index of each output coordinate pixel in the given radar coordinate + Azimuth index of each output coordinate pixel in the given radar coordinate system. Must be the same shape as phase_data_block. range_indices: numpy.ndarray (float64) - range index of each output coordinate pixel in the given radar coordinate + Range index of each output coordinate pixel in the given radar coordinate system. Must be the same shape as phase_data_block. + conjugate: bool + If True, get the conjugate of the phase. )" ); @@ -148,23 +150,24 @@ void addbindings_resamp(py::module & m) py::arg("input_range_first_pixel"), py::arg("conjugate"), R"( - Acquire the phase of the given carrier at each given index of a radar scene. + Evaluate and modulate or demodulate the phase carrier onto the given SLC data + block. Parameters ---------- slc_data_block: numpy.ndarray (complex64) - The output phase array to modulate. - carrier_phase: isce3.core.LUT2d - An LUT2d describing the carrier frequency of the radar data over azimuth - and range. + The block of SLC data to modulate. + carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d + Carrier phase, in radian, as a function of azimuth and range. This phase + will be modulated to or demodulated from the image. radar_grid: isce3.product.RadarGridParameters - Radar grid parameters of the radar swath. - azimuth_indices: numpy.ndarray (float64) - azimuth index of each output coordinate pixel in the given radar coordinate - system. Must be the same shape as phase_data_block. - range_indices: numpy.ndarray (float64) - range index of each output coordinate pixel in the given radar coordinate - system. Must be the same shape as phase_data_block. + Parameters for the given radar grid. + input_azimuth_first_line: numpy.ndarray (float64) + Line index of the first sample of the block of input data with respect to + the origin of the full SLC scene + input_range_first_pixel: numpy.ndarray (float64) + Pixel index of the first sample of the block of input data with respect to + the origin of the full SLC scene conjugate: bool, optional If True, modulate the conjugate of the phase. )" @@ -188,22 +191,23 @@ void addbindings_resamp(py::module & m) py::arg("range_indices"), py::arg("conjugate"), R"( - Acquire the phase of the given carrier at each given index of a radar scene. + Evaluate and modulate or demodulate the phase carrier onto the given SLC data + block at the given indices. Parameters ---------- slc_data_block: numpy.ndarray (complex64) The output phase array to modulate. - carrier_phase: isce3.core.LUT2d - An LUT2d describing the carrier frequency of the radar data over azimuth - and range. + carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d + Carrier phase, in radian, as a function of azimuth and range. This phase + will be modulated to or demodulated from the image. radar_grid: isce3.product.RadarGridParameters - Radar grid parameters of the radar swath. + Parameters for the given radar grid. azimuth_indices: numpy.ndarray (float64) - azimuth index of each output coordinate pixel in the given radar coordinate + Azimuth index of each output coordinate pixel in the given radar coordinate system. Must be the same shape as phase_data_block. range_indices: numpy.ndarray (float64) - range index of each output coordinate pixel in the given radar coordinate + Range index of each output coordinate pixel in the given radar coordinate system. Must be the same shape as phase_data_block. conjugate: bool, optional If True, modulate the conjugate of the phase. From 15c5b9db2e36cc35f6833907ec01738bc60f28e5 Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 27 Mar 2024 21:00:10 +0000 Subject: [PATCH 15/35] output remodulation fix in resample_slc --- python/packages/isce3/image/v2/resample_slc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index 119e7497f..2f448b545 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -276,6 +276,7 @@ def resample_slc_blocks( for carrier in phase_carriers: modulate_at_coords( slc_data_block=output_block, + out=output_block, carrier_phase=carrier, radar_grid=input_radar_grid, azimuth_indices=azimuth_index_grid, From e6e7b8623c6c6b64978a59bcfaa8403743a0550f Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 27 Mar 2024 21:06:52 +0000 Subject: [PATCH 16/35] Moved modulate bindings to their own python submodule --- python/extensions/pybind_isce3/image/Resample.cpp | 9 ++++++--- python/extensions/pybind_isce3/image/Resample.h | 4 +++- python/extensions/pybind_isce3/image/image.cpp | 6 ++++-- python/packages/isce3/image/v2/modulate.py | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/python/extensions/pybind_isce3/image/Resample.cpp b/python/extensions/pybind_isce3/image/Resample.cpp index fcc603700..edd208de0 100644 --- a/python/extensions/pybind_isce3/image/Resample.cpp +++ b/python/extensions/pybind_isce3/image/Resample.cpp @@ -12,7 +12,6 @@ namespace py = pybind11; -template void addbindings_resamp(py::module & m) { // Write _resample_to_coords as a private function. This will be used by a wrapper @@ -51,8 +50,12 @@ void addbindings_resamp(py::module & m) The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. )" ); +} +template +void addbindings_modulate(py::module & m) +{ m.def( "_get_modulation_phase", py::overload_cast< @@ -215,5 +218,5 @@ void addbindings_resamp(py::module & m) ); } -template void addbindings_resamp>(py::module & m); -template void addbindings_resamp(py::module & m); +template void addbindings_modulate>(py::module & m); +template void addbindings_modulate(py::module & m); diff --git a/python/extensions/pybind_isce3/image/Resample.h b/python/extensions/pybind_isce3/image/Resample.h index 2d71907aa..9fb6c8f91 100644 --- a/python/extensions/pybind_isce3/image/Resample.h +++ b/python/extensions/pybind_isce3/image/Resample.h @@ -1,5 +1,7 @@ #pragma once #include -template void addbindings_resamp(pybind11::module&); + +template +void addbindings_modulate(pybind11::module&); diff --git a/python/extensions/pybind_isce3/image/image.cpp b/python/extensions/pybind_isce3/image/image.cpp index ecdf0a7ed..e548d61de 100644 --- a/python/extensions/pybind_isce3/image/image.cpp +++ b/python/extensions/pybind_isce3/image/image.cpp @@ -9,10 +9,12 @@ void addsubmodule_image(py::module & m) { py::module m_image = m.def_submodule("image"); py::module m_image_v2 = m_image.def_submodule("v2"); + py::module m_image_modulate = m_image.def_submodule("modulate"); // Add the resample v2 functionality to the v2 module. - addbindings_resamp>(m_image_v2); - addbindings_resamp(m_image_v2); + addbindings_resamp(m_image_v2); + addbindings_modulate>(m_image_modulate); + addbindings_modulate(m_image_modulate); // forward declare bound classes for v1 py::class_ pyResampSlc(m_image, "ResampSlc"); diff --git a/python/packages/isce3/image/v2/modulate.py b/python/packages/isce3/image/v2/modulate.py index 13832017f..11ff226eb 100644 --- a/python/packages/isce3/image/v2/modulate.py +++ b/python/packages/isce3/image/v2/modulate.py @@ -5,7 +5,7 @@ from isce3.core import LUT2d from isce3.core.poly2d import Poly2d -from isce3.ext.isce3.image.v2 import ( +from isce3.ext.isce3.image.modulate import ( _get_modulation_phase, _get_modulation_phase_at_coords, _modulate, From eb27a85ab8bf3b4adfe2c0a7aaf72f229267cdb5 Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 27 Mar 2024 21:47:38 +0000 Subject: [PATCH 17/35] Used radar grid blocking, moved Modulate functions into their own namespace --- cxx/isce3/Headers.cmake | 1 + cxx/isce3/Sources.cmake | 1 + cxx/isce3/image/Modulate.cpp | 222 +++++++++++++++++ cxx/isce3/image/Modulate.h | 129 ++++++++++ cxx/isce3/image/Resample.cpp | 234 ------------------ cxx/isce3/image/Resample.h | 127 ---------- python/extensions/pybind_isce3/Sources.cmake | 1 + .../pybind_isce3/image/Modulate.cpp | 162 ++++++++++++ .../extensions/pybind_isce3/image/Modulate.h | 5 + .../pybind_isce3/image/Resample.cpp | 169 ------------- .../extensions/pybind_isce3/image/image.cpp | 3 + python/packages/isce3/image/v2/modulate.py | 22 +- .../packages/isce3/image/v2/resample_slc.py | 13 +- 13 files changed, 529 insertions(+), 560 deletions(-) create mode 100644 cxx/isce3/image/Modulate.cpp create mode 100644 cxx/isce3/image/Modulate.h create mode 100644 python/extensions/pybind_isce3/image/Modulate.cpp create mode 100644 python/extensions/pybind_isce3/image/Modulate.h diff --git a/cxx/isce3/Headers.cmake b/cxx/isce3/Headers.cmake index 5d2543079..e1d21bf6d 100644 --- a/cxx/isce3/Headers.cmake +++ b/cxx/isce3/Headers.cmake @@ -114,6 +114,7 @@ geometry/metadataCubes.h geogrid/getRadarGrid.h geogrid/relocateRaster.h image/forward.h +image/Modulate.h image/Resample.h image/ResampSlc.h image/ResampSlc.icc diff --git a/cxx/isce3/Sources.cmake b/cxx/isce3/Sources.cmake index 171dbe5c2..30005a8cc 100644 --- a/cxx/isce3/Sources.cmake +++ b/cxx/isce3/Sources.cmake @@ -59,6 +59,7 @@ geometry/TopoLayers.cpp geometry/metadataCubes.cpp geogrid/getRadarGrid.cpp geogrid/relocateRaster.cpp +image/Modulate.cpp image/Resample.cpp image/ResampSlc.cpp io/gdal/Dataset.cpp diff --git a/cxx/isce3/image/Modulate.cpp b/cxx/isce3/image/Modulate.cpp new file mode 100644 index 000000000..1ff63d85a --- /dev/null +++ b/cxx/isce3/image/Modulate.cpp @@ -0,0 +1,222 @@ +#include "Modulate.h" + +#include +#include +#include + +namespace isce3::image::modulate { + + +template +std::complex _getPixelCarrierPhase( + const double azimuth, + const double range, + const AzRgFunc& carrier_phase, + bool conjugate = false +) { + // Evaluate the pixel's carrier phase + // unit: phase (radians) + const double phase = carrier_phase.eval(azimuth, range); + + // Convert the carrier phase into a unit phasor (i.e. an angle on the unit + // circle in the complex plane). + // unit: unitless (complex) + if(conjugate){ + return std::complex(std::cos(phase), -std::sin(phase)); + } else { + return std::complex(std::cos(phase), std::sin(phase)); + } +} + + +template +void getModulationPhase( + ArrayRef2D> out, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const bool conjugate +) +{ + // unit: azimuth row indices (int) + const size_t phase_block_length = out.rows(); + // unit: range column indices (int) + const size_t phase_block_width = out.cols(); + + // remove carrier from radar data +#pragma omp parallel for collapse(2) + for (size_t az_index = 0; az_index < phase_block_length; ++az_index) { + for (size_t rg_index = 0; rg_index < phase_block_width; ++rg_index) { + // unit: time (seconds) + const double azimuth = + radar_grid.sensingStart() + az_index / radar_grid.prf(); + + // unit: distance (meters) + const double range = radar_grid.startingRange() + + rg_index * radar_grid.rangePixelSpacing(); + + // Get the carrier unit phasor for this pixel + const auto phasor = _getPixelCarrierPhase( + azimuth, range, carrier_phase, conjugate + ); + + // Write the phasor into the output data block + out(az_index, rg_index) = phasor; + } + } // end multithreaded block +} + + +template +void getModulationPhaseAtCoords( + ArrayRef2D> out, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const ConstArrayRef2D azimuth_indices, + const ConstArrayRef2D range_indices, + const bool conjugate +) +{ + const size_t outWidth = out.cols(); + const size_t outLength = out.rows(); + +#pragma omp parallel for collapse(2) + for (size_t az_index = 0; az_index < outLength; ++az_index){ + for (size_t rg_index = 0; rg_index < outWidth; ++rg_index){ + + // Get the indices on the output grid corresponding to these indices + // on the input grid overall. + const double az_carrier_index = azimuth_indices(az_index, rg_index); + const double rg_carrier_index = range_indices(az_index, rg_index); + + // Azimuth time at the current output pixel + const double azimuth = radar_grid.sensingStart() + az_carrier_index / + radar_grid.prf(); + + // Slant Range at the current output pixel + const double range = radar_grid.startingRange() + rg_carrier_index * + radar_grid.rangePixelSpacing(); + + // Get the carrier phasor for this pixel + const auto phasor = _getPixelCarrierPhase( + azimuth, range, carrier_phase, conjugate + ); + + // Write the phasor into the output data block + out(az_index, rg_index) = phasor; + } + } // end multithreaded block +} + + +template +void modulate( + ArrayRef2D> slc_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const bool conjugate +) +{ + // unit: azimuth row indices (int) + const size_t phase_block_length = slc_data_block.rows(); + // unit: range column indices (int) + const size_t phase_block_width = slc_data_block.cols(); + + // remove carrier from radar data +#pragma omp parallel for collapse(2) + for ( size_t az_index = 0; az_index < phase_block_length; ++az_index) { + for (size_t rg_index = 0; rg_index < phase_block_width; ++rg_index) { + // unit: time (seconds) + const double azimuth = + radar_grid.sensingStart() + az_index / radar_grid.prf(); + + // unit: distance (meters) + const double range = radar_grid.startingRange() + + rg_index * radar_grid.rangePixelSpacing(); + + // Get the carrier phasor for this pixel + const auto phasor = _getPixelCarrierPhase( + azimuth, range, carrier_phase, conjugate + ); + + // Modulate the phasor into the output data block + slc_data_block(az_index, rg_index) *= phasor; + } + } // end multithreaded block +} + + +template +void modulateAtCoords( + ArrayRef2D> slc_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const ConstArrayRef2D azimuth_indices, + const ConstArrayRef2D range_indices, + const bool conjugate +) +{ + const size_t outWidth = slc_data_block.cols(); + const size_t outLength = slc_data_block.rows(); + +#pragma omp parallel for collapse(2) + for (size_t az_index = 0; az_index < outLength; ++az_index){ + for (size_t rg_index = 0; rg_index < outWidth; ++rg_index){ + + // Get the indices on the output grid corresponding to these indices + // on the input grid overall. + const double az_carrier_index = azimuth_indices(az_index, rg_index); + const double rg_carrier_index = range_indices(az_index, rg_index); + + // Azimuth time at the current output pixel + const double azimuth = radar_grid.sensingStart() + az_carrier_index / + radar_grid.prf(); + + // Slant Range at the current output pixel + const double range = radar_grid.startingRange() + rg_carrier_index * + radar_grid.rangePixelSpacing(); + + // Get the carrier phasor for this pixel + const auto phasor = _getPixelCarrierPhase( + azimuth, range, carrier_phase, conjugate + ); + + // Modulate the phasor into the output data block + slc_data_block(az_index, rg_index) *= phasor; + } + } // end multithreaded block +} + + +#define EXPLICIT_INSTANTIATION(AzRgFunc) \ +template void getModulationPhase( \ + ArrayRef2D> out, \ + const AzRgFunc& carrier_phase, \ + const isce3::product::RadarGridParameters& radar_grid, \ + const bool conjugate \ +); \ +template void getModulationPhaseAtCoords( \ + ArrayRef2D> out, \ + const AzRgFunc& carrier_phase, \ + const isce3::product::RadarGridParameters& radar_grid, \ + const ConstArrayRef2D azimuth_indices, \ + const ConstArrayRef2D range_indices, \ + const bool conjugate \ +); \ +template void modulate( \ + ArrayRef2D> slc_data_block, \ + const AzRgFunc& carrier_phase, \ + const isce3::product::RadarGridParameters& radar_grid, \ + const bool conjugate \ +); \ +template void modulateAtCoords( \ + ArrayRef2D> slc_data_block, \ + const AzRgFunc& carrier_phase, \ + const isce3::product::RadarGridParameters& radar_grid, \ + const ConstArrayRef2D azimuth_indices, \ + const ConstArrayRef2D range_indices, \ + const bool conjugate \ +) +EXPLICIT_INSTANTIATION(isce3::core::LUT2d); +EXPLICIT_INSTANTIATION(isce3::core::Poly2d); + +} // end namespace isce3::image::modulate diff --git a/cxx/isce3/image/Modulate.h b/cxx/isce3/image/Modulate.h new file mode 100644 index 000000000..fac1ac214 --- /dev/null +++ b/cxx/isce3/image/Modulate.h @@ -0,0 +1,129 @@ +#include +#include +#include + +#include +#include +#include + +namespace isce3::image::modulate { + +template +using Array2D = Eigen::Array; + +template +using ArrayRef2D = Eigen::Ref>; + +template +using ConstArrayRef2D = Eigen::Ref>; + +/** + * Acquire the phase of the given carrier of a radar scene. + * + * @param[out] out + * Block of data to be written to. All data in block will be overwritten. + * unit: array2D of complex + * @tparam[in] carrier_phase + * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. + * @param[in] radar_grid + * parameters for the given radar grid + * @param[in] conjugate + * if true, get the conjugate of the phase. + */ +template +void getModulationPhase( + ArrayRef2D> out, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const bool conjugate +); + + +/** + * Acquire the phase of the given carrier at each given index of a radar scene. + * + * @param[out] out + * Block of data to be written to. All data in block will be overwritten. + * unit: array2D of complex + * @tparam[in] carrier_phase + * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. + * @param[in] radar_grid + * parameters for the given radar grid + * @param[in] azimuth_indices + * azimuth index of each pixel in the output block w.r.t the radar grid. Must be the + * same shape as `out`. + * unit: azimuth row indices (int) + * @param[in] range_indices + * range index of each pixel in the output block w.r.t the radar grid. Must be the + * same shape as `out`. + * unit: range column indices (int) + * @param[in] conjugate + * if true, modulate the conjugate of the phase. + */ +template +void getModulationPhaseAtCoords( + ArrayRef2D> out, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const ConstArrayRef2D azimuth_indices, + const ConstArrayRef2D range_indices, + const bool conjugate +); + + +/** + * Remove range and azimuth phase carrier from a block of input radar SLC data + * + * @param[out] slc_data_block + * the block of data SLC to be modulated. + * unit: array2D of complex + * @tparam[in] carrier_phase + * carrier phase of the SLC data, in radian, as a function of azimuth and range. + * This phase will be modulated to or demodulated from the image. + * @param[in] radar_grid + * parameters for the given radar grid + * @param[in] conjugate + * if true, modulate the conjugate of the phase. + */ +template +void modulate( + ArrayRef2D> slc_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const bool conjugate +); + + +/** + * Evaluate and modulate or demodulate the phase carrier onto the given SLC data block. + * + * @param[out] slc_data_block + * the block of data SLC to be modulated. + * unit: array2D of complex + * @tparam[in] carrier_phase + * carrier phase of the SLC data, in radian, as a function of azimuth and range. + * This phase will be modulated to or demodulated from the image. + * @param[in] radar_grid + * parameters for the given radar grid + * @param[in] azimuth_indices + * azimuth index of each pixel in the output block w.r.t the radar grid. Must be the + * same shape as `slc_data_block`. + * unit: azimuth row indices (int) + * @param[in] range_indices + * range index of each pixel in the output block w.r.t the radar grid. Must be the + * same shape as `slc_data_block`. + * unit: range column indices (int) + * @param[in] conjugate + * if true, modulate the conjugate of the phase. + */ +template +void modulateAtCoords( + ArrayRef2D> slc_data_block, + const AzRgFunc& carrier_phase, + const isce3::product::RadarGridParameters& radar_grid, + const ConstArrayRef2D azimuth_indices, + const ConstArrayRef2D range_indices, + const bool conjugate +); + +} // namespace isce3::image::modulate diff --git a/cxx/isce3/image/Resample.cpp b/cxx/isce3/image/Resample.cpp index b217070e9..577636e4a 100644 --- a/cxx/isce3/image/Resample.cpp +++ b/cxx/isce3/image/Resample.cpp @@ -7,203 +7,6 @@ namespace isce3::image::v2 { - -template -std::complex _getPixelCarrierPhase( - const double azimuth, - const double range, - const AzRgFunc& carrier_phase, - bool conjugate = false -) { - // Evaluate the pixel's carrier phase - // unit: phase (radians) - const double phase = carrier_phase.eval(azimuth, range); - - // Convert the carrier phase into a unit phasor (i.e. an angle on the unit - // circle in the complex plane). - // unit: unitless (complex) - if(conjugate){ - return std::complex(std::cos(phase), -std::sin(phase)); - } else { - return std::complex(std::cos(phase), std::sin(phase)); - } -} - - -template -void getModulationPhase( - ArrayRef2D> out, - const AzRgFunc& carrier_phase, - const isce3::product::RadarGridParameters& radar_grid, - const size_t input_azimuth_first_line, - const size_t input_range_first_pixel, - const bool conjugate -) -{ - // unit: azimuth row indices (int) - const size_t phase_block_length = out.rows(); - // unit: range column indices (int) - const size_t phase_block_width = out.cols(); - - // remove carrier from radar data -#pragma omp parallel for collapse(2) - for (size_t az_index = 0; az_index < phase_block_length; ++az_index) { - for (size_t rg_index = 0; rg_index < phase_block_width; ++rg_index) { - // Offset for block starting line - // unit: azimuth row indices (int) - const auto az_carrier_index = az_index + input_azimuth_first_line; - // unit: time (seconds) - const double azimuth = - radar_grid.sensingStart() + az_carrier_index / radar_grid.prf(); - - // Offset for block starting pixel - // unit: range column indices (int) - const auto rg_carrier_index = rg_index + input_range_first_pixel; - // unit: distance (meters) - const double range = radar_grid.startingRange() + - rg_carrier_index * radar_grid.rangePixelSpacing(); - - // Get the carrier unit phasor for this pixel - const auto phasor = _getPixelCarrierPhase( - azimuth, range, carrier_phase, conjugate - ); - - // Write the phasor into the output data block - out(az_index, rg_index) = phasor; - } - } // end multithreaded block -} - - -template -void getModulationPhaseAtCoords( - ArrayRef2D> out, - const AzRgFunc& carrier_phase, - const isce3::product::RadarGridParameters& radar_grid, - const ConstArrayRef2D azimuth_indices, - const ConstArrayRef2D range_indices, - const bool conjugate -) -{ - const size_t outWidth = out.cols(); - const size_t outLength = out.rows(); - -#pragma omp parallel for collapse(2) - for (size_t az_index = 0; az_index < outLength; ++az_index){ - for (size_t rg_index = 0; rg_index < outWidth; ++rg_index){ - - // Get the indices on the output grid corresponding to these indices - // on the input grid overall. - const double az_carrier_index = azimuth_indices(az_index, rg_index); - const double rg_carrier_index = range_indices(az_index, rg_index); - - // Azimuth time at the current output pixel - const double azimuth = radar_grid.sensingStart() + az_carrier_index / - radar_grid.prf(); - - // Slant Range at the current output pixel - const double range = radar_grid.startingRange() + rg_carrier_index * - radar_grid.rangePixelSpacing(); - - // Get the carrier phasor for this pixel - const auto phasor = _getPixelCarrierPhase( - azimuth, range, carrier_phase, conjugate - ); - - // Write the phasor into the output data block - out(az_index, rg_index) = phasor; - } - } // end multithreaded block -} - - -template -void modulate( - ArrayRef2D> slc_data_block, - const AzRgFunc& carrier_phase, - const isce3::product::RadarGridParameters& radar_grid, - const size_t input_azimuth_first_line, - const size_t input_range_first_pixel, - const bool conjugate -) -{ - // unit: azimuth row indices (int) - const size_t phase_block_length = slc_data_block.rows(); - // unit: range column indices (int) - const size_t phase_block_width = slc_data_block.cols(); - - // remove carrier from radar data -#pragma omp parallel for collapse(2) - for ( size_t az_index = 0; az_index < phase_block_length; ++az_index) { - for (size_t rg_index = 0; rg_index < phase_block_width; ++rg_index) { - // Offset for block starting line - // unit: azimuth row indices (int) - const auto az_carrier_index = az_index + input_azimuth_first_line; - // unit: time (seconds) - const double azimuth = - radar_grid.sensingStart() + az_carrier_index / radar_grid.prf(); - - // Offset for block starting pixel - // unit: range column indices (int) - const auto rg_carrier_index = rg_index + input_range_first_pixel; - // unit: distance (meters) - const double range = radar_grid.startingRange() + - rg_carrier_index * radar_grid.rangePixelSpacing(); - - // Get the carrier phasor for this pixel - const auto phasor = _getPixelCarrierPhase( - azimuth, range, carrier_phase, conjugate - ); - - // Modulate the phasor into the output data block - slc_data_block(az_index, rg_index) *= phasor; - } - } // end multithreaded block -} - - -template -void modulateAtCoords( - ArrayRef2D> slc_data_block, - const AzRgFunc& carrier_phase, - const isce3::product::RadarGridParameters& radar_grid, - const ConstArrayRef2D azimuth_indices, - const ConstArrayRef2D range_indices, - const bool conjugate -) -{ - const size_t outWidth = slc_data_block.cols(); - const size_t outLength = slc_data_block.rows(); - -#pragma omp parallel for collapse(2) - for (size_t az_index = 0; az_index < outLength; ++az_index){ - for (size_t rg_index = 0; rg_index < outWidth; ++rg_index){ - - // Get the indices on the output grid corresponding to these indices - // on the input grid overall. - const double az_carrier_index = azimuth_indices(az_index, rg_index); - const double rg_carrier_index = range_indices(az_index, rg_index); - - // Azimuth time at the current output pixel - const double azimuth = radar_grid.sensingStart() + az_carrier_index / - radar_grid.prf(); - - // Slant Range at the current output pixel - const double range = radar_grid.startingRange() + rg_carrier_index * - radar_grid.rangePixelSpacing(); - - // Get the carrier phasor for this pixel - const auto phasor = _getPixelCarrierPhase( - azimuth, range, carrier_phase, conjugate - ); - - // Modulate the phasor into the output data block - slc_data_block(az_index, rg_index) *= phasor; - } - } // end multithreaded block -} - - void resampleToCoords( ArrayRef2D> resampled_data_block, const ConstArrayRef2D> input_data_block, @@ -371,41 +174,4 @@ void resampleToCoords( } // end omp parallel } // end resampleToCoords - -#define EXPLICIT_INSTANTIATION(AzRgFunc) \ -template void getModulationPhase( \ - ArrayRef2D> out, \ - const AzRgFunc& carrier_phase, \ - const isce3::product::RadarGridParameters& radar_grid, \ - const size_t input_azimuth_first_line, \ - const size_t input_range_first_pixel, \ - const bool conjugate \ -); \ -template void getModulationPhaseAtCoords( \ - ArrayRef2D> out, \ - const AzRgFunc& carrier_phase, \ - const isce3::product::RadarGridParameters& radar_grid, \ - const ConstArrayRef2D azimuth_indices, \ - const ConstArrayRef2D range_indices, \ - const bool conjugate \ -); \ -template void modulate( \ - ArrayRef2D> slc_data_block, \ - const AzRgFunc& carrier_phase, \ - const isce3::product::RadarGridParameters& radar_grid, \ - const size_t input_azimuth_first_line, \ - const size_t input_range_first_pixel, \ - const bool conjugate \ -); \ -template void modulateAtCoords( \ - ArrayRef2D> slc_data_block, \ - const AzRgFunc& carrier_phase, \ - const isce3::product::RadarGridParameters& radar_grid, \ - const ConstArrayRef2D azimuth_indices, \ - const ConstArrayRef2D range_indices, \ - const bool conjugate \ -) -EXPLICIT_INSTANTIATION(isce3::core::LUT2d); -EXPLICIT_INSTANTIATION(isce3::core::Poly2d); - } // end namespace isce3::image::v2 diff --git a/cxx/isce3/image/Resample.h b/cxx/isce3/image/Resample.h index b35d0c6b2..05647b132 100644 --- a/cxx/isce3/image/Resample.h +++ b/cxx/isce3/image/Resample.h @@ -3,7 +3,6 @@ #include #include -#include #include namespace isce3::image::v2 { @@ -17,132 +16,6 @@ using ArrayRef2D = Eigen::Ref>; template using ConstArrayRef2D = Eigen::Ref>; -/** - * Acquire the phase of the given carrier of a radar scene. - * - * @param[out] out - * Block of data to be written to. All data in block will be overwritten. - * unit: array2D of complex - * @tparam[in] carrier_phase - * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. - * @param[in] radar_grid - * parameters for the given radar grid - * @param[in] input_azimuth_first_line - * line index of the first sample of the block of input data with respect to the origin - * of the full SLC scene - * unit: azimuth row indices (int) - * @param[in] input_range_first_pixel - * pixel index of the first sample of the block of input data with respect to the origin - * of the full SLC scene - * unit: range column indices (int) - * @param[in] conjugate - * if true, get the conjugate of the phase. - */ -template -void getModulationPhase( - ArrayRef2D> out, - const AzRgFunc& carrier_phase, - const isce3::product::RadarGridParameters& radar_grid, - const size_t input_azimuth_first_line, - const size_t input_range_first_pixel, - const bool conjugate -); - - -/** - * Acquire the phase of the given carrier at each given index of a radar scene. - * - * @param[out] out - * Block of data to be written to. All data in block will be overwritten. - * unit: array2D of complex - * @tparam[in] carrier_phase - * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. - * @param[in] radar_grid - * parameters for the given radar grid - * @param[in] azimuth_indices - * azimuth index of each output coordinate pixel in the resampling coordinate system - * unit: azimuth row indices (int) - * @param[in] range_indices - * range index of each output coordinate pixel in the resampling coordinate system - * unit: range column indices (int) - * @param[in] conjugate - * if true, modulate the conjugate of the phase. - */ -template -void getModulationPhaseAtCoords( - ArrayRef2D> out, - const AzRgFunc& carrier_phase, - const isce3::product::RadarGridParameters& radar_grid, - const ConstArrayRef2D azimuth_indices, - const ConstArrayRef2D range_indices, - const bool conjugate -); - - -/** - * Remove range and azimuth phase carrier from a block of input radar SLC data - * - * @param[out] slc_data_block - * the block of data SLC to be modulated. - * unit: array2D of complex - * @tparam[in] carrier_phase - * carrier phase of the SLC data, in radian, as a function of azimuth and range. - * This phase will be modulated to or demodulated from the image. - * @param[in] radar_grid - * parameters for the given radar grid - * @param[in] input_azimuth_first_line - * line index of the first sample of the block of input data with respect to the origin - * of the full SLC scene - * unit: azimuth row indices (int) - * @param[in] input_range_first_pixel - * pixel index of the first sample of the block of input data with respect to the origin - * of the full SLC scene - * unit: range column indices (int) - * @param[in] conjugate - * if true, modulate the conjugate of the phase. - */ -template -void modulate( - ArrayRef2D> slc_data_block, - const AzRgFunc& carrier_phase, - const isce3::product::RadarGridParameters& radar_grid, - const size_t input_azimuth_first_line, - const size_t input_range_first_pixel, - const bool conjugate -); - - -/** - * Evaluate and modulate or demodulate the phase carrier onto the given SLC data block. - * - * @param[out] slc_data_block - * the block of data SLC to be modulated. - * unit: array2D of complex - * @tparam[in] carrier_phase - * carrier phase of the SLC data, in radian, as a function of azimuth and range. - * This phase will be modulated to or demodulated from the image. - * @param[in] radar_grid - * parameters for the given radar grid - * @param[in] azimuth_indices - * azimuth index of each output coordinate pixel in the resampling coordinate system - * unit: azimuth row indices (int) - * @param[in] range_indices - * range index of each output coordinate pixel in the resampling coordinate system - * unit: range column indices (int) - * @param[in] conjugate - * if true, modulate the conjugate of the phase. - */ -template -void modulateAtCoords( - ArrayRef2D> slc_data_block, - const AzRgFunc& carrier_phase, - const isce3::product::RadarGridParameters& radar_grid, - const ConstArrayRef2D azimuth_indices, - const ConstArrayRef2D range_indices, - const bool conjugate -); - - /** Interpolate input SLC block into the index values of the output block. * * @param[out] resampled_data_block diff --git a/python/extensions/pybind_isce3/Sources.cmake b/python/extensions/pybind_isce3/Sources.cmake index 6732d3dcc..5b23c924b 100644 --- a/python/extensions/pybind_isce3/Sources.cmake +++ b/python/extensions/pybind_isce3/Sources.cmake @@ -59,6 +59,7 @@ geogrid/relocateRaster.cpp geogrid/geogrid.cpp geometry/lookIncFromSr.cpp image/image.cpp +image/Modulate.cpp image/Resample.cpp image/ResampSlc.cpp io/gdal/Dataset.cpp diff --git a/python/extensions/pybind_isce3/image/Modulate.cpp b/python/extensions/pybind_isce3/image/Modulate.cpp new file mode 100644 index 000000000..834cea583 --- /dev/null +++ b/python/extensions/pybind_isce3/image/Modulate.cpp @@ -0,0 +1,162 @@ +#include "Modulate.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace py = pybind11; + + +template +void addbindings_modulate(py::module & m) +{ + m.def( + "_get_modulation_phase", + py::overload_cast< + isce3::image::modulate::ArrayRef2D>, + const AzRgFunc&, + const isce3::product::RadarGridParameters&, + const bool + >(&isce3::image::modulate::getModulationPhase), + py::arg("out"), + py::arg("carrier_phase"), + py::arg("radar_grid"), + py::arg("conjugate"), + R"( + Acquire the phase of the given carrier of a radar scene. + + Parameters + ---------- + out: numpy.ndarray (complex64) + The output phase array to modify. Anything in this array will be + overwritten. + carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d + Carrier phase, in radian, as a function of azimuth and range. + radar_grid: isce3.product.RadarGridParameters + Parameters for the given radar grid. + conjugate: bool + If True, get the conjugate of the phase. + )" + ); + + + m.def( + "_get_modulation_phase_at_coords", + py::overload_cast< + isce3::image::modulate::ArrayRef2D>, + const AzRgFunc&, + const isce3::product::RadarGridParameters&, + const isce3::image::modulate::ArrayRef2D, + const isce3::image::modulate::ArrayRef2D, + const bool + >(&isce3::image::modulate::getModulationPhaseAtCoords), + py::arg("out"), + py::arg("carrier_phase"), + py::arg("radar_grid"), + py::arg("azimuth_indices"), + py::arg("range_indices"), + py::arg("conjugate"), + R"( + Acquire the phase of the given carrier at each given index of a radar scene. + + Parameters + ---------- + out: numpy.ndarray (complex64) + The output phase array to modify. Anything in this array will be + overwritten. + carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d + Carrier phase, in radian, as a function of azimuth and range. + radar_grid: isce3.product.RadarGridParameters + Parameters for the given radar grid. + azimuth_indices: numpy.ndarray (float64) + Azimuth index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + range_indices: numpy.ndarray (float64) + Range index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + conjugate: bool + If True, get the conjugate of the phase. + )" + ); + + + m.def( + "_modulate", + py::overload_cast< + isce3::image::modulate::ArrayRef2D>, + const AzRgFunc&, + const isce3::product::RadarGridParameters&, + const bool + >(&isce3::image::modulate::modulate), + py::arg("slc_data_block"), + py::arg("carrier_phase"), + py::arg("radar_grid"), + py::arg("conjugate"), + R"( + Evaluate and modulate or demodulate the phase carrier onto the given SLC data + block. + + Parameters + ---------- + slc_data_block: numpy.ndarray (complex64) + The block of SLC data to modulate. + carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d + Carrier phase, in radian, as a function of azimuth and range. This phase + will be modulated to or demodulated from the image. + radar_grid: isce3.product.RadarGridParameters + Parameters for the given radar grid. + conjugate: bool, optional + If True, modulate the conjugate of the phase. + )" + ); + + + m.def( + "_modulate_at_coords", + py::overload_cast< + isce3::image::modulate::ArrayRef2D>, + const AzRgFunc&, + const isce3::product::RadarGridParameters&, + const isce3::image::modulate::ArrayRef2D, + const isce3::image::modulate::ArrayRef2D, + const bool + >(&isce3::image::modulate::modulateAtCoords), + py::arg("slc_data_block"), + py::arg("carrier_phase"), + py::arg("radar_grid"), + py::arg("azimuth_indices"), + py::arg("range_indices"), + py::arg("conjugate"), + R"( + Evaluate and modulate or demodulate the phase carrier onto the given SLC data + block at the given indices. + + Parameters + ---------- + slc_data_block: numpy.ndarray (complex64) + The output phase array to modulate. + carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d + Carrier phase, in radian, as a function of azimuth and range. This phase + will be modulated to or demodulated from the image. + radar_grid: isce3.product.RadarGridParameters + Parameters for the given radar grid. + azimuth_indices: numpy.ndarray (float64) + Azimuth index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + range_indices: numpy.ndarray (float64) + Range index of each output coordinate pixel in the given radar coordinate + system. Must be the same shape as phase_data_block. + conjugate: bool, optional + If True, modulate the conjugate of the phase. + )" + ); +} + +template void addbindings_modulate>(py::module & m); +template void addbindings_modulate(py::module & m); diff --git a/python/extensions/pybind_isce3/image/Modulate.h b/python/extensions/pybind_isce3/image/Modulate.h new file mode 100644 index 000000000..4555ad681 --- /dev/null +++ b/python/extensions/pybind_isce3/image/Modulate.h @@ -0,0 +1,5 @@ +#pragma once +#include + +template +void addbindings_modulate(pybind11::module&); diff --git a/python/extensions/pybind_isce3/image/Resample.cpp b/python/extensions/pybind_isce3/image/Resample.cpp index edd208de0..b915c34c6 100644 --- a/python/extensions/pybind_isce3/image/Resample.cpp +++ b/python/extensions/pybind_isce3/image/Resample.cpp @@ -51,172 +51,3 @@ void addbindings_resamp(py::module & m) )" ); } - - -template -void addbindings_modulate(py::module & m) -{ - m.def( - "_get_modulation_phase", - py::overload_cast< - isce3::image::v2::ArrayRef2D>, - const AzRgFunc&, - const isce3::product::RadarGridParameters&, - const size_t, - const size_t, - const bool - >(&isce3::image::v2::getModulationPhase), - py::arg("out"), - py::arg("carrier_phase"), - py::arg("radar_grid"), - py::arg("input_azimuth_first_line"), - py::arg("input_range_first_pixel"), - py::arg("conjugate"), - R"( - Acquire the phase of the given carrier of a radar scene. - - Parameters - ---------- - out: numpy.ndarray (complex64) - The output phase array to modify. Anything in this array will be - overwritten. - carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d - Carrier phase, in radian, as a function of azimuth and range. - radar_grid: isce3.product.RadarGridParameters - Parameters for the given radar grid. - input_azimuth_first_line: numpy.ndarray (float64) - Line index of the first sample of the block of input data with respect to - the origin of the full SLC scene - input_range_first_pixel: numpy.ndarray (float64) - Pixel index of the first sample of the block of input data with respect to - the origin of the full SLC scene - conjugate: bool - If True, get the conjugate of the phase. - )" - ); - - - m.def( - "_get_modulation_phase_at_coords", - py::overload_cast< - isce3::image::v2::ArrayRef2D>, - const AzRgFunc&, - const isce3::product::RadarGridParameters&, - const isce3::image::v2::ArrayRef2D, - const isce3::image::v2::ArrayRef2D, - const bool - >(&isce3::image::v2::getModulationPhaseAtCoords), - py::arg("out"), - py::arg("carrier_phase"), - py::arg("radar_grid"), - py::arg("azimuth_indices"), - py::arg("range_indices"), - py::arg("conjugate"), - R"( - Acquire the phase of the given carrier at each given index of a radar scene. - - Parameters - ---------- - out: numpy.ndarray (complex64) - The output phase array to modify. Anything in this array will be - overwritten. - carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d - Carrier phase, in radian, as a function of azimuth and range. - radar_grid: isce3.product.RadarGridParameters - Parameters for the given radar grid. - azimuth_indices: numpy.ndarray (float64) - Azimuth index of each output coordinate pixel in the given radar coordinate - system. Must be the same shape as phase_data_block. - range_indices: numpy.ndarray (float64) - Range index of each output coordinate pixel in the given radar coordinate - system. Must be the same shape as phase_data_block. - conjugate: bool - If True, get the conjugate of the phase. - )" - ); - - - m.def( - "_modulate", - py::overload_cast< - isce3::image::v2::ArrayRef2D>, - const AzRgFunc&, - const isce3::product::RadarGridParameters&, - const size_t, - const size_t, - const bool - >(&isce3::image::v2::modulate), - py::arg("slc_data_block"), - py::arg("carrier_phase"), - py::arg("radar_grid"), - py::arg("input_azimuth_first_line"), - py::arg("input_range_first_pixel"), - py::arg("conjugate"), - R"( - Evaluate and modulate or demodulate the phase carrier onto the given SLC data - block. - - Parameters - ---------- - slc_data_block: numpy.ndarray (complex64) - The block of SLC data to modulate. - carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d - Carrier phase, in radian, as a function of azimuth and range. This phase - will be modulated to or demodulated from the image. - radar_grid: isce3.product.RadarGridParameters - Parameters for the given radar grid. - input_azimuth_first_line: numpy.ndarray (float64) - Line index of the first sample of the block of input data with respect to - the origin of the full SLC scene - input_range_first_pixel: numpy.ndarray (float64) - Pixel index of the first sample of the block of input data with respect to - the origin of the full SLC scene - conjugate: bool, optional - If True, modulate the conjugate of the phase. - )" - ); - - - m.def( - "_modulate_at_coords", - py::overload_cast< - isce3::image::v2::ArrayRef2D>, - const AzRgFunc&, - const isce3::product::RadarGridParameters&, - const isce3::image::v2::ArrayRef2D, - const isce3::image::v2::ArrayRef2D, - const bool - >(&isce3::image::v2::modulateAtCoords), - py::arg("slc_data_block"), - py::arg("carrier_phase"), - py::arg("radar_grid"), - py::arg("azimuth_indices"), - py::arg("range_indices"), - py::arg("conjugate"), - R"( - Evaluate and modulate or demodulate the phase carrier onto the given SLC data - block at the given indices. - - Parameters - ---------- - slc_data_block: numpy.ndarray (complex64) - The output phase array to modulate. - carrier_phase: isce3.core.LUT2d or isce3.core.Poly2d - Carrier phase, in radian, as a function of azimuth and range. This phase - will be modulated to or demodulated from the image. - radar_grid: isce3.product.RadarGridParameters - Parameters for the given radar grid. - azimuth_indices: numpy.ndarray (float64) - Azimuth index of each output coordinate pixel in the given radar coordinate - system. Must be the same shape as phase_data_block. - range_indices: numpy.ndarray (float64) - Range index of each output coordinate pixel in the given radar coordinate - system. Must be the same shape as phase_data_block. - conjugate: bool, optional - If True, modulate the conjugate of the phase. - )" - ); -} - -template void addbindings_modulate>(py::module & m); -template void addbindings_modulate(py::module & m); diff --git a/python/extensions/pybind_isce3/image/image.cpp b/python/extensions/pybind_isce3/image/image.cpp index e548d61de..339d831ca 100644 --- a/python/extensions/pybind_isce3/image/image.cpp +++ b/python/extensions/pybind_isce3/image/image.cpp @@ -1,5 +1,6 @@ #include "image.h" +#include "Modulate.h" #include "Resample.h" #include "ResampSlc.h" @@ -13,6 +14,8 @@ void addsubmodule_image(py::module & m) // Add the resample v2 functionality to the v2 module. addbindings_resamp(m_image_v2); + + // Add modulation functionality. addbindings_modulate>(m_image_modulate); addbindings_modulate(m_image_modulate); diff --git a/python/packages/isce3/image/v2/modulate.py b/python/packages/isce3/image/v2/modulate.py index 11ff226eb..c79101194 100644 --- a/python/packages/isce3/image/v2/modulate.py +++ b/python/packages/isce3/image/v2/modulate.py @@ -18,8 +18,6 @@ def modulate( slc_data_block: np.ndarray[np.complex64], carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, - input_azimuth_first_line: int, - input_range_first_pixel: int, conjugate: bool = False, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: @@ -35,12 +33,6 @@ def modulate( This phase will be modulated to or demodulated from the image. radar_grid : RadarGridParameters Parameters for the given radar grid. - input_azimuth_first_line : int - Line index of the first sample of the block of input data with respect to the - origin of the full SLC scene - input_range_first_pixel : int - Pixel index of the first sample of the block of input data with respect to the - origin of the full SLC scene conjugate : bool, optional If True, modulate the conjugate of the phase, by default False out : np.ndarray of np.complex64 | None, optional @@ -61,11 +53,9 @@ def modulate( np.copyto(out_array, slc_data_block) _modulate( - slc_data_block=out, + slc_data_block=out_array, carrier_phase=carrier_phase, radar_grid=radar_grid, - input_azimuth_first_line=input_azimuth_first_line, - input_range_first_pixel=input_range_first_pixel, conjugate=conjugate, ) @@ -152,8 +142,6 @@ def modulate_at_coords( def get_modulation_phase( carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, - input_azimuth_first_line: int, - input_range_first_pixel: int, conjugate: bool = False, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: @@ -166,12 +154,6 @@ def get_modulation_phase( Carrier phase, in radian, as a function of azimuth and range. radar_grid : RadarGridParameters Parameters for the given radar grid. - input_azimuth_first_line : int - Line index of the first sample of the block of input data with respect to the - origin of the full SLC scene - input_range_first_pixel : int - Pixel index of the first sample of the block of input data with respect to the - origin of the full SLC scene conjugate : bool, optional If True, get the conjugate of the phase, by default False out : np.ndarray[np.complex64] | None, optional @@ -194,8 +176,6 @@ def get_modulation_phase( out=out_array, carrier_phase=carrier_phase, radar_grid=radar_grid, - input_azimuth_first_line=input_azimuth_first_line, - input_range_first_pixel=input_range_first_pixel, conjugate=conjugate, ) diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index 2f448b545..5c24050e3 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -228,25 +228,20 @@ def resample_slc_blocks( # Run the resampling algorithm on the given blocks. for i in range(len(input_blocks)): input_block = input_blocks[i] + block_grid = input_radar_grid[in_slices] if phase_carriers is not None: if not quiet: info_channel.log( f"demodulating input SLC for block {out_block_slice}..." ) - - in_az_slice, in_rg_slice = in_slices - in_az_first_line = in_az_slice.start - in_rg_first_pixel = in_rg_slice.start for carrier in phase_carriers: modulate( slc_data_block=input_block, out=input_block, carrier_phase=carrier, - radar_grid=input_radar_grid, - input_azimuth_first_line=in_az_first_line, - input_range_first_pixel=in_rg_first_pixel, + radar_grid=block_grid, conjugate=True, ) @@ -261,7 +256,7 @@ def resample_slc_blocks( input_block, range_index_grid, azimuth_index_grid, - input_radar_grid[in_slices], + block_grid, doppler, fill_value, ) @@ -278,7 +273,7 @@ def resample_slc_blocks( slc_data_block=output_block, out=output_block, carrier_phase=carrier, - radar_grid=input_radar_grid, + radar_grid=block_grid, azimuth_indices=azimuth_index_grid, range_indices=range_index_grid, conjugate=False, From 85bb655f68f9d32809416838f5510237d05deb38 Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 27 Mar 2024 21:57:02 +0000 Subject: [PATCH 18/35] Made the "at coords" functions private in C++ --- cxx/isce3/image/Modulate.cpp | 8 +++--- cxx/isce3/image/Modulate.h | 26 ++++++++++++------- .../pybind_isce3/image/Modulate.cpp | 4 +-- python/packages/isce3/image/v2/modulate.py | 8 +++--- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/cxx/isce3/image/Modulate.cpp b/cxx/isce3/image/Modulate.cpp index 1ff63d85a..53e005130 100644 --- a/cxx/isce3/image/Modulate.cpp +++ b/cxx/isce3/image/Modulate.cpp @@ -67,7 +67,7 @@ void getModulationPhase( template -void getModulationPhaseAtCoords( +void _getModulationPhaseAtCoords( ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -146,7 +146,7 @@ void modulate( template -void modulateAtCoords( +void _modulateAtCoords( ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -194,7 +194,7 @@ template void getModulationPhase( \ const isce3::product::RadarGridParameters& radar_grid, \ const bool conjugate \ ); \ -template void getModulationPhaseAtCoords( \ +template void _getModulationPhaseAtCoords( \ ArrayRef2D> out, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ @@ -208,7 +208,7 @@ template void modulate( \ const isce3::product::RadarGridParameters& radar_grid, \ const bool conjugate \ ); \ -template void modulateAtCoords( \ +template void _modulateAtCoords( \ ArrayRef2D> slc_data_block, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ diff --git a/cxx/isce3/image/Modulate.h b/cxx/isce3/image/Modulate.h index fac1ac214..f3a33d2bc 100644 --- a/cxx/isce3/image/Modulate.h +++ b/cxx/isce3/image/Modulate.h @@ -24,9 +24,9 @@ using ConstArrayRef2D = Eigen::Ref>; * Block of data to be written to. All data in block will be overwritten. * unit: array2D of complex * @tparam[in] carrier_phase - * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. + * azimuth carrier phase of the SLC data, ::modulate, as a function of azimuth and range. * @param[in] radar_grid - * parameters for the given radar grid + * parameters for the given radar grid corresponding to the output block. * @param[in] conjugate * if true, get the conjugate of the phase. */ @@ -41,14 +41,17 @@ void getModulationPhase( /** * Acquire the phase of the given carrier at each given index of a radar scene. + * + * Note that this function does not perform size checks between `out` and the indices + * blocks and radar grid. * * @param[out] out * Block of data to be written to. All data in block will be overwritten. * unit: array2D of complex * @tparam[in] carrier_phase - * azimuth carrier phase of the SLC data, in radian, as a function of azimuth and range. + * azimuth carrier phase of the SLC data, in radians, as a function of azimuth and range. * @param[in] radar_grid - * parameters for the given radar grid + * parameters for the given radar grid corresponding to the output block. * @param[in] azimuth_indices * azimuth index of each pixel in the output block w.r.t the radar grid. Must be the * same shape as `out`. @@ -61,7 +64,7 @@ void getModulationPhase( * if true, modulate the conjugate of the phase. */ template -void getModulationPhaseAtCoords( +void _getModulationPhaseAtCoords( ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -78,10 +81,10 @@ void getModulationPhaseAtCoords( * the block of data SLC to be modulated. * unit: array2D of complex * @tparam[in] carrier_phase - * carrier phase of the SLC data, in radian, as a function of azimuth and range. + * carrier phase of the SLC data, in radians, as a function of azimuth and range. * This phase will be modulated to or demodulated from the image. * @param[in] radar_grid - * parameters for the given radar grid + * parameters for the given radar grid corresponding to `slc_data_block`. * @param[in] conjugate * if true, modulate the conjugate of the phase. */ @@ -96,15 +99,18 @@ void modulate( /** * Evaluate and modulate or demodulate the phase carrier onto the given SLC data block. + * + * Note that this function does not perform size checks between `slc_data_block` and the + * indices blocks and radar grid. * * @param[out] slc_data_block * the block of data SLC to be modulated. * unit: array2D of complex * @tparam[in] carrier_phase - * carrier phase of the SLC data, in radian, as a function of azimuth and range. + * carrier phase of the SLC data, in radians, as a function of azimuth and range. * This phase will be modulated to or demodulated from the image. * @param[in] radar_grid - * parameters for the given radar grid + * parameters for the given radar grid corresponding to `slc_data_block`. * @param[in] azimuth_indices * azimuth index of each pixel in the output block w.r.t the radar grid. Must be the * same shape as `slc_data_block`. @@ -117,7 +123,7 @@ void modulate( * if true, modulate the conjugate of the phase. */ template -void modulateAtCoords( +void _modulateAtCoords( ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, diff --git a/python/extensions/pybind_isce3/image/Modulate.cpp b/python/extensions/pybind_isce3/image/Modulate.cpp index 834cea583..5f2deb252 100644 --- a/python/extensions/pybind_isce3/image/Modulate.cpp +++ b/python/extensions/pybind_isce3/image/Modulate.cpp @@ -55,7 +55,7 @@ void addbindings_modulate(py::module & m) const isce3::image::modulate::ArrayRef2D, const isce3::image::modulate::ArrayRef2D, const bool - >(&isce3::image::modulate::getModulationPhaseAtCoords), + >(&isce3::image::modulate::_getModulationPhaseAtCoords), py::arg("out"), py::arg("carrier_phase"), py::arg("radar_grid"), @@ -126,7 +126,7 @@ void addbindings_modulate(py::module & m) const isce3::image::modulate::ArrayRef2D, const isce3::image::modulate::ArrayRef2D, const bool - >(&isce3::image::modulate::modulateAtCoords), + >(&isce3::image::modulate::_modulateAtCoords), py::arg("slc_data_block"), py::arg("carrier_phase"), py::arg("radar_grid"), diff --git a/python/packages/isce3/image/v2/modulate.py b/python/packages/isce3/image/v2/modulate.py index c79101194..8f464b1e2 100644 --- a/python/packages/isce3/image/v2/modulate.py +++ b/python/packages/isce3/image/v2/modulate.py @@ -32,7 +32,7 @@ def modulate( Carrier phase of the SLC data, in radian, as a function of azimuth and range. This phase will be modulated to or demodulated from the image. radar_grid : RadarGridParameters - Parameters for the given radar grid. + Parameters for the given radar grid corresponding to `slc_data_block`. conjugate : bool, optional If True, modulate the conjugate of the phase, by default False out : np.ndarray of np.complex64 | None, optional @@ -83,7 +83,7 @@ def modulate_at_coords( Carrier phase of the SLC data, in radian, as a function of azimuth and range. This phase will be modulated to or demodulated from the image. radar_grid : RadarGridParameters - Parameters for the given radar grid. + Parameters for the given radar grid corresponding to `slc_data_block`. azimuth_indices : np.ndarray of np.float64 Azimuth index of each output coordinate pixel in the given radar coordinate system. Must be the same shape as phase_data_block. @@ -153,7 +153,7 @@ def get_modulation_phase( carrier_phase : LUT2d or Poly2d Carrier phase, in radian, as a function of azimuth and range. radar_grid : RadarGridParameters - Parameters for the given radar grid. + Parameters for the given radar grid corresponding to the output block. conjugate : bool, optional If True, get the conjugate of the phase, by default False out : np.ndarray[np.complex64] | None, optional @@ -198,7 +198,7 @@ def get_modulation_phase_at_coords( carrier_phase : LUT2d or Poly2d Carrier phase, in radian, as a function of azimuth and range. radar_grid : RadarGridParameters - Parameters for the given radar grid. + Parameters for the radar grid corresponding to the output block. azimuth_indices : np.ndarray of np.float64 Azimuth index of each output coordinate pixel in the given radar coordinate system. Must be the same shape as phase_data_block. From b75ceb275e8cadd0a783d3c920bd90fc3a14a84f Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 27 Mar 2024 22:11:52 +0000 Subject: [PATCH 19/35] Separated the modulation code into its own namespaces and modules --- python/extensions/pybind_isce3/image/Modulate.cpp | 8 ++++---- python/packages/isce3/image/{v2 => }/modulate.py | 0 python/packages/isce3/image/v2/resample_slc.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename python/packages/isce3/image/{v2 => }/modulate.py (100%) diff --git a/python/extensions/pybind_isce3/image/Modulate.cpp b/python/extensions/pybind_isce3/image/Modulate.cpp index 5f2deb252..18d93566b 100644 --- a/python/extensions/pybind_isce3/image/Modulate.cpp +++ b/python/extensions/pybind_isce3/image/Modulate.cpp @@ -52,8 +52,8 @@ void addbindings_modulate(py::module & m) isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, const isce3::product::RadarGridParameters&, - const isce3::image::modulate::ArrayRef2D, - const isce3::image::modulate::ArrayRef2D, + const isce3::image::modulate::ArrayRefConst2D, + const isce3::image::modulate::ArrayRefConst2D, const bool >(&isce3::image::modulate::_getModulationPhaseAtCoords), py::arg("out"), @@ -123,8 +123,8 @@ void addbindings_modulate(py::module & m) isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, const isce3::product::RadarGridParameters&, - const isce3::image::modulate::ArrayRef2D, - const isce3::image::modulate::ArrayRef2D, + const isce3::image::modulate::ArrayRefConst2D, + const isce3::image::modulate::ArrayRefConst2D, const bool >(&isce3::image::modulate::_modulateAtCoords), py::arg("slc_data_block"), diff --git a/python/packages/isce3/image/v2/modulate.py b/python/packages/isce3/image/modulate.py similarity index 100% rename from python/packages/isce3/image/v2/modulate.py rename to python/packages/isce3/image/modulate.py diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index 5c24050e3..e2c1d7a63 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -9,7 +9,7 @@ from isce3.core.poly2d import Poly2d from isce3.core.resample_block_generators import get_blocks, get_blocks_by_offsets from isce3.ext.isce3.image.v2 import _resample_to_coords -from isce3.image.v2.modulate import modulate, modulate_at_coords +from isce3.image.modulate import modulate, modulate_at_coords from isce3.io.dataset import DatasetReader, DatasetWriter from isce3.product import RadarGridParameters From 5ae20e779d644d689b760894f2f096608d4af441 Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 27 Mar 2024 22:21:41 +0000 Subject: [PATCH 20/35] Removed an excess function declaration from pybind Resample.h --- python/extensions/pybind_isce3/image/Resample.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/python/extensions/pybind_isce3/image/Resample.h b/python/extensions/pybind_isce3/image/Resample.h index 9fb6c8f91..86585ab31 100644 --- a/python/extensions/pybind_isce3/image/Resample.h +++ b/python/extensions/pybind_isce3/image/Resample.h @@ -2,6 +2,3 @@ #include void addbindings_resamp(pybind11::module&); - -template -void addbindings_modulate(pybind11::module&); From 22d62f3b8912b460e01e8663c7d88227823c353c Mon Sep 17 00:00:00 2001 From: thudson Date: Mon, 1 Apr 2024 20:20:01 +0000 Subject: [PATCH 21/35] Fixed a C++ bug that would cause confusing errors on out-of-frame indices --- cxx/isce3/core/Poly2d.h | 11 +++++++++++ cxx/isce3/image/Modulate.cpp | 18 +++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/cxx/isce3/core/Poly2d.h b/cxx/isce3/core/Poly2d.h index 5d96c3875..539b1e540 100644 --- a/cxx/isce3/core/Poly2d.h +++ b/cxx/isce3/core/Poly2d.h @@ -72,6 +72,17 @@ class isce3::core::Poly2d { /**Get coefficient by indices*/ inline double getCoeff(int row, int col) const; + /** + * Check if point resides in domain of Poly2d. Added for interoperability with + * LUT2d, but always assumed to be true. + */ + inline bool contains(double y, double x) const { + if (std::isnan(y) || std::isnan(x)){ + return false; + } + return true; + } + /**Evaluate polynomial at given y/azimuth/row ,x/range/col*/ double eval(double y, double x) const; diff --git a/cxx/isce3/image/Modulate.cpp b/cxx/isce3/image/Modulate.cpp index 53e005130..f720477cd 100644 --- a/cxx/isce3/image/Modulate.cpp +++ b/cxx/isce3/image/Modulate.cpp @@ -53,6 +53,10 @@ void getModulationPhase( // unit: distance (meters) const double range = radar_grid.startingRange() + rg_index * radar_grid.rangePixelSpacing(); + + if(not carrier_phase.contains(azimuth, range)){ + continue; + } // Get the carrier unit phasor for this pixel const auto phasor = _getPixelCarrierPhase( @@ -95,6 +99,10 @@ void _getModulationPhaseAtCoords( // Slant Range at the current output pixel const double range = radar_grid.startingRange() + rg_carrier_index * radar_grid.rangePixelSpacing(); + + if(not carrier_phase.contains(azimuth, range)){ + continue; + } // Get the carrier phasor for this pixel const auto phasor = _getPixelCarrierPhase( @@ -132,6 +140,10 @@ void modulate( // unit: distance (meters) const double range = radar_grid.startingRange() + rg_index * radar_grid.rangePixelSpacing(); + + if(not carrier_phase.contains(azimuth, range)){ + continue; + } // Get the carrier phasor for this pixel const auto phasor = _getPixelCarrierPhase( @@ -174,7 +186,11 @@ void _modulateAtCoords( // Slant Range at the current output pixel const double range = radar_grid.startingRange() + rg_carrier_index * radar_grid.rangePixelSpacing(); - + + if(not carrier_phase.contains(azimuth, range)){ + continue; + } + // Get the carrier phasor for this pixel const auto phasor = _getPixelCarrierPhase( azimuth, range, carrier_phase, conjugate From 5fe5b810eb00e527783ace35a42a1ac5e2baa27e Mon Sep 17 00:00:00 2001 From: thudson Date: Mon, 1 Apr 2024 21:38:36 +0000 Subject: [PATCH 22/35] Added a battery of tests that check each of the modulation functions. --- tests/python/packages/CMakeLists.txt | 1 + tests/python/packages/isce3/image/modulate.py | 218 ++++++++++++++++++ .../packages/isce3/resample_slc_utils.py | 6 +- 3 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 tests/python/packages/isce3/image/modulate.py diff --git a/tests/python/packages/CMakeLists.txt b/tests/python/packages/CMakeLists.txt index d37588fe1..7b92997f9 100644 --- a/tests/python/packages/CMakeLists.txt +++ b/tests/python/packages/CMakeLists.txt @@ -19,6 +19,7 @@ isce3/geometry/bounding_radar_grid.py isce3/geometry/polygons.py isce3/geometry/radar_grid_spacing.py isce3/image/resample_slc.py +isce3/image/modulate.py isce3/io/gdal/gdal_raster.py isce3/io/background.py isce3/matchtemplate/import_ampcor.py diff --git a/tests/python/packages/isce3/image/modulate.py b/tests/python/packages/isce3/image/modulate.py new file mode 100644 index 000000000..2d7ffabc0 --- /dev/null +++ b/tests/python/packages/isce3/image/modulate.py @@ -0,0 +1,218 @@ +"""Test resample_slc ver. 2""" +from __future__ import annotations + +import pytest +import numpy as np + +from isce3.image.modulate import ( + get_modulation_phase, + get_modulation_phase_at_coords, + modulate, + modulate_at_coords, +) +from isce3.core import DateTime, LUT2d +from isce3.product import RadarGridParameters + +from .resample_slc_utils import ( + generate_doppler_ramp_complex, + validate_test_results, +) + + +def generate_carrier_lut( + grid_params: RadarGridParameters, frequency: float +) -> LUT2d: + """ + Create a constant carrier LUT. + + Parameters + ---------- + grid_params : RadarGridParameters + The radar grid parameters to evaluate azimuth and range with. + frequency : float + The frequency of the LUT to be created. + + Returns + ------- + LUT2d + The generated LUT. + """ + # Trivially, for zero doppler just return an LUT2d that always evaluates to 0. + if frequency == 0: + return LUT2d() + + array_length = grid_params.length + array_width = grid_params.width + az_indices = np.arange(array_length) + rg_indices = np.arange(array_width) + + az_time = grid_params.sensing_start + az_indices / grid_params.prf + rg_dist = grid_params.starting_range + rg_indices / grid_params.range_pixel_spacing + + # Evaluate the integral + lut_array = np.full( + shape=(array_length, array_width), + fill_value=2 * np.pi * frequency, + dtype=np.float64, + ) * az_time[:, np.newaxis] + + lut = LUT2d( + rg_dist, + az_time, + lut_array, + ) + + return lut + + +@pytest.mark.parametrize( + "function", + [ + "get_modulation_phase", + "get_modulation_phase_at_coords", + "modulate", + "modulate_at_coords" + ] +) +class TestModulate: + """Tests for the image.modulate code.""" + + @pytest.mark.parametrize("frequency", [0.1, 0.25, 0.5]) + @pytest.mark.parametrize("conjugate", [True, False]) + def test_modulation_constant_frequency( + self, frequency: float, function: str, conjugate: bool + ) -> tuple[np.ndarray[np.complex64], np.ndarray[np.complex64]]: + """ + Tests the Resample SLC V2 on a randomly distributed target signal. + + This test is designed to assess if Resample SLC works for a reasonably educated + attempt at a realistic input signal. Fail cases caught by this test include all + those tested for in the sinusoidal test, but using a more rigorous and + complicated secondary signal. + + This test also tests the zero-doppler case as well as a set of several doppler + frequencies as provided by the frequency fixture. + + Fixtures + ---------- + frequency : float + A doppler frequency for this test, in Hz. + """ + # The size of the generated image. + az_length = 100 + rg_width = 100 + out_shape = (az_length, rg_width) + + # Set up a dummy radar grid + radar_grid: RadarGridParameters = RadarGridParameters( + sensing_start=1, + wavelength=1, + prf=1, + starting_range=1, + range_pixel_spacing=1, + look_side="right", + length=az_length, + width=rg_width, + ref_epoch=DateTime(), + ) + + # Generate a doppler centroid that has a ramp + lut: LUT2d = generate_carrier_lut( + grid_params=radar_grid, + frequency=frequency, + ) + + # This will hold the output of the function + signal = np.full( + (az_length, rg_width), + fill_value=1. + 0.j, + dtype=np.complex64, + ) + + # This will hold the actual expected value + doppler_ramp_complex: np.ndarray[np.complex64] + + # Perform the interpolation. + if function in ["get_modulation_phase", "modulate"]: + + # Create the expected output signal + doppler_ramp_complex = signal * generate_doppler_ramp_complex( + grid_params=radar_grid, + az_indices=np.arange(az_length), + doppler_frequency=frequency, + )[:, np.newaxis] + + # Run the selected function + if function == "get_modulation_phase": + signal = get_modulation_phase( + carrier_phase=lut, + radar_grid=radar_grid, + conjugate=conjugate, + ) + + elif function == "modulate": + signal = modulate( + slc_data_block=signal, + carrier_phase=lut, + radar_grid=radar_grid, + conjugate=conjugate, + ) + + elif function in ["get_modulation_phase_at_coords", "modulate_at_coords"]: + # Set the offsets at random positions with a range of -1.5 to 1.5 with a + # flat probability distribution. This ensures that the difference in phase + # and potential edge effects near the ends of an image are detectable. + mag_offset = 3 + az_offsets = np.random.random(out_shape) * mag_offset - mag_offset / 2 + rg_offsets = np.random.random(out_shape) * mag_offset - mag_offset / 2 + + # Add the offsets to these indices to get the indices to evaluate at. + rows, cols = np.indices(out_shape) + azimuth_indices = np.array(az_offsets + rows, dtype=np.float64) + range_indices = np.array(rg_offsets + cols, dtype=np.float64) + + # Create the expected output signal + doppler_ramp_complex = generate_doppler_ramp_complex( + grid_params=radar_grid, + az_indices=azimuth_indices, + doppler_frequency=frequency, + ) + + # Run the selected function + if function == "get_modulation_phase_at_coords": + signal = get_modulation_phase_at_coords( + carrier_phase=lut, + radar_grid=radar_grid, + azimuth_indices=azimuth_indices, + range_indices=range_indices, + conjugate=conjugate, + ) + elif function == "modulate_at_coords": + signal = modulate_at_coords( + slc_data_block=signal, + carrier_phase=lut, + radar_grid=radar_grid, + azimuth_indices=azimuth_indices, + range_indices=range_indices, + conjugate=conjugate, + ) + if conjugate: + doppler_ramp_complex = np.conjugate(doppler_ramp_complex) + + try: + # Validate the generated data against the true data. + validate_test_results( + test_arr=signal, + true_arr=doppler_ramp_complex, + correlation_min=0.99999, + phase_stdev_max=1e-6, + nan_percent_max=3, + buffer_size_az=2, + buffer_size_rg=2, + az_offset=0, + rg_offset=0, + ) + except AssertionError as err: + err.add_note(function) + err.add_note(f"frequency: {frequency}") + raise err diff --git a/tests/python/packages/isce3/resample_slc_utils.py b/tests/python/packages/isce3/resample_slc_utils.py index 7728de2c6..30e9a822b 100644 --- a/tests/python/packages/isce3/resample_slc_utils.py +++ b/tests/python/packages/isce3/resample_slc_utils.py @@ -133,20 +133,20 @@ def validate_test_results( if not corr > correlation_min: fail = True fail_strings.append( - f"Correlation {corr} of resample output is less than minimum acceptable " + f"Correlation {corr} of test output is less than minimum acceptable " f"value {correlation_min}" ) if not phase_stdev_degrees < phase_stdev_max: fail = True fail_strings.append( - f"Phase standard deviation between ground-truth and resample output " + f"Phase standard deviation between ground-truth and test output " f"{phase_stdev_degrees} is greater than maximum acceptable value " f"{phase_stdev_max}" ) if not nan_percent < nan_percent_max: fail = True fail_strings.append( - f"percentage of NaNs in resample output: {nan_percent}% is greater than " + f"percentage of NaNs in test output: {nan_percent}% is greater than " f"maximum acceptable value: {nan_percent_max}%" ) From 8795217e67ad044898a2be993b0687f639de710f Mon Sep 17 00:00:00 2001 From: thudson Date: Mon, 1 Apr 2024 23:14:36 +0000 Subject: [PATCH 23/35] Added a fill_value parameter to the functions to avoid incomplete modulation of data --- cxx/isce3/image/Modulate.cpp | 28 +++++++++++----- cxx/isce3/image/Modulate.h | 20 +++++++++--- .../pybind_isce3/image/Modulate.cpp | 32 ++++++++++++++++--- python/packages/isce3/image/modulate.py | 16 ++++++++++ 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/cxx/isce3/image/Modulate.cpp b/cxx/isce3/image/Modulate.cpp index f720477cd..de619e28b 100644 --- a/cxx/isce3/image/Modulate.cpp +++ b/cxx/isce3/image/Modulate.cpp @@ -34,7 +34,8 @@ void getModulationPhase( ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, - const bool conjugate + const bool conjugate, + const std::complex fill_value ) { // unit: azimuth row indices (int) @@ -55,6 +56,7 @@ void getModulationPhase( rg_index * radar_grid.rangePixelSpacing(); if(not carrier_phase.contains(azimuth, range)){ + out(az_index, rg_index) = fill_value; continue; } @@ -77,7 +79,8 @@ void _getModulationPhaseAtCoords( const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, const ConstArrayRef2D range_indices, - const bool conjugate + const bool conjugate, + const std::complex fill_value ) { const size_t outWidth = out.cols(); @@ -101,6 +104,7 @@ void _getModulationPhaseAtCoords( radar_grid.rangePixelSpacing(); if(not carrier_phase.contains(azimuth, range)){ + out(az_index, rg_index) = fill_value; continue; } @@ -121,7 +125,8 @@ void modulate( ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, - const bool conjugate + const bool conjugate, + const std::complex fill_value ) { // unit: azimuth row indices (int) @@ -142,6 +147,7 @@ void modulate( rg_index * radar_grid.rangePixelSpacing(); if(not carrier_phase.contains(azimuth, range)){ + slc_data_block(az_index, rg_index) = fill_value; continue; } @@ -164,7 +170,8 @@ void _modulateAtCoords( const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, const ConstArrayRef2D range_indices, - const bool conjugate + const bool conjugate, + const std::complex fill_value ) { const size_t outWidth = slc_data_block.cols(); @@ -188,6 +195,7 @@ void _modulateAtCoords( radar_grid.rangePixelSpacing(); if(not carrier_phase.contains(azimuth, range)){ + slc_data_block(az_index, rg_index) = fill_value; continue; } @@ -208,7 +216,8 @@ template void getModulationPhase( \ ArrayRef2D> out, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ - const bool conjugate \ + const bool conjugate, \ + const std::complex fill_value \ ); \ template void _getModulationPhaseAtCoords( \ ArrayRef2D> out, \ @@ -216,13 +225,15 @@ template void _getModulationPhaseAtCoords( \ const isce3::product::RadarGridParameters& radar_grid, \ const ConstArrayRef2D azimuth_indices, \ const ConstArrayRef2D range_indices, \ - const bool conjugate \ + const bool conjugate, \ + const std::complex fill_value \ ); \ template void modulate( \ ArrayRef2D> slc_data_block, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ - const bool conjugate \ + const bool conjugate, \ + const std::complex fill_value \ ); \ template void _modulateAtCoords( \ ArrayRef2D> slc_data_block, \ @@ -230,7 +241,8 @@ template void _modulateAtCoords( \ const isce3::product::RadarGridParameters& radar_grid, \ const ConstArrayRef2D azimuth_indices, \ const ConstArrayRef2D range_indices, \ - const bool conjugate \ + const bool conjugate, \ + const std::complex fill_value \ ) EXPLICIT_INSTANTIATION(isce3::core::LUT2d); EXPLICIT_INSTANTIATION(isce3::core::Poly2d); diff --git a/cxx/isce3/image/Modulate.h b/cxx/isce3/image/Modulate.h index f3a33d2bc..94c89f4a5 100644 --- a/cxx/isce3/image/Modulate.h +++ b/cxx/isce3/image/Modulate.h @@ -29,13 +29,16 @@ using ConstArrayRef2D = Eigen::Ref>; * parameters for the given radar grid corresponding to the output block. * @param[in] conjugate * if true, get the conjugate of the phase. + * @param[in] fill_value + * The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. */ template void getModulationPhase( ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, - const bool conjugate + const bool conjugate, + const std::complex fill_value ); @@ -62,6 +65,8 @@ void getModulationPhase( * unit: range column indices (int) * @param[in] conjugate * if true, modulate the conjugate of the phase. + * @param[in] fill_value + * The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. */ template void _getModulationPhaseAtCoords( @@ -70,7 +75,8 @@ void _getModulationPhaseAtCoords( const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, const ConstArrayRef2D range_indices, - const bool conjugate + const bool conjugate, + const std::complex fill_value ); @@ -87,13 +93,16 @@ void _getModulationPhaseAtCoords( * parameters for the given radar grid corresponding to `slc_data_block`. * @param[in] conjugate * if true, modulate the conjugate of the phase. + * @param[in] fill_value + * The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. */ template void modulate( ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, - const bool conjugate + const bool conjugate, + const std::complex fill_value ); @@ -121,6 +130,8 @@ void modulate( * unit: range column indices (int) * @param[in] conjugate * if true, modulate the conjugate of the phase. + * @param[in] fill_value + * The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. */ template void _modulateAtCoords( @@ -129,7 +140,8 @@ void _modulateAtCoords( const isce3::product::RadarGridParameters& radar_grid, const ConstArrayRef2D azimuth_indices, const ConstArrayRef2D range_indices, - const bool conjugate + const bool conjugate, + const std::complex fill_value ); } // namespace isce3::image::modulate diff --git a/python/extensions/pybind_isce3/image/Modulate.cpp b/python/extensions/pybind_isce3/image/Modulate.cpp index 18d93566b..03b5c57ab 100644 --- a/python/extensions/pybind_isce3/image/Modulate.cpp +++ b/python/extensions/pybind_isce3/image/Modulate.cpp @@ -22,12 +22,16 @@ void addbindings_modulate(py::module & m) isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, const isce3::product::RadarGridParameters&, - const bool + const bool, + const std::complex >(&isce3::image::modulate::getModulationPhase), py::arg("out"), py::arg("carrier_phase"), py::arg("radar_grid"), py::arg("conjugate"), + py::arg("fill_value") = + std::complex(std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN()), R"( Acquire the phase of the given carrier of a radar scene. @@ -42,6 +46,8 @@ void addbindings_modulate(py::module & m) Parameters for the given radar grid. conjugate: bool If True, get the conjugate of the phase. + fill_value: complex + The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. )" ); @@ -54,7 +60,8 @@ void addbindings_modulate(py::module & m) const isce3::product::RadarGridParameters&, const isce3::image::modulate::ArrayRefConst2D, const isce3::image::modulate::ArrayRefConst2D, - const bool + const bool, + const std::complex >(&isce3::image::modulate::_getModulationPhaseAtCoords), py::arg("out"), py::arg("carrier_phase"), @@ -62,6 +69,9 @@ void addbindings_modulate(py::module & m) py::arg("azimuth_indices"), py::arg("range_indices"), py::arg("conjugate"), + py::arg("fill_value") = + std::complex(std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN()), R"( Acquire the phase of the given carrier at each given index of a radar scene. @@ -82,6 +92,8 @@ void addbindings_modulate(py::module & m) system. Must be the same shape as phase_data_block. conjugate: bool If True, get the conjugate of the phase. + fill_value: complex + The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. )" ); @@ -92,12 +104,16 @@ void addbindings_modulate(py::module & m) isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, const isce3::product::RadarGridParameters&, - const bool + const bool, + const std::complex >(&isce3::image::modulate::modulate), py::arg("slc_data_block"), py::arg("carrier_phase"), py::arg("radar_grid"), py::arg("conjugate"), + py::arg("fill_value") = + std::complex(std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN()), R"( Evaluate and modulate or demodulate the phase carrier onto the given SLC data block. @@ -113,6 +129,8 @@ void addbindings_modulate(py::module & m) Parameters for the given radar grid. conjugate: bool, optional If True, modulate the conjugate of the phase. + fill_value: complex + The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. )" ); @@ -125,7 +143,8 @@ void addbindings_modulate(py::module & m) const isce3::product::RadarGridParameters&, const isce3::image::modulate::ArrayRefConst2D, const isce3::image::modulate::ArrayRefConst2D, - const bool + const bool, + const std::complex >(&isce3::image::modulate::_modulateAtCoords), py::arg("slc_data_block"), py::arg("carrier_phase"), @@ -133,6 +152,9 @@ void addbindings_modulate(py::module & m) py::arg("azimuth_indices"), py::arg("range_indices"), py::arg("conjugate"), + py::arg("fill_value") = + std::complex(std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN()), R"( Evaluate and modulate or demodulate the phase carrier onto the given SLC data block at the given indices. @@ -154,6 +176,8 @@ void addbindings_modulate(py::module & m) system. Must be the same shape as phase_data_block. conjugate: bool, optional If True, modulate the conjugate of the phase. + fill_value: complex + The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. )" ); } diff --git a/python/packages/isce3/image/modulate.py b/python/packages/isce3/image/modulate.py index 8f464b1e2..89da3dfa9 100644 --- a/python/packages/isce3/image/modulate.py +++ b/python/packages/isce3/image/modulate.py @@ -19,6 +19,7 @@ def modulate( carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, conjugate: bool = False, + fill_value: np.complex64 = np.nan + 1.j * np.nan, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: """ @@ -35,6 +36,8 @@ def modulate( Parameters for the given radar grid corresponding to `slc_data_block`. conjugate : bool, optional If True, modulate the conjugate of the phase, by default False + fill_value: complex + The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. out : np.ndarray of np.complex64 | None, optional The array to output data to, or None. If given, must be the same size as slc_data_block. Any contents of this array will he overwritten, by default None @@ -57,6 +60,7 @@ def modulate( carrier_phase=carrier_phase, radar_grid=radar_grid, conjugate=conjugate, + fill_value=fill_value, ) return out_array @@ -69,6 +73,7 @@ def modulate_at_coords( azimuth_indices: np.ndarray[np.float64], range_indices: np.ndarray[np.float64], conjugate: bool = False, + fill_value: np.complex64 = np.nan + 1.j * np.nan, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: """ @@ -92,6 +97,8 @@ def modulate_at_coords( system. Must be the same shape as phase_data_block. conjugate : bool, optional If True, modulate the conjugate of the phase, by default False + fill_value: complex + The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. out : np.ndarray of np.complex64 | None, optional The array to output data to, or None. If given, must be the same size as slc_data_block. Any contents of this array will he overwritten, by default None @@ -134,6 +141,7 @@ def modulate_at_coords( azimuth_indices=azimuth_indices, range_indices=range_indices, conjugate=conjugate, + fill_value=fill_value, ) return out_array @@ -143,6 +151,7 @@ def get_modulation_phase( carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, conjugate: bool = False, + fill_value: np.complex64 = np.nan + 1.j * np.nan, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: """ @@ -156,6 +165,8 @@ def get_modulation_phase( Parameters for the given radar grid corresponding to the output block. conjugate : bool, optional If True, get the conjugate of the phase, by default False + fill_value: complex + The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. out : np.ndarray[np.complex64] | None, optional The output phase array to modify. Anything in this array will be overwritten. Defaults to None @@ -177,6 +188,7 @@ def get_modulation_phase( carrier_phase=carrier_phase, radar_grid=radar_grid, conjugate=conjugate, + fill_value=fill_value, ) return out_array @@ -188,6 +200,7 @@ def get_modulation_phase_at_coords( azimuth_indices: np.ndarray[np.float64], range_indices: np.ndarray[np.float64], conjugate: bool = False, + fill_value: np.complex64 = np.nan + 1.j * np.nan, out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: """ @@ -207,6 +220,8 @@ def get_modulation_phase_at_coords( system. Must be the same shape as phase_data_block. conjugate : bool, optional If True, get the conjugate of the phase, by default False + fill_value: complex + The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. out : np.ndarray[np.complex64] | None, optional The output phase array to modify. Anything in this array will be overwritten. Defaults to None @@ -247,6 +262,7 @@ def get_modulation_phase_at_coords( azimuth_indices=azimuth_indices, range_indices=range_indices, conjugate=conjugate, + fill_value=fill_value, ) return out_array From 57be653513cd479e1fa66e8efee45d1996e31bb7 Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 2 Apr 2024 15:48:09 +0000 Subject: [PATCH 24/35] Commenting, docstring update, switched to use kwargs --- tests/python/packages/isce3/image/modulate.py | 96 ++++++++++--------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/tests/python/packages/isce3/image/modulate.py b/tests/python/packages/isce3/image/modulate.py index 2d7ffabc0..45b730632 100644 --- a/tests/python/packages/isce3/image/modulate.py +++ b/tests/python/packages/isce3/image/modulate.py @@ -83,20 +83,16 @@ def test_modulation_constant_frequency( self, frequency: float, function: str, conjugate: bool ) -> tuple[np.ndarray[np.complex64], np.ndarray[np.complex64]]: """ - Tests the Resample SLC V2 on a randomly distributed target signal. - - This test is designed to assess if Resample SLC works for a reasonably educated - attempt at a realistic input signal. Fail cases caught by this test include all - those tested for in the sinusoidal test, but using a more rigorous and - complicated secondary signal. - - This test also tests the zero-doppler case as well as a set of several doppler - frequencies as provided by the frequency fixture. + Tests the four carrier phase acquisition/modulation functions. Fixtures - ---------- + -------- frequency : float - A doppler frequency for this test, in Hz. + A carrier frequency for this test, in Hz. + function : str + The name of the function to test. + conjugate : bool + If True, conjugate the output signal; else do not. """ # The size of the generated image. az_length = 100 @@ -122,20 +118,26 @@ def test_modulation_constant_frequency( frequency=frequency, ) - # This will hold the output of the function - signal = np.full( - (az_length, rg_width), - fill_value=1. + 0.j, - dtype=np.complex64, - ) - # This will hold the actual expected value doppler_ramp_complex: np.ndarray[np.complex64] + # All of the functions call for a radar grid, carrier phase, and conjugate bool. + # Begin putting together a set of keyword arguments, since the function + # signatures are all very similar. + kwargs = { + "radar_grid": radar_grid, + "carrier_phase": lut, + "conjugate": conjugate + } + + # This will hold the signal that is output from the function. + signal: np.ndarray[np.complex64] + # Perform the interpolation. if function in ["get_modulation_phase", "modulate"]: - - # Create the expected output signal + + # Create the expected output signal. For this test, a simple doppler ramp + # is the output. doppler_ramp_complex = signal * generate_doppler_ramp_complex( grid_params=radar_grid, az_indices=np.arange(az_length), @@ -144,19 +146,17 @@ def test_modulation_constant_frequency( # Run the selected function if function == "get_modulation_phase": - signal = get_modulation_phase( - carrier_phase=lut, - radar_grid=radar_grid, - conjugate=conjugate, - ) - + signal = get_modulation_phase(**kwargs) elif function == "modulate": - signal = modulate( - slc_data_block=signal, - carrier_phase=lut, - radar_grid=radar_grid, - conjugate=conjugate, + # A dummy SLC of 1 + 0j to modulate - this will give the carrier + # phase. + input_slc = np.full( + (az_length, rg_width), + fill_value=1. + 0.j, + dtype=np.complex64, ) + kwargs["slc_data_block"] = input_slc + signal = modulate(**kwargs) elif function in ["get_modulation_phase_at_coords", "modulate_at_coords"]: # Set the offsets at random positions with a range of -1.5 to 1.5 with a @@ -171,31 +171,35 @@ def test_modulation_constant_frequency( azimuth_indices = np.array(az_offsets + rows, dtype=np.float64) range_indices = np.array(rg_offsets + cols, dtype=np.float64) - # Create the expected output signal + # Create the expected output signal. For this test, a simple doppler ramp + # is the output. doppler_ramp_complex = generate_doppler_ramp_complex( grid_params=radar_grid, az_indices=azimuth_indices, doppler_frequency=frequency, ) + # the "at_coords" functions require a set of azimuth and range indices to + # evaluate at. + kwargs["azimuth_indices"] = azimuth_indices + kwargs["range_indices"] = range_indices + # Run the selected function if function == "get_modulation_phase_at_coords": - signal = get_modulation_phase_at_coords( - carrier_phase=lut, - radar_grid=radar_grid, - azimuth_indices=azimuth_indices, - range_indices=range_indices, - conjugate=conjugate, - ) + signal = get_modulation_phase_at_coords(**kwargs) elif function == "modulate_at_coords": - signal = modulate_at_coords( - slc_data_block=signal, - carrier_phase=lut, - radar_grid=radar_grid, - azimuth_indices=azimuth_indices, - range_indices=range_indices, - conjugate=conjugate, + # A dummy SLC of 1 + 0j to modulate - this will give the carrier + # phase. + input_slc = np.full( + (az_length, rg_width), + fill_value=1. + 0.j, + dtype=np.complex64, ) + kwargs["slc_data_block"] = input_slc + signal = modulate_at_coords(**kwargs) + + # If conjugate was true, the ground-truth array is currently the conjugate + # of the output array (we hope) and must be conjugated for validation. if conjugate: doppler_ramp_complex = np.conjugate(doppler_ramp_complex) From bad39fb18fad9bf72e97b5591a5e2978abce283a Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 2 Apr 2024 15:56:24 +0000 Subject: [PATCH 25/35] Further annotation improvements in unit tests --- tests/python/packages/isce3/image/modulate.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/python/packages/isce3/image/modulate.py b/tests/python/packages/isce3/image/modulate.py index 45b730632..37c4009c7 100644 --- a/tests/python/packages/isce3/image/modulate.py +++ b/tests/python/packages/isce3/image/modulate.py @@ -205,6 +205,9 @@ def test_modulation_constant_frequency( try: # Validate the generated data against the true data. + # The correlation is expected to be very high and the standard deviation + # very low. The percentage of NaN values will be variable depending on + # offset distance for the "at_coords" functions and zero for the others. validate_test_results( test_arr=signal, true_arr=doppler_ramp_complex, @@ -217,6 +220,9 @@ def test_modulation_constant_frequency( rg_offset=0, ) except AssertionError as err: - err.add_note(function) + # If an error is caught in the validation function, add some clarifying + # notes for readability. + err.add_note(f"function: {function}") err.add_note(f"frequency: {frequency}") + err.add_note(f"conjugate: {conjugate}") raise err From 265022532ec538ca78666ad79426c308be105429 Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 2 Apr 2024 15:58:41 +0000 Subject: [PATCH 26/35] Fixed an error --- tests/python/packages/isce3/image/modulate.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/python/packages/isce3/image/modulate.py b/tests/python/packages/isce3/image/modulate.py index 37c4009c7..44ebef5f4 100644 --- a/tests/python/packages/isce3/image/modulate.py +++ b/tests/python/packages/isce3/image/modulate.py @@ -136,9 +136,16 @@ def test_modulation_constant_frequency( # Perform the interpolation. if function in ["get_modulation_phase", "modulate"]: + # A field of 1 + 0j for use with other functions + empty_signal = np.full( + (az_length, rg_width), + fill_value=1. + 0.j, + dtype=np.complex64, + ) + # Create the expected output signal. For this test, a simple doppler ramp # is the output. - doppler_ramp_complex = signal * generate_doppler_ramp_complex( + doppler_ramp_complex = empty_signal * generate_doppler_ramp_complex( grid_params=radar_grid, az_indices=np.arange(az_length), doppler_frequency=frequency, @@ -150,11 +157,7 @@ def test_modulation_constant_frequency( elif function == "modulate": # A dummy SLC of 1 + 0j to modulate - this will give the carrier # phase. - input_slc = np.full( - (az_length, rg_width), - fill_value=1. + 0.j, - dtype=np.complex64, - ) + input_slc = empty_signal kwargs["slc_data_block"] = input_slc signal = modulate(**kwargs) From eb831bd530649567dfcd8e8a06b757e7a213e3c5 Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 2 Apr 2024 16:11:11 +0000 Subject: [PATCH 27/35] Improved docstring description for "fill_value" argument --- cxx/isce3/image/Modulate.h | 16 +++++++++++---- .../pybind_isce3/image/Modulate.cpp | 20 +++++++++++++++---- python/packages/isce3/image/modulate.py | 20 +++++++++++++++---- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/cxx/isce3/image/Modulate.h b/cxx/isce3/image/Modulate.h index 94c89f4a5..3f8858c99 100644 --- a/cxx/isce3/image/Modulate.h +++ b/cxx/isce3/image/Modulate.h @@ -30,7 +30,9 @@ using ConstArrayRef2D = Eigen::Ref>; * @param[in] conjugate * if true, get the conjugate of the phase. * @param[in] fill_value - * The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + * The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined here as + * any pixels that cannot be evaluated by the carrier_phase function. Poly2d functions + * do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. */ template void getModulationPhase( @@ -66,7 +68,9 @@ void getModulationPhase( * @param[in] conjugate * if true, modulate the conjugate of the phase. * @param[in] fill_value - * The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + * The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined here as + * any pixels that cannot be evaluated by the carrier_phase function. Poly2d functions + * do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. */ template void _getModulationPhaseAtCoords( @@ -94,7 +98,9 @@ void _getModulationPhaseAtCoords( * @param[in] conjugate * if true, modulate the conjugate of the phase. * @param[in] fill_value - * The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + * The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined here as + * any pixels that cannot be evaluated by the carrier_phase function. Poly2d functions + * do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. */ template void modulate( @@ -131,7 +137,9 @@ void modulate( * @param[in] conjugate * if true, modulate the conjugate of the phase. * @param[in] fill_value - * The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + * The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined here as + * any pixels that cannot be evaluated by the carrier_phase function. Poly2d functions + * do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. */ template void _modulateAtCoords( diff --git a/python/extensions/pybind_isce3/image/Modulate.cpp b/python/extensions/pybind_isce3/image/Modulate.cpp index 03b5c57ab..f7cd4ea6c 100644 --- a/python/extensions/pybind_isce3/image/Modulate.cpp +++ b/python/extensions/pybind_isce3/image/Modulate.cpp @@ -47,7 +47,10 @@ void addbindings_modulate(py::module & m) conjugate: bool If True, get the conjugate of the phase. fill_value: complex - The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + The value to fill out-of-bounds pixels with. Out-of-bounds pixels are + defined here as any pixels that cannot be evaluated by the carrier_phase + function. Poly2d functions do not have out-of-bounds pixels, but LUT2d + functions may. Defaults to NaN + j*NaN. )" ); @@ -93,7 +96,10 @@ void addbindings_modulate(py::module & m) conjugate: bool If True, get the conjugate of the phase. fill_value: complex - The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + The value to fill out-of-bounds pixels with. Out-of-bounds pixels are + defined here as any pixels that cannot be evaluated by the carrier_phase + function. Poly2d functions do not have out-of-bounds pixels, but LUT2d + functions may. Defaults to NaN + j*NaN. )" ); @@ -130,7 +136,10 @@ void addbindings_modulate(py::module & m) conjugate: bool, optional If True, modulate the conjugate of the phase. fill_value: complex - The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + The value to fill out-of-bounds pixels with. Out-of-bounds pixels are + defined here as any pixels that cannot be evaluated by the carrier_phase + function. Poly2d functions do not have out-of-bounds pixels, but LUT2d + functions may. Defaults to NaN + j*NaN. )" ); @@ -177,7 +186,10 @@ void addbindings_modulate(py::module & m) conjugate: bool, optional If True, modulate the conjugate of the phase. fill_value: complex - The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + The value to fill out-of-bounds pixels with. Out-of-bounds pixels are + defined here as any pixels that cannot be evaluated by the carrier_phase + function. Poly2d functions do not have out-of-bounds pixels, but LUT2d + functions may. Defaults to NaN + j*NaN. )" ); } diff --git a/python/packages/isce3/image/modulate.py b/python/packages/isce3/image/modulate.py index 89da3dfa9..ce183fc49 100644 --- a/python/packages/isce3/image/modulate.py +++ b/python/packages/isce3/image/modulate.py @@ -37,7 +37,10 @@ def modulate( conjugate : bool, optional If True, modulate the conjugate of the phase, by default False fill_value: complex - The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined + here as any pixels that cannot be evaluated by the carrier_phase function. + Poly2d functions do not have out-of-bounds pixels, but LUT2d functions may. + Defaults to NaN + j*NaN. out : np.ndarray of np.complex64 | None, optional The array to output data to, or None. If given, must be the same size as slc_data_block. Any contents of this array will he overwritten, by default None @@ -98,7 +101,10 @@ def modulate_at_coords( conjugate : bool, optional If True, modulate the conjugate of the phase, by default False fill_value: complex - The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined + here as any pixels that cannot be evaluated by the carrier_phase function. + Poly2d functions do not have out-of-bounds pixels, but LUT2d functions may. + Defaults to NaN + j*NaN. out : np.ndarray of np.complex64 | None, optional The array to output data to, or None. If given, must be the same size as slc_data_block. Any contents of this array will he overwritten, by default None @@ -166,7 +172,10 @@ def get_modulation_phase( conjugate : bool, optional If True, get the conjugate of the phase, by default False fill_value: complex - The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined + here as any pixels that cannot be evaluated by the carrier_phase function. + Poly2d functions do not have out-of-bounds pixels, but LUT2d functions may. + Defaults to NaN + j*NaN. out : np.ndarray[np.complex64] | None, optional The output phase array to modify. Anything in this array will be overwritten. Defaults to None @@ -221,7 +230,10 @@ def get_modulation_phase_at_coords( conjugate : bool, optional If True, get the conjugate of the phase, by default False fill_value: complex - The value to fill out-of-bounds pixels with. Defaults to NaN + j*NaN. + The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined + here as any pixels that cannot be evaluated by the carrier_phase function. + Poly2d functions do not have out-of-bounds pixels, but LUT2d functions may. + Defaults to NaN + j*NaN. out : np.ndarray[np.complex64] | None, optional The output phase array to modify. Anything in this array will be overwritten. Defaults to None From 98b4dbef6054b4bd99cd2165bc63c55ae35c556f Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 2 Apr 2024 21:50:39 +0000 Subject: [PATCH 28/35] Added a range carrier to the tests to test in both dimensions --- tests/python/packages/isce3/image/modulate.py | 175 ++++++++++++------ 1 file changed, 114 insertions(+), 61 deletions(-) diff --git a/tests/python/packages/isce3/image/modulate.py b/tests/python/packages/isce3/image/modulate.py index 44ebef5f4..f4545daea 100644 --- a/tests/python/packages/isce3/image/modulate.py +++ b/tests/python/packages/isce3/image/modulate.py @@ -19,8 +19,51 @@ ) +def generate_carrier_ramp_complex( + grid_params: RadarGridParameters, + az_indices: np.ndarray, + rg_indices: np.ndarray, + az_frequency: float, + rg_frequency: float, +) -> np.ndarray: + """ + Evaluate the carrier phase ramp at the given set of indices. + + Parameters + ---------- + grid_params : RadarGridParameters + The radar grid parameters object for the doppler ramp. + az_indices : np.ndarray + The indices to evaluate the carrier at. + rg_indices : np.ndarray + The indices to evaluate the carrier at. + az_frequency : float + The azimuth carrier frequency. + rg_frequency : float + The range carrier frequency. + + Returns + ------- + np.ndarray + An array of the carrier phase ramp evaluated at each index passed in. + """ + # Trivially, for zero-doppler, just return an array of 1+0j + if az_frequency == 0 or rg_frequency == 0: + return np.ones(az_indices.shape, dtype=np.complex64) + + # Get the absolute azimuth time at each index. + azimuth = grid_params.sensing_start + az_indices / grid_params.prf + range = grid_params.starting_range + rg_indices * grid_params.range_pixel_spacing + + # Evaluate and return the ramp. + az_ramp = np.exp(1.0j * 2 * np.pi * az_frequency * azimuth) + rg_ramp = np.exp(1.0j * 2 * np.pi * rg_frequency * range) + + return az_ramp * rg_ramp + + def generate_carrier_lut( - grid_params: RadarGridParameters, frequency: float + grid_params: RadarGridParameters, az_frequency: float, rg_frequency: float ) -> LUT2d: """ Create a constant carrier LUT. @@ -29,40 +72,42 @@ def generate_carrier_lut( ---------- grid_params : RadarGridParameters The radar grid parameters to evaluate azimuth and range with. - frequency : float - The frequency of the LUT to be created. + az_frequency : float + The azimuth carrier frequency of the LUT to be created. + rg_frequency : float + The range carrier frequency of the LUT to be created. Returns ------- LUT2d The generated LUT. """ - # Trivially, for zero doppler just return an LUT2d that always evaluates to 0. - if frequency == 0: - return LUT2d() + # Get the dimensions and indices of the array array_length = grid_params.length array_width = grid_params.width az_indices = np.arange(array_length) rg_indices = np.arange(array_width) - az_time = grid_params.sensing_start + az_indices / grid_params.prf - rg_dist = grid_params.starting_range + rg_indices / grid_params.range_pixel_spacing + # Get the azimuth times and range distances + azimuth = grid_params.sensing_start + az_indices / grid_params.prf + range = grid_params.starting_range + rg_indices * grid_params.range_pixel_spacing - # Evaluate the integral - lut_array = np.full( + # Get the LUT array by evaluating the array at each index + az_array = np.full( shape=(array_length, array_width), - fill_value=2 * np.pi * frequency, + fill_value=2 * np.pi * az_frequency, dtype=np.float64, - ) * az_time[:, np.newaxis] - - lut = LUT2d( - rg_dist, - az_time, - lut_array, - ) + ) * azimuth[:, np.newaxis] + rg_array = np.full( + shape=(array_length, array_width), + fill_value=2 * np.pi * rg_frequency, + dtype=np.float64, + ) * range[np.newaxis, :] + lut_array = az_array + rg_array - return lut + # Generate the LUT + return LUT2d(range, azimuth, lut_array) @pytest.mark.parametrize( @@ -77,18 +122,25 @@ def generate_carrier_lut( class TestModulate: """Tests for the image.modulate code.""" - @pytest.mark.parametrize("frequency", [0.1, 0.25, 0.5]) + @pytest.mark.parametrize("az_freq", [0.1, 0.25, 0.5]) + @pytest.mark.parametrize("rg_freq", [0.1, 0.25, 0.5]) @pytest.mark.parametrize("conjugate", [True, False]) def test_modulation_constant_frequency( - self, frequency: float, function: str, conjugate: bool + self, + az_freq: float, + rg_freq: float, + function: str, + conjugate: bool, ) -> tuple[np.ndarray[np.complex64], np.ndarray[np.complex64]]: """ Tests the four carrier phase acquisition/modulation functions. Fixtures -------- - frequency : float - A carrier frequency for this test, in Hz. + az_freq : float + The azimuth carrier frequency for this test, in Hz. + rg_freq : float + The azimuth carrier frequency for this test, in Hz. function : str The name of the function to test. conjugate : bool @@ -115,49 +167,51 @@ def test_modulation_constant_frequency( # Generate a doppler centroid that has a ramp lut: LUT2d = generate_carrier_lut( grid_params=radar_grid, - frequency=frequency, + az_frequency=az_freq, + rg_frequency=rg_freq, ) - # This will hold the actual expected value - doppler_ramp_complex: np.ndarray[np.complex64] - # All of the functions call for a radar grid, carrier phase, and conjugate bool. # Begin putting together a set of keyword arguments, since the function # signatures are all very similar. + signal = np.full( + (az_length, rg_width), + fill_value=1. + 0.j, + dtype=np.complex64, + ) + + carrier_ramp_complex: np.ndarray[np.complex64] + kwargs = { "radar_grid": radar_grid, "carrier_phase": lut, "conjugate": conjugate } - # This will hold the signal that is output from the function. - signal: np.ndarray[np.complex64] - # Perform the interpolation. if function in ["get_modulation_phase", "modulate"]: - - # A field of 1 + 0j for use with other functions - empty_signal = np.full( - (az_length, rg_width), - fill_value=1. + 0.j, - dtype=np.complex64, - ) - - # Create the expected output signal. For this test, a simple doppler ramp - # is the output. - doppler_ramp_complex = empty_signal * generate_doppler_ramp_complex( + az_indices, rg_indices = np.indices((az_length, rg_width), dtype=np.float64) + + # Create the expected output signal. For this test, a simple phase ramp in + # complex phasor format is the output. + carrier_ramp_complex = generate_carrier_ramp_complex( grid_params=radar_grid, - az_indices=np.arange(az_length), - doppler_frequency=frequency, - )[:, np.newaxis] + az_indices=az_indices, + rg_indices=rg_indices, + az_frequency=az_freq, + rg_frequency=rg_freq, + ) - # Run the selected function if function == "get_modulation_phase": signal = get_modulation_phase(**kwargs) elif function == "modulate": # A dummy SLC of 1 + 0j to modulate - this will give the carrier # phase. - input_slc = empty_signal + input_slc = np.full( + (az_length, rg_width), + fill_value=1. + 0.j, + dtype=np.complex64, + ) kwargs["slc_data_block"] = input_slc signal = modulate(**kwargs) @@ -166,28 +220,27 @@ def test_modulation_constant_frequency( # flat probability distribution. This ensures that the difference in phase # and potential edge effects near the ends of an image are detectable. mag_offset = 3 - az_offsets = np.random.random(out_shape) * mag_offset - mag_offset / 2 - rg_offsets = np.random.random(out_shape) * mag_offset - mag_offset / 2 + az_offsets = np.random.random(out_shape) * mag_offset - mag_offset/2 + rg_offsets = np.random.random(out_shape) * mag_offset - mag_offset/2 - # Add the offsets to these indices to get the indices to evaluate at. + # Add the offsets to these indices to get the indices in the ground truth grid. rows, cols = np.indices(out_shape) azimuth_indices = np.array(az_offsets + rows, dtype=np.float64) range_indices = np.array(rg_offsets + cols, dtype=np.float64) - # Create the expected output signal. For this test, a simple doppler ramp - # is the output. - doppler_ramp_complex = generate_doppler_ramp_complex( + # Create the expected output signal. For this test, a simple phase ramp in + # complex phasor format is the output. + carrier_ramp_complex = generate_carrier_ramp_complex( grid_params=radar_grid, az_indices=azimuth_indices, - doppler_frequency=frequency, + rg_indices=range_indices, + az_frequency=az_freq, + rg_frequency=rg_freq, ) - # the "at_coords" functions require a set of azimuth and range indices to - # evaluate at. kwargs["azimuth_indices"] = azimuth_indices kwargs["range_indices"] = range_indices - # Run the selected function if function == "get_modulation_phase_at_coords": signal = get_modulation_phase_at_coords(**kwargs) elif function == "modulate_at_coords": @@ -201,11 +254,10 @@ def test_modulation_constant_frequency( kwargs["slc_data_block"] = input_slc signal = modulate_at_coords(**kwargs) - # If conjugate was true, the ground-truth array is currently the conjugate + # If conjugate is True, the ground-truth array is currently the conjugate # of the output array (we hope) and must be conjugated for validation. if conjugate: - doppler_ramp_complex = np.conjugate(doppler_ramp_complex) - + carrier_ramp_complex = np.conjugate(carrier_ramp_complex) try: # Validate the generated data against the true data. # The correlation is expected to be very high and the standard deviation @@ -213,7 +265,7 @@ def test_modulation_constant_frequency( # offset distance for the "at_coords" functions and zero for the others. validate_test_results( test_arr=signal, - true_arr=doppler_ramp_complex, + true_arr=carrier_ramp_complex, correlation_min=0.99999, phase_stdev_max=1e-6, nan_percent_max=3, @@ -226,6 +278,7 @@ def test_modulation_constant_frequency( # If an error is caught in the validation function, add some clarifying # notes for readability. err.add_note(f"function: {function}") - err.add_note(f"frequency: {frequency}") + err.add_note(f"az_freq: {az_freq}") + err.add_note(f"rg_freq: {rg_freq}") err.add_note(f"conjugate: {conjugate}") raise err From 70ef19619eaaebfa65befafc432bbe04c51824eb Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 2 Apr 2024 22:11:38 +0000 Subject: [PATCH 29/35] small additional change to interface --- tests/python/packages/isce3/image/modulate.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/python/packages/isce3/image/modulate.py b/tests/python/packages/isce3/image/modulate.py index f4545daea..35c5f9fe5 100644 --- a/tests/python/packages/isce3/image/modulate.py +++ b/tests/python/packages/isce3/image/modulate.py @@ -13,10 +13,7 @@ from isce3.core import DateTime, LUT2d from isce3.product import RadarGridParameters -from .resample_slc_utils import ( - generate_doppler_ramp_complex, - validate_test_results, -) +from .resample_slc_utils import validate_test_results def generate_carrier_ramp_complex( @@ -63,7 +60,9 @@ def generate_carrier_ramp_complex( def generate_carrier_lut( - grid_params: RadarGridParameters, az_frequency: float, rg_frequency: float + grid_params: RadarGridParameters, + az_frequency: float, + rg_frequency: float, ) -> LUT2d: """ Create a constant carrier LUT. From 2398ea41342017acbda644ab67d9ef76b9064dc5 Mon Sep 17 00:00:00 2001 From: thudson Date: Mon, 7 Oct 2024 15:23:51 +0000 Subject: [PATCH 30/35] rebasing fixes --- python/extensions/pybind_isce3/image/Modulate.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/extensions/pybind_isce3/image/Modulate.cpp b/python/extensions/pybind_isce3/image/Modulate.cpp index f7cd4ea6c..5b2bda6c9 100644 --- a/python/extensions/pybind_isce3/image/Modulate.cpp +++ b/python/extensions/pybind_isce3/image/Modulate.cpp @@ -61,8 +61,8 @@ void addbindings_modulate(py::module & m) isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, const isce3::product::RadarGridParameters&, - const isce3::image::modulate::ArrayRefConst2D, - const isce3::image::modulate::ArrayRefConst2D, + const isce3::image::modulate::ConstArrayRef2D, + const isce3::image::modulate::ConstArrayRef2D, const bool, const std::complex >(&isce3::image::modulate::_getModulationPhaseAtCoords), @@ -150,8 +150,8 @@ void addbindings_modulate(py::module & m) isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, const isce3::product::RadarGridParameters&, - const isce3::image::modulate::ArrayRefConst2D, - const isce3::image::modulate::ArrayRefConst2D, + const isce3::image::modulate::ConstArrayRef2D, + const isce3::image::modulate::ConstArrayRef2D, const bool, const std::complex >(&isce3::image::modulate::_modulateAtCoords), From fdd27c079096369b99f8b784bd52330c09283dac Mon Sep 17 00:00:00 2001 From: thudson Date: Thu, 10 Oct 2024 15:59:16 +0000 Subject: [PATCH 31/35] conform modulate function interfaces to be similar to resample_to_coords --- python/packages/isce3/image/modulate.py | 96 ++++++++----------- .../packages/isce3/image/v2/resample_slc.py | 8 +- 2 files changed, 42 insertions(+), 62 deletions(-) diff --git a/python/packages/isce3/image/modulate.py b/python/packages/isce3/image/modulate.py index ce183fc49..21002798a 100644 --- a/python/packages/isce3/image/modulate.py +++ b/python/packages/isce3/image/modulate.py @@ -20,7 +20,6 @@ def modulate( radar_grid: RadarGridParameters, conjugate: bool = False, fill_value: np.complex64 = np.nan + 1.j * np.nan, - out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: """ Evaluate and modulate or demodulate the phase carrier onto the given SLC data block. @@ -41,9 +40,6 @@ def modulate( here as any pixels that cannot be evaluated by the carrier_phase function. Poly2d functions do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. - out : np.ndarray of np.complex64 | None, optional - The array to output data to, or None. If given, must be the same size as - slc_data_block. Any contents of this array will he overwritten, by default None Returns ------- @@ -51,22 +47,18 @@ def modulate( The modulated SLC block. If `out` was given, this will be the same array as the `out` array. """ - out_array = out if out is not None else np.full( - (radar_grid.length, radar_grid.width), - fill_value=np.nan + 1.0j * np.nan, - dtype=np.complex64, - ) - np.copyto(out_array, slc_data_block) + out_arr = slc_data_block.copy() + out_arr = np.require(out_arr, dtype=np.complex64, requirements=["C", "W"]) _modulate( - slc_data_block=out_array, + slc_data_block=out_arr, carrier_phase=carrier_phase, radar_grid=radar_grid, conjugate=conjugate, fill_value=fill_value, ) - return out_array + return out_arr def modulate_at_coords( @@ -77,7 +69,6 @@ def modulate_at_coords( range_indices: np.ndarray[np.float64], conjugate: bool = False, fill_value: np.complex64 = np.nan + 1.j * np.nan, - out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: """ Evaluate and modulate or demodulate the phase carrier onto the given SLC data block @@ -105,9 +96,6 @@ def modulate_at_coords( here as any pixels that cannot be evaluated by the carrier_phase function. Poly2d functions do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. - out : np.ndarray of np.complex64 | None, optional - The array to output data to, or None. If given, must be the same size as - slc_data_block. Any contents of this array will he overwritten, by default None Returns ------- @@ -116,32 +104,35 @@ def modulate_at_coords( the `out` array. """ error_channel = journal.error("modulate.modulate_at_coords") - - out_array = out if out is not None else np.full( - (radar_grid.length, radar_grid.width), - fill_value=np.nan + 1.0j * np.nan, - dtype=np.complex64, - ) - np.copyto(out_array, slc_data_block) - if out_array.shape != azimuth_indices.shape: + if azimuth_indices.shape != range_indices.shape: err_log = ( - f"Output block shape {out_array.shape} and azimuth indices block shape " - f"{azimuth_indices.shape} are unequal." + f"Azimuth indices block shape {azimuth_indices.shape} and range indices " + f"block shape {range_indices.shape} are unequal." ) error_channel.log(err_log) raise ValueError(err_log) - if out_array.shape != range_indices.shape: + if azimuth_indices.shape != slc_data_block.shape: err_log = ( - f"Output block shape {out_array.shape} and range indices block shape " - f"{azimuth_indices.shape} are unequal." + f"Inputs block shape {azimuth_indices.shape} and SLC data block shape " + f"{slc_data_block.shape} are unequal." ) error_channel.log(err_log) raise ValueError(err_log) + out_arr = slc_data_block.copy() + out_arr = np.require(out_arr, dtype=np.complex64, requirements=["C", "W"]) + + # Ensure that all of the index data blocks meet the requirements of the + # _modulate_at_coords pybind (correct dtype, with flag C_CONTIGUOUS) + # These function calls will return conforming copies of the data blocks if they + # are not already conforming. + range_indices = np.require(range_indices, dtype=np.float64, requirements=["C"]) + azimuth_indices = np.require(azimuth_indices, dtype=np.float64, requirements=["C"]) + _modulate_at_coords( - slc_data_block=out_array, + slc_data_block=out_arr, carrier_phase=carrier_phase, radar_grid=radar_grid, azimuth_indices=azimuth_indices, @@ -150,7 +141,7 @@ def modulate_at_coords( fill_value=fill_value, ) - return out_array + return out_arr def get_modulation_phase( @@ -158,7 +149,6 @@ def get_modulation_phase( radar_grid: RadarGridParameters, conjugate: bool = False, fill_value: np.complex64 = np.nan + 1.j * np.nan, - out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: """ Acquire the phase of the given carrier of a radar scene. @@ -176,9 +166,6 @@ def get_modulation_phase( here as any pixels that cannot be evaluated by the carrier_phase function. Poly2d functions do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. - out : np.ndarray[np.complex64] | None, optional - The output phase array to modify. Anything in this array will be overwritten. - Defaults to None Returns ------- @@ -186,21 +173,21 @@ def get_modulation_phase( The carrier phase, in the form of complex unit vectors. If `out` was given, this will be the same array as the `out` array. """ - out_array = out if out is not None else np.full( + out_arr = np.full( (radar_grid.length, radar_grid.width), - fill_value=np.nan + 1.0j * np.nan, + fill_value=fill_value, dtype=np.complex64, ) _get_modulation_phase( - out=out_array, + out=out_arr, carrier_phase=carrier_phase, radar_grid=radar_grid, conjugate=conjugate, fill_value=fill_value, ) - return out_array + return out_arr def get_modulation_phase_at_coords( @@ -210,7 +197,6 @@ def get_modulation_phase_at_coords( range_indices: np.ndarray[np.float64], conjugate: bool = False, fill_value: np.complex64 = np.nan + 1.j * np.nan, - out: np.ndarray[np.complex64] | None = None, ) -> np.ndarray[np.complex64]: """ Acquire the phase of the given carrier at each given index of a radar scene. @@ -245,30 +231,26 @@ def get_modulation_phase_at_coords( will be the same array as the `out` array. """ error_channel = journal.error("modulate.get_modulation_phase_at_coords") - out_array = out if out is not None else np.full( - azimuth_indices.shape, - fill_value=np.nan + 1.0j * np.nan, - dtype=np.complex64, - ) - if out_array.shape != azimuth_indices.shape: + if azimuth_indices.shape != range_indices.shape: err_log = ( - f"Output block shape {out_array.shape} and azimuth indices block shape " - f"{azimuth_indices.shape} are unequal." + f"Azimuth indices block shape {azimuth_indices.shape} and range indices " + f"block shape {range_indices.shape} are unequal." ) error_channel.log(err_log) raise ValueError(err_log) - if out_array.shape != range_indices.shape: - err_log = ( - f"Output block shape {out_array.shape} and range indices block shape " - f"{azimuth_indices.shape} are unequal." - ) - error_channel.log(err_log) - raise ValueError(err_log) + out_arr = np.full(azimuth_indices.shape, fill_value=fill_value, dtype=np.complex64) + + # Ensure that all of the index data blocks meet the requirements of the + # _get_modulation_phase_at_coords pybind (correct dtype, with flag C_CONTIGUOUS) + # These function calls will return conforming copies of the data blocks if they + # are not already conforming. + range_indices = np.require(range_indices, dtype=np.float64, requirements=["C"]) + azimuth_indices = np.require(azimuth_indices, dtype=np.float64, requirements=["C"]) _get_modulation_phase_at_coords( - out=out_array, + out=out_arr, carrier_phase=carrier_phase, radar_grid=radar_grid, azimuth_indices=azimuth_indices, @@ -277,4 +259,4 @@ def get_modulation_phase_at_coords( fill_value=fill_value, ) - return out_array + return out_arr diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index e2c1d7a63..73d1d30a4 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -237,9 +237,8 @@ def resample_slc_blocks( ) for carrier in phase_carriers: - modulate( + input_block = modulate( slc_data_block=input_block, - out=input_block, carrier_phase=carrier, radar_grid=block_grid, conjugate=True, @@ -269,9 +268,8 @@ def resample_slc_blocks( ) for carrier in phase_carriers: - modulate_at_coords( + output_block = modulate_at_coords( slc_data_block=output_block, - out=output_block, carrier_phase=carrier, radar_grid=block_grid, azimuth_indices=azimuth_index_grid, @@ -458,7 +456,7 @@ def resample_to_coords( raise ValueError(err_log) # Ensure that all of the input data blocks meet the requirements of the - # _resample_to_coords pybind (correct dtype, with flags C_CONTIGUOUS and WRITABLE) + # _resample_to_coords pybind (correct dtype, with flag C_CONTIGUOUS) # These function calls will return conforming copies of the data blocks if they # are not already conforming. input_data_block = np.require( From 99c292942bb34039045618c1bb3024aa9b3ee6e0 Mon Sep 17 00:00:00 2001 From: thudson Date: Thu, 10 Oct 2024 20:28:43 +0000 Subject: [PATCH 32/35] Small documentation and error reporting improvements --- python/packages/isce3/image/modulate.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/python/packages/isce3/image/modulate.py b/python/packages/isce3/image/modulate.py index 21002798a..4e4935b0a 100644 --- a/python/packages/isce3/image/modulate.py +++ b/python/packages/isce3/image/modulate.py @@ -34,7 +34,7 @@ def modulate( radar_grid : RadarGridParameters Parameters for the given radar grid corresponding to `slc_data_block`. conjugate : bool, optional - If True, modulate the conjugate of the phase, by default False + If True, modulate the conjugate of the phase. Defaults to False. fill_value: complex The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined here as any pixels that cannot be evaluated by the carrier_phase function. @@ -44,8 +44,7 @@ def modulate( Returns ------- np.ndarray of np.complex64 - The modulated SLC block. If `out` was given, this will be the same array as - the `out` array. + The modulated SLC block. """ out_arr = slc_data_block.copy() out_arr = np.require(out_arr, dtype=np.complex64, requirements=["C", "W"]) @@ -90,7 +89,7 @@ def modulate_at_coords( Range index of each output coordinate pixel in the given radar coordinate system. Must be the same shape as phase_data_block. conjugate : bool, optional - If True, modulate the conjugate of the phase, by default False + If True, modulate the conjugate of the phase. Defaults to False. fill_value: complex The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined here as any pixels that cannot be evaluated by the carrier_phase function. @@ -100,8 +99,7 @@ def modulate_at_coords( Returns ------- np.ndarray of np.complex64 - The modulated SLC block. If `out` was given, this will be the same array as - the `out` array. + The modulated SLC block. """ error_channel = journal.error("modulate.modulate_at_coords") @@ -115,7 +113,7 @@ def modulate_at_coords( if azimuth_indices.shape != slc_data_block.shape: err_log = ( - f"Inputs block shape {azimuth_indices.shape} and SLC data block shape " + f"Indices block shapes {azimuth_indices.shape} and SLC data block shape " f"{slc_data_block.shape} are unequal." ) error_channel.log(err_log) @@ -160,7 +158,7 @@ def get_modulation_phase( radar_grid : RadarGridParameters Parameters for the given radar grid corresponding to the output block. conjugate : bool, optional - If True, get the conjugate of the phase, by default False + If True, get the conjugate of the phase. Defaults to False. fill_value: complex The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined here as any pixels that cannot be evaluated by the carrier_phase function. @@ -170,8 +168,7 @@ def get_modulation_phase( Returns ------- np.ndarray of np.complex64 - The carrier phase, in the form of complex unit vectors. If `out` was given, this - will be the same array as the `out` array. + The carrier phase, in the form of complex unit vectors. """ out_arr = np.full( (radar_grid.length, radar_grid.width), @@ -214,21 +211,17 @@ def get_modulation_phase_at_coords( Range index of each output coordinate pixel in the given radar coordinate system. Must be the same shape as phase_data_block. conjugate : bool, optional - If True, get the conjugate of the phase, by default False + If True, get the conjugate of the phase. Defaults to False. fill_value: complex The value to fill out-of-bounds pixels with. Out-of-bounds pixels are defined here as any pixels that cannot be evaluated by the carrier_phase function. Poly2d functions do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. - out : np.ndarray[np.complex64] | None, optional - The output phase array to modify. Anything in this array will be overwritten. - Defaults to None Returns ------- np.ndarray of np.complex64 - The carrier phase, in the form of complex unit vectors. If `out` was given, this - will be the same array as the `out` array. + The carrier phase, in the form of complex unit vectors. """ error_channel = journal.error("modulate.get_modulation_phase_at_coords") From 9d0275528ca3b3bba9924fbf8a8f1648910ff177 Mon Sep 17 00:00:00 2001 From: thudson Date: Wed, 19 Feb 2025 01:08:06 +0000 Subject: [PATCH 33/35] modulate -> modulate_carrier_phase, get_modulation_phase -> get_carrier_phase --- cxx/isce3/image/Modulate.cpp | 16 +++---- cxx/isce3/image/Modulate.h | 8 ++-- .../pybind_isce3/image/Modulate.cpp | 16 +++---- python/packages/isce3/image/modulate.py | 32 +++++++------- .../packages/isce3/image/v2/resample_slc.py | 9 ++-- tests/python/packages/isce3/image/modulate.py | 42 ++++++++++--------- 6 files changed, 65 insertions(+), 58 deletions(-) diff --git a/cxx/isce3/image/Modulate.cpp b/cxx/isce3/image/Modulate.cpp index de619e28b..2743612d0 100644 --- a/cxx/isce3/image/Modulate.cpp +++ b/cxx/isce3/image/Modulate.cpp @@ -30,7 +30,7 @@ std::complex _getPixelCarrierPhase( template -void getModulationPhase( +void getCarrierPhase( ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -73,7 +73,7 @@ void getModulationPhase( template -void _getModulationPhaseAtCoords( +void _getCarrierPhaseAtCoords( ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -121,7 +121,7 @@ void _getModulationPhaseAtCoords( template -void modulate( +void modulateCarrierPhase( ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -164,7 +164,7 @@ void modulate( template -void _modulateAtCoords( +void _modulateCarrierPhaseAtCoords( ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -212,14 +212,14 @@ void _modulateAtCoords( #define EXPLICIT_INSTANTIATION(AzRgFunc) \ -template void getModulationPhase( \ +template void getCarrierPhase( \ ArrayRef2D> out, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ const bool conjugate, \ const std::complex fill_value \ ); \ -template void _getModulationPhaseAtCoords( \ +template void _getCarrierPhaseAtCoords( \ ArrayRef2D> out, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ @@ -228,14 +228,14 @@ template void _getModulationPhaseAtCoords( \ const bool conjugate, \ const std::complex fill_value \ ); \ -template void modulate( \ +template void modulateCarrierPhase( \ ArrayRef2D> slc_data_block, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ const bool conjugate, \ const std::complex fill_value \ ); \ -template void _modulateAtCoords( \ +template void _modulateCarrierPhaseAtCoords( \ ArrayRef2D> slc_data_block, \ const AzRgFunc& carrier_phase, \ const isce3::product::RadarGridParameters& radar_grid, \ diff --git a/cxx/isce3/image/Modulate.h b/cxx/isce3/image/Modulate.h index 3f8858c99..a7cc80835 100644 --- a/cxx/isce3/image/Modulate.h +++ b/cxx/isce3/image/Modulate.h @@ -35,7 +35,7 @@ using ConstArrayRef2D = Eigen::Ref>; * do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. */ template -void getModulationPhase( +void getCarrierPhase( ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -73,7 +73,7 @@ void getModulationPhase( * do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. */ template -void _getModulationPhaseAtCoords( +void _getCarrierPhaseAtCoords( ArrayRef2D> out, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -103,7 +103,7 @@ void _getModulationPhaseAtCoords( * do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. */ template -void modulate( +void modulateCarrierPhase( ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, @@ -142,7 +142,7 @@ void modulate( * do not have out-of-bounds pixels, but LUT2d functions may. Defaults to NaN + j*NaN. */ template -void _modulateAtCoords( +void _modulateCarrierPhaseAtCoords( ArrayRef2D> slc_data_block, const AzRgFunc& carrier_phase, const isce3::product::RadarGridParameters& radar_grid, diff --git a/python/extensions/pybind_isce3/image/Modulate.cpp b/python/extensions/pybind_isce3/image/Modulate.cpp index 5b2bda6c9..92f789d43 100644 --- a/python/extensions/pybind_isce3/image/Modulate.cpp +++ b/python/extensions/pybind_isce3/image/Modulate.cpp @@ -17,14 +17,14 @@ template void addbindings_modulate(py::module & m) { m.def( - "_get_modulation_phase", + "_get_carrier_phase", py::overload_cast< isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, const isce3::product::RadarGridParameters&, const bool, const std::complex - >(&isce3::image::modulate::getModulationPhase), + >(&isce3::image::modulate::getCarrierPhase), py::arg("out"), py::arg("carrier_phase"), py::arg("radar_grid"), @@ -56,7 +56,7 @@ void addbindings_modulate(py::module & m) m.def( - "_get_modulation_phase_at_coords", + "_get_carrier_phase_at_coords", py::overload_cast< isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, @@ -65,7 +65,7 @@ void addbindings_modulate(py::module & m) const isce3::image::modulate::ConstArrayRef2D, const bool, const std::complex - >(&isce3::image::modulate::_getModulationPhaseAtCoords), + >(&isce3::image::modulate::_getCarrierPhaseAtCoords), py::arg("out"), py::arg("carrier_phase"), py::arg("radar_grid"), @@ -105,14 +105,14 @@ void addbindings_modulate(py::module & m) m.def( - "_modulate", + "_modulate_carrier_phase", py::overload_cast< isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, const isce3::product::RadarGridParameters&, const bool, const std::complex - >(&isce3::image::modulate::modulate), + >(&isce3::image::modulate::modulateCarrierPhase), py::arg("slc_data_block"), py::arg("carrier_phase"), py::arg("radar_grid"), @@ -145,7 +145,7 @@ void addbindings_modulate(py::module & m) m.def( - "_modulate_at_coords", + "_modulate_carrier_phase_at_coords", py::overload_cast< isce3::image::modulate::ArrayRef2D>, const AzRgFunc&, @@ -154,7 +154,7 @@ void addbindings_modulate(py::module & m) const isce3::image::modulate::ConstArrayRef2D, const bool, const std::complex - >(&isce3::image::modulate::_modulateAtCoords), + >(&isce3::image::modulate::_modulateCarrierPhaseAtCoords), py::arg("slc_data_block"), py::arg("carrier_phase"), py::arg("radar_grid"), diff --git a/python/packages/isce3/image/modulate.py b/python/packages/isce3/image/modulate.py index 4e4935b0a..3dbce6ed4 100644 --- a/python/packages/isce3/image/modulate.py +++ b/python/packages/isce3/image/modulate.py @@ -6,15 +6,15 @@ from isce3.core import LUT2d from isce3.core.poly2d import Poly2d from isce3.ext.isce3.image.modulate import ( - _get_modulation_phase, - _get_modulation_phase_at_coords, - _modulate, - _modulate_at_coords, + _get_carrier_phase, + _get_carrier_phase_at_coords, + _modulate_carrier_phase, + _modulate_carrier_phase_at_coords, ) from isce3.product import RadarGridParameters -def modulate( +def modulate_carrier_phase( slc_data_block: np.ndarray[np.complex64], carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, @@ -49,7 +49,7 @@ def modulate( out_arr = slc_data_block.copy() out_arr = np.require(out_arr, dtype=np.complex64, requirements=["C", "W"]) - _modulate( + _modulate_carrier_phase( slc_data_block=out_arr, carrier_phase=carrier_phase, radar_grid=radar_grid, @@ -60,7 +60,7 @@ def modulate( return out_arr -def modulate_at_coords( +def modulate_carrier_phase_at_coords( slc_data_block: np.ndarray[np.complex64], carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, @@ -101,7 +101,7 @@ def modulate_at_coords( np.ndarray of np.complex64 The modulated SLC block. """ - error_channel = journal.error("modulate.modulate_at_coords") + error_channel = journal.error("modulate.modulate_carrier_phase_at_coords") if azimuth_indices.shape != range_indices.shape: err_log = ( @@ -123,13 +123,13 @@ def modulate_at_coords( out_arr = np.require(out_arr, dtype=np.complex64, requirements=["C", "W"]) # Ensure that all of the index data blocks meet the requirements of the - # _modulate_at_coords pybind (correct dtype, with flag C_CONTIGUOUS) + # _modulate_carrier_phase_at_coords pybind (correct dtype, with flag C_CONTIGUOUS) # These function calls will return conforming copies of the data blocks if they # are not already conforming. range_indices = np.require(range_indices, dtype=np.float64, requirements=["C"]) azimuth_indices = np.require(azimuth_indices, dtype=np.float64, requirements=["C"]) - _modulate_at_coords( + _modulate_carrier_phase_at_coords( slc_data_block=out_arr, carrier_phase=carrier_phase, radar_grid=radar_grid, @@ -142,7 +142,7 @@ def modulate_at_coords( return out_arr -def get_modulation_phase( +def get_carrier_phase( carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, conjugate: bool = False, @@ -176,7 +176,7 @@ def get_modulation_phase( dtype=np.complex64, ) - _get_modulation_phase( + _get_carrier_phase( out=out_arr, carrier_phase=carrier_phase, radar_grid=radar_grid, @@ -187,7 +187,7 @@ def get_modulation_phase( return out_arr -def get_modulation_phase_at_coords( +def get_carrier_phase_at_coords( carrier_phase: LUT2d | Poly2d, radar_grid: RadarGridParameters, azimuth_indices: np.ndarray[np.float64], @@ -223,7 +223,7 @@ def get_modulation_phase_at_coords( np.ndarray of np.complex64 The carrier phase, in the form of complex unit vectors. """ - error_channel = journal.error("modulate.get_modulation_phase_at_coords") + error_channel = journal.error("modulate.get_carrier_phase_at_coords") if azimuth_indices.shape != range_indices.shape: err_log = ( @@ -236,13 +236,13 @@ def get_modulation_phase_at_coords( out_arr = np.full(azimuth_indices.shape, fill_value=fill_value, dtype=np.complex64) # Ensure that all of the index data blocks meet the requirements of the - # _get_modulation_phase_at_coords pybind (correct dtype, with flag C_CONTIGUOUS) + # _get_carrier_phase_at_coords pybind (correct dtype, with flag C_CONTIGUOUS) # These function calls will return conforming copies of the data blocks if they # are not already conforming. range_indices = np.require(range_indices, dtype=np.float64, requirements=["C"]) azimuth_indices = np.require(azimuth_indices, dtype=np.float64, requirements=["C"]) - _get_modulation_phase_at_coords( + _get_carrier_phase_at_coords( out=out_arr, carrier_phase=carrier_phase, radar_grid=radar_grid, diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index 73d1d30a4..ca6ba0b6e 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -9,7 +9,10 @@ from isce3.core.poly2d import Poly2d from isce3.core.resample_block_generators import get_blocks, get_blocks_by_offsets from isce3.ext.isce3.image.v2 import _resample_to_coords -from isce3.image.modulate import modulate, modulate_at_coords +from isce3.image.modulate import ( + modulate_carrier_phase, + modulate_carrier_phase_at_coords, +) from isce3.io.dataset import DatasetReader, DatasetWriter from isce3.product import RadarGridParameters @@ -237,7 +240,7 @@ def resample_slc_blocks( ) for carrier in phase_carriers: - input_block = modulate( + input_block = modulate_carrier_phase( slc_data_block=input_block, carrier_phase=carrier, radar_grid=block_grid, @@ -268,7 +271,7 @@ def resample_slc_blocks( ) for carrier in phase_carriers: - output_block = modulate_at_coords( + output_block = modulate_carrier_phase_at_coords( slc_data_block=output_block, carrier_phase=carrier, radar_grid=block_grid, diff --git a/tests/python/packages/isce3/image/modulate.py b/tests/python/packages/isce3/image/modulate.py index 35c5f9fe5..0743f8d9d 100644 --- a/tests/python/packages/isce3/image/modulate.py +++ b/tests/python/packages/isce3/image/modulate.py @@ -5,10 +5,10 @@ import numpy as np from isce3.image.modulate import ( - get_modulation_phase, - get_modulation_phase_at_coords, - modulate, - modulate_at_coords, + get_carrier_phase, + get_carrier_phase_at_coords, + modulate_carrier_phase, + modulate_carrier_phase_at_coords, ) from isce3.core import DateTime, LUT2d from isce3.product import RadarGridParameters @@ -112,10 +112,10 @@ def generate_carrier_lut( @pytest.mark.parametrize( "function", [ - "get_modulation_phase", - "get_modulation_phase_at_coords", - "modulate", - "modulate_at_coords" + "get_carrier_phase", + "get_carrier_phase_at_coords", + "modulate_carrier_phase", + "modulate_carrier_phase_at_coords" ] ) class TestModulate: @@ -188,7 +188,7 @@ def test_modulation_constant_frequency( } # Perform the interpolation. - if function in ["get_modulation_phase", "modulate"]: + if function in ["get_carrier_phase", "modulate_carrier_phase"]: az_indices, rg_indices = np.indices((az_length, rg_width), dtype=np.float64) # Create the expected output signal. For this test, a simple phase ramp in @@ -201,9 +201,9 @@ def test_modulation_constant_frequency( rg_frequency=rg_freq, ) - if function == "get_modulation_phase": - signal = get_modulation_phase(**kwargs) - elif function == "modulate": + if function == "get_carrier_phase": + signal = get_carrier_phase(**kwargs) + elif function == "modulate_carrier_phase": # A dummy SLC of 1 + 0j to modulate - this will give the carrier # phase. input_slc = np.full( @@ -212,9 +212,12 @@ def test_modulation_constant_frequency( dtype=np.complex64, ) kwargs["slc_data_block"] = input_slc - signal = modulate(**kwargs) + signal = modulate_carrier_phase(**kwargs) - elif function in ["get_modulation_phase_at_coords", "modulate_at_coords"]: + elif function in [ + "get_carrier_phase_at_coords", + "modulate_carrier_phase_at_coords" + ]: # Set the offsets at random positions with a range of -1.5 to 1.5 with a # flat probability distribution. This ensures that the difference in phase # and potential edge effects near the ends of an image are detectable. @@ -222,7 +225,8 @@ def test_modulation_constant_frequency( az_offsets = np.random.random(out_shape) * mag_offset - mag_offset/2 rg_offsets = np.random.random(out_shape) * mag_offset - mag_offset/2 - # Add the offsets to these indices to get the indices in the ground truth grid. + # Add the offsets to these indices to get the indices in the ground truth + # grid. rows, cols = np.indices(out_shape) azimuth_indices = np.array(az_offsets + rows, dtype=np.float64) range_indices = np.array(rg_offsets + cols, dtype=np.float64) @@ -240,9 +244,9 @@ def test_modulation_constant_frequency( kwargs["azimuth_indices"] = azimuth_indices kwargs["range_indices"] = range_indices - if function == "get_modulation_phase_at_coords": - signal = get_modulation_phase_at_coords(**kwargs) - elif function == "modulate_at_coords": + if function == "get_carrier_phase_at_coords": + signal = get_carrier_phase_at_coords(**kwargs) + elif function == "modulate_carrier_phase_at_coords": # A dummy SLC of 1 + 0j to modulate - this will give the carrier # phase. input_slc = np.full( @@ -251,7 +255,7 @@ def test_modulation_constant_frequency( dtype=np.complex64, ) kwargs["slc_data_block"] = input_slc - signal = modulate_at_coords(**kwargs) + signal = modulate_carrier_phase_at_coords(**kwargs) # If conjugate is True, the ground-truth array is currently the conjugate # of the output array (we hope) and must be conjugated for validation. From 6dc38a89cc5fc04f7eb8eb22fd592e7fe5a471ed Mon Sep 17 00:00:00 2001 From: thudson Date: Mon, 16 Mar 2026 23:49:15 +0000 Subject: [PATCH 34/35] Fix error from rebase: `output_block` not correctly declared --- python/packages/isce3/image/v2/resample_slc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/isce3/image/v2/resample_slc.py b/python/packages/isce3/image/v2/resample_slc.py index ca6ba0b6e..8e4be4ad0 100644 --- a/python/packages/isce3/image/v2/resample_slc.py +++ b/python/packages/isce3/image/v2/resample_slc.py @@ -254,7 +254,7 @@ def resample_slc_blocks( # Reporting input block shape for debugging info_channel.log(f"Input block: {in_slices}") - output_blocks[i] = resample( + output_block = resample( input_block, range_index_grid, azimuth_index_grid, From 1e12dd602f2e4fe14584be6e1db979f6e69e827a Mon Sep 17 00:00:00 2001 From: thudson Date: Tue, 17 Mar 2026 00:30:10 +0000 Subject: [PATCH 35/35] Fix broken import in test --- tests/python/packages/isce3/image/modulate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/packages/isce3/image/modulate.py b/tests/python/packages/isce3/image/modulate.py index 0743f8d9d..e409438b8 100644 --- a/tests/python/packages/isce3/image/modulate.py +++ b/tests/python/packages/isce3/image/modulate.py @@ -13,7 +13,7 @@ from isce3.core import DateTime, LUT2d from isce3.product import RadarGridParameters -from .resample_slc_utils import validate_test_results +from ..resample_slc_utils import validate_test_results def generate_carrier_ramp_complex(