Skip to content

Commit b4e8408

Browse files
authored
spectrum normalization switched back on; docs updates (#227)
1 parent 6891e7e commit b4e8408

File tree

5 files changed

+137
-93
lines changed

5 files changed

+137
-93
lines changed

docs/0-quickstart.ipynb

Lines changed: 93 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"import jax.numpy as jnp\n",
2020
"import matplotlib.pyplot as plt\n",
2121
"\n",
22-
"from scarlet2 import *"
22+
"import scarlet2 as sc2"
2323
]
2424
},
2525
{
@@ -71,14 +71,11 @@
7171
"cell_type": "markdown",
7272
"metadata": {},
7373
"source": [
74-
"## Define Observation and Frame\n",
74+
"## Define the Observation\n",
7575
"\n",
76-
"An {py:class}`~scarlet2.Observation` combines several data elements with a {py:class}`~scarlet2.Frame`, similar to header-data arrangement in FITS files. In addition to the actual science image cube, you can and often must provide weights for all elements in the data cube, an image cube of the PSF model (one image for all or one for each channel), an {py:class}`astropy.wcs.WCS` structure to translate from pixel to sky coordinates, and labels for all channels.\n",
76+
"An {py:class}`~scarlet2.Observation` stored several data arrays. In addition to the actual science image cube, you can and often must provide weights for all elements in the data cube, an image cube of the PSF model (one image for all or one for each channel), an {py:class}`astropy.wcs.WCS` structure to translate from pixel to sky coordinates, and labels for all channels.\n",
7777
"\n",
78-
"The {py:class}`~scarlet2.Frame` is a description of the hyperspectral cube of the observations. Think of it as the metadata, what aspects of the sky are described here. At the least, a {py:class}`~scarlet2.Frame` holds the `shape` of the cube, for which we use the convention `(C, H, W)` for the number of elements in 3 dimensions: `C` for the number of bands/channels and `H/W` for the height and width of the images.\n",
79-
"\n",
80-
"\n",
81-
"\n"
78+
"The observation stores any needed meta-data in {py:class}`~scarlet2.Frame` (similar to the header in a FITS file). At the least, a {py:class}`~scarlet2.Frame` holds the `shape` of the cube, for which we use the convention `(C, H, W)` for the number of elements in 3 dimensions: `C` for the number of bands/channels and `H/W` for the height and width of the images."
8279
]
8380
},
8481
{
@@ -87,55 +84,64 @@
8784
"metadata": {},
8885
"outputs": [],
8986
"source": [
90-
"obs = Observation(\n",
87+
"obs = sc2.Observation(\n",
9188
" data,\n",
9289
" weights,\n",
93-
" psf=ArrayPSF(psf),\n",
90+
" psf=sc2.ArrayPSF(psf),\n",
9491
" channels=channels,\n",
95-
")\n",
96-
"model_frame = Frame.from_observations(obs)"
92+
")"
9793
]
9894
},
9995
{
10096
"cell_type": "markdown",
10197
"metadata": {},
10298
"source": [
103-
"_scarlet2_ will construct another {py:class}`~scarlet2.Frame` for the _model_ of the sky you seek to fit. It differs from the observation frame because the model needs to be superior in terms of spatial resolution, PSF blurring, spectral coverage, or all of the above, so that the observation can be computed from the model by a degradation operation, the so-called \"forward operator. In _scarlet2_, {py:class}`~scarlet2.renderer.Renderer` implements this operation and contains, e.g., PSF difference kernels and resampling transformations.\n",
104-
"\n",
105-
"In this example, we assume that bands and pixel locations are identical between the model and the observation. Because we have ground-based images, the degradation comes from the PSF. With different PSFs in each band, we need to define a reference PSF that is sufficiently narrow. Our default is minimal Gaussian PSF that is barely well-sampled (standard deviation of 0.7 pixels) as our reference kernel:\n",
106-
"\n",
107-
"Note also the validation results that are automatically generated.\n",
108-
"These are executed automatically on behalf of the user and act as guardrails.\n",
109-
"Here we can see that check number ``007`` produced a error and prints a message indicating that the PSF is not the same for all channels.\n",
110-
"While validation errors will not cause the code to stop running, they do indicate a likely problem that warrants attention.\n",
111-
"Automatic validation is performed at several times in ``scarlet2``, which can be seen later in this notebook.\n",
99+
"Note the validation results, which are automatically generated (unless switched off) and act as early warning signs.\n",
100+
"We'll see more of these validation tests later in this notebook.\n",
101+
"Here the check number ``007`` indicates that the PSF is not centered at the same position for all channels.\n",
102+
"The offsets are minor, so we'll proceed, but we're going to come back to this issue at the end.\n",
112103
"For additional information about automatic validation, please see the [\"Validation\" section](3-validation) of the documentation."
113104
]
114105
},
106+
{
107+
"cell_type": "markdown",
108+
"metadata": {},
109+
"source": [
110+
"## Display the Image Cube\n",
111+
"\n",
112+
"We can now make use of the plotting function {py:func}`scarlet2.plot.observation` to create a RGB image of our data.\n",
113+
"We're going to use the {py:class}`~scarlet2.plot.AsinhAutomaticNorm`, which scales the observed data by a {math}`\\mathrm{sinh}^{-1}` function that is automatically tuned to reveal both the noise level and the highlights, to create a color-consistent RGB image."
114+
]
115+
},
115116
{
116117
"cell_type": "code",
117118
"execution_count": null,
118119
"metadata": {},
119120
"outputs": [],
120121
"source": [
121-
"print(model_frame)"
122+
"norm = sc2.plot.AsinhAutomaticNorm(obs)\n",
123+
"sc2.plot.observation(obs, norm=norm, sky_coords=centers, show_psf=True, add_labels=True)\n",
124+
"plt.show()"
122125
]
123126
},
124127
{
125128
"cell_type": "markdown",
126129
"metadata": {},
127130
"source": [
128-
"If other properties of the model are desired, they can be provided to {py:func}`scarlet2.Frame.from_observations`."
131+
"Since we did't provide a `wcs` for this {py:class}`~scarlet2.Observation`, all spatial coordinates (like `centers` above) are in image pixels. Otherwise RA/Dec coordinates (as `astropy.SkyCoord`) are required."
129132
]
130133
},
131134
{
132135
"cell_type": "markdown",
133-
"metadata": {},
136+
"metadata": {
137+
"ExecuteTime": {
138+
"end_time": "2025-10-10T03:47:09.322341Z",
139+
"start_time": "2025-10-10T03:47:09.307669Z"
140+
}
141+
},
134142
"source": [
135-
"## Display Image Cube\n",
136-
"\n",
137-
"We can now make use of the plotting function {py:func}`scarlet2.plot.observation` to create a RGB image of our data.\n",
138-
"We're going to use the {py:class}`~scarlet2.plot.AsinhAutomaticNorm`, which scales the observed data by a {math}`\\mathrm{sinh}^{-1}` function that is automatically tuned to reveal both the noise level and the highlights, to create a color-consistent RGB image."
143+
"## Define the Model Frame\n",
144+
"_scarlet2_ needs another {py:class}`~scarlet2.Frame`, which describes the properties of the _model_ of the sky you seek to fit. Given one or several observations, _scarlet2_ can make an educated guess what model frame would be best:"
139145
]
140146
},
141147
{
@@ -144,16 +150,34 @@
144150
"metadata": {},
145151
"outputs": [],
146152
"source": [
147-
"norm = plot.AsinhAutomaticNorm(obs)\n",
148-
"plot.observation(obs, norm=norm, sky_coords=centers, show_psf=True, add_labels=True)\n",
149-
"plt.show()"
153+
"model_frame = sc2.Frame.from_observations(obs)\n",
154+
"print(model_frame)"
150155
]
151156
},
152157
{
153158
"cell_type": "markdown",
154159
"metadata": {},
155160
"source": [
156-
"Since we use no `wcs` in this {py:class}`~scarlet2.Observation`, all coordinates are already in image pixels, otherwise RA/Dec pairs are expected as `astropy` sky coordinates."
161+
"If only one observation is given (as above), we assume that bands and pixel locations are identical between the model and the observation. Because we have ground-based images, we need to account for different PSFs in each band, which means the model frame needs to define a reference PSF that is sufficiently narrow. Our default is a minimal Gaussian PSF that is barely well-sampled (standard deviation of 0.7 pixels) as the PSF reference.\n",
162+
"If other properties of the model frame are desired, they can be provided to {py:func}`scarlet2.Frame.from_observations`.\n",
163+
"\n",
164+
"But what's the point of the model frame? Why is it needed at all?\n",
165+
"\n",
166+
"```{info}\n",
167+
"\n",
168+
"To fit multiple images with different PSF, different bands, different resolutions, etc..., we must define a model that is superior in all of these aspects, so that each observation can be computed from the model by a degradation operation, the so-called \"forward operator\". The point of joint modeling is to find one superior model of the sky that is consistent with all observations.\n",
169+
"```\n",
170+
"\n",
171+
"In _scarlet2_, the forward operators are implemented as {py:class}`~scarlet2.renderer.Renderer`, which contains, e.g., PSF difference kernels and resampling transformations. It was created earlier by {py:func}`~scarlet2.Frame.from_observation`."
172+
]
173+
},
174+
{
175+
"cell_type": "code",
176+
"execution_count": null,
177+
"metadata": {},
178+
"outputs": [],
179+
"source": [
180+
"obs.renderer"
157181
]
158182
},
159183
{
@@ -173,15 +197,15 @@
173197
"metadata": {},
174198
"outputs": [],
175199
"source": [
176-
"with Scene(model_frame) as scene:\n",
200+
"with sc2.Scene(model_frame) as scene:\n",
177201
" for center in centers:\n",
178202
" try:\n",
179-
" spectrum, morph = init.from_gaussian_moments(obs, center, min_corr=0.99)\n",
203+
" spectrum, morph = sc2.init.from_gaussian_moments(obs, center, min_corr=0.99)\n",
180204
" except ValueError:\n",
181-
" spectrum = init.pixel_spectrum(obs, center)\n",
182-
" morph = init.compact_morphology()\n",
205+
" spectrum = sc2.init.pixel_spectrum(obs, center)\n",
206+
" morph = sc2.init.compact_morphology()\n",
183207
"\n",
184-
" Source(center, spectrum, morph)"
208+
" sc2.Source(center, spectrum, morph)"
185209
]
186210
},
187211
{
@@ -197,19 +221,19 @@
197221
"metadata": {},
198222
"outputs": [],
199223
"source": [
200-
"with Scene(model_frame) as scene:\n",
224+
"with sc2.Scene(model_frame) as scene:\n",
201225
" for i, center in enumerate(centers):\n",
202226
" if i == 0: # we know source 0 is a star\n",
203-
" spectrum = init.pixel_spectrum(obs, center, correct_psf=True)\n",
204-
" PointSource(center, spectrum)\n",
227+
" spectrum = sc2.init.pixel_spectrum(obs, center, correct_psf=True)\n",
228+
" sc2.PointSource(center, spectrum)\n",
205229
" else:\n",
206230
" try:\n",
207-
" spectrum, morph = init.from_gaussian_moments(obs, center, min_corr=0.99)\n",
231+
" spectrum, morph = sc2.init.from_gaussian_moments(obs, center, min_corr=0.99)\n",
208232
" except ValueError:\n",
209-
" spectrum = init.pixel_spectrum(obs, center)\n",
210-
" morph = init.compact_morphology()\n",
233+
" spectrum = sc2.init.pixel_spectrum(obs, center)\n",
234+
" morph = sc2.init.compact_morphology()\n",
211235
"\n",
212-
" Source(center, spectrum, morph)"
236+
" sc2.Source(center, spectrum, morph)"
213237
]
214238
},
215239
{
@@ -299,21 +323,21 @@
299323
"\n",
300324
"# best step size for parameters with a relative \"uncertainty\":\n",
301325
"# big/bright sources adjust more quickly\n",
302-
"spec_step = partial(relative_step, factor=0.05)\n",
303-
"morph_step = partial(relative_step, factor=1e-3)\n",
326+
"spec_step = partial(sc2.relative_step, factor=0.05)\n",
327+
"morph_step = partial(sc2.relative_step, factor=1e-3)\n",
304328
"\n",
305-
"with Parameters(scene) as parameters:\n",
329+
"with sc2.Parameters(scene) as parameters:\n",
306330
" for i in range(len(scene.sources)):\n",
307-
" Parameter(\n",
331+
" sc2.Parameter(\n",
308332
" scene.sources[i].spectrum,\n",
309333
" name=f\"spectrum:{i}\",\n",
310334
" constraint=constraints.positive,\n",
311335
" stepsize=spec_step,\n",
312336
" )\n",
313337
" if i == 0:\n",
314-
" Parameter(scene.sources[i].center, name=f\"center:{i}\", stepsize=0.1)\n",
338+
" sc2.Parameter(scene.sources[i].center, name=f\"center:{i}\", stepsize=0.1)\n",
315339
" else:\n",
316-
" Parameter(\n",
340+
" sc2.Parameter(\n",
317341
" scene.sources[i].morphology,\n",
318342
" name=f\"morph:{i}\",\n",
319343
" constraint=constraints.unit_interval,\n",
@@ -380,7 +404,7 @@
380404
"metadata": {},
381405
"outputs": [],
382406
"source": [
383-
"plot.scene(\n",
407+
"sc2.plot.scene(\n",
384408
" scene_,\n",
385409
" obs,\n",
386410
" norm=norm,\n",
@@ -440,9 +464,9 @@
440464
"matplotlib.rc(\"image\", interpolation=\"none\", origin=\"lower\")\n",
441465
"\n",
442466
"fig, axes = plt.subplots(1, 3, figsize=(10, 4))\n",
443-
"axes[0].imshow(plot.img_to_rgb(source_array, norm=norm))\n",
444-
"axes[1].imshow(plot.img_to_rgb(source_in_scene_array, norm=norm))\n",
445-
"axes[2].imshow(plot.img_to_rgb(source_as_seen, norm=norm))\n",
467+
"axes[0].imshow(sc2.plot.img_to_rgb(source_array, norm=norm))\n",
468+
"axes[1].imshow(sc2.plot.img_to_rgb(source_in_scene_array, norm=norm))\n",
469+
"axes[2].imshow(sc2.plot.img_to_rgb(source_as_seen, norm=norm))\n",
446470
"plt.show()"
447471
]
448472
},
@@ -464,15 +488,15 @@
464488
"metadata": {},
465489
"outputs": [],
466490
"source": [
467-
"plot.sources(\n",
491+
"sc2.plot.sources(\n",
468492
" scene_,\n",
469493
" norm=norm,\n",
470494
" observation=obs,\n",
471495
" show_model=True,\n",
472496
" show_rendered=True,\n",
473497
" show_observed=True,\n",
474498
" show_spectrum=True,\n",
475-
" add_markers=False,\n",
499+
" add_labels=False,\n",
476500
" add_boxes=True,\n",
477501
")\n",
478502
"plt.show()"
@@ -501,7 +525,7 @@
501525
"source": [
502526
"print(\"----------------- {}\".format(channels))\n",
503527
"for k, src in enumerate(scene_.sources):\n",
504-
" print(\"Source {}, Fluxes: {}\".format(k, measure.flux(src)))"
528+
" print(\"Source {}, Fluxes: {}\".format(k, sc2.measure.flux(src)))"
505529
]
506530
},
507531
{
@@ -517,7 +541,7 @@
517541
"metadata": {},
518542
"outputs": [],
519543
"source": [
520-
"g = measure.Moments(scene_.sources[1])\n",
544+
"g = sc2.measure.Moments(scene_.sources[1])\n",
521545
"print(\"Source 1 ellipticity:\", g.ellipticity)"
522546
]
523547
},
@@ -548,11 +572,11 @@
548572
"metadata": {},
549573
"outputs": [],
550574
"source": [
551-
"import scarlet2.io\n",
575+
"import scarlet2.io as io\n",
552576
"\n",
553577
"id = 35\n",
554578
"filename = \"hsc_cosmos.h5\"\n",
555-
"scarlet2.io.model_to_h5(scene_, filename, id=id, overwrite=True)"
579+
"io.model_to_h5(scene_, filename, id=id, overwrite=True)"
556580
]
557581
},
558582
{
@@ -568,8 +592,8 @@
568592
"metadata": {},
569593
"outputs": [],
570594
"source": [
571-
"scene__ = scarlet2.io.model_from_h5(filename, id=id)\n",
572-
"plot.scene(scene__, observation=obs, norm=norm, add_boxes=True)\n",
595+
"scene__ = io.model_from_h5(filename, id=id)\n",
596+
"sc2.plot.scene(scene__, observation=obs, norm=norm, add_boxes=True)\n",
573597
"plt.show()"
574598
]
575599
},
@@ -592,23 +616,23 @@
592616
" yx = [(14.0, 44.0), (42.0, 9.0)]\n",
593617
" for center in yx:\n",
594618
" center = jnp.array(center)\n",
595-
" spectrum = init.pixel_spectrum(obs, center)\n",
596-
" morph = init.compact_morphology()\n",
597-
" Source(center, spectrum, morph)\n",
619+
" spectrum = sc2.init.pixel_spectrum(obs, center)\n",
620+
" morph = sc2.init.compact_morphology()\n",
621+
" sc2.Source(center, spectrum, morph)\n",
598622
"\n",
599623
"# need to remake the parameter structure because we have more free parameters now\n",
600-
"with Parameters(scene__) as parameters__:\n",
624+
"with sc2.Parameters(scene__) as parameters__:\n",
601625
" for i in range(len(scene__.sources)):\n",
602-
" Parameter(\n",
626+
" sc2.Parameter(\n",
603627
" scene__.sources[i].spectrum,\n",
604628
" name=f\"spectrum:{i}\",\n",
605629
" constraint=constraints.positive,\n",
606630
" stepsize=spec_step,\n",
607631
" )\n",
608632
" if i == 0:\n",
609-
" Parameter(scene__.sources[i].center, name=f\"center:{i}\", stepsize=0.1)\n",
633+
" sc2.Parameter(scene__.sources[i].center, name=f\"center:{i}\", stepsize=0.1)\n",
610634
" else:\n",
611-
" Parameter(\n",
635+
" sc2.Parameter(\n",
612636
" scene__.sources[i].morphology,\n",
613637
" name=f\"morph:{i}\",\n",
614638
" constraint=constraints.unit_interval,\n",
@@ -632,7 +656,7 @@
632656
"metadata": {},
633657
"outputs": [],
634658
"source": [
635-
"plot.scene(\n",
659+
"sc2.plot.scene(\n",
636660
" scene___,\n",
637661
" obs,\n",
638662
" norm=norm,\n",

docs/howto/multiresolution.ipynb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,16 @@
155155
")"
156156
]
157157
},
158+
{
159+
"cell_type": "code",
160+
"execution_count": null,
161+
"id": "b6a9d6d9e07722cd",
162+
"metadata": {},
163+
"outputs": [],
164+
"source": [
165+
"model_frame"
166+
]
167+
},
158168
{
159169
"cell_type": "markdown",
160170
"id": "abb5bbf9-0eb7-4c37-bb55-43904ec6af48",
@@ -236,6 +246,16 @@
236246
");"
237247
]
238248
},
249+
{
250+
"cell_type": "code",
251+
"execution_count": null,
252+
"id": "5af1c3e91450403a",
253+
"metadata": {},
254+
"outputs": [],
255+
"source": [
256+
"obs_hsc.renderer"
257+
]
258+
},
239259
{
240260
"cell_type": "markdown",
241261
"id": "5765f3385e73e562",

0 commit comments

Comments
 (0)