Add basic interferogram+coherence workflow, v1 of web UI, isce2 comparison notebook#152
Open
scottstanie wants to merge 34 commits into
Open
Add basic interferogram+coherence workflow, v1 of web UI, isce2 comparison notebook#152scottstanie wants to merge 34 commits into
scottstanie wants to merge 34 commits into
Conversation
Add a FastAPI backend + React/Vite/Leaflet frontend under src/sweets/web/
with four high-leverage features:
- Search: query CMR/ASF for OPERA CSLC bursts, S1 SAFE bursts, or NISAR
GSLC frames via /api/search; map overlays the granule polygons and
runs the same missing-data filter sweets uses on downloads so the
user sees which bursts will actually make it through.
- Config: /api/schema serves Workflow.model_json_schema() and the form
is auto-generated via @rjsf/core - every Workflow/Dolphin/Tropo field
shows up without hand-written form code.
- Monitor: /api/jobs/{id}/start spawns 'sweets run' as a subprocess,
/api/ws/jobs/{id}/logs streams the logs over a WebSocket, and the
step bar ticks through download -> geocode -> ifg -> stitch -> unwrap.
- View: /api/jobs/{id}/view shells 'bowser setup-dolphin <work_dir>'
and (optionally) starts 'bowser serve' so the user lands in the
mature viewer rather than a from-scratch results UI.
Launch with 'pixi run -e web sweets-server' for the backend and
'pixi run -e web web-dev' for the Vite frontend (proxies /api to :8000).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add four screenshots under docs/web/screenshots/ (search-empty, search-results-with-coverage, config-form, jobs-empty) and reference them from src/sweets/web/README.md. - Move the /api/health route registration above the static / mount; the StaticFiles catch-all was shadowing /api/health (no other route was affected, but the order is brittle either way). - Nest <input> elements inside <label> in SearchPanel for proper a11y association - also lets Playwright's get_by_label drive the form. - Lock the npm install graph via package-lock.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UI iteration on the web-ui-react branch driven by Scott's feedback: - Search: surface `flight_direction` on each granule feature and add a `tracks` summary to /api/search. The sidebar now shows a chip per (track, ASC/DESC) pair with the granule count; clicking a chip re-runs the search narrowed to that single track (sweets processes one per job). - Search: expandable Granules list, plus CSV and `wget -i`-style URL list exports. - Map: keep the dashed AOI rectangle on top of the burst overlay and include it in fitBounds so the user always sees their requested area. - Layout v2: top nav with tabs (Search / Config / Jobs); Config tab now takes the full viewport instead of being crammed into the 380px sidebar. Search and Jobs keep the sidebar+map split. - Config form: trimmed to the basic Workflow knobs by default (work_dir, slc_posting, pol_type, gpu_enabled, n_workers, threads_per_worker, overwrite). "Show advanced" toggles the nested dolphin / tropo subforms back in. Rewrites JSON Schema 2020-12 `prefixItems` to `items` so pydantic-emitted tuple fields like `slc_posting` render as proper number-pair inputs instead of an "Unsupported field schema" error. - Screenshots refreshed; README rewritten around the new flows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New scripts/web_screenshots.py: builds the frontend, boots a temporary uvicorn server on a free port, drives Playwright through every demo flow (search, track-narrow, granule list, basic config, advanced dolphin + tropo sections, jobs), kills the server. Idempotent — writes PNGs into docs/web/screenshots/ in place. - pixi `web-screenshots` task wired up; `playwright` added to the web feature so `pixi install -e web` pulls it in. Chromium binary still needs a one-time `playwright install chromium`. - README: new "Refreshing the screenshots" section documenting the cadence (after any UI change), with the one-time setup + the refresh command. README also now embeds 03b-config-advanced-dolphin.png and 03c-config-advanced-tropo.png — the advanced view does render fine, it was just clipped by the .content.full overflow in earlier captures. The dolphin + tropo subforms (DolphinOptions / TropoOptions pydantic models) come through cleanly under "Show advanced": half-window, strides, ministack size, unwrap method, snaphu cost, tropo enable/height-max/ interp-method, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ran an actual sweets pipeline end-to-end through the UI: - Source: OPERA CSLC, track 71 - Burst IDs: t071_151228_iw2 + t071_151228_iw3 (2 bursts, full IW2/IW3 pair) - Time range: 2024-01-01 to 2024-04-01 (3 months, 8 dates per burst) - n_workers=3, threads_per_worker=2 - Bbox: (-118.30, 34.10, -118.28, 34.12) — tight LA AOI - Wall time: 3m 36s on a 2024 M-series MacBook - Output: 16 OPERA CSLCs + DEM + watermask + stitched geometry + 21 unwrapped interferograms + 7 timeseries pairs + velocity.tif Two new screenshots captured during / after the run: - 04b-jobs-running.png: step 1 (download) pulsing, WS log tail streaming sardem + OPERA fetch messages, manifest already populating. - 04c-jobs-completed.png: all five step segments lit blue, status green COMPLETED, full 86-entry manifest. Found and fixed two bugs while driving it: - JobsPanel step-bar never lit in real time. The executor only commits current_step to the DB in its `finally` block, so the bar showed 0/5 throughout a run. The WebSocket already carries the live step alongside each log line — wire that into a local liveStep state, take the max with job.current_step, and the bar animates correctly while a job runs. - /api/jobs/* responses didn't reflect the live step either, so the list view still showed "step 0/5" for a running job. Backend now patches the returned current_step with LogManager's in-memory value before serializing — no extra DB writes per log line. README updated to embed both new shots with the demo configuration spelled out so anyone can reproduce. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Found while reviewing the web-ui-react branch with fresh eyes:
- The Search tab held start/end/track/frame in component-local state,
so jobs created from Config shipped a `search: {kind, bbox}` only.
BurstSearch/OperaCslcSearch require `start`, so every UI-created
job failed Workflow validation in the executor subprocess. Lifted
the params into shared state and built a proper discriminated-union
payload in ConfigPanel, with a "Create & start" button.
- JobsPanel re-rendered every 3s from the polling loop and passed a
fresh `onStep` closure into JobLogs, which had it in the useEffect
deps. The WebSocket tore down and reconnected every 3s, re-shipping
history each time. Moved onStep into a ref; keyed the effect on
jobId only. Server already sends a terminal status frame on its own.
- log_manager.append runs on the executor's worker thread and called
queue.put_nowait on loop-bound asyncio Queues. Rewrote to dispatch
via loop.call_soon_threadsafe with a threading.Lock on the
subscriber set. Dropped the dead asyncio.Lock field.
Smaller cleanups bundled in:
- datetime.utcnow() -> datetime.now(timezone.utc) (3.12+ deprecation)
- _norm_direction now handles NISAR's AscendingFlag "T"/"F" instead
of letting raw "T"/"F" leak into the track chips
- JobUpdate trimmed to user-settable fields (pid was writable via PATCH)
- Executor falls back to Path.cwd() when work_dir isn't in config so
the manifest/bowser endpoints can still find outputs
- Drop unused titiler.core dep; drop react-leaflet + react-leaflet-draw
(MapView uses raw Leaflet); add httpx for fastapi.testclient
- Redundant env={**os.environ} on bowser Popen
- web/__init__.py docstring still said "Svelte frontend"
- New tests/test_web.py: 4 smoke tests covering /api/health,
/api/schema, JobCreate validation, no-spawn job lifecycle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second-pass cleanups on top of the React UI: - Add `_sweep_stale_jobs` in the lifespan: if the server crashed or was restarted mid-job, the row stays RUNNING forever with an orphaned pid and a pipe we can't reattach to. On startup, best-effort SIGTERM each RUNNING pid and flip the row to FAILED with a marker note so the user knows what happened. - WebSocket loop now breaks when the watched job is deleted mid-stream. Previously `if job and job.status in (...)` had no fall-through for job=None, so the loop spun forever subscribed to an orphaned buffer with the socket held open. - Replace `_with_live_step` (which mutated the SQLModel DB instance in read endpoints — a latent footgun if anyone added a `session.commit()` later in the request) with `_to_read`, which builds a fresh JobRead and folds in the live step there. - MapView's init effect had `setBbox` in its deps; the state context's memoized value rebuilds on every state change, so the effect re-fired (then short-circuited) on every search result or tab switch. Empty deps + a guard inside. - Simplify the step-bar `cls` ladder — the third branch `step >= idx` could only ever match `step === idx && !running`, so it was just obscuring the actual two cases. - Drop the unused `db_path` parameter from `get_database_url` (renamed `_database_url`, internal-only). - New test: `test_stale_running_jobs_are_swept` covers the lifespan reconciliation. The fixture also patches `sweets.web.app.engine` so the lifespan path uses the per-test SQLite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pping New end-to-end IFG path: download -> geocode (shared with displacement workflow) -> blockwise crossmul -> optional SNAPHU/SPURT unwrapping. - `_crossmul.py`: pure NumPy/SciPy blockwise crossmul; boxcar or Gaussian multilooking; writes wrapped-phase + coherence GeoTIFFs - `ifg.py`: IfgWorkflow config model (Pydantic YamlModel) with CrossmulOptions, NetworkOptions (max_bandwidth / reference_date / max_temporal_baseline), and IfgUnwrapOptions; pairs grouped per burst to handle different spatial extents; resume-safe (skips existing pairs) - CLI: `sweets ifg-run <config.yaml>` subcommand via tyro - Web API: GET /api/schema/ifg returns IfgWorkflow JSON schema - Executor: auto-detects IFG vs displacement config by "crossmul" key - Plotting: plot_ifg_pairs() and save_ifg_qa_metrics() for QA output Tested on real Sentinel-1 data (2 bursts, 86 pairs, max_bandwidth=2, 10x40 looks): median coherence 0.197, ~20 min wall-clock for crossmul. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ferogram) Adds a segmented button toggle to the Config tab that switches between the displacement and interferogram workflows. Both schemas are fetched on mount so the switch is instant. The IFG view surfaces network, crossmul, and unwrap as basic fields (the main levers users need) and hides worker counts behind "Show advanced". api.ts gains ifgSchema(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ader order Burst picker (SearchPanel): - After a search, a BURSTS section lists unique burst IDs with granule counts and checkboxes; only shown when 2+ bursts are present - Selecting a subset persists to global state (selectedBurstIds); the Config tab injects it as search.burst_ids so only those bursts are downloaded - Resets to null (all) on every new search Schema fixes (ConfigPanel): - rewriteSchemaInPlace() replaces rewriteTuplesInPlace(); in addition to the prefixItems→items tuple rewrite it now also collapses anyOf:[T,null] → T so that Optional[float]/Optional[str] fields (e.g. max_temporal_baseline, reference_date) render as a plain number/text input instead of a schema-picker dropdown - Removed ui:help + label:false from IFG nested sections (network, crossmul, unwrap) so RJSF renders the section title as a fieldset legend above the fields rather than as a help blurb below them Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…button)
RJSF + draft-07: sibling properties of a \$ref are ignored, so Pydantic's
{"\$ref": "#/\$defs/Foo", "description": "..."} was seen as a plain object
with no properties, causing the "Add new key" widget.
Fix: rewriteSchemaInPlace() now converts {\$ref:X, ...rest} → {allOf:[{\$ref:X}],
...rest} before RJSF processes the schema. This matches the form RJSF knows
how to merge-and-render correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
log_manager: _Subscriber dataclass held an asyncio.Queue which is not hashable, so set.add() raised TypeError. Fix: eq=False on the dataclass restores identity-based __hash__ without changing equality semantics. ifg: _burst_id_from_gslc now parses OPERA CSLC filenames when the grandparent-directory heuristic fails. OPERA filenames embed the burst ID as _T048-101101-IW3_ which is normalised to t048_101101_iw3. Flat OPERA download directories no longer collapse all bursts into "single". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- results.py bowser endpoint: detect IFG jobs (crossmul key in config) and return interferograms/ as the target path with an explanatory message instead of pointing at the nonexistent dolphin/ directory - results.py manifest: add IFG output globs (wrapped phase, coherence, unwrapped, qa json, qa plot) so the Results panel lists IFG files - JobsPanel: IFG jobs render "Results directory" + "Show path" button instead of the bowser setup/open buttons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ifg.py: _date_from_gslc now delegates to opera_utils.get_dates, which handles OPERA's ISO-format acquisition dates (20240101T232835Z) that the old 8-digit split couldn't parse. ConfigPanel: seed formData with getDefaultFormState() when the schema loads so integer/float fields show their schema defaults (512, 2, etc.) rather than empty inputs. Also reset to defaults when switching workflow type. Hide snaphu_ntiles and snaphu_tile_overlap via ui:widget=hidden — Union[tuple[int,int], Literal["auto"]] has no sensible RJSF widget and was producing the stale "Add" button; these knobs are rarely changed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Calling getDefaultFormState on the full schema injected bbox, dem_bbox, orbit_dir, search etc. into formData. pickFields then removed those from the visible schema, so RJSF rendered each extra key as an additionalProperties key-value widget with a blue Add button. Fix: pass the pickFields-filtered schema as the target schema and the full schema as rootSchema (for \$ref resolution). Defaults are only computed for fields actually shown. Also seed defaults when toggling Show Advanced so newly-visible fields get their defaults. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t hook Adds `StitchOptions` + `_stitch_ifgs()` to `IfgWorkflow` (step 4). Uses `dolphin.stitching.merge_by_date` to merge per-burst IFG outputs into one file per date-pair; optionally crops to the job bbox. Burst-alignment (feat/burst-alignment) is wired as an opt-in hook. Web UI exposes `stitch` in the IFG basic field list. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
plotting: extract pair name from filename stem instead of parent dir (stitched files are flat; parent.name was "stitched" not the date pair) web UI: auto-fill work_dir as ~/sweets-jobs/<name>-<YYYYmmdd-HHMM> so each job gets a unique directory by default even when the name is reused Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…inuity crossmul now saves _ifg.tif (complex64) + _coherence.tif instead of _wrapped_phase.tif. _stitch_ifgs stitches the complex files (GDAL interpolates real+imag independently, which is correct), then derives _wrapped_phase.tif via np.angle() after merging. _derive_wrapped_phase / _ensure_wrapped_phase helpers added. _collect_existing_ifg_products updated to look for *_ifg.tif. plot_ifg_pairs falls back to *_ifg.tif when no wrapped-phase files exist. Manifest globs updated to include ifg-complex. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Checking only for non-empty existing_static_layers() meant a partial download (e.g. job killed mid-transfer) silently left truncated files. Now compares count against expected burst count so missing or truncated files trigger a fresh download. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ported burst_alignment module locally so the feature is available without requiring the dolphin feat/burst-alignment branch. Wires align_bursts() into _stitch_ifgs before merge_by_date, controlled by stitch.run_burst_align and stitch.burst_align_degree. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…products Using rglob on the ifg_dir was including previously-stitched _ifg.tif files in the per-burst input list, causing burst alignment to process stale stitched outputs alongside per-burst files and producing malformed output filenames. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aths
When per-burst IFGs live under {burst_id}/{date1}_{date2}/ subdirs, the
relative path used as a uniqueness prefix already contains the dates.
Concatenating that with the file stem produced doubled date tokens
(e.g. t064_135521_iw1_20250403_20250415__20250403_20250415_ifg.aligned.tif),
which caused opera_utils.get_dates to return 4 dates instead of 2 and
broke the merge_by_date grouping/output filename.
Strip YYYYMMDD tokens from the prefix so only the burst ID remains.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… date stripping
Replace the raw r"_?\d{8}" pattern in _build_output_paths with
_date_format_to_regex(file_date_fmt) from opera_utils, tying the
date-stripping logic to the same date format the rest of the pipeline uses.
Also threads file_date_fmt through the function signature.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Passing all 351 burst files (27 bursts x 13 pairs) to align_bursts as a flat list caused the overlap detector to compare same-geography bursts from different date pairs, producing full-overlap garbage phase offsets that introduced seam artifacts in every burst. Group by date pair first so each call only sees the 27 bursts for one interferogram. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds docs/ifg_workflow.ipynb — an ipywidgets notebook that mirrors the web UI for JupyterHub users who prefer a notebook-first experience. What the notebook covers: - Tabbed config form: AOI (folium map), Search & Download, Processing options (network/crossmul/stitch/unwrap), and Output directory - Auto-builds and previews the YAML on load via _on_build(None) - "Build config" / "Save YAML" buttons backed by IfgWorkflow validation - Step-table run widget: starting-step selector + "Run workflow" button - Results cell: plot_ifg_pairs() embeds the IFG thumbnails inline - Appendix: programmatic usage without the widget UI Pre-filled for OPERA DISP frame 16941 (T064, Mojave, Apr-Jun 2025). Executed cleanly with jupyter nbconvert on the existing test dataset, producing embedded wrapped-phase + coherence thumbnails for all 13 pairs. Also adds: - scripts/make_ifg_notebook.py — generator script (edit here, re-run) - scripts/notebook_screenshots.py — playwright-based screenshot driver - docs/screenshots/final-*.png — 8 HTML-render screenshots proving execution - docs/ifg-workflow-test-guide.md — step-by-step test reference - .gitignore: exclude docs/ifg_workflow_executed.* (large generated files) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix systematic geolocation offset: _output_geotransform() was shifting the GDAL origin by (looks-1)/2 pixels. GDAL origins are pixel corners, so the output corner stays at (x0, y0); only pixel spacing scales. - Fix overwrite=True silently reusing stale IFG/wrapped-phase products: pass overwrite to run_crossmul() and _derive_wrapped_phase() so that re-runs with changed looks/bbox/stitching actually recompute outputs. - Fix Gaussian multilook block width overrun: scipy fallback was using ceil(n/stride) via [::stride] rather than floor(n/stride), which could produce wider blocks than the output raster. Clip input to stride multiples before filtering, matching the JAX strided-conv path. - Fix unwrapped products missing from web results manifest: _run_unwrap() writes to work_dir/unwrapped/ but manifest only searched interferograms/**/unwrapped/. Add top-level unwrapped/*.tif glob. - Fix notebook hard-coded /Volumes/WD_BLACK path: replace with ~/sweets-output/frame-16941 so it works on any machine including JupyterHub. Regenerate docs/ifg_workflow.ipynb. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test_crossmul.py (27 tests): - Pure math: kernel normalization/symmetry, boxcar/gaussian shape and constant-preservation, non-divisible truncation (P2 regression), geotransform origin invariance (P1 regression), coherence edge cases - Integration: run_crossmul with synthetic GeoTIFF SLCs verifies output shape, geotransform, phase sign convention (ref * conj(sec)), overwrite=False skip and overwrite=True refresh test_burst_alignment.py (25 tests): - BurstCorrection evaluate (constant + planar), is_planar - _wrap_scalar/_wrap round-trip, _weighted_median CDF behaviour, _aggregate_pair mean/median real and complex - _valid_mask for real nodata and complex zero-nodata - _solve_offset_lsq: 2-burst recovery, 3-burst chain, empty input - _apply_correction: real subtraction, complex phase removal, nodata passthrough - Integration: estimate/apply/align on synthetic overlapping GeoTIFFs with known constant offsets for both real and complex inputs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P1: stitched wrapped phase now respects overwrite=True, so re-runs with overwrite enabled refresh the _wrapped_phase.tif instead of reusing the stale file from a prior run. P2: clarify AOI-mode static-layer completeness check with an explicit comment noting the known limitation (count can't be verified without a network query; overwrite=True is the escape hatch). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Burst alignment is now on by default — it removes inter-burst phase offsets at seams and is low-cost relative to crossmul. Users who want plain merge_by_date can pass stitch.run_burst_align=False. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stitches per-burst IFGs with and without burst alignment into separate output directories and saves a side-by-side wrapped phase figure for visual QA. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detects burst seam rows from the wrapped difference image and plots ±60-row crops showing no-align, aligned, and difference for each seam. Makes the per-burst constant offsets visually obvious. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Interferogram workflow (IfgWorkflow) — a self-contained pipeline for users who want multilooked interferograms + coherence without the full dolphin displacement stack. Five steps: OPERA CSLC download → geometry → crossmul → stitch/burst-align → unwrap. Controllable via starting_step for restarts.
Crossmul engine (_crossmul.py) — blockwise interferogram formation with boxcar or Gaussian multilooking. Gaussian path uses JAX strided separable convolution (~33× faster than scipy filter+decimate for 10×40 looks). Complex IFG is saved before wrapping so GDAL can interpolate real/imag during stitching without crossing ±π discontinuities.
Burst alignment (_burst_alignment.py) — ported from dolphin; estimates and removes inter-burst phase offsets at seams via weighted-median LSQ, applied as a polynomial correction.
React web UI — replaces the previous scaffold with a working single-page app: AOI map, track picker, burst-ID selector, workflow-type toggle (displacement vs interferogram), live step progress bar, and results viewer. Backed by FastAPI + WebSocket job executor.
JupyterHub notebook — docs/ifg_workflow.ipynb walks through the full IFG workflow interactively, suitable for running on a shared JupyterHub against a real OPERA frame.
Tests — 52 new unit + integration tests covering _crossmul (geotransform origin, non-divisible truncation, phase sign convention, overwrite semantics) and _burst_alignment (LSQ solver, weighted-median, polynomial correction, end-to-end seam recovery).
Bug fixes — stale wrapped phase on overwrite re-run (P1), geotransform origin shift after multilooking (P1), non-divisible input truncation in Gaussian path (P2).