Skip to content

Remove pyproj Dependency to Resolve conda-forge Feedstock Issues - #339

Merged
Tyler-g-hudson merged 2 commits into
developfrom
remove_pyproj_deps
Aug 12, 2026
Merged

Remove pyproj Dependency to Resolve conda-forge Feedstock Issues#339
Tyler-g-hudson merged 2 commits into
developfrom
remove_pyproj_deps

Conversation

@Tyler-g-hudson

Copy link
Copy Markdown
Contributor

Context

This PR removes the direct dependency on pyproj from ISCE3, resolving issues encountered in the conda-forge feedstock PR (conda-forge/isce3-feedstock#122) where introducing pyproj as a dependency caused build errors after lines were introduced to the code in 0.25.13. ISCE3 does not have a listed dependency on pyproj so we either need to remove these lines or handle the dependency management issues that come with officially adding this dependency to the repo.

Summary of Changes

This PR completely removes direct usage of pyproj from the ISCE3 codebase by replacing it with equivalent functionality from osgeo.osr (GDAL), which is already a core dependency.

Analysis Results:

  • Only 1 file actively used pyproj in production code: python/packages/isce3/unwrap/preprocess.py
  • 1 dead code file (tests/cxx/isce3/core/ellipsoid/llhxyz.py) has been removed
  • pyproj was never explicitly declared in environment.yml - it appeared only as a transitive dependency from pyaps3 or raider-base

Changes Made

1. Modified python/packages/isce3/unwrap/preprocess.py

Removed:

from pyproj import Transformer

transformer_4326_to_watermask = Transformer.from_crs(4326, bbox_epsg, always_xy=True)
decimated_blocks['x'], decimated_blocks['y'] = transformer_4326_to_watermask.transform(
    decimated_blocks['x'], decimated_blocks['y'])

Replaced with:

# Create source and destination spatial references
srs_src = osr.SpatialReference()
srs_src.ImportFromEPSG(4326)
srs_dst = osr.SpatialReference()
srs_dst.ImportFromEPSG(bbox_epsg)

# Set axis mapping to traditional GIS order (equivalent to pyproj's always_xy=True)
srs_src.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER)
srs_dst.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER)

# Create and apply coordinate transformation
transformer = osr.CoordinateTransformation(srs_src, srs_dst)
original_shape = decimated_blocks['x'].shape
x_flat = decimated_blocks['x'].ravel()
y_flat = decimated_blocks['y'].ravel()
x_y_points = np.column_stack((x_flat, y_flat))
transformed = np.array(transformer.TransformPoints(x_y_points))
decimated_blocks['x'] = transformed[:, 0].reshape(original_shape)
decimated_blocks['y'] = transformed[:, 1].reshape(original_shape)

Rationale:

  • Uses osgeo.osr which is already imported and used extensively in the same file (lines 280-290)
  • Maintains identical functionality for watermask coordinate reprojection (EPSG:4326 → arbitrary EPSG)
  • SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER) ensures (lon, lat) order, equivalent to pyproj's always_xy=True
  • Both pyproj and GDAL use the PROJ library underneath - no performance impact expected

2. Deleted Dead Code

Removed: tests/cxx/isce3/core/ellipsoid/llhxyz.py

Reason:

  • Not imported or executed by any code (verified via comprehensive search)
  • Not referenced in CMakeLists.txt or test configuration files
  • Historical test data generator from 2017 - the generated test data has been hardcoded into tests/cxx/isce3/core/ellipsoid/ellipsoid.cpp
  • Used deprecated pyproj 1.x API (pyproj.transform())

Benefits

Resolves conda-forge feedstock issues - eliminates malformed pyproj==3.7.1 dependency spec
No new dependencies - uses existing GDAL >= 3.6 requirement
Follows established patterns - osgeo.osr is used in 29 files across the codebase
Same performance - both pyproj and GDAL use the PROJ library underneath
Removes dead code - eliminates unused test file with deprecated API
Simplifies maintenance - one less direct dependency to track

Technical Details

GDAL Version Compatibility

  • ISCE3 requires GDAL >= 3.6 (specified in environment.yml)
  • SetAxisMappingStrategy() was introduced in GDAL 3.0.0 (2019)
  • No try/except needed - the method is guaranteed to exist in all supported environments

Axis Mapping Importance

  • GDAL 3.x follows authority-defined axis order: EPSG:4326 = (latitude, longitude)
  • OAMS_TRADITIONAL_GIS_ORDER forces (longitude, latitude) order expected by GIS software
  • Without this setting, coordinates would be swapped, causing spatial transformation errors
  • Equivalent to pyproj's always_xy=True parameter

