Skip to content

Commit 071a25e

Browse files
authored
Merge pull request #269 from Caltech-IPAC/raen/issues/165/spherex-bad-gateway
Catch spherex read errors
2 parents 221877a + b0cb0c1 commit 071a25e

3 files changed

Lines changed: 95 additions & 16 deletions

File tree

tutorials/spherex/spherex_cutouts.md

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ The following packages must be installed to run this notebook.
5353

5454
```{code-cell} ipython3
5555
import concurrent.futures
56+
import http.client
5657
import time
58+
import urllib.error
5759
5860
import astropy.units as u
5961
import matplotlib.pyplot as plt
@@ -187,6 +189,34 @@ def process_cutout(row, ra, dec, cache):
187189
row["hdus"] = hdus
188190
```
189191

192+
We provide a small convenience wrapper around `process_cutout` that is used in the rest of this notebook.
193+
It catches transient read errors and simply skips those cutouts, which is sufficient for this demonstration.
194+
For science use cases, users may instead want to implement their own retry logic or error handling strategy.
195+
196+
```{code-cell} ipython3
197+
def process_cutout_with_error_handling(row, ra, dec, cache):
198+
'''
199+
Call `process_cutout` while catching transient read errors.
200+
201+
Parameters:
202+
===========
203+
204+
row : astropy.table row
205+
Row of a table that will be changed in place by this function. The table
206+
is created by the SQL TAP query.
207+
ra,dec : coordinates (astropy units)
208+
Ra and Dec coordinates (same as used for the TAP query) with attached astropy units
209+
cache : bool
210+
If set to `True`, the output of cached and the cutout processing will run faster next time.
211+
Turn this feature off by setting `cache = False`.
212+
'''
213+
try:
214+
process_cutout(row, ra, dec, cache=cache)
215+
except (TimeoutError, urllib.error.HTTPError, http.client.IncompleteRead):
216+
# Transient read errors. Skip this cutout.
217+
row["hdus"] = None
218+
```
219+
190220
## 7. Download the Cutouts
191221

192222
This process can take a while.
@@ -223,8 +253,13 @@ results_table_serial["hdus"] = np.full(len(results_table_serial), None)
223253
224254
t1 = time.time()
225255
for row in results_table_serial:
226-
process_cutout(row, ra, dec, cache=False)
256+
process_cutout_with_error_handling(row, ra, dec, cache=False)
227257
print("Time to create cutouts in serial mode: {:2.2f} minutes.".format((time.time() - t1) / 60))
258+
259+
# Drop rows that failed to download.
260+
failed_cutouts_serial = results_table_serial[results_table_serial["hdus"] == None]
261+
print("Failed to get the following cutouts:\n", failed_cutouts_serial["uri"])
262+
results_table_serial = results_table_serial[results_table_serial["hdus"] != None]
228263
```
229264

230265
### 7.2 Parallel Approach
@@ -257,9 +292,15 @@ results_table_parallel["hdus"] = np.full(len(results_table_parallel), None)
257292
258293
t1 = time.time()
259294
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
260-
futures = [executor.submit(process_cutout, row, ra, dec, False) for row in results_table_parallel]
295+
futures = [executor.submit(process_cutout_with_error_handling, row, ra, dec, False)
296+
for row in results_table_parallel]
261297
concurrent.futures.wait(futures)
262298
print("Time to create cutouts in parallel mode: {:2.2f} minutes.".format((time.time() - t1) / 60))
299+
300+
# Drop rows that failed to download.
301+
failed_cutouts_parallel = results_table_parallel[results_table_parallel["hdus"] == None]
302+
print("Failed to get the following cutouts:\n", failed_cutouts_parallel["uri"])
303+
results_table_parallel = results_table_parallel[results_table_parallel["hdus"] != None]
263304
```
264305

265306
## 8. Create a summary table HDU with renamed columns
@@ -343,7 +384,7 @@ plt.show()
343384

344385
## About this notebook
345386

346-
**Updated:** 24 October 2025
387+
**Updated:** 5 March 2026
347388

