|
11 | 11 | "cell_type": "markdown", |
12 | 12 | "metadata": {}, |
13 | 13 | "source": [ |
14 | | - "We'll start with the same synthetic cube as the 3D RM-synth example -- see that page for details -- and run RM-synthesis again here to get the dirty FDF and RMSF cubes." |
| 14 | + "We start with the same synthetic cube as the [3D RM-synthesis](rmsynth_3d.ipynb) example (see that page for details) and run RM-synthesis again here to get the dirty FDF and RMSF cubes." |
15 | 15 | ] |
16 | 16 | }, |
17 | 17 | { |
|
67 | 67 | "from rm_lite.utils.synthesis import faraday_simple_spectrum\n", |
68 | 68 | "\n", |
69 | 69 | "# Two compact polarised sources (2D Gaussian blobs, reusing the existing\n", |
70 | | - "# 1D gaussian() on a radial distance grid) -- one bottom-right, one\n", |
71 | | - "# top-left -- on an otherwise unpolarised background -- mostly-empty sky\n", |
| 70 | + "# 1D gaussian() on a radial distance grid), one bottom-right and one\n", |
| 71 | + "# top-left, on an otherwise unpolarised background. Mostly-empty sky\n", |
72 | 72 | "# is what the noise estimator below needs.\n", |
73 | 73 | "frac_pol_map = gaussian(radius_grid, amplitude=0.6, mean=0.0, fwhm=6.0) + gaussian(\n", |
74 | 74 | " radius2_grid, amplitude=0.6, mean=0.0, fwhm=6.0\n", |
|
148 | 148 | "cell_type": "markdown", |
149 | 149 | "metadata": {}, |
150 | 150 | "source": [ |
151 | | - "Now run 3D RM-CLEAN with `rmclean_3d`. The Hogbom clean loop (`rm_lite.utils.clean.rmclean`) is inherently per-pixel, so it runs once per spatial chunk via `dask.delayed`. It returns four outputs (clean FDF, model FDF, residual FDF, iteration count) from that one call -- they're split out of the same delayed computation rather than re-running the clean loop per output.\n", |
| 151 | + "Now run 3D RM-CLEAN with `rmclean_3d`. The Hogbom clean loop (`rm_lite.utils.clean.rmclean`) is inherently per-pixel, so it runs once per spatial chunk via `dask.delayed`. It returns four outputs (clean FDF, model FDF, residual FDF, iteration count) from that one call, split out of the same delayed computation rather than re-running the clean loop per output.\n", |
152 | 152 | "\n", |
153 | | - "Unlike the 1D convenience wrapper `run_rmclean_from_synth`, `rmclean_3d` takes raw flux thresholds directly rather than deriving an auto-mask/auto-threshold itself -- a whole cube doesn't have one noise value, so it needs one explicit uniform threshold rather than a per-pixel one. We derive it here from `synth.theoretical_noise`, the per-channel-noise-derived FDF noise `rmsynth_3d` already computed -- the same value `rmclean_3d_from_synth` (below) scales automatically via its own `auto_mask`/`auto_threshold`." |
| 153 | + "Unlike the 1D convenience wrapper `run_rmclean_from_synth`, `rmclean_3d` takes raw flux thresholds directly rather than deriving an auto-mask/auto-threshold itself. A whole cube doesn't have one noise value, so it needs one explicit uniform threshold rather than a per-pixel one. We derive it here from `synth.theoretical_noise`, the per-channel-noise-derived FDF noise `rmsynth_3d` already computed, the same value `rmclean_3d_from_synth` (below) scales automatically via its own `auto_mask`/`auto_threshold`." |
154 | 154 | ] |
155 | 155 | }, |
156 | 156 | { |
|
190 | 190 | "cell_type": "markdown", |
191 | 191 | "metadata": {}, |
192 | 192 | "source": [ |
193 | | - "`rmclean_3d` above takes the dirty FDF/RMSF cubes and phi arrays unpacked from `synth` one by one. `rmclean_3d_from_synth` takes the `RMSynth3DResults` itself and unpacks it internally -- mirroring the 1D `run_rmclean_from_synth` convenience, including deriving `mask`/`threshold` itself from `auto_mask`/`auto_threshold` scaling `synth.theoretical_noise` (the same computation as above). It also takes `moment_threshold_snr` (default 5), the SNR cut applied to the clean FDF before the Faraday moment maps are computed (see below)." |
| 193 | + "`rmclean_3d` above takes the dirty FDF/RMSF cubes and phi arrays unpacked from `synth` one by one. `rmclean_3d_from_synth` takes the `RMSynth3DResults` itself and unpacks it internally, mirroring the 1D `run_rmclean_from_synth` convenience, including deriving `mask`/`threshold` itself from `auto_mask`/`auto_threshold` scaling `synth.theoretical_noise` (the same computation as above). It also takes `moment_threshold_snr` (default 5), the SNR cut applied to the clean FDF before the Faraday moment maps are computed (see below)." |
194 | 194 | ] |
195 | 195 | }, |
196 | 196 | { |
|
251 | 251 | "source": [ |
252 | 252 | "## Serialisation: zarr vs FITS\n", |
253 | 253 | "\n", |
254 | | - "As with the RM-synth outputs, the clean/model/residual FDF cubes are complex -- zarr again is the natural fit, writing lazily chunk by chunk. The iteration-count map is different: it's real-valued (an integer per pixel), so it has no complex-split problem and writes to FITS directly, same as any ordinary 2D image." |
| 254 | + "As with the RM-synth outputs, the clean/model/residual FDF cubes are complex, so zarr again is the natural fit, writing lazily chunk by chunk. The iteration-count map is different: it is real-valued (an integer per pixel), so it has no complex-split problem and writes to FITS directly, like any ordinary 2D image." |
255 | 255 | ] |
256 | 256 | }, |
257 | 257 | { |
|
298 | 298 | "source": [ |
299 | 299 | "## Faraday moments\n", |
300 | 300 | "\n", |
301 | | - "`rmclean_3d` (and `rmclean_3d_from_synth`) compute the zeroth, first, and second moments of the Faraday depth spectrum (see [Dickey et al. 2019](https://arxiv.org/abs/1812.05399)) directly on the clean FDF cube, and return them as lazy 2D maps on the result -- `clean.mom0_map`, `clean.mom1_map`, `clean.mom2_map`:\n", |
| 301 | + "`rmclean_3d` (and `rmclean_3d_from_synth`) compute the zeroth, first, and second moments of the Faraday depth spectrum (see [Dickey et al. 2019](https://arxiv.org/abs/1812.05399)) directly on the clean FDF cube, and return them as lazy 2D maps on the result: `clean.mom0_map`, `clean.mom1_map`, `clean.mom2_map`.\n", |
302 | 302 | "\n", |
303 | | - "- **mom0** -- total polarised intensity. The FDF amplitude is in units *per RMSF*, so the sum over Faraday depth is divided by the RMSF area (a Gaussian of FWHM `fwhm_rmsf_radm2`); an unresolved component of peak amplitude $P$ gives $\\mathrm{mom0} = P$, in the input flux units.\n", |
304 | | - "- **mom1** -- intensity-weighted mean Faraday depth, in rad m$^{-2}$.\n", |
305 | | - "- **mom2** -- intensity-weighted Faraday depth dispersion (standard deviation), in rad m$^{-2}$.\n", |
| 303 | + "- **mom0**: total polarised intensity. The FDF amplitude is in units *per RMSF*, so the sum over Faraday depth is divided by the RMSF area (a Gaussian of FWHM `fwhm_rmsf_radm2`); an unresolved component of peak amplitude $P$ gives $\\mathrm{mom0} = P$, in the input flux units.\n", |
| 304 | + "- **mom1**: intensity-weighted mean Faraday depth, in rad m$^{-2}$.\n", |
| 305 | + "- **mom2**: intensity-weighted Faraday depth dispersion (standard deviation), in rad m$^{-2}$.\n", |
306 | 306 | "\n", |
307 | | - "By default no amplitude threshold is applied (`clean.mom*_map` below), so the noise floor biases the moments -- especially mom2. The underlying function is `rm_lite.utils.synthesis.calc_faraday_moments`; call it directly on `clean.clean_fdf_cube` to re-threshold without re-cleaning, or pass `moment_threshold_snr` to `rmclean_3d_from_synth` (`moment_threshold` to `rmclean_3d`) to bake a cut into the result maps." |
| 307 | + "By default no amplitude threshold is applied (`clean.mom*_map` below), so the noise floor biases the moments, especially mom2. The underlying function is `rm_lite.utils.synthesis.calc_faraday_moments`; call it directly on `clean.clean_fdf_cube` to re-threshold without re-cleaning, or pass `moment_threshold_snr` to `rmclean_3d_from_synth` (`moment_threshold` to `rmclean_3d`) to bake a cut into the result maps." |
308 | 308 | ] |
309 | 309 | }, |
310 | 310 | { |
|
344 | 344 | "cell_type": "markdown", |
345 | 345 | "metadata": {}, |
346 | 346 | "source": [ |
347 | | - "To suppress the noise-floor bias, exclude amplitudes below a threshold -- here 5x the theoretical FDF noise that `rmsynth_3d` already computed. The intended way is to ask for it up front, e.g. `rmclean_3d_from_synth(synth, moment_threshold_snr=5.0)`, and read `clean.mom*_map`. To explore thresholds *without re-cleaning*, call `calc_faraday_moments` directly on the clean cube, as below:" |
| 347 | + "To suppress the noise-floor bias, exclude amplitudes below a threshold, here 5x the theoretical FDF noise that `rmsynth_3d` already computed. The intended way is to ask for it up front, e.g. `rmclean_3d_from_synth(synth, moment_threshold_snr=5.0)`, and read `clean.mom*_map`. To explore thresholds *without re-cleaning*, call `calc_faraday_moments` directly on the clean cube, as below:" |
348 | 348 | ] |
349 | 349 | }, |
350 | 350 | { |
|
377 | 377 | "cell_type": "markdown", |
378 | 378 | "metadata": {}, |
379 | 379 | "source": [ |
380 | | - "Pixels with no amplitude above the threshold have `mom0 = 0` and NaN mom1/mom2. At the faint edges of the sources -- where only a sample or two clears the cut -- mom1 is still noisy, so compare against the input RM map only where mom0 is comfortably detected:" |
| 380 | + "Pixels with no amplitude above the threshold have `mom0 = 0` and NaN mom1/mom2. At the faint edges of the sources, where only a sample or two clears the cut, mom1 is still noisy, so compare against the input RM map only where mom0 is comfortably detected:" |
381 | 381 | ] |
382 | 382 | }, |
383 | 383 | { |
|
429 | 429 | "source": [ |
430 | 430 | "### Debiased moments\n", |
431 | 431 | "\n", |
432 | | - "`np.abs()` of a noisy complex FDF is Ricean-distributed, giving polarised intensity a positive noise bias. A >5-sigma cut (as above) sidesteps most of it, but the bias accumulates whenever you *integrate without a cut* -- exactly what mom0 does -- so the unthresholded mom0 of an empty sightline is a positive floor, not zero.\n", |
| 432 | + "`np.abs()` of a noisy complex FDF is Ricean-distributed, giving polarised intensity a positive noise bias. A >5-sigma cut (as above) sidesteps most of it, but the bias accumulates whenever you *integrate without a cut*, exactly what mom0 does, so the unthresholded mom0 of an empty sightline is a positive floor, not zero.\n", |
433 | 433 | "\n", |
434 | 434 | "`rm_lite.utils.synthesis.debias_fdf` implements the bias suppression of [Mueller, Beck & Krause (2017)](https://ui.adsabs.harvard.edu/abs/2017A%26A...600A..63M/abstract), adapted for Faraday cubes. Their method median-filters the polarisation angle over the spatial axes (via its cos/sin components, dodging the angle wrap) and projects Q + iU onto the filtered direction, $P^* = U \\sin\\theta_m + Q \\cos\\theta_m$: in signal regions $P^* \\approx |F|$, while in noise regions $P^*$ is *zero-mean, signed* Gaussian noise that cancels when summed, instead of a positive Rayleigh floor.\n", |
435 | 435 | "\n", |
436 | | - "Applied naively per Faraday depth plane, that filtering fails wherever RM varies across the sky: the FDF angle is $2\\psi_0 + 2\\lambda_0^2(\\mathrm{RM} - \\phi)$, so this scene's RM gradient spins it by $\\sim$ 1 rad per pixel and the projection would destroy real signal. `debias_fdf` removes that deterministic ramp first, derotating each spectrum by a per-pixel peak Faraday depth estimate (half-max centroid), so only the intrinsic angle $2\\psi_0$ -- smooth for physical sources -- is filtered. This makes it robust to any background RM structure; the remaining caveat is sightlines with several components at very different Faraday depths, where only the dominant peak is fully derotated.\n", |
| 436 | + "Applied naively per Faraday depth plane, that filtering fails wherever RM varies across the sky: the FDF angle is $2\\psi_0 + 2\\lambda_0^2(\\mathrm{RM} - \\phi)$, so this scene's RM gradient spins it by $\\sim$ 1 rad per pixel and the projection would destroy real signal. `debias_fdf` removes that deterministic ramp first, derotating each spectrum by a per-pixel peak Faraday depth estimate (half-max centroid), so only the intrinsic angle $2\\psi_0$, smooth for physical sources, is filtered. This makes it robust to any background RM structure; the remaining caveat is sightlines with several components at very different Faraday depths, where only the dominant peak is fully derotated.\n", |
437 | 437 | "\n", |
438 | | - "`calc_faraday_moments` treats *real* input as-is (no `abs()`), so the signed debiased amplitudes integrate correctly -- or pass `debias=True` and let it call `debias_fdf` internally:" |
| 438 | + "`calc_faraday_moments` treats *real* input as-is (no `abs()`), so the signed debiased amplitudes integrate correctly, or pass `debias=True` and let it call `debias_fdf` internally:" |
439 | 439 | ] |
440 | 440 | }, |
441 | 441 | { |
|
0 commit comments