Testing Recommendations

  • Run unwrapping preprocessing tests to verify watermask reprojection functionality
  • Test with watermasks in different EPSG codes (e.g., 3031, 3413, 32606)

Note: Full test suite will be automatically run by the ISCE3 CI system.

References


Note: While pyproj may still appear as a transitive dependency from pyaps3 or raider-base, ISCE3 no longer imports or uses it directly. This resolves the feedstock dependency specification issue while maintaining full functionality.

@Tyler-g-hudson

Copy link
Copy Markdown
Contributor Author

@seongsujeong This PR replaces some lines that you wrote in the unwrapping code, please confirm that the functionality of the code is sufficient to replace the prior code.

@seongsujeong @oberonia78 Please weigh in with high priority, as this issue is preventing users from getting recent ISCE3 versions on conda-forge

@seongsujeong

Copy link
Copy Markdown
Contributor

I tested the code with randomly generated latitude / longitudes. The lat / lon arrays are transformed by current code and what is suggested in this PR. The target EPSG is 3031 in this test.

I see differences which does not make sense. Let me share the notebook offline, so that you can take a look.

Screenshot 2026-07-22 at 15 43 32

@seongsujeong

Copy link
Copy Markdown
Contributor

I tested the code with randomly generated latitude / longitudes. The lat / lon arrays are transformed by current code and what is suggested in this PR. The target EPSG is 3031 in this test.

I see differences which does not make sense. Let me share the notebook offline, so that you can take a look.

Screenshot 2026-07-22 at 15 43 32

Please disregard my comment above. It was due to a typo in the notebook.

@seongsujeong seongsujeong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The change was tested and confirmed that the transformed coordinates are identical. I see few seconds of increase in the runtime, which I do not think significant.

@Tyler-g-hudson

Copy link
Copy Markdown
Contributor Author

@seongsujeong Thanks for checking on this!!

@Tyler-g-hudson

Copy link
Copy Markdown
Contributor Author

@hfattahi @oberonia78 Seongsu has given an approval after testing the PR, please review when able. I will be gone until Tuesday after we finish this evening so feel free to squash/merge if and when we have appropriate approvals

@hfattahi

Copy link
Copy Markdown
Contributor

@Tyler-g-hudson since we are not using pyproj extensively and you are replacing one usage with direct gdal use plus eliminating one unit test, then the solution seems fine to me for now. However, does this solution mean that ISCE3 users can not have one environment with both ISCE3 and pyproj ?
Also you mentioned raider which I did not fully understand. If raider uses pyproj, then aren't we still in trouble as InSAR workflow uses raider?

@xhuang-jpl we need to make sure this PR runs fine for an end to end InSAR run (inlcuding using water mask and tropo delay).

BTW, I looked at conda forge feedstock error log and for clarity it is worth mentioning the exact issue:

2026-07-22T18:52:22.6174911Z ├─ isce3 =0.25.16 py311cuda129h66f6e2a_0_cuda is not installable because it requires
2026-07-22T18:52:22.6175347Z │  ├─ libgdal-core >=3.12.4,<3.13.0a0 *, which requires
2026-07-22T18:52:22.6175695Z │  │  └─ proj >=9.8.1,<9.9.0a0 *, which can be installed;
2026-07-22T18:52:22.6176060Z │  └─ pyproj ==3.7.1 * but there are no viable options
2026-07-22T18:52:22.6176379Z │     ├─ pyproj 3.7.1 would require
2026-07-22T18:52:22.6176785Z │     │  └─ proj >=9.5.1,<9.6.0a0 *, which conflicts with any installable versions previously reported;
2026-07-22T18:52:22.6177169Z │     └─ pyproj 3.7.1 would require
2026-07-22T18:52:22.6177557Z │        └─ proj >=9.6.0,<9.7.0a0 *, which conflicts with any installable versions previously reported;
2026-07-22T18:52:22.6178061Z ├─ libgdal-core >=3.12.4,<3.13.0a0 *, which can be installed (as previously explained);
2026-07-22T18:52:22.6178576Z └─ pyproj =3.7.1 *, which cannot be installed (as previously explained).

Looks like we are allowing gdal lower than 3.13.0a0 and right now GDAL is at 3.13.2 . We need extensive testing before using 3.13.x. In the meantime we need a solution like what @Tyler-g-hudson has proposed here.

@xhuang-jpl

Copy link
Copy Markdown
Contributor

