diff options
| author | Yuri Victorovich <yuri@FreeBSD.org> | 2026-03-08 04:01:54 +0000 |
|---|---|---|
| committer | Yuri Victorovich <yuri@FreeBSD.org> | 2026-03-08 16:58:19 +0000 |
| commit | 0e4dd22ffe17aa85f7b1b15e7f20c368b0f4ff3b (patch) | |
| tree | 1439c2a3f92728d2b01da25936b3b1f22bd72ff4 | |
| parent | 69b94c29f537d59ce8e1c57b168b6ad1bbfcbd79 (diff) | |
astro/py-alerce: New port: Python library to interact with ALeRCE services and databases
| -rw-r--r-- | astro/Makefile | 1 | ||||
| -rw-r--r-- | astro/py-alerce/Makefile | 25 | ||||
| -rw-r--r-- | astro/py-alerce/distinfo | 3 | ||||
| -rw-r--r-- | astro/py-alerce/files/example-query_supernova_images.py | 309 | ||||
| -rw-r--r-- | astro/py-alerce/pkg-descr | 13 |
5 files changed, 351 insertions, 0 deletions
diff --git a/astro/Makefile b/astro/Makefile index 8b4235cb3f65..48716a987a98 100644 --- a/astro/Makefile +++ b/astro/Makefile @@ -82,6 +82,7 @@ SUBDIR += phoon SUBDIR += pngphoon SUBDIR += pp3 + SUBDIR += py-alerce SUBDIR += py-astlib SUBDIR += py-astral SUBDIR += py-astroML diff --git a/astro/py-alerce/Makefile b/astro/py-alerce/Makefile new file mode 100644 index 000000000000..b431e783d7af --- /dev/null +++ b/astro/py-alerce/Makefile @@ -0,0 +1,25 @@ +PORTNAME= alerce +PORTVERSION= 2.3.0 +CATEGORIES= astro python +MASTER_SITES= PYPI +PKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX} + +MAINTAINER= yuri@FreeBSD.org +COMMENT= Python library to interact with ALeRCE services and databases +WWW= https://alerce.readthedocs.io/en/latest/index.html \ + https://github.com/alercebroker/alerce_client + +LICENSE= MIT +LICENSE_FILE= ${WRKSRC}/LICENSE.txt + +RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}astropy>=4.0.1:astro/py-astropy@${PY_FLAVOR} \ + ${PYTHON_PKGNAMEPREFIX}ipython>0:devel/ipython@${PY_FLAVOR} \ + ${PYTHON_PKGNAMEPREFIX}pandas>=1.1.2:math/py-pandas@${PY_FLAVOR} \ + ${PYTHON_PKGNAMEPREFIX}requests>=2.24.0:www/py-requests@${PY_FLAVOR} + +USES= python +USE_PYTHON= distutils autoplist + +NO_ARCH= yes + +.include <bsd.port.mk> diff --git a/astro/py-alerce/distinfo b/astro/py-alerce/distinfo new file mode 100644 index 000000000000..893b2164cec8 --- /dev/null +++ b/astro/py-alerce/distinfo @@ -0,0 +1,3 @@ +TIMESTAMP = 1772934593 +SHA256 (alerce-2.3.0.tar.gz) = 8dead5d06a544294f24c6b1213080adc01205c8cc584e03a654178e13cc5dc36 +SIZE (alerce-2.3.0.tar.gz) = 18429 diff --git a/astro/py-alerce/files/example-query_supernova_images.py b/astro/py-alerce/files/example-query_supernova_images.py new file mode 100644 index 000000000000..311069353274 --- /dev/null +++ b/astro/py-alerce/files/example-query_supernova_images.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +""" +Example script demonstrating how to use the ALeRCE Python client +to query supernova images from the Vera C. Rubin Observatory (LSST). + +ALeRCE (Automatic Learning for the Rapid Classification of Events) +processes alerts from the Rubin Observatory and provides easy access +to astronomical object data, lightcurves, and image stamps. +""" + +from alerce.core import Alerce +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + + +def query_supernova_data(oid, survey="lsst"): + """ + Query supernova data including lightcurve and image stamps. + + Parameters: + ----------- + oid : str + Object ID for the supernova candidate + survey : str + Survey name (default: "lsst" for Rubin Observatory) + + Returns: + -------- + tuple: (lightcurve_data, stamps_data) + """ + # Initialize the ALeRCE client + client = Alerce() + + print(f"Querying object: {oid}") + print(f"Survey: {survey}") + print("-" * 50) + + # Get lightcurve data + try: + lightcurve = client.query_lightcurve(oid=oid, survey=survey, format="pandas") + print(f"✓ Retrieved lightcurve with {len(lightcurve)} detections") + except Exception as e: + print(f"✗ Error retrieving lightcurve: {e}") + lightcurve = None + + # Get image stamps (cutouts) + try: + stamps = client.get_stamps(oid=oid, survey=survey) + print(f"✓ Retrieved image stamps") + except Exception as e: + print(f"✗ Error retrieving stamps: {e}") + stamps = None + + return lightcurve, stamps + + +def display_stamps(stamps): + """ + Display science, reference, and difference image stamps. + + Parameters: + ----------- + stamps : dict + Stamp data from ALeRCE (FITS HDU objects) + """ + if not stamps: + print("No stamps available to display") + return + + print("\nProcessing image stamps...") + + fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + + # Map stamp names to plot titles + stamp_mappings = [ + (['cutoutScience', 'science'], 'Science Image'), + (['cutoutTemplate', 'template', 'reference'], 'Reference Template'), + (['cutoutDifference', 'difference', 'diff'], 'Difference Image') + ] + + for ax, (possible_names, title) in zip(axes, stamp_mappings): + image_found = False + + try: + # Try to find the stamp with various possible names + stamp_data = None + used_name = None + for name in possible_names: + if name in stamps: + stamp_data = stamps[name] + used_name = name + break + + if stamp_data is not None: + # Check if it's an Astropy FITS HDU object + if hasattr(stamp_data, 'data'): + # Extract the image data from FITS HDU + image_array = stamp_data.data + + if image_array is not None and image_array.size > 0: + # Normalize for better display + vmin = np.percentile(image_array, 1) + vmax = np.percentile(image_array, 99) + + im = ax.imshow(image_array, cmap='gray', origin='lower', + vmin=vmin, vmax=vmax) + ax.set_title(title) + ax.axis('off') + image_found = True + print(f" ✓ Displayed {title} (shape: {image_array.shape})") + else: + print(f" ✗ {title}: Empty data array") + else: + print(f" ✗ {title}: Not a FITS HDU object (type: {type(stamp_data)})") + + except Exception as e: + print(f" ✗ Error loading {title}: {str(e)}") + + if not image_found: + ax.text(0.5, 0.5, f'{title}\nNot Available', + ha='center', va='center', transform=ax.transAxes, + fontsize=12) + ax.axis('off') + + plt.tight_layout() + plt.savefig('supernova_stamps.png', dpi=150, bbox_inches='tight') + print("\n✓ Saved image stamps to: supernova_stamps.png") + plt.show() + + +def plot_lightcurve(lightcurve): + """ + Plot the supernova lightcurve. + + Parameters: + ----------- + lightcurve : pandas.DataFrame or dict + Lightcurve data from ALeRCE + """ + if lightcurve is None: + print("No lightcurve data available to plot") + return + + # Handle nested structure - extract detections + if isinstance(lightcurve, dict): + if 'detections' in lightcurve: + detections = lightcurve['detections'] + if isinstance(detections, pd.DataFrame): + lightcurve = detections + else: + print("Detections data is not a DataFrame") + return + else: + print("No 'detections' key found in lightcurve data") + return + + if len(lightcurve) == 0: + print("No detections in lightcurve data") + return + + plt.figure(figsize=(12, 6)) + + # Determine column names (they may vary between surveys) + filter_col = None + for col in ['fid', 'filter', 'band', 'filtername']: + if col in lightcurve.columns: + filter_col = col + break + + mag_col = None + for col in ['magpsf', 'mag', 'magnitude']: + if col in lightcurve.columns: + mag_col = col + break + + magerr_col = None + for col in ['sigmapsf', 'e_mag', 'magerr', 'mag_err']: + if col in lightcurve.columns: + magerr_col = col + break + + time_col = None + for col in ['mjd', 'time', 'jd']: + if col in lightcurve.columns: + time_col = col + break + + if not mag_col or not time_col: + print(f"Error: Could not find required columns. Available columns: {list(lightcurve.columns)}") + return + + # Group by filter/band if available + if filter_col: + for fid in lightcurve[filter_col].unique(): + band_data = lightcurve[lightcurve[filter_col] == fid] + if magerr_col: + plt.errorbar(band_data[time_col], band_data[mag_col], + yerr=band_data[magerr_col], + fmt='o', label=f'Filter {fid}', alpha=0.7, markersize=8) + else: + plt.plot(band_data[time_col], band_data[mag_col], + 'o', label=f'Filter {fid}', alpha=0.7, markersize=8) + else: + # No filter information, plot all data together + if magerr_col: + plt.errorbar(lightcurve[time_col], lightcurve[mag_col], + yerr=lightcurve[magerr_col], + fmt='o', alpha=0.7, markersize=8) + else: + plt.plot(lightcurve[time_col], lightcurve[mag_col], 'o', alpha=0.7, markersize=8) + + plt.xlabel('Modified Julian Date (MJD)' if time_col == 'mjd' else 'Time', fontsize=12) + plt.ylabel('Magnitude', fontsize=12) + plt.gca().invert_yaxis() # Invert y-axis (brighter objects have lower magnitude) + plt.title('Supernova Lightcurve', fontsize=14) + if filter_col: + plt.legend() + plt.grid(True, alpha=0.3) + plt.tight_layout() + plt.savefig('supernova_lightcurve.png', dpi=150, bbox_inches='tight') + print("✓ Saved lightcurve to: supernova_lightcurve.png") + plt.show() + + +def main(): + """ + Main function to demonstrate querying supernova data from Rubin Observatory. + """ + print("=" * 60) + print("ALeRCE Supernova Query Example") + print("Vera C. Rubin Observatory / LSST") + print("=" * 60) + print() + + # Example Rubin LSST supernova candidate object ID + # Note: Replace with a valid LSST object ID from the ALeRCE database + # You can find object IDs at: https://science.alerce.online/alerce-lsst/ + example_oid = "170032901203689551" + + print("NOTE: This example uses a placeholder object ID.") + print("For real data, obtain valid LSST object IDs from:") + print(" - ALeRCE Explorer: https://science.alerce.online/alerce-lsst/") + print(" - Or query the ALeRCE API for classified supernovae") + print() + + # Query the data + lightcurve, stamps = query_supernova_data(example_oid, survey="lsst") + + print() + + # Display results + if lightcurve is not None: + print("\nLightcurve Summary:") + + # Handle nested structure + detections = None + if isinstance(lightcurve, dict): + if 'detections' in lightcurve: + detections = lightcurve['detections'] + if isinstance(detections, pd.DataFrame): + print(f" - Number of detections: {len(detections)}") + print(f" - Available columns: {list(detections.columns)}") + + # Check for time column + time_col = None + for col in ['mjd', 'time', 'jd']: + if col in detections.columns: + time_col = col + break + + if time_col: + print(f" - Time range: {detections[time_col].min():.2f} to {detections[time_col].max():.2f}") + + # Check for filter column + filter_col = None + for col in ['fid', 'filter', 'band', 'filtername']: + if col in detections.columns: + filter_col = col + break + + if filter_col: + print(f" - Filters: {detections[filter_col].unique()}") + + # Plot lightcurve + plot_lightcurve(lightcurve) + + if stamps is not None: + print("\nImage Stamps:") + num_stamps = len(stamps) if isinstance(stamps, list) else 1 + print(f" - Retrieved {num_stamps} stamp(s)") + + # Display stamps + display_stamps(stamps) + else: + print("\n⚠ No image stamps were retrieved") + print(" This may be because:") + print(" - The object ID doesn't exist in the database") + print(" - Image stamps are not available for this object") + print(" - The survey parameter may need adjustment") + + print() + print("=" * 60) + print("Query complete!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/astro/py-alerce/pkg-descr b/astro/py-alerce/pkg-descr new file mode 100644 index 000000000000..1df68e910bbc --- /dev/null +++ b/astro/py-alerce/pkg-descr @@ -0,0 +1,13 @@ +ALeRCE Python Client is a library designed to interact with ALeRCE +(Automatic Learning for the Rapid Classification of Events) services +and databases. + +Its primary functions include: +* Multi-Survey Support: Querying astronomical data from both the Zwicky + Transient Facility (ZTF) and the LSST/Rubin Observatory. +* Data Retrieval: Accessing astronomical objects, lightcurves, stamps, + and classification results. +* Advanced Queries: Performing crossmatches (catsHTM conesearch) and + filtering by classifier or probability. +* Flexible Formats: Returning data in JSON, Pandas DataFrames, or + VOTable formats for easy analysis. |
