Skip to content

Commit 1986cb2

Browse files
authored
Merge pull request #96 from bsipocz/MAINT_euclid4_astropyfication
MAINT: cleanup euclid4 and introduced a couple of astropy features
2 parents abd4807 + e4eb18c commit 1986cb2

1 file changed

Lines changed: 109 additions & 132 deletions

File tree

tutorials/euclid_access/4_Euclid_intro_PHZ_catalog.md

Lines changed: 109 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,24 @@ If you have questions about this notebook, please contact the [IRSA helpdesk](ht
5151

5252
```{code-cell} ipython3
5353
# Uncomment the next line to install dependencies if needed.
54-
# !pip install matplotlib pandas 'astropy>=5.3' 'astroquery>=0.4.10' fsspec firefly_client
54+
# !pip install matplotlib 'astropy>=5.3' 'astroquery>=0.4.10' fsspec firefly_client
5555
```
5656

5757
```{code-cell} ipython3
5858
import os
5959
import re
6060
import urllib
6161
62+
import numpy as np
6263
import matplotlib.pyplot as plt
6364
6465
from astropy.coordinates import SkyCoord
6566
from astropy.io import fits
6667
from astropy.nddata import Cutout2D
67-
from astropy.table import Table
68+
from astropy.table import QTable
6869
from astropy import units as u
6970
from astropy.utils.data import download_file
70-
from astropy.visualization import ImageNormalize, PercentileInterval, AsinhStretch, LogStretch
71+
from astropy.visualization import ImageNormalize, PercentileInterval, AsinhStretch, LogStretch, quantity_support
7172
from astropy.wcs import WCS
7273
7374
from firefly_client import FireflyClient
@@ -90,32 +91,30 @@ coord = SkyCoord(ra, dec, unit='deg', frame='icrs')
9091

9192
This searches specifically in the euclid_DpdMerBksMosaic "collection" which is the MER images and catalogs.
9293

93-
```{code-cell} ipython3
94-
image_table = Irsa.query_sia(pos=(coord, search_radius), collection='euclid_DpdMerBksMosaic')
95-
```
94+
+++
9695

9796
```{note}
98-
This table lists all MER mosaic images available in this position. These mosaics include the Euclid VIS, Y, J, H images, as well as ground-based telescopes which have been put on the same pixel scale. For more information, see the [Euclid documentation at IPAC](https://euclid.caltech.edu/page/euclid-faq-tech/).
97+
This table lists all MER mosaic images available in this position. These mosaics include the Euclid VIS, Y, J, H images, as well as ground-based telescopes which have been put on the same pixel scale. For more information, see the [Euclid documentation at IPAC](https://euclid.caltech.edu/page/euclid-faq-tech/). We use the ``facility`` argument below to query for Euclid images only.
9998
```
10099

101100
```{code-cell} ipython3
102-
# Convert the table to pandas dataframe
103-
df_im_irsa=image_table.to_pandas()
101+
image_table = Irsa.query_sia(pos=(coord, search_radius), collection='euclid_DpdMerBksMosaic', facility='Euclid')
104102
```
105103

106-
```{code-cell} ipython3
107-
df_im_euclid=df_im_irsa[ (df_im_irsa['dataproduct_subtype']=='science') & (df_im_irsa['facility_name']=='Euclid')]
104+
Note that there are various image types are returned as well, we filter out the science images from these:
108105

109-
df_im_euclid.head()
106+
```{code-cell} ipython3
107+
science_images = image_table[image_table['dataproduct_subtype'] == 'science']
108+
science_images
110109
```
111110

112111
Choose the VIS image and pull the filename and tileID
113112

114113
```{code-cell} ipython3
115-
filename=df_im_euclid[df_im_euclid['energy_bandpassname']=='VIS']['access_url'].to_list()[0]
114+
filename = science_images[science_images['energy_bandpassname'] == 'VIS']['access_url'][0]
115+
tileID = science_images[science_images['energy_bandpassname'] == 'VIS']['obs_id'][0][:9]
116116
117-
tileID=re.search(r'TILE\s*(\d{9})', filename).group(1)
118-
print('The MER tile ID for this object is :',tileID)
117+
print(f'The MER tile ID for this object is : {tileID}')
119118
```
120119

121120
## 2. Download PHZ catalog from IRSA
@@ -172,10 +171,10 @@ The _unif fluxes are: "Unified flux recomputed after correction from galactic ex
172171
We specify the following conditions on our search:
173172
- We select just the galaxies where the flux is greater than zero, to ensure the appear in all four of the Euclid MER images.
174173
- Select only objects in a circle (search radius selected below) around our selected RA and Dec
175-
- phz_classification =2 means we select only galaxies
176-
- Using the phz_90_int1 and phz_90_int2, we select just the galaxies where the error on the photometric redshift is less than 20%
174+
- `phz_classification = 2` means we select only galaxies
175+
- Using the `phz_90_int1` and `phz_90_int2`, we select just the galaxies where the error on the photometric redshift is less than 20%
177176
- Select just the galaxies between a median redshift of 1.4 and 1.6
178-
- We search just a 3 arcminute box around an RA and Dec
177+
- We search just a 5 arcminute box around an RA and Dec
179178

180179
+++
181180

@@ -190,53 +189,47 @@ im_cutout= 5 * u.arcmin
190189
ra_cutout = 267.8
191190
dec_cutout = 66
192191
193-
coords_cutout = SkyCoord(ra_cutout, dec_cutout, unit=(u.deg, u.deg), frame='icrs')
194-
##########################################################################
195-
im_cutout_deg=im_cutout.to(u.deg).value
196-
197-
## Create ra and dec bounds for the box
198-
ra0=ra_cutout-im_cutout_deg/2
199-
ra1=ra_cutout+im_cutout_deg/2
200-
201-
dec0=dec_cutout-im_cutout_deg/2
202-
dec1=dec_cutout+im_cutout_deg/2
192+
coords_cutout = SkyCoord(ra_cutout, dec_cutout, unit='deg', frame='icrs')
193+
size_cutout = im_cutout.to(u.deg).value
203194
```
204195

205196
```{code-cell} ipython3
206-
adql = f"SELECT DISTINCT mer.object_id,mer.ra, mer.dec, phz.flux_vis_unif, phz.flux_y_unif, \
207-
phz.flux_j_unif, phz.flux_h_unif, phz.phz_classification, phz.phz_median, phz.phz_90_int1, phz.phz_90_int2 \
208-
FROM {table_mer} AS mer \
209-
JOIN {table_phz} as phz \
210-
ON mer.object_id = phz.object_id \
211-
WHERE 1 = CONTAINS(POINT('ICRS', mer.ra, mer.dec), CIRCLE('ICRS', {ra_cutout}, {dec_cutout}, {im_cutout_deg/2})) \
212-
AND phz.flux_vis_unif> 0 \
213-
AND phz.flux_y_unif > 0 \
214-
AND phz.flux_j_unif > 0 \
215-
AND phz.flux_h_unif > 0 \
216-
AND phz.phz_classification = 2 \
217-
AND ((phz.phz_90_int2 - phz.phz_90_int1) / (1 + phz.phz_median)) < 0.20 \
218-
AND phz.phz_median BETWEEN 1.4 AND 1.6 \
219-
"
220-
adql
197+
adql = ("SELECT DISTINCT mer.object_id, mer.ra, mer.dec, "
198+
"phz.flux_vis_unif, phz.flux_y_unif, phz.flux_j_unif, phz.flux_h_unif, "
199+
"phz.phz_classification, phz.phz_median, phz.phz_90_int1, phz.phz_90_int2 "
200+
f"FROM {table_mer} AS mer "
201+
f"JOIN {table_phz} as phz "
202+
"ON mer.object_id = phz.object_id "
203+
"WHERE 1 = CONTAINS(POINT('ICRS', mer.ra, mer.dec), "
204+
f"BOX('ICRS', {ra_cutout}, {dec_cutout}, {size_cutout/np.cos(coords_cutout.dec)}, {size_cutout})) "
205+
"AND phz.flux_vis_unif> 0 "
206+
"AND phz.flux_y_unif > 0 "
207+
"AND phz.flux_j_unif > 0 "
208+
"AND phz.flux_h_unif > 0 "
209+
"AND phz.phz_classification = 2 "
210+
"AND ((phz.phz_90_int2 - phz.phz_90_int1) / (1 + phz.phz_median)) < 0.20 "
211+
"AND phz.phz_median BETWEEN 1.4 AND 1.6")
221212
222213
223214
## Use TAP with this ADQL string
224-
result = Irsa.query_tap(adql)
225-
215+
result_galaxies = Irsa.query_tap(adql).to_table()
216+
result_galaxies[:5]
217+
```
226218

227-
## Convert table to pandas dataframe
228-
df_g_irsa = result.to_table().to_pandas()
219+
```{warning}
220+
Note that we use `to_table` above rather than `to_qtable`. While astropy's `QTable` is more powerful than its `Table`, as it e.g. handles the column units properly, we cannot use it here due to a known bug; it mishandles the large integer numbers in the `object_id` column and recast them as float during which process some precision is being lost.
229221
230-
# Display first few rows
231-
df_g_irsa.head()
222+
Once the bug is fixed, we plan to update the code in this notebook and simplify some of the approaches below.
232223
```
233224

234-
```{code-cell} ipython3
235-
len(df_g_irsa)
236-
```
225+
+++
237226

238227
## 3. Read in a cutout of the MER image from IRSA directly
239228

229+
+++
230+
231+
Due to the large field of view of the MER mosaic, let's cut out a smaller section (5'x5') of the MER mosaic to inspect the image.
232+
240233
```{code-cell} ipython3
241234
## Use fsspec to interact with the fits file without downloading the full file
242235
hdu = fits.open(filename, use_fsspec=True)
@@ -245,152 +238,136 @@ hdu = fits.open(filename, use_fsspec=True)
245238
header = hdu[0].header
246239
247240
## Read in the cutout of the image that you want
248-
cutout_data = Cutout2D(hdu[0].section, position=coords_cutout, size=im_cutout, wcs=WCS(hdu[0].header))
249-
250-
## Define a new fits file based on this smaller cutout, with accurate WCS based on the cutout size
251-
new_hdu = fits.PrimaryHDU(data=cutout_data.data, header=header)
252-
new_hdu.header.update(cutout_data.wcs.to_header())
253-
```
254-
255-
```{code-cell} ipython3
256-
im_mer_irsa = new_hdu.data
241+
cutout_image = Cutout2D(hdu[0].section, position=coords_cutout, size=im_cutout, wcs=WCS(header))
257242
```
258243

259244
```{code-cell} ipython3
260-
im_mer_irsa.shape
245+
cutout_image.data.shape
261246
```
262247

263248
```{code-cell} ipython3
264-
norm = ImageNormalize(im_mer_irsa, interval=PercentileInterval(99.9), stretch=AsinhStretch())
265-
plt.imshow(im_mer_irsa, cmap='gray', origin='lower', norm=norm)
249+
norm = ImageNormalize(cutout_image.data, interval=PercentileInterval(99.9), stretch=AsinhStretch())
250+
_ = plt.imshow(cutout_image.data, cmap='gray', origin='lower', norm=norm)
266251
```
267252

268253
## 4. Overplot the catalog on the MER mosaic image
269254

270-
```{code-cell} ipython3
271-
## Use the WCS package to extract the coordinates from the header of the image
272-
head_mer_irsa=new_hdu.header
273-
wcs_irsa=WCS(head_mer_irsa) # VIS
274-
```
255+
+++
275256

276-
```{code-cell} ipython3
277-
## Convert the catalog to match the pixels in the image
278-
xy_irsa = wcs_irsa.all_world2pix(df_g_irsa["ra"],df_g_irsa["dec"],0)
257+
```{tip}
258+
We can rely on astropy's WCSAxes framework for making plots of Astronomical data in Matplotlib. Please note the usage of `projection` and `transform` arguments in the code example below.
259+
260+
For more info, please visit the [WCSAxes documentation](https://docs.astropy.org/en/stable/visualization/wcsaxes/index.html).
279261
```
280262

281263
```{code-cell} ipython3
282-
df_g_irsa['x_pix']=xy_irsa[0]
283-
df_g_irsa['y_pix']=xy_irsa[1]
284-
```
264+
ax = plt.subplot(projection=cutout_image.wcs)
285265
286-
Due to the large field of view of the MER mosaic, let's cut out a smaller section (3'x3')of the MER mosaic to inspect the image
266+
ax.imshow(cutout_image.data, cmap='gray', origin='lower',
267+
norm=ImageNormalize(cutout_image.data, interval=PercentileInterval(99.9), stretch=LogStretch()))
268+
plt.scatter(result_galaxies['ra'], result_galaxies['dec'], s=36, facecolors='none', edgecolors='red',
269+
transform=ax.get_transform('world'))
287270
288-
+++
271+
_ = plt.title('Galaxies between z = 1.4 and 1.6')
272+
```
289273

290-
Plot MER catalog sources on the Euclid VIS image 3'x3' cutout
274+
## 5. Pull the spectra on the top brightest source based on object ID
291275

292276
```{code-cell} ipython3
293-
plt.imshow(im_mer_irsa, cmap='gray', origin='lower', norm=ImageNormalize(im_mer_irsa, interval=PercentileInterval(99.9), stretch=LogStretch()))
294-
colorbar = plt.colorbar()
295-
plt.scatter(df_g_irsa['x_pix'], df_g_irsa['y_pix'], s=36, facecolors='none', edgecolors='red')
296-
297-
plt.title('Galaxies between z = 1.4 and 1.6')
298-
plt.show()
277+
result_galaxies.sort(keys='flux_h_unif', reverse=True)
299278
```
300279

301-
Pull the spectra on the top brightest source based on object ID
302-
303280
```{code-cell} ipython3
304-
df_g_irsa_sort=df_g_irsa.sort_values(by='flux_h_unif',ascending=False)
281+
result_galaxies[:3]
305282
```
306283

284+
Let's pick one of these galaxies. Note that the table has been sorted above, we can use the same index here and below to access the data for this particular galaxy.
285+
307286
```{code-cell} ipython3
308-
df_g_irsa_sort.iloc[0:3]
287+
index = 2
288+
289+
obj_id = result_galaxies['object_id'][index]
290+
redshift = result_galaxies['phz_median'][index]
309291
```
310292

311-
```{code-cell} ipython3
312-
obj_id=df_g_irsa_sort['object_id'].iloc[2]
313-
redshift = df_g_irsa_sort['phz_median'].iloc[2]
293+
We will use TAP and an ASQL query to find the spectral data for this particular galaxy.
314294

315-
## Pull the data on these objects
316-
adql_object = f"SELECT * \
317-
FROM {table_1dspectra} \
318-
WHERE objectid = {obj_id}"
295+
```{code-cell} ipython3
296+
adql_object = f"SELECT * FROM {table_1dspectra} WHERE objectid = {obj_id}"
319297
320298
## Pull the data on this particular galaxy
321-
result2 = Irsa.query_tap(adql_object)
322-
df2=result2.to_table().to_pandas()
323-
df2
299+
result_spectra = Irsa.query_tap(adql_object).to_table()
300+
result_spectra
324301
```
325302

326-
Pull out the file name from the ``result`` table:
303+
Pull out the file name from the ``result_spectra`` table:
327304

328305
```{code-cell} ipython3
329-
file_uri = urllib.parse.urljoin(Irsa.tap_url, result2['uri'][0])
306+
file_uri = urllib.parse.urljoin(Irsa.tap_url, result_spectra['uri'][0])
330307
file_uri
331308
```
332309

333310
```{code-cell} ipython3
334311
with fits.open(file_uri) as hdul:
335-
hdu = hdul[df2['hdu'].iloc[0]]
336-
dat = Table.read(hdu, format='fits', hdu=1)
337-
df_obj_irsa = dat.to_pandas()
312+
spectrum = QTable.read(hdul[result_spectra['hdu'][0]], format='fits')
313+
spectrum_header = hdul[result_spectra['hdu'][0]].header
338314
```
339315

340316
### Now the data are read in, plot the spectrum
341317

342-
Divide by 10000 to convert from Angstrom to micron
318+
```{tip}
319+
As we use astropy.visualization’s quantity_support, matplotlib automatically picks up the axis units from the quantitites we plot.
320+
```
343321

344322
```{code-cell} ipython3
345-
plt.plot(df_obj_irsa['WAVELENGTH']/10000., df_obj_irsa['SIGNAL'])
323+
quantity_support()
324+
```
325+
326+
```{code-cell} ipython3
327+
plt.plot(spectrum['WAVELENGTH'].to(u.micron), spectrum['SIGNAL'])
346328
347-
plt.xlabel('Wavelength (microns)')
348-
plt.ylabel('Flux (erg / (s cm2))')
349329
plt.xlim(1.25, 1.85)
350-
plt.ylim(-0.5,0.5)
351-
plt.title('Object ID is '+str(obj_id)+'with phz_median='+str(redshift))
330+
plt.ylim(-0.5, 0.5)
331+
_ = plt.title(f"Object {obj_id} with phz_median={redshift}")
352332
```
353333

354-
Let's cut out a very small patch of the MER image to see what this galaxy looks like
334+
Let's cut out a very small patch of the MER image to see what this galaxy looks like. Remember that we sorted the table above, so can reuse the same index to pick up the coordinates for the galaxy. Otherwise we could filter on the object ID.
355335

356336
```{code-cell} ipython3
357-
## How large do you want the image cutout to be?
358-
im_cutout= 2.0 * u.arcsec
359-
360-
## Use the ra and dec of the galaxy
361-
ra = df_g_irsa[df_g_irsa['object_id']==obj_id]['ra'].iloc[0]
362-
dec = df_g_irsa[df_g_irsa['object_id']==obj_id]['dec'].iloc[0]
337+
result_galaxies[index]
338+
```
363339

364-
coords_cutout = SkyCoord(ra, dec, unit=(u.deg,u.deg), frame='icrs')
340+
```{code-cell} ipython3
341+
## How large do you want the image cutout to be?
342+
size_galaxy_cutout = 2.0 * u.arcsec
365343
```
366344

367-
Use ``fsspec`` to obtain a cutout of the fits file
345+
Use the `ra` and `dec` columns for the galaxy to create a `SkyCoord`.
368346

369347
```{code-cell} ipython3
370-
hdu = fits.open(filename, use_fsspec=True)
371-
372-
header = hdu[0].header
348+
coords_galaxy = SkyCoord(result_galaxies['ra'][index], result_galaxies['dec'][index], unit='deg')
373349
```
374350

375351
```{code-cell} ipython3
376-
## Read in the cutout of the image that you want
377-
cutout_data = Cutout2D(hdu[0].section, position=coords_cutout, size=im_cutout, wcs=WCS(hdu[0].header))
352+
coords_galaxy
378353
```
379354

355+
We haven't closed the image file above, so use `Cutout2D` again to cut out a section around the galaxy.
356+
380357
```{code-cell} ipython3
381-
new_hdu = fits.PrimaryHDU(data=cutout_data.data, header=header)
382-
new_hdu.header.update(cutout_data.wcs.to_header())
358+
cutout_galaxy = Cutout2D(hdu[0].section, position=coords_galaxy, size=size_galaxy_cutout, wcs=WCS(header))
383359
```
384360

361+
Plot to show the cutout on the galaxy
362+
385363
```{code-cell} ipython3
386-
## Plot a quick simple plot to show the cutout on the galaxy
364+
ax = plt.subplot(projection=cutout_galaxy.wcs)
387365
388-
plt.imshow(new_hdu.data, cmap='gray', origin='lower',
389-
norm=ImageNormalize(new_hdu.data, interval=PercentileInterval(99.9), stretch=AsinhStretch()))
390-
colorbar = plt.colorbar()
366+
ax.imshow(cutout_galaxy.data, cmap='gray', origin='lower',
367+
norm=ImageNormalize(cutout_galaxy.data, interval=PercentileInterval(99.9), stretch=AsinhStretch()))
391368
```
392369

393-
## 5. Load the image on Firefly to be able to interact with the data directly
370+
## 6. Load the image on Firefly to be able to interact with the data directly
394371

395372
+++
396373

@@ -423,7 +400,7 @@ fc.align_images(lock_match=True)
423400

424401
```{code-cell} ipython3
425402
csv_path = os.path.join(download_path, "mer_df.csv")
426-
df_g_irsa.to_csv(csv_path, index=False)
403+
result_galaxies.write(csv_path, format="csv")
427404
```
428405

429406
### Upload the CSV table to Firefly and display as an overlay on the FITS image
@@ -439,6 +416,6 @@ fc.show_table(uploaded_table)
439416

440417
**Author**: Tiffany Meshkat, Anahita Alavi, Anastasia Laity, Andreas Faisst, Brigitta Sipőcz, Dan Masters, Harry Teplitz, Jaladh Singhal, Shoubaneh Hemmati, Vandana Desai
441418

442-
**Updated**: 2025-03-31
419+
**Updated**: 2025-04-10
443420

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

0 commit comments

Comments
 (0)