thanks @hfattahi , I will do a full test with tropo and water mask included.

@xhuang-jpl

Copy link
Copy Markdown
Contributor

@hfattahi and @Tyler-g-hudson , I have tested the entire InSAR workflows including the tropo and water mask, which works fine.

@Tyler-g-hudson

Copy link
Copy Markdown
Contributor Author

@xhuang-jpl @hfattahi It looks like the tests have succeeded, are we ready to proceed? If so, I will need an approval

@Tyler-g-hudson

Copy link
Copy Markdown
Contributor Author

@hfattahi answering the following questions:

  1. However, does this solution mean that ISCE3 users can not have one environment with both ISCE3 and pyproj ?

They probably can, but the ISCE3 dependency graph is very complex which means that any additional package we add to it could have downstream effects like this.

The issue is that the conda-forge feedstock wants to build for an array of Python versions, and for one of those versions the build process fails if we require pyproj and doesn't fail if we don't, even if pyproj is used by some other dependencies. However, if we don't require pyproj in that build process and attempt to run it, it fails because it tries and fails to import pyproj in these files.

As for why the build fails for that specific version when pyproj is required, I'm not certain, and the behavior is counterintuitive. I've been researching it but have not come to any convincing conclusion regarding why it does this even though I don't pin a pyproj version in the conda-forge feedstock build.

  1. Also you mentioned raider which I did not fully understand. If raider uses pyproj, then aren't we still in trouble as InSAR workflow uses raider?

Not as far as I know, for the reasons discussed above.

@bhawkins

bhawkins commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

I'd guess the problem is a version conflict between isce3 pyproj direct dependency and the transitive dependency. That is, the dependency graph has something like

isce3 → pyproj (version=x)
isce3 → pyaps → pyproj (version=y)

If you can't reconcile the version constraints x and y simultaneously then the build would fail. I'm not sure where this stuff is specified, so I don't know for sure. But maybe we could modify the direct constraint x so that the build succeeds and we don't have to change the code?

@hfattahi hfattahi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Tyler-g-hudson . I think this is a reasonable fix for now until we will need to deal with pyproj in future.

@Tyler-g-hudson
Tyler-g-hudson merged commit 0a8df45 into develop Aug 12, 2026
8 checks passed
Tyler-g-hudson added a commit that referenced this pull request Aug 20, 2026
commit fc8112c
Author: xhuang-jpl <118782850+xhuang-jpl@users.noreply.github.com>
Date:   Thu Aug 20 11:35:37 2026 -0700

    Update Soil Moisture SAS to v0.4.3 (#349)

    * sm r3.4

    * change the SM commit id for R4.0.2

    * update the SM SAS version to v0.4.3

    ---------

    Co-authored-by: Xiaodong Huang <xhuang@nisar-adt-dev-3.jpl.nasa.gov>

commit 0d1600d
Author: SamNemo <11642807+nemo794@users.noreply.github.com>
Date:   Mon Aug 17 16:19:06 2026 -0700

    Update STATIC workflow for new water mask spec (#334)

    * Update STATIC workflow for new water mask spec.

    * Update STATIC runconfig with explicit classification values for water mask

    ---------

    Co-authored-by: Samantha C. Niemoeller <samantha.c.niemoeller@jpl.nasa.gov>

commit be7b3d9
Author: Brian Hawkins <1729052+bhawkins@users.noreply.github.com>
Date:   Thu Aug 13 18:13:07 2026 -0500

    Fix failing unit test test.cxx.isce3.io.raster.raster (#348)

    * Check raster dimensions

    * Initialize all rows in mask file.

commit 0a8df45
Author: Tyler G. Hudson <tyler.g.hudson@gmail.com>
Date:   Tue Aug 11 17:25:18 2026 -0700

    Remove pyproj Dependency to Resolve conda-forge Feedstock Issues (#339)

    * Remove dead code in llhxyz.py that depends on pyproj

    * Update reprojection code in unwrap/preprocess.py

commit 54f2c28
Author: Gustavo H. X. Shiroma <52007211+gshiroma@users.noreply.github.com>
Date:   Thu Jun 25 23:50:55 2026 -0700

    Add radar grid decimation option to compute the static layers layover/shadow mask (#328)

    * disable polarimetric symmetrization by default

    * revert changes to `symmetrize_cross_pol_channels`

    * Update GCOV and GSLC specification XMLs

    * Revert changes to the GCOV and GSLC specification XMLs

    * add radargrid decimation option to compute the layover shadow mask

    * simplify parameter names
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants