fastsar 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastsar-0.1.0/CHANGELOG.md +123 -0
- fastsar-0.1.0/CONTRIBUTING.md +75 -0
- fastsar-0.1.0/LICENSE +21 -0
- fastsar-0.1.0/MANIFEST.in +4 -0
- fastsar-0.1.0/PKG-INFO +121 -0
- fastsar-0.1.0/README.md +80 -0
- fastsar-0.1.0/examples/chain.py +76 -0
- fastsar-0.1.0/examples/form_capella_stripmap.py +64 -0
- fastsar-0.1.0/examples/form_umbra.py +33 -0
- fastsar-0.1.0/fastsar/__init__.py +16 -0
- fastsar-0.1.0/fastsar/_build.py +59 -0
- fastsar-0.1.0/fastsar/api.py +487 -0
- fastsar-0.1.0/fastsar/autofocus.py +136 -0
- fastsar-0.1.0/fastsar/bp.py +356 -0
- fastsar-0.1.0/fastsar/burst.py +265 -0
- fastsar-0.1.0/fastsar/cphd.py +212 -0
- fastsar-0.1.0/fastsar/exact.py +596 -0
- fastsar-0.1.0/fastsar/ffbp.py +516 -0
- fastsar-0.1.0/fastsar/ffbp2.py +924 -0
- fastsar-0.1.0/fastsar/ffbp_cpu.cpp +363 -0
- fastsar-0.1.0/fastsar/ffbp_cpu.py +291 -0
- fastsar-0.1.0/fastsar/ffbp_cuda.py +803 -0
- fastsar-0.1.0/fastsar/io.py +280 -0
- fastsar-0.1.0/fastsar/lowfp.py +44 -0
- fastsar-0.1.0/fastsar/memory.py +164 -0
- fastsar-0.1.0/fastsar/pallas_ffbp.py +648 -0
- fastsar-0.1.0/fastsar/patches.py +774 -0
- fastsar-0.1.0/fastsar/pfa2.py +386 -0
- fastsar-0.1.0/fastsar/products.py +420 -0
- fastsar-0.1.0/fastsar/quality.py +99 -0
- fastsar-0.1.0/fastsar/sim.py +344 -0
- fastsar-0.1.0/fastsar/stripmap.py +417 -0
- fastsar-0.1.0/fastsar.egg-info/PKG-INFO +121 -0
- fastsar-0.1.0/fastsar.egg-info/SOURCES.txt +59 -0
- fastsar-0.1.0/fastsar.egg-info/dependency_links.txt +1 -0
- fastsar-0.1.0/fastsar.egg-info/requires.txt +21 -0
- fastsar-0.1.0/fastsar.egg-info/top_level.txt +1 -0
- fastsar-0.1.0/pyproject.toml +48 -0
- fastsar-0.1.0/setup.cfg +4 -0
- fastsar-0.1.0/tests/run_all.py +140 -0
- fastsar-0.1.0/tests/test_accuracy.py +70 -0
- fastsar-0.1.0/tests/test_api.py +48 -0
- fastsar-0.1.0/tests/test_autofocus.py +64 -0
- fastsar-0.1.0/tests/test_bp.py +114 -0
- fastsar-0.1.0/tests/test_burst.py +167 -0
- fastsar-0.1.0/tests/test_chain.py +216 -0
- fastsar-0.1.0/tests/test_cphd.py +222 -0
- fastsar-0.1.0/tests/test_exact.py +170 -0
- fastsar-0.1.0/tests/test_ffbp_cpu.py +34 -0
- fastsar-0.1.0/tests/test_ffbp_cuda.py +126 -0
- fastsar-0.1.0/tests/test_insar.py +66 -0
- fastsar-0.1.0/tests/test_io.py +45 -0
- fastsar-0.1.0/tests/test_pallas_e2e_tpu.py +55 -0
- fastsar-0.1.0/tests/test_pallas_final.py +63 -0
- fastsar-0.1.0/tests/test_pallas_fused.py +118 -0
- fastsar-0.1.0/tests/test_patches.py +250 -0
- fastsar-0.1.0/tests/test_planning.py +297 -0
- fastsar-0.1.0/tests/test_products.py +52 -0
- fastsar-0.1.0/tests/test_stripmap.py +79 -0
- fastsar-0.1.0/tests/test_units.py +436 -0
- fastsar-0.1.0/tests/test_wide_angle.py +39 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
4
|
+
|
|
5
|
+
## 0.1.0 (2026-10-09)
|
|
6
|
+
|
|
7
|
+
First release.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `fastsar.ExactFormer`: exact backprojection onto the `form_image` grid, set up once per geometry, with CUDA and
|
|
12
|
+
C++ kernels (float64 tile centers, a third-order expansion within each tile, the tile size chosen from the
|
|
13
|
+
expansion's predicted error, cubic or linear interpolation of cropped range profiles);
|
|
14
|
+
`form_image(..., algorithm='bp')` with `interp`, `upsample` and `center`.
|
|
15
|
+
- `fastsar.form_cphd`: one call from a CPHD file to an image; spotlight or moving beam chosen from the scene
|
|
16
|
+
reference point, ground-plane grid at the scene reference height, SICD optional.
|
|
17
|
+
- `products.geolocate` and `products.locate`: latitude, longitude and height of `form_cphd` pixels on the image
|
|
18
|
+
plane, at a height or on a DEM, and the inverse; `products.read_dem` for DEM GeoTIFFs.
|
|
19
|
+
- `products.geocode_image`: a `form_cphd` image, or data from it, on a north-up map grid (UTM, any rasterio CRS,
|
|
20
|
+
or latitude/longitude) with terrain correction; `write_geotiff` writes complex and multi-band data.
|
|
21
|
+
- `products.write_sicd(path, out)` and `products.sicd_meta`: SICD from `form_cphd`'s output with its geometry
|
|
22
|
+
(PLANE grid, aperture polynomial), whose projection matches `geolocate`.
|
|
23
|
+
- `form_cphd(..., autofocus=True)` for spotlight collections; the result also carries the band, the spatial
|
|
24
|
+
bandwidths, the windows and the autofocus phase estimate.
|
|
25
|
+
- `sim.write_cphd`, `sim.to_ecf`: simulated collections as CPHD 1.0.1 files placed on the Earth.
|
|
26
|
+
- `ref=` on `ImageFormer` and `form_image`: per-pulse reference ranges other than `|ant|` (bistatic half paths,
|
|
27
|
+
vendor reference points). Polar format re-references the samples to `|ant|`.
|
|
28
|
+
- `ImageFormer.stage(S)`: the checks, scaling and upload of a history ahead of formation on JAX and TPU.
|
|
29
|
+
- `fastsar.MemoryWarning` when a former falls back to a slower path for lack of memory (smaller first-level
|
|
30
|
+
groups, CPU pulse blocks, CUDA streaming, a GPU window of mosaic range profiles), stating the memory full speed
|
|
31
|
+
needs; `ImageFormer.memory()`. A TPU that cannot hold the history raises `MemoryError`. `MemoryWarning`
|
|
32
|
+
subclasses `UserWarning`, so `python -W error` or pytest's `filterwarnings = error` turns a fallback into an
|
|
33
|
+
exception.
|
|
34
|
+
- Stripmap and sliding spotlight mosaics apply the azimuth window in the final stage of factorized backprojection
|
|
35
|
+
(C++, CUDA and JAX); range profiles are computed once per mosaic; the next patches are prepared on worker threads.
|
|
36
|
+
- `patches.gate_bins`, `patches.choose_buckets` and `patch_history(gate_len=)`: JAX and TPU mosaics plan the pulse
|
|
37
|
+
counts and range gates of their compiled programs for the whole mosaic.
|
|
38
|
+
- The CUDA former streams a host phase history whose device planes exceed half the free GPU memory through the
|
|
39
|
+
first level; the CPU former reads a complex64 history in place and sizes its first-level groups from the memory
|
|
40
|
+
available.
|
|
41
|
+
- JAX and TPU formers cache compiled programs and the plan's device arrays by plan signature (up to 32).
|
|
42
|
+
- Polar format keeps its compiled program and geometry arrays per collection geometry (the last four), and applies
|
|
43
|
+
the window, the spectral weighting and the scaling on the device.
|
|
44
|
+
- `ExactFormer` on CUDA halves its pulses per chunk on an out-of-memory error instead of failing (with a
|
|
45
|
+
`MemoryWarning`).
|
|
46
|
+
- Environment variables `FASTSAR_SHARED_PROFILES`, `FASTSAR_WEIGHT_TERMS`, `FASTSAR_WEIGHT_GRAD`,
|
|
47
|
+
`FASTSAR_MOSAIC_PREFETCH`, `FASTSAR_COMPILE_PATCHES`, `FASTSAR_PULSE_SLACK`, `FASTSAR_CPU_GROUP_GB`,
|
|
48
|
+
`FASTSAR_CUDA_GROUP`, `FASTSAR_TPU_GROUP`, `FASTSAR_CUDA_STREAM` and `FASTSAR_TIMING`
|
|
49
|
+
([performance.md](docs/performance.md#environment-variables)); numeric values out of range raise `ValueError`.
|
|
50
|
+
- `examples/chain.py`.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- `read_cphd`'s local frame takes z along the ellipsoid normal at the scene reference point instead of the
|
|
55
|
+
geocentric radial (up to 0.19 degrees apart). `form_cphd`'s ground plane is now horizontal at the scene, as
|
|
56
|
+
documented; its comparisons with vendor images in `docs/real-data.md` were measured before this change.
|
|
57
|
+
- `read_cphd`'s meta carries the collection start, collector and core name.
|
|
58
|
+
- `form_cphd` references a spotlight history to |ant - c|, the range to the grid center, so its images change.
|
|
59
|
+
- JAX and TPU mosaics widen range gates onto shared lengths, so their images differ slightly from before.
|
|
60
|
+
- `FASTSAR_MOSAIC_PREFETCH` defaults to up to 4 worker threads on a GPU or TPU (1 before; still 1 on the CPU).
|
|
61
|
+
- The JAX program cache holds 32 programs instead of 16.
|
|
62
|
+
- CUDA float32 histories are no longer scaled to their peak.
|
|
63
|
+
- The CUDA former windows and checks a host history on the GPU as its row blocks are uploaded, without a full
|
|
64
|
+
complex copy on the device (host-side windowing cost a 4-vCPU instance more than the formation); `ExactFormer`
|
|
65
|
+
scans for NaN and inf on the GPU.
|
|
66
|
+
- Importing FastSAR sets `XLA_PYTHON_CLIENT_PREALLOCATE=false` unless already set, so that JAX does not take 75% of
|
|
67
|
+
a GPU's memory that the CUDA formers share.
|
|
68
|
+
- CPU first-level groups follow the available memory (MemAvailable), so they vary on a busy host.
|
|
69
|
+
- `form_image`, `ImageFormer`, `backproject`, `patches.form_mosaic`, `io.read_cphd` and `form_cphd` check their
|
|
70
|
+
inputs: a phase history that is not 2-D or not complex, has NaN or inf samples, fewer than 2 pulses or a pulse
|
|
71
|
+
count different from the antenna path; antenna positions not [pulses, 3]; pixel counts that are not positive
|
|
72
|
+
integers, non-positive spacings, axes that are not orthonormal; unknown backends, and `cuda` or `tpu` on a
|
|
73
|
+
machine without one, raise `ValueError` or `TypeError` with the reason. complex128 and non-contiguous histories
|
|
74
|
+
are converted.
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- An all-zero phase history gave a NaN image on the JAX and TPU paths and with polar format, and a
|
|
79
|
+
ZeroDivisionError on the CPU path; it now gives a zero image.
|
|
80
|
+
- `api.final_weights` gave weight zero to the final subapertures centered beyond the collection, which hold the
|
|
81
|
+
decimation filters' tails of the edge pulses: a unit `aperture_weight` changed the image by -32 dB. They now
|
|
82
|
+
take the weight of the nearest pulse, and a unit weight leaves the image unchanged.
|
|
83
|
+
- `ffbp2.decimator_fir` differed from the column of `ffbp.decimator` by up to 3e-16; it is now equal bit for bit.
|
|
84
|
+
- `backproject` with a `ref` shorter than the pulse count read past its end in the C++ kernel.
|
|
85
|
+
- `patches.fill_gaps` divided by zero for a single position or a platform at rest.
|
|
86
|
+
- `sim.simulate_brute` held a [pulses, samples, scatterers] array (5.8 GB for the dropped-pulse case of
|
|
87
|
+
`tests/test_patches.py`); it now sums in blocks of scatterers.
|
|
88
|
+
- A failure while the mosaic prefetches the next patch now shuts its thread down.
|
|
89
|
+
|
|
90
|
+
### Tests
|
|
91
|
+
|
|
92
|
+
- `tests/run_all.py` runs every test script with a timeout and a memory limit and prints a summary.
|
|
93
|
+
- `tests/test_units.py`: decimation kernels, the JAX program cache, the C++ input paths and group sizes, aperture
|
|
94
|
+
weights, shared range profiles, pulse spans, gap filling, mosaic prefetch, input checks, `ref=`.
|
|
95
|
+
- `tests/test_cphd.py`: `read_cphd` and `form_cphd` end to end on simulated spotlight and stripmap collections
|
|
96
|
+
through a stand-in for sarpy's CPHD reader.
|
|
97
|
+
- `tests/test_chain.py`: a simulated CPHD through geolocation, map GeoTIFFs, SICD and autofocus.
|
|
98
|
+
- `tests/test_planning.py`: bucket planning, the CUDA and TPU memory models, the TPU out-of-memory retry, the
|
|
99
|
+
program cache under concurrent formers, and environment variable checks.
|
|
100
|
+
- `tests/test_accuracy.py`: the tile-size and oversampling figures of `docs/algorithms.md`.
|
|
101
|
+
|
|
102
|
+
### Documentation
|
|
103
|
+
|
|
104
|
+
- Processing-chain page and API reference; README reduced to a landing page with a supported-data table.
|
|
105
|
+
- Documentation matches the geolocation, map GeoTIFF, SICD and autofocus calls for `form_cphd` output; each fact
|
|
106
|
+
stated on one page.
|
|
107
|
+
- Shorter README with figures; the long-form material moved to `docs/`.
|
|
108
|
+
- Contributing guide, issue and pull request templates, and package metadata for PyPI-style tools.
|
|
109
|
+
|
|
110
|
+
### Initial functionality
|
|
111
|
+
|
|
112
|
+
- Spotlight image formation by factorized backprojection with kernels for x86 CPUs (C++/OpenMP), Nvidia GPUs
|
|
113
|
+
(CUDA through CuPy) and Cloud TPUs (Pallas), and the plain JAX program; `form_image` and `ImageFormer`.
|
|
114
|
+
- Final tile size chosen from the predicted error of the collection geometry.
|
|
115
|
+
- Polar format with the planar-wavefront displacement correction.
|
|
116
|
+
- Exact backprojection onto arbitrary points, monostatic or bistatic, with per-pulse reference ranges.
|
|
117
|
+
- CPHD reader: per-pulse frequency grids, moving reference points, channels and polarizations, SICD pixel-grid
|
|
118
|
+
alignment, troposphere delay, Capella phase sign, a warning for delay windows beyond the unambiguous range.
|
|
119
|
+
- Phase gradient autofocus.
|
|
120
|
+
- Stripmap focusing (range-Doppler, omega-k, backprojection), patch mosaics for long apertures and arbitrary
|
|
121
|
+
tracks, ScanSAR and TOPS burst modes.
|
|
122
|
+
- Products: multilook, interferogram, coherence, Pauli decomposition, range-Doppler projection and geocoding,
|
|
123
|
+
GeoTIFF and SICD output.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Contributing to FastSAR
|
|
2
|
+
|
|
3
|
+
Bug reports, questions and pull requests are welcome. Please open an issue first for a change that touches a
|
|
4
|
+
kernel or the public API, so the approach can be agreed before the code is written.
|
|
5
|
+
|
|
6
|
+
## Development setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/saulpingerman/FastSAR
|
|
10
|
+
cd FastSAR
|
|
11
|
+
pip install -e ".[io,test]" # add cuda on a machine with an Nvidia GPU: ".[cuda,io,test]"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
or `uv sync`, which installs the versions pinned in `uv.lock`. The CPU backend needs `g++` with OpenMP. Some
|
|
15
|
+
tests need packages that are not dependencies of the library: `finufft` (`tests/test_insar.py`), `rasterio`
|
|
16
|
+
(`products.write_geotiff`, `read_dem` and map projections other than latitude/longitude) and `sarkit` (optional
|
|
17
|
+
consistency checks in `tests/test_chain.py`).
|
|
18
|
+
|
|
19
|
+
## Running the tests
|
|
20
|
+
|
|
21
|
+
The tests are plain scripts, not pytest functions. Each prints its measurements, stops with an `AssertionError`
|
|
22
|
+
when a check fails, and exits 0 when it passes. Run them from the repository root:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python tests/run_all.py # every script below, with a timeout and a memory limit, and a summary
|
|
26
|
+
python tests/test_api.py # every backend this machine has against the JAX program, and polar format
|
|
27
|
+
python tests/test_ffbp_cpu.py # C++ kernels against the dense JAX image, two and three levels
|
|
28
|
+
python tests/test_ffbp_cuda.py # CUDA kernels against the dense JAX image (needs a GPU)
|
|
29
|
+
python tests/test_pallas_fused.py # TPU level kernel in interpret mode (runs on a CPU)
|
|
30
|
+
python tests/test_pallas_final.py # TPU final-stage kernel in interpret mode (runs on a CPU)
|
|
31
|
+
python tests/test_pallas_e2e_tpu.py # TPU kernels end to end in interpret mode (runs on a CPU)
|
|
32
|
+
python tests/test_autofocus.py cpu # phase gradient autofocus; the argument is the backend (default jax)
|
|
33
|
+
python tests/test_stripmap.py # stripmap omega-k and RDA against float64 backprojection
|
|
34
|
+
python tests/test_patches.py # patch mosaics for stripmap and non-linear tracks
|
|
35
|
+
python tests/test_burst.py # ScanSAR and TOPS burst focusing
|
|
36
|
+
python tests/test_products.py # layover projection, geocoding, multilooking, Pauli
|
|
37
|
+
python tests/test_bp.py # exact backprojection: backends, bistatic, orbital range, moving reference
|
|
38
|
+
python tests/test_io.py # CPHD helpers: frequency resampling, re-referencing, geodetic conversions
|
|
39
|
+
python tests/test_wide_angle.py # 10 to 360 degree apertures against exact backprojection
|
|
40
|
+
python tests/test_insar.py # change detection and interferometric height (needs finufft)
|
|
41
|
+
python tests/test_chain.py # simulated CPHD to geolocation, map GeoTIFFs, SICD and autofocus (needs sarpy, rasterio)
|
|
42
|
+
python tests/test_exact.py # ExactFormer: 1 to 600 km and a wide near-range grid against backprojection, validation
|
|
43
|
+
python tests/test_units.py # filters, program cache, aperture weights, mosaic pieces, input checks
|
|
44
|
+
python tests/test_cphd.py # read_cphd and form_cphd on simulated collections (stand-in CPHD reader)
|
|
45
|
+
python tests/test_planning.py # bucket planning, memory models, TPU out-of-memory retry, program cache
|
|
46
|
+
python tests/test_accuracy.py # tile-size and oversampling errors against exact backprojection
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`run_all.py` reports SKIP for a script that needs hardware this machine lacks (`test_ffbp_cuda.py` without a GPU);
|
|
50
|
+
`--require cuda,tpu` makes that a failure. A script whose test package is missing fails; install them with
|
|
51
|
+
`pip install -e ".[io,test]"`. It stops a script that runs past `-t` seconds (default 600) or holds more than
|
|
52
|
+
`--max-rss-gb` of memory (default 6 GB, 12 GB on a TPU host, whose runtime holds about 5 GB; or `FASTSAR_TEST_MAX_GB`), and exits non-zero if any script fails. `-v` prints
|
|
53
|
+
every script's output.
|
|
54
|
+
|
|
55
|
+
`test_autofocus.py` is the only script that takes a backend argument. The others pick the backends this machine
|
|
56
|
+
has (`fastsar.available_backends()`) or use the CPU. The tests use small simulated scenes and take seconds to a few
|
|
57
|
+
minutes each. For the CPU kernels, set `OMP_NUM_THREADS` to the number of physical cores.
|
|
58
|
+
|
|
59
|
+
`test_ffbp_cpu.py`, `test_ffbp_cuda.py` and the three `test_pallas_*.py` scripts compare each error with a limit
|
|
60
|
+
set about 5 dB above the value measured when the limit was set, and exit with a list of the failed cases.
|
|
61
|
+
|
|
62
|
+
## Pull requests
|
|
63
|
+
|
|
64
|
+
- Keep a pull request to one change, and state in the description which tests ran and on which devices.
|
|
65
|
+
- A change to a kernel should report the error against the JAX program or exact backprojection before and after,
|
|
66
|
+
as the tests print it.
|
|
67
|
+
- A change that adds a result to the documentation should say what data it comes from. Results from simulated
|
|
68
|
+
data are labeled as test results.
|
|
69
|
+
- Do not commit data files (CPHD, SICD, NITF, `.npy`) or build artifacts.
|
|
70
|
+
|
|
71
|
+
## Reporting a bug
|
|
72
|
+
|
|
73
|
+
Use the bug report template. Include the FastSAR commit, the backend, the device, the Python, JAX and CuPy
|
|
74
|
+
versions, and if possible a script that reproduces the problem on a simulated scene (`fastsar.sim`), since radar
|
|
75
|
+
data files are often large or not shareable.
|
fastsar-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Paul Singerman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fastsar-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastsar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Synthetic aperture radar image formation on x86 CPUs, Nvidia GPUs and Cloud TPUs
|
|
5
|
+
Author: Paul Singerman
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/saulpingerman/FastSAR
|
|
8
|
+
Project-URL: Documentation, https://github.com/saulpingerman/FastSAR/tree/main/docs
|
|
9
|
+
Project-URL: Source, https://github.com/saulpingerman/FastSAR
|
|
10
|
+
Project-URL: Issues, https://github.com/saulpingerman/FastSAR/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/saulpingerman/FastSAR/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: synthetic aperture radar,SAR,backprojection,image formation,CPHD,SICD,GPU,TPU
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: C++
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy>=2.2.6
|
|
25
|
+
Requires-Dist: scipy>=1.15.3
|
|
26
|
+
Requires-Dist: jax>=0.6.2
|
|
27
|
+
Provides-Extra: cuda
|
|
28
|
+
Requires-Dist: cupy-cuda12x; extra == "cuda"
|
|
29
|
+
Provides-Extra: io
|
|
30
|
+
Requires-Dist: sarpy; extra == "io"
|
|
31
|
+
Provides-Extra: tpu
|
|
32
|
+
Requires-Dist: jax[tpu]; extra == "tpu"
|
|
33
|
+
Provides-Extra: geo
|
|
34
|
+
Requires-Dist: rasterio; extra == "geo"
|
|
35
|
+
Provides-Extra: test
|
|
36
|
+
Requires-Dist: sarpy; extra == "test"
|
|
37
|
+
Requires-Dist: sarkit; extra == "test"
|
|
38
|
+
Requires-Dist: rasterio; extra == "test"
|
|
39
|
+
Requires-Dist: finufft; extra == "test"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# FastSAR
|
|
43
|
+
|
|
44
|
+
[](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/LICENSE)
|
|
45
|
+
[](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/pyproject.toml)
|
|
46
|
+
|
|
47
|
+
FastSAR forms synthetic aperture radar (SAR) images on x86 CPUs, Nvidia GPUs and Google Cloud TPUs from Python. It
|
|
48
|
+
reads frequency-domain CPHD files, forms spotlight, stripmap and sliding spotlight images by factorized
|
|
49
|
+
backprojection, and provides autofocus, interferometry, geolocation, map GeoTIFFs and SICD output.
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
*Panama Canal, Pacific entrance (Umbra open data, 2023-07-18): 12,207 by 8,808 pixels in 3.8 s on an Nvidia L4
|
|
54
|
+
(warm former, transfers included; float32, -59.9 dB against float64 exact backprojection), downsampled. A
|
|
55
|
+
c4d-highmem-16 CPU instance (16 vCPUs, 8 cores) takes 11.3 s. ISCE3, the fastest open-source code that forms this
|
|
56
|
+
collection, would take an estimated 4.8 h on that CPU and 27 min on the L4.*
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
*Cost per 1000 Panama images (October 2026 prices) against error over the lock, port and ship regions, relative to
|
|
61
|
+
the float64 reference ([performance](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/docs/performance.md), [comparison](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/docs/comparison.md)).*
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install fastsar # CPU (and JAX on whatever device it has)
|
|
67
|
+
pip install "fastsar[io]" # io: sarpy for CPHD and SICD
|
|
68
|
+
pip install "fastsar[cuda]" # cuda: CuPy for Nvidia GPUs
|
|
69
|
+
pip install "fastsar[tpu]" # tpu: jax[tpu] for Cloud TPUs
|
|
70
|
+
pip install "fastsar[geo]" # geo: rasterio for GeoTIFFs and DEMs
|
|
71
|
+
pip install "fastsar[cuda,io,geo]" # extras combine
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
From a clone: `pip install -e ".[cuda,io]"` or `uv sync`. The CPU kernels need `g++` with OpenMP and compile on
|
|
75
|
+
first use; the float16 CUDA kernel needs `nvcc`. On the CPU set
|
|
76
|
+
`OMP_NUM_THREADS=<physical cores> OMP_PLACES=cores OMP_PROC_BIND=close`.
|
|
77
|
+
|
|
78
|
+
## Quickstart
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import fastsar
|
|
82
|
+
from fastsar import products
|
|
83
|
+
|
|
84
|
+
out = fastsar.form_cphd('scene_CPHD.cphd') # mode, grid and window from the file
|
|
85
|
+
img = out['image'] # complex64 [nx, ny] on a ground plane at the scene height
|
|
86
|
+
lat, lon, h = products.geolocate(out, 100, 200) # pixel (100, 200) on the ground
|
|
87
|
+
products.write_geotiff('amp.tif', **products.geocode_image(out)) # amplitude on a north-up UTM grid
|
|
88
|
+
products.write_sicd('scene.nitf', out) # complex image with its geometry
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
[`examples/chain.py`](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/examples/chain.py) runs this chain from the command line (`--simulate` needs no data);
|
|
92
|
+
[`form_umbra.py`](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/examples/form_umbra.py) and [`form_capella_stripmap.py`](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/examples/form_capella_stripmap.py) form
|
|
93
|
+
vendor grids.
|
|
94
|
+
|
|
95
|
+
## Supported data, modes and devices
|
|
96
|
+
|
|
97
|
+
| Data | Modes | Status |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Capella CPHD | stripmap, spotlight, sliding spotlight | checked against vendor SICDs; a 2022 dynamic stripmap is correct near the center only |
|
|
100
|
+
| Umbra CPHD | spotlight | three open-data collections on the vendor's grid |
|
|
101
|
+
| ICEYE CPHD | dwell spotlight | formed (28 GB history); not compared with the vendor image |
|
|
102
|
+
| TOA-domain CPHD, raw Level-0 | | not supported |
|
|
103
|
+
| Simulated echoes | stripmap, ScanSAR, TOPS, any track | `fastsar.stripmap`, `burst`, `patches` |
|
|
104
|
+
|
|
105
|
+
| | CPU (`cpu`) | Nvidia GPU (`cuda`) | Cloud TPU (`tpu`) | Any JAX device (`jax`) |
|
|
106
|
+
|---|---|---|---|---|
|
|
107
|
+
| Factorized backprojection | C++/OpenMP, AVX-512 or AVX2 | CUDA via CuPy | Pallas | reference program |
|
|
108
|
+
| Precision | float32 | float32, float16 | three-pass (default), single-pass | float32, float16 |
|
|
109
|
+
| Exact backprojection (`ExactFormer`) | C++/OpenMP, float32 with float64 tile centers | CUDA, the same | falls back to `jax` | float32, float64 block centers |
|
|
110
|
+
|
|
111
|
+
Polar format (`algorithm='pfa'`) runs as a JAX program on any device.
|
|
112
|
+
|
|
113
|
+
## Documentation
|
|
114
|
+
|
|
115
|
+
[docs/README.md](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/docs/README.md) lists every page; start with the [processing chain](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/docs/processing-chain.md).
|
|
116
|
+
Measurement records are in [sar-accel-study](https://github.com/saulpingerman/sar-accel-study). A citation file
|
|
117
|
+
will come with the preprint.
|
|
118
|
+
|
|
119
|
+
## Contributing and license
|
|
120
|
+
|
|
121
|
+
[CONTRIBUTING.md](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/CONTRIBUTING.md) explains how to run the tests. MIT license ([LICENSE](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/LICENSE)).
|
fastsar-0.1.0/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# FastSAR
|
|
2
|
+
|
|
3
|
+
[](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/LICENSE)
|
|
4
|
+
[](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/pyproject.toml)
|
|
5
|
+
|
|
6
|
+
FastSAR forms synthetic aperture radar (SAR) images on x86 CPUs, Nvidia GPUs and Google Cloud TPUs from Python. It
|
|
7
|
+
reads frequency-domain CPHD files, forms spotlight, stripmap and sliding spotlight images by factorized
|
|
8
|
+
backprojection, and provides autofocus, interferometry, geolocation, map GeoTIFFs and SICD output.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
*Panama Canal, Pacific entrance (Umbra open data, 2023-07-18): 12,207 by 8,808 pixels in 3.8 s on an Nvidia L4
|
|
13
|
+
(warm former, transfers included; float32, -59.9 dB against float64 exact backprojection), downsampled. A
|
|
14
|
+
c4d-highmem-16 CPU instance (16 vCPUs, 8 cores) takes 11.3 s. ISCE3, the fastest open-source code that forms this
|
|
15
|
+
collection, would take an estimated 4.8 h on that CPU and 27 min on the L4.*
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
*Cost per 1000 Panama images (October 2026 prices) against error over the lock, port and ship regions, relative to
|
|
20
|
+
the float64 reference ([performance](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/docs/performance.md), [comparison](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/docs/comparison.md)).*
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install fastsar # CPU (and JAX on whatever device it has)
|
|
26
|
+
pip install "fastsar[io]" # io: sarpy for CPHD and SICD
|
|
27
|
+
pip install "fastsar[cuda]" # cuda: CuPy for Nvidia GPUs
|
|
28
|
+
pip install "fastsar[tpu]" # tpu: jax[tpu] for Cloud TPUs
|
|
29
|
+
pip install "fastsar[geo]" # geo: rasterio for GeoTIFFs and DEMs
|
|
30
|
+
pip install "fastsar[cuda,io,geo]" # extras combine
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
From a clone: `pip install -e ".[cuda,io]"` or `uv sync`. The CPU kernels need `g++` with OpenMP and compile on
|
|
34
|
+
first use; the float16 CUDA kernel needs `nvcc`. On the CPU set
|
|
35
|
+
`OMP_NUM_THREADS=<physical cores> OMP_PLACES=cores OMP_PROC_BIND=close`.
|
|
36
|
+
|
|
37
|
+
## Quickstart
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import fastsar
|
|
41
|
+
from fastsar import products
|
|
42
|
+
|
|
43
|
+
out = fastsar.form_cphd('scene_CPHD.cphd') # mode, grid and window from the file
|
|
44
|
+
img = out['image'] # complex64 [nx, ny] on a ground plane at the scene height
|
|
45
|
+
lat, lon, h = products.geolocate(out, 100, 200) # pixel (100, 200) on the ground
|
|
46
|
+
products.write_geotiff('amp.tif', **products.geocode_image(out)) # amplitude on a north-up UTM grid
|
|
47
|
+
products.write_sicd('scene.nitf', out) # complex image with its geometry
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
[`examples/chain.py`](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/examples/chain.py) runs this chain from the command line (`--simulate` needs no data);
|
|
51
|
+
[`form_umbra.py`](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/examples/form_umbra.py) and [`form_capella_stripmap.py`](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/examples/form_capella_stripmap.py) form
|
|
52
|
+
vendor grids.
|
|
53
|
+
|
|
54
|
+
## Supported data, modes and devices
|
|
55
|
+
|
|
56
|
+
| Data | Modes | Status |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Capella CPHD | stripmap, spotlight, sliding spotlight | checked against vendor SICDs; a 2022 dynamic stripmap is correct near the center only |
|
|
59
|
+
| Umbra CPHD | spotlight | three open-data collections on the vendor's grid |
|
|
60
|
+
| ICEYE CPHD | dwell spotlight | formed (28 GB history); not compared with the vendor image |
|
|
61
|
+
| TOA-domain CPHD, raw Level-0 | | not supported |
|
|
62
|
+
| Simulated echoes | stripmap, ScanSAR, TOPS, any track | `fastsar.stripmap`, `burst`, `patches` |
|
|
63
|
+
|
|
64
|
+
| | CPU (`cpu`) | Nvidia GPU (`cuda`) | Cloud TPU (`tpu`) | Any JAX device (`jax`) |
|
|
65
|
+
|---|---|---|---|---|
|
|
66
|
+
| Factorized backprojection | C++/OpenMP, AVX-512 or AVX2 | CUDA via CuPy | Pallas | reference program |
|
|
67
|
+
| Precision | float32 | float32, float16 | three-pass (default), single-pass | float32, float16 |
|
|
68
|
+
| Exact backprojection (`ExactFormer`) | C++/OpenMP, float32 with float64 tile centers | CUDA, the same | falls back to `jax` | float32, float64 block centers |
|
|
69
|
+
|
|
70
|
+
Polar format (`algorithm='pfa'`) runs as a JAX program on any device.
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
[docs/README.md](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/docs/README.md) lists every page; start with the [processing chain](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/docs/processing-chain.md).
|
|
75
|
+
Measurement records are in [sar-accel-study](https://github.com/saulpingerman/sar-accel-study). A citation file
|
|
76
|
+
will come with the preprint.
|
|
77
|
+
|
|
78
|
+
## Contributing and license
|
|
79
|
+
|
|
80
|
+
[CONTRIBUTING.md](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/CONTRIBUTING.md) explains how to run the tests. MIT license ([LICENSE](https://github.com/saulpingerman/FastSAR/blob/v0.1.0/LICENSE)).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""From a CPHD file to products: a complex SICD with its geometry, a map-projected amplitude GeoTIFF (dB, UTM) and a
|
|
2
|
+
PNG quicklook of it.
|
|
3
|
+
|
|
4
|
+
python examples/chain.py scene_CPHD.cphd out [--sicd scene_SICD.ntf] [--dem dem.tif --geoid 31.5] [--autofocus]
|
|
5
|
+
python examples/chain.py --simulate out # writes a small simulated collection to out_sim.cphd first
|
|
6
|
+
|
|
7
|
+
Writes out.nitf (SICD), out_db.tif and out_db.png. --looks sets the multilooking (along track, across track) before
|
|
8
|
+
detection; --dem a DEM GeoTIFF for terrain correction, with --geoid the geoid's height above the ellipsoid at the
|
|
9
|
+
scene when the DEM gives heights above the geoid (without --dem the map lies at the image center's height).
|
|
10
|
+
"""
|
|
11
|
+
import argparse, time
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
import fastsar
|
|
16
|
+
from fastsar import products, sim
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def simulate(path):
|
|
20
|
+
"""A 100 m spotlight scene at 6 km (0.5 m resolution): a 3 x 3 grid of point targets over weak clutter."""
|
|
21
|
+
rng = np.random.default_rng(0)
|
|
22
|
+
col = sim.make_collect(res=0.5, scene=100.0, r0=6e3, graze_deg=35.0)
|
|
23
|
+
g = np.linspace(-30.0, 30.0, 3)
|
|
24
|
+
pts = np.stack([*np.meshgrid(g, g), np.zeros((3, 3))], -1).reshape(-1, 3)
|
|
25
|
+
cl = np.concatenate([rng.uniform(-45, 45, (3000, 2)), np.zeros((3000, 1))], 1)
|
|
26
|
+
pos = np.concatenate([pts, cl])
|
|
27
|
+
amp = np.concatenate([np.full(9, 30.0), 0.5 * (rng.standard_normal(3000) + 1j * rng.standard_normal(3000))])
|
|
28
|
+
S = sum(sim.simulate_brute(col, pos[i:i + 50], amp[i:i + 50]) for i in range(0, len(pos), 50))
|
|
29
|
+
sim.write_cphd(path, col, S.astype(np.complex64), lat=40.7934, lon=-77.86, height=351.0, heading=30.0)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
ap = argparse.ArgumentParser()
|
|
34
|
+
ap.add_argument('cphd', nargs='?'); ap.add_argument('out')
|
|
35
|
+
ap.add_argument('--simulate', action='store_true'); ap.add_argument('--sicd')
|
|
36
|
+
ap.add_argument('--dem'); ap.add_argument('--geoid', type=float, default=0.0)
|
|
37
|
+
ap.add_argument('--autofocus', action='store_true'); ap.add_argument('--backend', default='auto')
|
|
38
|
+
ap.add_argument('--looks', type=int, nargs=2, default=(2, 2)); ap.add_argument('--spacing', type=float)
|
|
39
|
+
a = ap.parse_args()
|
|
40
|
+
if a.simulate:
|
|
41
|
+
a.cphd = a.out + '_sim.cphd'
|
|
42
|
+
simulate(a.cphd)
|
|
43
|
+
t = time.perf_counter()
|
|
44
|
+
out = fastsar.form_cphd(a.cphd, sicd=a.sicd, backend=a.backend, autofocus=a.autofocus)
|
|
45
|
+
img = out['image']
|
|
46
|
+
print(f'{out["mode"]} image {img.shape}, {out["spx"]:.3f} x {out["spy"]:.3f} m pixels, '
|
|
47
|
+
f'{time.perf_counter() - t:.1f} s')
|
|
48
|
+
for n in out['notes']:
|
|
49
|
+
print(' ' + n)
|
|
50
|
+
nx, ny = img.shape
|
|
51
|
+
lat, lon, h = products.geolocate(out, [0, 0, nx - 1, nx - 1, nx / 2], [0, ny - 1, 0, ny - 1, ny / 2])
|
|
52
|
+
print('corners and center (lat, lon, height on the image plane):')
|
|
53
|
+
for v in zip(lat, lon, h):
|
|
54
|
+
print(' %.6f %.6f %.1f' % v)
|
|
55
|
+
|
|
56
|
+
products.write_sicd(a.out + '.nitf', out)
|
|
57
|
+
dem = products.read_dem(a.dem, offset=a.geoid) if a.dem else None
|
|
58
|
+
power = products.multilook(img, *a.looks)
|
|
59
|
+
geo = products.geocode_image(out, power, spacing=a.spacing, height=dem)
|
|
60
|
+
geo['data'] = products.to_db(geo['data'])
|
|
61
|
+
products.write_geotiff(a.out + '_db.tif', **geo)
|
|
62
|
+
print(f'wrote {a.out}.nitf and {a.out}_db.tif ({geo["crs"]}, {geo["data"].shape[1]} x {geo["data"].shape[0]})')
|
|
63
|
+
try:
|
|
64
|
+
import matplotlib
|
|
65
|
+
matplotlib.use('Agg')
|
|
66
|
+
import matplotlib.pyplot as plt
|
|
67
|
+
db = geo['data']
|
|
68
|
+
top = np.nanpercentile(db, 99.5)
|
|
69
|
+
plt.imsave(a.out + '_db.png', np.nan_to_num(db, nan=top - 40), cmap='gray', vmin=top - 40, vmax=top)
|
|
70
|
+
print(f'wrote {a.out}_db.png (north up)')
|
|
71
|
+
except ImportError:
|
|
72
|
+
print('no matplotlib: PNG skipped')
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == '__main__':
|
|
76
|
+
main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Form a crop of a Capella stripmap collection from its CPHD with the patch mosaic, sample it on the pixels of the
|
|
2
|
+
vendor's SICD (a range / zero-Doppler grid) and compare the two amplitude images.
|
|
3
|
+
|
|
4
|
+
python examples/form_capella_stripmap.py scene_CPHD.cphd scene_SICD.ntf out [--size 512] [--backend cpu]
|
|
5
|
+
|
|
6
|
+
The scene reference point of a stripmap CPHD moves with the beam; read_cphd re-references the phase history to the
|
|
7
|
+
mid-aperture point, and each pulse's own point gives the beam center. The beam's extent along track is the azimuth
|
|
8
|
+
bandwidth the vendor processed (SICD Grid.Col.ImpRespBW).
|
|
9
|
+
"""
|
|
10
|
+
import argparse, time
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
import fastsar
|
|
15
|
+
from fastsar import io, patches, products
|
|
16
|
+
|
|
17
|
+
C = 299792458.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
ap = argparse.ArgumentParser()
|
|
22
|
+
ap.add_argument('cphd'); ap.add_argument('sicd'); ap.add_argument('out')
|
|
23
|
+
ap.add_argument('--size', type=int, default=512); ap.add_argument('--backend', default='cpu')
|
|
24
|
+
ap.add_argument('--spacing', type=float, default=0.35)
|
|
25
|
+
a = ap.parse_args()
|
|
26
|
+
from sarpy.io.complex.converter import open_complex
|
|
27
|
+
from sarpy.io.phase_history.converter import open_phase_history
|
|
28
|
+
col, meta = io.read_cphd(a.cphd, meta=True)
|
|
29
|
+
S, ant, f0, df = col['S'], col['ant'], col['fmin'], col['df']
|
|
30
|
+
P, K = S.shape
|
|
31
|
+
srp = io.ecf_to_local(open_phase_history(a.cphd).read_pvp_variable('SRPPos', 0), meta)
|
|
32
|
+
lo, hi = meta['pulses'] # the pulses read_cphd kept
|
|
33
|
+
srp = srp[lo:hi]
|
|
34
|
+
rd = open_complex(a.sicd)
|
|
35
|
+
sm = rd.sicd_meta
|
|
36
|
+
dsin = float(sm.Grid.Col.ImpRespBW) * C / (f0 + K / 2 * df) / 2 # processed spread of sin(look angle)
|
|
37
|
+
d = np.gradient(ant, axis=0)
|
|
38
|
+
d /= np.linalg.norm(d, axis=1, keepdims=True)
|
|
39
|
+
s_c = ((srp - ant) * d).sum(1) / np.linalg.norm(srp - ant, axis=1) # beam center of each pulse
|
|
40
|
+
|
|
41
|
+
def beam(idx, pts):
|
|
42
|
+
w = np.asarray(pts)[None] - ant[idx][:, None]
|
|
43
|
+
return ((w * d[idx][:, None]).sum(-1) / np.linalg.norm(w, axis=-1) - s_c[idx][:, None]) / (dsin / 2)
|
|
44
|
+
|
|
45
|
+
n = a.size
|
|
46
|
+
r0, c0 = int(sm.ImageData.NumRows) // 2 - n // 2, int(sm.ImageData.NumCols) // 2 - n // 2
|
|
47
|
+
rr, cc = np.meshgrid(np.arange(r0, r0 + n), np.arange(c0, c0 + n), indexing='ij')
|
|
48
|
+
pts = io.sicd_points(sm, rr, cc, meta)
|
|
49
|
+
e1, e2 = np.array([1.0, 0, 0]), np.array([0, 1.0, 0])
|
|
50
|
+
lo, hi = pts.reshape(-1, 3).min(0), pts.reshape(-1, 3).max(0)
|
|
51
|
+
origin = np.array([lo[0] - 5, lo[1] - 5, pts[..., 2].mean()])
|
|
52
|
+
nx, ny = int((hi[0] - lo[0] + 10) / a.spacing), int((hi[1] - lo[1] + 10) / a.spacing)
|
|
53
|
+
t = time.perf_counter()
|
|
54
|
+
img = patches.form_mosaic(dict(S=S, fmin=f0, df=df, ref=meta['ref'], band=(f0, f0 + K * df)), ant, origin, nx, ny,
|
|
55
|
+
a.spacing, a.spacing, e1, e2, beam=beam, backend=a.backend)
|
|
56
|
+
print(f'{nx} x {ny} ground pixels formed in {time.perf_counter() - t:.0f} s')
|
|
57
|
+
ours = products.sample(img, np.stack([(pts - origin) @ e1 / a.spacing, (pts - origin) @ e2 / a.spacing], -1))
|
|
58
|
+
v = rd[r0:r0 + n, c0:c0 + n]
|
|
59
|
+
print(f'amplitude correlation with the vendor image: {np.corrcoef(np.abs(ours).ravel(), np.abs(v).ravel())[0, 1]:.3f}')
|
|
60
|
+
np.save(a.out + '.npy', ours)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == '__main__':
|
|
64
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Form an Umbra spotlight image on the vendor's grid and save it as a complex64 .npy and an amplitude PNG.
|
|
2
|
+
|
|
3
|
+
python examples/form_umbra.py scene_CPHD.cphd scene_SICD.nitf out [--backend cpu] [--precision float32]
|
|
4
|
+
"""
|
|
5
|
+
import argparse, time
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
import fastsar
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
ap = argparse.ArgumentParser()
|
|
14
|
+
ap.add_argument('cphd'); ap.add_argument('sicd'); ap.add_argument('out')
|
|
15
|
+
ap.add_argument('--backend', default='auto'); ap.add_argument('--precision', default='float32')
|
|
16
|
+
ap.add_argument('--algorithm', default='ffbp')
|
|
17
|
+
a = ap.parse_args()
|
|
18
|
+
col = fastsar.io.read_cphd(a.cphd, sicd=a.sicd)
|
|
19
|
+
print('pulses, samples', col['S'].shape, 'grid', col['nx'], col['ny'], 'backends', fastsar.available_backends())
|
|
20
|
+
t = time.perf_counter()
|
|
21
|
+
img = fastsar.form_image(**col, algorithm=a.algorithm, backend=a.backend, precision=a.precision)
|
|
22
|
+
print(f'formed in {time.perf_counter() - t:.1f} s (includes compilation on the first call)')
|
|
23
|
+
np.save(a.out + '.npy', img)
|
|
24
|
+
try:
|
|
25
|
+
import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot as plt
|
|
26
|
+
db = 20 * np.log10(np.abs(img) + 1e-12); top = np.percentile(db, 99.9)
|
|
27
|
+
plt.imsave(a.out + '.png', np.clip(db.T, top - 45, top), cmap='gray', vmin=top - 45, vmax=top, origin='lower')
|
|
28
|
+
except ImportError:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == '__main__':
|
|
33
|
+
main()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Fast spotlight SAR image formation: factorized backprojection with kernels for Cloud TPUs (Pallas), Nvidia GPUs
|
|
2
|
+
(CUDA) and x86 CPUs (C++/OpenMP), and polar format with its geometric resampling. See fastsar.api.form_image."""
|
|
3
|
+
import os as _os
|
|
4
|
+
|
|
5
|
+
# JAX takes 75% of a GPU's memory when it first runs there unless told otherwise; FastSAR's CUDA kernels (CuPy) and
|
|
6
|
+
# its JAX programs share the GPU, so JAX allocates as it goes (a setting the user made before importing JAX stands);
|
|
7
|
+
# JAX keeps what it has allocated, and the CuPy formers shrink their working sets to what is left
|
|
8
|
+
_os.environ.setdefault('XLA_PYTHON_CLIENT_PREALLOCATE', 'false')
|
|
9
|
+
from .api import form_image, available_backends, ImageFormer # noqa: F401
|
|
10
|
+
from . import io, autofocus, stripmap, burst, patches, quality, products # noqa: F401
|
|
11
|
+
from .bp import backproject, plane_points # noqa: F401
|
|
12
|
+
from .exact import ExactFormer # noqa: F401
|
|
13
|
+
from .cphd import form_cphd # noqa: F401
|
|
14
|
+
from .memory import MemoryWarning # noqa: F401
|
|
15
|
+
|
|
16
|
+
__version__ = '0.1.0'
|