Removed Survey model, it is now a field in the Pixel model; all code rewritten for the change; added set_contaminated management command to set the source contamination flag; added CatalogSource model for the art-xc source catalog storage, the table is filled by the set_contaminated management script; the upper limit view now outputs extra fields, including a list of sources and their properties within 120 arcseconds

This commit is contained in:
2025-05-18 15:26:20 +03:00
parent ffaa663fdd
commit bfaf103729
5 changed files with 1766 additions and 91 deletions

140
views.py
View File

@@ -4,7 +4,7 @@
# search for pixels non-inclusively
import healpy as hp
import astropy.units as u
from astropy.coordinates import SkyCoord
from astropy.coordinates import SkyCoord, Angle
import numpy as np
import scipy.special as sp
@@ -17,7 +17,7 @@ from rest_framework import status
from django.shortcuts import get_object_or_404
from uplim.models import Pixel
from uplim.models import Pixel, CatalogSource
# SANITIZE RESPONSE DATA BEFORE JSON CONVERSION FOR DEBUGGING NANS
# now NaNs are converted to 'null' beforehand
@@ -54,6 +54,7 @@ def parse_survey_param(raw):
# PIXEL VIEW (MOSTLY FOR TESTING)
# add healpix indices into the output
# **************************************************************
class PixelAggregateView(APIView):
@@ -94,43 +95,37 @@ class PixelAggregateView(APIView):
# **************************************************************
qs = Pixel.objects.filter(
hpid=hpid,
survey__number__in=survey_numbers
survey__in=survey_numbers
)
if not qs.exists():
# no matching pixel(s) → 404
get_object_or_404(Pixel, hpid=hpid, survey__number__in=survey_numbers)
get_object_or_404(
Pixel,
hpid=hpid,
survey__in=survey_numbers
)
result = qs.aggregate(
aggregates = qs.aggregate(
#pixel_hpid=hpid,
#survey_number=survey_numbers,
total_counts=Sum("counts"),
total_exposure=Sum("exposure")
)
plusdata = {
'pixel_hpid' : hpid,
'surveys' : survey_numbers
}
result = {**aggregates, **plusdata}
# RETURN THE SUMS
# **************************************************************
return Response(result, status=status.HTTP_200_OK)
# class PixelDetailView(APIView):
# """
# API endpoint that returns the pixel data (counts, exposure, rate)
# for a given hpid.
# """
# def get(self, request, hpid):
# # Get the survey using the survey_number field.
# # survey = get_object_or_404(Survey, number=survey_number)
# # Get the pixel corresponding to the survey and hpid.
# pixel = get_object_or_404(Pixel, hpid=hpid)
# # Serialize the pixel data to JSON.
# serializer = PixelSerializer(pixel)
# return Response(serializer.data, status=status.HTTP_200_OK)
# UPPER LIMIT COMPUTATION VIEW
# **************************************************************
@@ -225,17 +220,24 @@ class UpperLimitView(APIView):
item for item in outer_pixel_list if item not in inner_pixel_list
]
source_pixels = Pixel.objects.filter(
hpid__in = source_pixel_list,
survey__number__in = survey_numbers
survey__in = survey_numbers
)
annulus_pixels = Pixel.objects.filter(
hpid__in = annulus_pixel_list,
survey__number__in = survey_numbers
survey__in = survey_numbers
)
# check contamination
contamination = (
source_pixels.filter(contaminated=True).exists() or
annulus_pixels.filter(contaminated=True).exists()
)
if not source_pixels.exists() and not annulus_pixels.exists():
return Response(
{"detail": "No pixel data for the given survey selection."},
@@ -311,43 +313,87 @@ class UpperLimitView(APIView):
S = N - B # counts as simply counts within aperture
# with the background estimate subtracted
CR = S / t / EEF # count rate
CR = S / t / EEF # source count rate
BR = B / t # background rate within aperture
FL = CR * ECF # conversion to flux
Flux = max(FL, 0) # flux cannot be lower than zero
# RESULT ASSEMBLY
# NEARBY SOURCES CHECK
# ****************************************************************
radius_as = 120
radius_deg = radius_as / 3600
dec_min = max(dec - radius_deg, -90)
dec_max = min(dec + radius_deg, 90)
# cheap belt query
belt_sources = CatalogSource.objects.filter(
dec_deg__gte = dec_min,
dec_deg__lte = dec_max
)
center_coord = SkyCoord(ra, dec, unit='deg')
nearby_sources = []
#refine belt to circular region using astropy separation
for catsrc in belt_sources:
catsrc_coord = SkyCoord(catsrc.ra_deg, catsrc.dec_deg, unit='deg')
if center_coord.separation(catsrc_coord).deg <= radius_deg:
nearby_sources.append(
{
'srcid' : catsrc.srcid,
'name' : catsrc.name,
'ra_deg' : catsrc.ra_deg,
'dec_deg' : catsrc.dec_deg,
'pos_error' : catsrc.pos_error,
'significance' : catsrc.significance,
'flux' : catsrc.flux,
'flux_error' : catsrc.flux_error,
'catalog_name' : catsrc.catalog_name,
'new_xray' : catsrc.new_xray,
'source_type' : catsrc.source_type
}
)
# RESULT JSON
# ****************************************************************
result = {
'ClassicUpperLimit' : classic_count_ul,
'ClassicLowerLimit' : classic_count_ll,
'ClassicUpperLimit' : classic_count_ul,
'ClassicLowerLimit' : classic_count_ll,
'ClassicCountRateUpperLimit' : classic_rate_ul,
'ClassicCountRateLowerLimit' : classic_rate_ll,
'ClassicFluxUpperLimit' : classic_flux_ul,
'ClassicFluxLowerLimit' : classic_flux_ll,
'ClassicFluxUpperLimit' : classic_flux_ul,
'ClassicFluxLowerLimit' : classic_flux_ll,
'BayesianUpperLimit' : bayesian_count_ul,
'BayesianLowerLimit' : bayesian_count_ll,
'BayesianUpperLimit' : bayesian_count_ul,
'BayesianLowerLimit' : bayesian_count_ll,
'BayesianCountRateUpperLimit' : bayesian_rate_ul,
'BayesianCountRateLowerLimit' : bayesian_rate_ll,
'BayesianFluxUpperLimit' : bayesian_flux_ul,
'BayesianFluxLowerLimit' : bayesian_flux_ll,
'BayesianFluxUpperLimit' : bayesian_flux_ul,
'BayesianFluxLowerLimit' : bayesian_flux_ll,
'FluxEstimate' : Flux,
'FluxEstimate' : Flux,
# raw counts and exposure omitted from the response
'ApertureCounts' : N,
'ApertureBackgroundCounts' : B,
'SourceCounts' : S,
'Exposure' : t,
# 'N' : N,
# 'Nnpix' : Nnpix,
# 'Bcounts' : Bcounts,
# 'Bnpix' : Bnpix,
# 'B' : B,
# 'tsum' : tsum,
# 't' : t
'SourceRate' : CR,
'BackgroundRate' : BR,
'Contamination' : contamination,
'NearbySources' : nearby_sources
}