348389
**Contact:** [IRSA Helpdesk](https://irsa.ipac.caltech.edu/docs/help_desk.html) with questions or problems.
349390

tutorials/spherex/spherex_intro.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ The following packages must be installed to run this notebook.
6060
## 4. Imports
6161

6262
```{code-cell} ipython3
63+
import http.client
64+
import time
65+
import urllib.error
66+
6367
import numpy as np
6468
import matplotlib.pyplot as plt
6569
@@ -152,10 +156,26 @@ You can put this URL into a browser to download the file. Or you can work with i
152156

153157
+++
154158

155-
Use Astropy to examine the header of the URL from the previous step.
159+
First, use Astropy to load the data using the URL from the previous step.
160+
Transient read errors occur sometimes, so we'll catch those and retry a few times.
161+
162+
```{code-cell} ipython3
163+
# Max number of times to retry HTTP errors.
164+
max_retries = 3
165+
for attempt in range(max_retries):
166+
try:
167+
# Load the data.
168+
hdulist = fits.open(spectral_image_url)
169+
break
170+
except (TimeoutError, urllib.error.HTTPError, http.client.IncompleteRead):
171+
if attempt == max_retries - 1:
172+
raise
173+
time.sleep(10 * (attempt + 1))
174+
```
175+
176+
Examine the header.
156177

157178
```{code-cell} ipython3
158-
hdulist = fits.open(spectral_image_url)
159179
hdulist.info()
160180
```
161181

@@ -454,6 +474,6 @@ print("Dimensions of VALUES array:", wavelengths.shape)
454474
**Contact:** [IRSA Helpdesk](https://irsa.ipac.caltech.edu/docs/help_desk.html) with questions
455475
or problems.
456476

457-
**Updated:** 24 October 2025
477+
**Updated:** 5 March 2026
458478

459479
**Runtime:** approximately 30 seconds

tutorials/spherex/spherex_psf.md

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,10 @@ The following packages must be installed to run this notebook.
5555
```
5656

5757
```{code-cell} ipython3
58+
import http.client
5859
import re
5960
import time
61+
import urllib.error
6062
6163
import astropy.units as u
6264
import matplotlib.pyplot as plt
@@ -133,18 +135,34 @@ print(spectral_image_url)
133135
## 5. Read in a SPHEREx Cutout
134136

135137
Next, we use standard astropy tools to open the fits image and to read the different headers and data.
138+
Transient read errors occur sometimes, so we'll catch those and retry a few times.
136139

137140
```{tip}
138141
As we do below, you can use `hdul.info()` to print the list of FITS layers of the downloaded cutout.
139142
```
140143

141144
```{code-cell} ipython3
142-
with fits.open(spectral_image_url) as hdul:
143-
hdul.info()
144-
cutout_header = hdul['IMAGE'].header
145-
psf_header = hdul['PSF'].header
146-
cutout = hdul['IMAGE'].data
147-
psfcube = hdul['PSF'].data
145+
# Max number of times to retry transient read errors.
146+
max_retries = 3
147+
for attempt in range(max_retries):
148+
try:
149+
# Read the data.
150+
with fits.open(spectral_image_url) as hdul:
151+
cutout_header = hdul['IMAGE'].header
152+
psf_header = hdul['PSF'].header
153+
cutout = hdul['IMAGE'].data
154+
psfcube = hdul['PSF'].data
155+
break
156+
except (TimeoutError, urllib.error.HTTPError, http.client.IncompleteRead):
157+
if attempt == max_retries - 1:
158+
raise
159+
time.sleep(10 * (attempt + 1))
160+
```
161+
162+
Examine the header.
163+
164+
```{code-cell} ipython3
165+
hdul.info()
148166
```
149167

150168
The downloaded SPHEREx image cutout contains 5 FITS layers, which are described in the [SPHEREx Explanatory Supplement](https://irsa.ipac.caltech.edu/data/SPHEREx/docs/SPHEREx_Expsupp_QR.pdf).
@@ -279,11 +297,11 @@ plt.show()
279297

280298
## 9. Using the SPHEREx PSF in Forward Modeling (e.g., Tractor)
281299

282-
The PSF returned by this notebook is oversampled relative to the native SPHEREx detector pixel grid.
300+
The PSF returned by this notebook is oversampled relative to the native SPHEREx detector pixel grid.
283301
This is intentional: the PSF is evaluated on a fine sub-pixel grid so that it can represent different intra-pixel source positions accurately.
284302

285-
Tools such as Tractor do not expect an oversampled PSF directly.
286-
Instead, they require a PSF that is pixel-integrated at the native detector resolution and evaluated at the correct sub-pixel phase of the source.
303+
Tools such as Tractor do not expect an oversampled PSF directly.
304+
Instead, they require a PSF that is pixel-integrated at the native detector resolution and evaluated at the correct sub-pixel phase of the source.
287305
If you pass the oversampled PSF directly into Tractor without resampling, the effective PSF width and normalization will be incorrect, which can lead to systematic differences relative to the SPHEREx Spectrophotometry Tool.
288306

289307
To use this PSF for forward modeling or fitting, you must:
@@ -299,7 +317,7 @@ To use this PSF for forward modeling or fitting, you must:
299317

300318
## About this notebook
301319

302-
**Updated:** 24 October 2025
320+
**Updated:** 5 March 2026
303321

304322
**Contact:** Contact [IRSA Helpdesk](https://irsa.ipac.caltech.edu/docs/help_desk.html) with questions or problems.
305323

0 commit comments

Comments
 (0)