Repository 'desi_legacy_survey_astro_tool'
hg clone https://toolshed.g2.bx.psu.edu/repos/astroteam/desi_legacy_survey_astro_tool

Changeset 0:ea39f416af9d (2025-04-28)
Next changeset 1:7fad1abc2e4a (2025-06-13)
Commit message:
planemo upload for repository https://github.com/esg-epfl-apc/tools-astro/tree/main/tools commit 7315eeb043225f7cfd1cf0f3a95e181289d7de32
added:
Catalog.py
Image.py
Spectrum.py
desi.py
desi_legacy_survey_astro_tool.xml
b
diff -r 000000000000 -r ea39f416af9d Catalog.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Catalog.py Mon Apr 28 14:59:01 2025 +0000
[
@@ -0,0 +1,255 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+#!/usr/bin/env python
+
+# This script is generated with nb2galaxy
+
+# flake8: noqa
+
+import json
+import os
+import shutil
+
+import astropy.units as u
+import numpy as np
+
+# Conventional astronomical tools, also to be traced by Renku plugin, there is domain-specific ontology built in
+from astropy.coordinates import Angle  # Angles
+from astropy.coordinates import SkyCoord  # High-level coordinates
+from astropy.table import Table
+from desi import DESILegacySurvey
+from oda_api.json import CustomJSONEncoder
+
+nano_maggies_to_microJy = 3.631  # constant of conversion
+
+src_name = "Mrk 421"  # http://odahub.io/ontology#AstrophysicalObject
+RA = 166.113808  # http://odahub.io/ontology#PointOfInterestRA
+DEC = 38.208833  # http://odahub.io/ontology#PointOfInterestDEC
+Radius = 1  # http://odahub.io/ontology#AngleMinutes
+data_release = (
+    10  # http://odahub.io/ontology#Integer ; oda:label "Data Release"
+)
+
+_galaxy_wd = os.getcwd()
+
+with open("inputs.json", "r") as fd:
+    inp_dic = json.load(fd)
+if "C_data_product_" in inp_dic.keys():
+    inp_pdic = inp_dic["C_data_product_"]
+else:
+    inp_pdic = inp_dic
+src_name = str(inp_pdic["src_name"])
+RA = float(inp_pdic["RA"])
+DEC = float(inp_pdic["DEC"])
+Radius = float(inp_pdic["Radius"])
+data_release = int(inp_pdic["data_release"])
+
+# https://arxiv.org/pdf/2208.08513  Table 2
+# def compute_magnitude(flux, mw_transmission):
+#     return 22.5 - 2.5 * np.log10(flux / mw_transmission)
+
+# def compute_error_mag(flux, flux_ivar):
+#     dflux = 1 / np.sqrt(flux_ivar)
+#     # 2.5 / ln(10) * (dflux / flux)
+#     return 1.0857 * dflux / flux
+
+def clean_flux(flux, flux_ivar):
+    new_flux = np.exp(np.log(flux * nano_maggies_to_microJy))
+    new_flux_ivar = np.exp(
+        np.log(nano_maggies_to_microJy / np.sqrt(flux_ivar))
+    )
+    return new_flux, new_flux_ivar
+
+ra_s = RA
+dec_s = DEC
+dr = data_release
+source = SkyCoord(ra_s, dec_s, unit="degree")
+Radius = Angle(Radius * u.arcmin)
+
+case_ = 0
+if source.galactic.b > 0:
+    if dec_s >= 32:
+        print("MzLS")
+        case_ = 1
+    else:
+        print("DECALS")
+else:
+    print("DECALS")
+
+error_message = f"No data found, i.e. (RA, Dec) = ({ra_s}, {dec_s}) is outside the covered region by Data Release {dr}."
+try:
+    query = DESILegacySurvey.query_region(
+        coordinates=source, radius=Radius, data_release=dr
+    )
+except:
+    raise RuntimeError(error_message)
+
+if len(query) == 0:
+    raise RuntimeError(error_message)
+
+tap_result = query
+
+from oda_api.data_products import ODAAstropyTable
+
+ra = tap_result["ra"]
+dec = tap_result["dec"]
+t = tap_result["type"]
+s_r = tap_result["shape_r"]
+
+flux_g, flux_g_err = clean_flux(
+    tap_result["flux_g"], tap_result["flux_ivar_g"]
+)
+flux_r, flux_r_err = clean_flux(
+    tap_result["flux_r"], tap_result["flux_ivar_r"]
+)
+flux_z, flux_z_err = clean_flux(
+    tap_result["flux_z"], tap_result["flux_ivar_z"]
+)
+flux_w1, flux_w1_err = clean_flux(
+    tap_result["flux_w1"], tap_result["flux_ivar_w1"]
+)
+flux_w2, flux_w2_err = clean_flux(
+    tap_result["flux_w2"], tap_result["flux_ivar_w2"]
+)
+flux_w3, flux_w3_err = clean_flux(
+    tap_result["flux_w3"], tap_result["flux_ivar_w3"]
+)
+flux_w4, flux_w4_err = clean_flux(
+    tap_result["flux_w4"], tap_result["flux_ivar_w4"]
+)
+
+ebv = tap_result["ebv"]
+ref_cat = tap_result["ref_cat"]
+ref_cat[ref_cat == ""] = "NN"
+
+try:
+    flux_i, flux_i_err = clean_flux(
+        tap_result["flux_i"], tap_result["flux_ivar_i"]
+    )
+except:
+    flux_i = -99 * np.ones_like(flux_g)
+    flux_i_err = -99 * np.ones_like(flux_g)
+    flux_i, flux_i_err = clean_flux(flux_i, flux_i_err)
+
+data = [
+    ra,
+    dec,
+    t,
+    flux_g,
+    flux_g_err,
+    flux_r,
+    flux_r_err,
+    flux_z,
+    flux_z_err,
+    flux_i,
+    flux_i_err,
+    flux_w1,
+    flux_w1_err,
+    flux_w2,
+    flux_w2_err,
+    flux_w3,
+    flux_w3_err,
+    flux_w4,
+    flux_w4_err,
+    ebv,
+    ref_cat,
+    s_r,
+]
+names = (
+    "RA",
+    "DEC",
+    "Type",
+    "flux_g",
+    "flux_g_err",
+    "flux_r",
+    "flux_r_err",
+    "flux_z",
+    "flux_z_err",
+    "flux_i",
+    "flux_i_err",
+    "flux_w1",
+    "flux_w1_err",
+    "flux_w2",
+    "flux_w2_err",
+    "flux_w3",
+    "flux_w3_err",
+    "flux_w4",
+    "flux_w4_err",
+    "ebv",
+    "ref_cat",
+    "shape_r",
+)
+cat = ODAAstropyTable(Table(data, names=names))
+
+flux_error_list = [
+    "flux_i_err",
+    "flux_g_err",
+    "flux_r_err",
+    "flux_z_err",
+    "flux_w1_err",
+    "flux_w2_err",
+]
+flux_list = ["flux_i", "flux_g", "flux_r", "flux_z", "flux_w1", "flux_w2"]
+if case_ == 0:
+    filter_list = [
+        "DECam|DECam.i",
+        "DECam|DECam.g",
+        "DECam|DECam.r",
+        "DECam|DECam.z",
+        "WISE|WISE.W1",
+        "WISE|WISE.W2",
+    ]
+elif case_ == 1:
+    filter_list = [
+        "DECam|DECam.i",
+        "DESI|bass.g",
+        "DESI|bass.r",
+        "DESI|MzLS.z",
+        "WISE|WISE.W1",
+        "WISE|WISE.W2",
+    ]
+
+dict_filters = {
+    "filter": filter_list,
+    "flux_error": flux_error_list,
+    "flux": flux_list,
+}
+dict_filters
+
+catalog_table = cat  # http://odahub.io/ontology#ODAAstropyTable
+dictionary_filters = dict_filters  # http://odahub.io/ontology#ODATextProduct
+
+# output gathering
+_galaxy_meta_data = {}
+_oda_outs = []
+_oda_outs.append(
+    ("out_Catalog_catalog_table", "catalog_table_galaxy.output", catalog_table)
+)
+_oda_outs.append(
+    (
+        "out_Catalog_dictionary_filters",
+        "dictionary_filters_galaxy.output",
+        dictionary_filters,
+    )
+)
+
+for _outn, _outfn, _outv in _oda_outs:
+    _galaxy_outfile_name = os.path.join(_galaxy_wd, _outfn)
+    if isinstance(_outv, str) and os.path.isfile(_outv):
+        shutil.move(_outv, _galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
+    elif getattr(_outv, "write_fits_file", None):
+        _outv.write_fits_file(_galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "fits"}
+    elif getattr(_outv, "write_file", None):
+        _outv.write_file(_galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
+    else:
+        with open(_galaxy_outfile_name, "w") as fd:
+            json.dump(_outv, fd, cls=CustomJSONEncoder)
+        _galaxy_meta_data[_outn] = {"ext": "json"}
+
+with open(os.path.join(_galaxy_wd, "galaxy.json"), "w") as fd:
+    json.dump(_galaxy_meta_data, fd)
+print("*** Job finished successfully ***")
b
diff -r 000000000000 -r ea39f416af9d Image.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Image.py Mon Apr 28 14:59:01 2025 +0000
[
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+#!/usr/bin/env python
+
+# This script is generated with nb2galaxy
+
+# flake8: noqa
+
+import json
+import os
+import shutil
+
+import astropy.units as u
+
+# conventional python routines
+import matplotlib.pyplot as plt
+import numpy as np
+from astropy.coordinates import Angle  # Angles
+from astropy.coordinates import SkyCoord  # High-level coordinates
+from astropy.io import fits
+
+# Conventional astronomical tools, also to be traced by Renku plugin, there is domain-specific ontology built in
+from astropy.wcs import WCS
+
+# from astroquery.desi import DESILegacySurvey
+# from astroq.desi import DESILegacySurvey
+from desi import DESILegacySurvey
+from matplotlib.colors import LogNorm
+from oda_api.json import CustomJSONEncoder
+
+get_ipython().run_line_magic("matplotlib", "inline")   # noqa: F821
+from matplotlib.colors import LogNorm
+
+# if not(os.path.isdir('figs')):
+#     os.makedirs('figs')
+# if not(os.path.isdir('data')):
+#     os.makedirs('data')
+
+src_name = "Mrk 421"  # http://odahub.io/ontology#AstrophysicalObject
+RA = 166.113808  # http://odahub.io/ontology#PointOfInterestRA
+DEC = 38.208833  # http://odahub.io/ontology#PointOfInterestDEC
+Radius = 3  # http://odahub.io/ontology#AngleMinutes
+pixsize = (
+    1.0  # http://odahub.io/ontology#AngleSeconds ; oda:label "Pixel size"
+)
+band = "g"  # http://odahub.io/ontology#String ; oda:allowed_value "g","r","i","z" ; oda:label "Band"
+data_release = (
+    10  # http://odahub.io/ontology#Integer ; oda:label "Data Release"
+)
+
+_galaxy_wd = os.getcwd()
+
+with open("inputs.json", "r") as fd:
+    inp_dic = json.load(fd)
+if "C_data_product_" in inp_dic.keys():
+    inp_pdic = inp_dic["C_data_product_"]
+else:
+    inp_pdic = inp_dic
+src_name = str(inp_pdic["src_name"])
+RA = float(inp_pdic["RA"])
+DEC = float(inp_pdic["DEC"])
+Radius = float(inp_pdic["Radius"])
+pixsize = float(inp_pdic["pixsize"])
+band = str(inp_pdic["band"])
+data_release = int(inp_pdic["data_release"])
+
+ra_s = RA
+dec_s = DEC
+image_size = Radius
+image_band = band
+dr = data_release
+image_size = Angle(image_size * u.arcmin)
+pixsize = Angle(pixsize * u.arcsec)
+npix = int(2 * image_size / pixsize)
+source = SkyCoord(ra_s, dec_s, unit="degree")
+
+if dr < 10 and band == "i":
+    raise RuntimeError(
+        f"No data found. Data Release {dr} does not have '{band}' band."
+    )
+
+try:
+    query = DESILegacySurvey.get_images(
+        position=source,
+        survey="dr%d" % dr,
+        coordinates="icrs",
+        data_release=dr,
+        pixels=npix,
+        radius=image_size,
+        image_band=image_band,
+    )
+except:
+    raise RuntimeError(
+        f"No data found. Maybe (RA, Dec) = ({ra_s}, {dec_s}) is outside the covered region by Data Release {dr}."
+    )
+
+hdul = query[0]
+hdul[0].header
+
+query[0].writeto("Image.fits", overwrite=True)
+
+# paramstring='ra='+str(ra_s)+'&dec='+str(dec_s)+'&size='+str(npix)+'&layer=ls-dr'+str(dr)+'&pixscale='+str(pixsize)+'&bands='+image_band
+# suffix = hashlib.md5(paramstring.encode()).hexdigest()
+# filename='data/image_legacysurvey_%s.fits'%( suffix )
+
+# if os.path.exists(filename):
+#         os.remove(filename)
+# hdul.writeto(filename)
+
+hdu = hdul[0]
+image = hdu.data
+w = WCS(hdu.header)
+sky = w.pixel_to_world(0, 0)
+ra_max_image = sky.ra.degree
+dec_min_image = sky.dec.degree
+sky = w.pixel_to_world(npix - 1, npix - 1)
+ra_min_image = sky.ra.degree
+dec_max_image = sky.dec.degree
+
+plt.figure(figsize=(17, 13))
+im = plt.imshow(
+    image,
+    norm=LogNorm(vmax=np.max(image), vmin=np.max(image) / 1e3),
+    origin="lower",
+    extent=(ra_max_image, ra_min_image, dec_min_image, dec_max_image),
+)
+plt.grid(color="black", ls="solid")
+# plt.scatter(ra,dec,color='red',alpha=0.9)
+plt.scatter([ra_s], [dec_s], color="blue", linewidth=4, alpha=0.3)
+
+plt.xlabel("RA", fontsize=16)
+plt.ylabel("DEC", fontsize=16)
+
+plt.tick_params(axis="both", which="major", labelsize=16)
+plt.colorbar(im)
+
+plt.savefig("Image.png", format="png", bbox_inches="tight")
+
+from oda_api.data_products import ImageDataProduct, PictureProduct
+
+bin_image = PictureProduct.from_file("Image.png")
+fits_image = ImageDataProduct.from_fits_file("Image.fits")
+
+picture = bin_image  # http://odahub.io/ontology#ODAPictureProduct
+fits = fits_image  # http://odahub.io/ontology#Image
+
+# output gathering
+_galaxy_meta_data = {}
+_oda_outs = []
+_oda_outs.append(("out_Image_picture", "picture_galaxy.output", picture))
+_oda_outs.append(("out_Image_fits", "fits_galaxy.output", fits))
+
+for _outn, _outfn, _outv in _oda_outs:
+    _galaxy_outfile_name = os.path.join(_galaxy_wd, _outfn)
+    if isinstance(_outv, str) and os.path.isfile(_outv):
+        shutil.move(_outv, _galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
+    elif getattr(_outv, "write_fits_file", None):
+        _outv.write_fits_file(_galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "fits"}
+    elif getattr(_outv, "write_file", None):
+        _outv.write_file(_galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
+    else:
+        with open(_galaxy_outfile_name, "w") as fd:
+            json.dump(_outv, fd, cls=CustomJSONEncoder)
+        _galaxy_meta_data[_outn] = {"ext": "json"}
+
+with open(os.path.join(_galaxy_wd, "galaxy.json"), "w") as fd:
+    json.dump(_galaxy_meta_data, fd)
+print("*** Job finished successfully ***")
b
diff -r 000000000000 -r ea39f416af9d Spectrum.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Spectrum.py Mon Apr 28 14:59:01 2025 +0000
[
@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+#!/usr/bin/env python
+
+# This script is generated with nb2galaxy
+
+# flake8: noqa
+
+import json
+import os
+import shutil
+
+import astropy.units as u
+import numpy as np
+
+# Conventional astronomical tools, also to be traced by Renku plugin, there is domain-specific ontology built in
+from astropy.coordinates import Angle  # Angles
+from astropy.coordinates import SkyCoord  # High-level coordinates
+from astropy.table import Table
+from desi import DESILegacySurvey
+from numpy import pi, sqrt
+from oda_api.json import CustomJSONEncoder
+
+nano_maggies_to_microJy = 3.631  # constant of conversion
+nano_maggies_to_Jy = 3.631e-6  # constant of conversion
+
+import matplotlib.pyplot as plt
+
+src_name = "Mrk 421"  # http://odahub.io/ontology#AstrophysicalObject
+RA = 166.113808  # http://odahub.io/ontology#PointOfInterestRA
+DEC = 38.208833  # http://odahub.io/ontology#PointOfInterestDEC
+Radius = 0.1  # http://odahub.io/ontology#AngleMinutes
+data_release = (
+    10  # http://odahub.io/ontology#Integer ; oda:label "Data Release"
+)
+
+_galaxy_wd = os.getcwd()
+
+with open("inputs.json", "r") as fd:
+    inp_dic = json.load(fd)
+if "C_data_product_" in inp_dic.keys():
+    inp_pdic = inp_dic["C_data_product_"]
+else:
+    inp_pdic = inp_dic
+src_name = str(inp_pdic["src_name"])
+RA = float(inp_pdic["RA"])
+DEC = float(inp_pdic["DEC"])
+Radius = float(inp_pdic["Radius"])
+data_release = int(inp_pdic["data_release"])
+
+def clean_flux(flux, flux_ivar):
+    new_flux = flux * nano_maggies_to_microJy
+    new_flux_ivar = nano_maggies_to_microJy / np.sqrt(flux_ivar)
+    return new_flux, new_flux_ivar
+
+ra_s = RA
+dec_s = DEC
+dr = data_release
+source = SkyCoord(ra_s, dec_s, unit="degree")
+Radius = Angle(Radius * u.arcmin)
+
+case_ = 0
+if source.galactic.b > 0:
+    if dec_s >= 32:
+        print("MzLS")
+        case_ = 1
+    else:
+        print("DECALS")
+else:
+    print("DECALS")
+
+error_message = f"No data found, i.e. (RA, Dec) = ({ra_s}, {dec_s}) is outside the covered region by Data Release {dr}."
+try:
+    query = DESILegacySurvey.query_region(
+        coordinates=source, radius=Radius, data_release=dr
+    )
+except:
+    raise RuntimeError(error_message)
+
+if len(query) == 0:
+    raise RuntimeError(error_message)
+
+tap_result = query
+
+wavelength = np.array(
+    [4770, 6231, 9134, 3.368e4, 4.618e4, 12.082e4, 22.194e4]
+)  # in Angstroem
+wavelength = wavelength * 1e-8  # in cm
+frequency = 3.0e10 / wavelength  # in Hz
+energy = 2 * pi * 6.67e-16 * frequency  # in eV
+factor = 1e-23 * frequency  # conversion of Jy to erg/cm2s
+
+filters = {
+    "g": 4770,
+    "r": 6231,
+    "z": 9134,
+    "w1": 3.368e4,
+    "w2": 4.618e4,
+    "w3": 12.082e4,
+    "w4": 22.194e4,
+}
+nsources = len(query["ra"])
+ras = np.array(query["ra"])
+decs = np.array(query["dec"])
+
+coords = SkyCoord(ra=ras, dec=decs, unit="deg", frame="icrs")
+sep = source.separation(coords).arcmin
+t = query["type"]
+for i in range(nsources):
+    if t[i] == "DUP":
+        sep[i] = 100.0
+index = np.argmin(sep)
+
+label = ["g", "r", "z", "w1", "w2", "w3", "w4"]
+flux = np.zeros(len(label))
+flux_err = np.zeros(len(label))
+for ind in range(nsources):
+    if t[ind] != "DUP":
+        for j, i in enumerate(label):
+            y1 = query[ind]["flux_" + i]
+            if y1 > 0:
+                y1 = y1 / query["mw_transmission_" + i][ind]  # in nanomaggies
+                y1_err = (
+                    1.0
+                    / sqrt(query[ind]["flux_ivar_" + i])
+                    / query["mw_transmission_" + i][ind]
+                )  # in nanomaggies
+                flux[j] += y1
+                flux_err[j] += y1_err**2
+
+flux *= nano_maggies_to_Jy * factor
+flux_err = sqrt(flux_err)
+flux_err *= nano_maggies_to_Jy * factor
+plt.errorbar(energy, flux, flux_err, marker="o", color="black", linewidth=2)
+plt.yscale("log")
+plt.xscale("log")
+plt.legend(loc="lower right")
+plt.xlabel("$E$, [eV]", fontsize=16)
+plt.ylabel("$E^2dN/dE$, [erg/(cm$^2$s)]", fontsize=16)
+plt.savefig("Spectrum.png")
+
+from oda_api.data_products import ODAAstropyTable
+
+data = [energy, flux, flux_err]
+names = ("Energy[eV]", "Flux[erg/cm2s]", "Flux_err[erg/cm2s]")
+spec = ODAAstropyTable(Table(data, names=names))
+
+from oda_api.data_products import PictureProduct
+
+bin_image = PictureProduct.from_file("Spectrum.png")
+
+spectrum_table = spec  # http://odahub.io/ontology#ODAAstropyTable
+spectrum_png = bin_image  # http://odahub.io/ontology#ODAPictureProduct
+
+# output gathering
+_galaxy_meta_data = {}
+_oda_outs = []
+_oda_outs.append(
+    (
+        "out_Spectrum_spectrum_table",
+        "spectrum_table_galaxy.output",
+        spectrum_table,
+    )
+)
+_oda_outs.append(
+    ("out_Spectrum_spectrum_png", "spectrum_png_galaxy.output", spectrum_png)
+)
+
+for _outn, _outfn, _outv in _oda_outs:
+    _galaxy_outfile_name = os.path.join(_galaxy_wd, _outfn)
+    if isinstance(_outv, str) and os.path.isfile(_outv):
+        shutil.move(_outv, _galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
+    elif getattr(_outv, "write_fits_file", None):
+        _outv.write_fits_file(_galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "fits"}
+    elif getattr(_outv, "write_file", None):
+        _outv.write_file(_galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
+    else:
+        with open(_galaxy_outfile_name, "w") as fd:
+            json.dump(_outv, fd, cls=CustomJSONEncoder)
+        _galaxy_meta_data[_outn] = {"ext": "json"}
+
+with open(os.path.join(_galaxy_wd, "galaxy.json"), "w") as fd:
+    json.dump(_galaxy_meta_data, fd)
+print("*** Job finished successfully ***")
b
diff -r 000000000000 -r ea39f416af9d desi.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/desi.py Mon Apr 28 14:59:01 2025 +0000
[
b'@@ -0,0 +1,388 @@\n+# Licensed under a 3-clause BSD style license - see LICENSE.rst\n+\n+\n+# put all imports organized as shown below\n+# 1. standard library imports\n+import socket\n+# 2. third party imports\n+import urllib\n+\n+from astropy.coordinates import SkyCoord\n+import requests\n+from astropy.table import Table, vstack\n+from astropy.io import fits\n+from astropy import config as _config\n+import astropy.utils.data as aud\n+import time\n+\n+# 3. local imports - use relative imports\n+# commonly required local imports shown below as example\n+# all Query classes should inherit from BaseQuery.\n+from astroquery.exceptions import NoResultsWarning\n+\n+# from ..query import BaseQuery\n+# has common functions required by most modules\n+# prepend_docstr is a way to copy docstrings between methods\n+# async_to_sync generates the relevant query tools from _async methods\n+# import configurable items declared in __init__.py\n+# from . import conf\n+\n+import pyvo as vo\n+from numpy import cos, pi\n+# export all the public classes and methods\n+__all__ = [\'DESILegacySurvey\', \'DESILegacySurveyClass\']\n+\n+# declare global variables and constants if any\n+\n+\n+# Now begin your main class\n+# should be decorated with the async_to_sync imported previously\n+from astropy.io.fits import HDUList\n+\n+\n+class Conf(_config.ConfigNamespace):\n+    """\n+    Configuration parameters for `astroquery.desi`.\n+    """\n+    server = _config.ConfigItem(\n+        [\'https://portal.nersc.gov/cfs/cosmo/data/legacysurvey/\',\n+         ],\n+        \'base url\')\n+\n+    timeout = _config.ConfigItem(\n+        30,\n+        \'Time limit for connecting to template_module server.\')\n+\n+\n+class FileContainer:\n+    """\n+    A File Object container, meant to offer lazy access to downloaded FITS\n+    files.\n+    """\n+\n+    def __init__(self, target, **kwargs):\n+        kwargs.setdefault(\'cache\', True)\n+        self._target = target\n+        self._timeout = kwargs.get(\'remote_timeout\', aud.conf.remote_timeout)\n+        self._readable_object = get_readable_fileobj(target, **kwargs)\n+\n+    def get_fits(self):\n+        """\n+        Assuming the contained file is a FITS file, read it\n+        and return the file parsed as FITS HDUList\n+        """\n+        filedata = self.get_string()\n+\n+        if len(filedata) == 0:\n+            raise TypeError("The file retrieved was empty.")\n+\n+        self._fits = fits.HDUList.fromstring(filedata)\n+\n+        return self._fits\n+\n+    def get_string(self):\n+        """\n+        Download the file as a string\n+        """\n+        if not hasattr(self, \'_string\'):\n+            try:\n+                with self._readable_object as f:\n+                    data = f.read()\n+                    self._string = data\n+            except urllib.error.URLError as e:\n+                if isinstance(e.reason, socket.timeout):\n+                    raise TimeoutError("Query timed out, time elapsed {t}s".\n+                                       format(t=self._timeout))\n+                else:\n+                    raise e\n+\n+        return self._string\n+\n+    def __repr__(self):\n+        if hasattr(self, \'_fits\'):\n+            return f"Downloaded FITS file: {self._fits!r}"\n+        else:\n+            return f"Downloaded object from URL {self._target} with ID {id(self._readable_object)}"\n+\n+\n+def get_readable_fileobj(*args, **kwargs):\n+    """\n+    Overload astropy\'s get_readable_fileobj so that we can safely monkeypatch\n+    it in astroquery without affecting astropy core functionality\n+    """\n+    return aud.get_readable_fileobj(*args, **kwargs)\n+\n+\n+class DESILegacySurveyClass():\n+\n+    """\n+    Not all the methods below are necessary but these cover most of the common\n+    cases, new methods may be added if necessary, follow the guidelines at\n+    <http://astroquery.readthedocs.io/en/latest/api.html>\n+    """\n+    # use the Configuration Items imported from __init__.py to set the URL,\n+    # TIMEOUT, etc.\n+    conf = Conf()\n+    URL = conf.server\n+    TIMEOUT = conf.timeout\n+\n+    # all query'..b'er(image_url, encoding=\'binary\', show_progress=show_progress)\n+\n+        try:\n+            fits_file = file_container.get_fits()\n+        except (requests.exceptions.HTTPError, urllib.error.HTTPError) as e:\n+            # TODO not sure this is the most suitable exception\n+            raise NoResultsWarning(f"{str(e)} - Problem retrieving the file at the url: {str(image_url)}")\n+\n+        return [fits_file]\n+\n+    def get_images(self, position, survey, coordinates=None, data_release=9,\n+                   projection=None, pixels=None, scaling=None,\n+                   sampler=None, resolver=None, deedger=None, lut=None,\n+                   grid=None, gridlabels=None, radius=None, height=None,\n+                   width=None, cache=True, show_progress=True, image_band=\'g\'):\n+        response = self.get_images_async(position, survey, coordinates=coordinates, data_release=data_release,\n+                                         projection=projection, pixels=pixels, scaling=scaling,\n+                                         sampler=sampler, resolver=resolver, deedger=deedger, lut=lut,\n+                                         grid=grid, gridlabels=gridlabels, radius=radius, height=height,\n+                                         width=width, cache=cache, show_progress=show_progress, image_band=image_band)\n+        # if get_query_payload:\n+        return response\n+        # result = self._parse_result(response)\n+        # return result\n+\n+    # as we mentioned earlier use various python regular expressions, etc\n+    # to create the dict of HTTP request parameters by parsing the user\n+    # entered values. For cleaner code keep this as a separate private method:\n+\n+    def _args_to_payload(self, *args, **kwargs):\n+        request_payload = dict()\n+        # code to parse input and construct the dict\n+        # goes here. Then return the dict to the caller\n+\n+        return request_payload\n+\n+    # the methods above call the private _parse_result method.\n+    # This should parse the raw HTTP response and return it as\n+    # an `astropy.table.Table`. Below is the skeleton:\n+\n+    def _parse_result(self, responses):\n+        tables_list = []\n+        output_table = Table()\n+\n+        # handles the cases of query_region with TAP which returns a Table\n+        # or get_images which should return a list of HDUList, and here in DESILegacySurvey\n+        # a list of one HDUList object is returned\n+        if isinstance(responses, Table) or \\\n+                (isinstance(responses, list) and isinstance(responses[0], HDUList)):\n+            return responses\n+\n+        # try to parse the result into an astropy.Table, else\n+        # return the raw result with an informative error message.\n+        try:\n+            if not isinstance(responses, list):\n+                responses = [responses]\n+\n+            # do something with regex to get the result into\n+            # astropy.Table form. return the Table.\n+            # data = io.BytesIO(response.content)\n+            # table = Table.read(data)\n+\n+            for r in responses:\n+                if r.status_code == 200:\n+                    # TODO figure out on how to avoid writing in a file\n+                    with open(\'/tmp/file_content\', \'wb\') as fin:\n+                        fin.write(r.content)\n+\n+                    table = Table.read(\'/tmp/file_content\', hdu=1)\n+                    tables_list.append(table)\n+\n+            if len(tables_list) > 0:\n+                output_table = vstack(tables_list)\n+\n+        except ValueError:\n+            # catch common errors here, but never use bare excepts\n+            # return raw result/ handle in some way\n+            pass\n+\n+        return output_table\n+\n+\n+# the default tool for users to interact with is an instance of the Class\n+DESILegacySurvey = DESILegacySurveyClass()\n+\n+# once your class is done, tests should be written\n+# See ./tests for examples on this\n+\n+# Next you should write the docs in astroquery/docs/module_name\n+# using Sphinx.\n'
b
diff -r 000000000000 -r ea39f416af9d desi_legacy_survey_astro_tool.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/desi_legacy_survey_astro_tool.xml Mon Apr 28 14:59:01 2025 +0000
[
@@ -0,0 +1,125 @@
+<tool id="desi_legacy_survey_astro_tool" name="DESI Legacy Survey" version="0.0.1+galaxy0" profile="24.0">
+  <requirements>
+    <requirement type="package" version="6.1.4">astropy</requirement>
+    <requirement type="package" version="3.10.1">matplotlib</requirement>
+    <requirement type="package" version="1.21.4">wget</requirement>
+    <requirement type="package" version="7.16.6">nbconvert</requirement>
+    <requirement type="package" version="0.4.10">astroquery</requirement>
+    <requirement type="package" version="1.2.33">oda-api</requirement>
+    <requirement type="package" version="9.1.0">ipython</requirement>
+    <!--Requirements string 'nb2workflow>=1.3.30' can't be converted automatically. Please add the galaxy/conda requirement manually or modify the requirements file!-->
+  </requirements>
+  <command detect_errors="exit_code">ipython '$__tool_directory__/${C_data_product_.DPselector_}.py'</command>
+  <environment_variables>
+    <environment_variable name="BASEDIR">$__tool_directory__</environment_variable>
+    <environment_variable name="GALAXY_TOOL_DIR">$__tool_directory__</environment_variable>
+  </environment_variables>
+  <configfiles>
+    <inputs name="inputs" filename="inputs.json" data_style="paths" />
+  </configfiles>
+  <inputs>
+    <conditional name="C_data_product_">
+      <param name="DPselector_" type="select" label="Data Product">
+        <option value="Catalog" selected="true">Catalog</option>
+        <option value="Spectrum" selected="false">Spectrum</option>
+        <option value="Image" selected="false">Image</option>
+      </param>
+      <when value="Catalog">
+        <param name="src_name" type="text" value="Mrk 421" label="src_name" optional="false" />
+        <param name="RA" type="float" value="166.113808" label="RA (unit: deg)" optional="false" />
+        <param name="DEC" type="float" value="38.208833" label="DEC (unit: deg)" optional="false" />
+        <param name="Radius" type="float" value="1" label="Radius (unit: arcmin)" optional="false" />
+        <param name="data_release" type="integer" value="10" label="Data Release" optional="false" />
+      </when>
+      <when value="Spectrum">
+        <param name="src_name" type="text" value="Mrk 421" label="src_name" optional="false" />
+        <param name="RA" type="float" value="166.113808" label="RA (unit: deg)" optional="false" />
+        <param name="DEC" type="float" value="38.208833" label="DEC (unit: deg)" optional="false" />
+        <param name="Radius" type="float" value="0.1" label="Radius (unit: arcmin)" optional="false" />
+        <param name="data_release" type="integer" value="10" label="Data Release" optional="false" />
+      </when>
+      <when value="Image">
+        <param name="src_name" type="text" value="Mrk 421" label="src_name" optional="false" />
+        <param name="RA" type="float" value="166.113808" label="RA (unit: deg)" optional="false" />
+        <param name="DEC" type="float" value="38.208833" label="DEC (unit: deg)" optional="false" />
+        <param name="Radius" type="float" value="3" label="Radius (unit: arcmin)" optional="false" />
+        <param name="pixsize" type="float" value="1.0" label="Pixel size (unit: arcsec)" optional="false" />
+        <param name="band" type="select" label="Band" optional="false">
+          <option value="g" selected="true">g</option>
+          <option value="i">i</option>
+          <option value="r">r</option>
+          <option value="z">z</option>
+        </param>
+        <param name="data_release" type="integer" value="10" label="Data Release" optional="false" />
+      </when>
+    </conditional>
+  </inputs>
+  <outputs>
+    <data label="${tool.name} -&gt; Catalog catalog_table" name="out_Catalog_catalog_table" format="auto" from_work_dir="catalog_table_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'Catalog'</filter>
+    </data>
+    <data label="${tool.name} -&gt; Catalog dictionary_filters" name="out_Catalog_dictionary_filters" format="auto" from_work_dir="dictionary_filters_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'Catalog'</filter>
+    </data>
+    <data label="${tool.name} -&gt; Spectrum spectrum_table" name="out_Spectrum_spectrum_table" format="auto" from_work_dir="spectrum_table_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'Spectrum'</filter>
+    </data>
+    <data label="${tool.name} -&gt; Spectrum spectrum_png" name="out_Spectrum_spectrum_png" format="auto" from_work_dir="spectrum_png_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'Spectrum'</filter>
+    </data>
+    <data label="${tool.name} -&gt; Image picture" name="out_Image_picture" format="auto" from_work_dir="picture_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'Image'</filter>
+    </data>
+    <data label="${tool.name} -&gt; Image fits" name="out_Image_fits" format="auto" from_work_dir="fits_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'Image'</filter>
+    </data>
+  </outputs>
+  <tests>
+    <test expect_num_outputs="2">
+      <conditional name="C_data_product_">
+        <param name="DPselector_" value="Catalog" />
+        <param name="src_name" value="Mrk 421" />
+        <param name="RA" value="166.113808" />
+        <param name="DEC" value="38.208833" />
+        <param name="Radius" value="1" />
+        <param name="data_release" value="10" />
+      </conditional>
+      <assert_stdout>
+        <has_text text="*** Job finished successfully ***" />
+      </assert_stdout>
+    </test>
+    <test expect_num_outputs="2">
+      <conditional name="C_data_product_">
+        <param name="DPselector_" value="Spectrum" />
+        <param name="src_name" value="Mrk 421" />
+        <param name="RA" value="166.113808" />
+        <param name="DEC" value="38.208833" />
+        <param name="Radius" value="0.1" />
+        <param name="data_release" value="10" />
+      </conditional>
+      <assert_stdout>
+        <has_text text="*** Job finished successfully ***" />
+      </assert_stdout>
+    </test>
+    <test expect_num_outputs="2">
+      <conditional name="C_data_product_">
+        <param name="DPselector_" value="Image" />
+        <param name="src_name" value="Mrk 421" />
+        <param name="RA" value="166.113808" />
+        <param name="DEC" value="38.208833" />
+        <param name="Radius" value="3" />
+        <param name="pixsize" value="1.0" />
+        <param name="band" value="g" />
+        <param name="data_release" value="10" />
+      </conditional>
+      <assert_stdout>
+        <has_text text="*** Job finished successfully ***" />
+      </assert_stdout>
+    </test>
+  </tests>
+  <help>And here, the help text for the galaxy tool goes.
+</help>
+  <citations>
+    <citation type="doi">10.3847/1538-3881/ab089d</citation>
+  </citations>
+</tool>
\ No newline at end of file