update
This commit is contained in:
247
views.py
247
views.py
@@ -7,7 +7,9 @@ import astropy.units as u
|
||||
from astropy.coordinates import SkyCoord
|
||||
|
||||
import scipy.special as sp
|
||||
from astropy.stats import poisson_conf_interval
|
||||
|
||||
from django.db.models import Sum
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
@@ -15,41 +17,129 @@ from rest_framework import status
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from axc_ul.models import Pixel
|
||||
#from axc_ul_server.axc_ul.serializers import PixelSerializer
|
||||
|
||||
from .serializers import PixelSerializer
|
||||
|
||||
class PixelDetailView(APIView):
|
||||
# SURVEY PARAMETER PARSER
|
||||
# **************************************************************
|
||||
|
||||
def parse_survey_param(raw: str) -> list[int]:
|
||||
surveys = set()
|
||||
for part in raw.split(","):
|
||||
if "-" in part:
|
||||
start, end = part.split("-", 1)
|
||||
surveys.update(range(int(start), int(end) + 1))
|
||||
else:
|
||||
surveys.add(int(part))
|
||||
return sorted(surveys)
|
||||
|
||||
|
||||
# PIXEL VIEW (MOSTLY FOR TESTING)
|
||||
# **************************************************************
|
||||
|
||||
class PixelAggregateView(APIView):
|
||||
"""
|
||||
API endpoint that returns the pixel data (counts, exposure, rate)
|
||||
for a given survey number and hpid.
|
||||
GET /pixel-aggregate/?pixel=<hpid>&survey=<spec>
|
||||
- <spec> can be a single number (e.g. 5),
|
||||
a CSV list (e.g. 1,3,7) or a hyphen range (e.g. 2-5).
|
||||
Returns sums of counts & exposure across the selected surveys.
|
||||
"""
|
||||
def get(self, request, hpid):
|
||||
# Get the survey using the survey_number field.
|
||||
# survey = get_object_or_404(Survey, number=survey_number)
|
||||
|
||||
def get(self, request):
|
||||
# GET PARAMETERS FROM THE QUERY
|
||||
# **************************************************************
|
||||
raw_pixel = request.query_params.get("pixel")
|
||||
raw_survey = request.query_params.get("survey")
|
||||
|
||||
# 400 BADREQUEST
|
||||
# **************************************************************
|
||||
if raw_pixel is None or raw_survey is None:
|
||||
return Response(
|
||||
{"detail": "Both `pixel` and `survey` parameters are required."},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# FILTER THE INPUTS
|
||||
# **************************************************************
|
||||
try:
|
||||
hpid = int(raw_pixel)
|
||||
except ValueError:
|
||||
return Response(
|
||||
{"detail": "`pixel` must be an integer."},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
survey_numbers = parse_survey_param(raw_survey)
|
||||
except ValueError:
|
||||
return Response(
|
||||
{"detail": "Malformed `survey`; use N, N,M or N-M formats."},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# FILTER AND AGGREGATE
|
||||
# **************************************************************
|
||||
qs = Pixel.objects.filter(
|
||||
hpid=hpid,
|
||||
survey__number__in=survey_numbers
|
||||
)
|
||||
if not qs.exists():
|
||||
# no matching pixel(s) → 404
|
||||
get_object_or_404(Pixel, hpid=hpid, survey__number__in=survey_numbers)
|
||||
|
||||
result = qs.aggregate(
|
||||
total_counts=Sum("counts"),
|
||||
total_exposure=Sum("exposure")
|
||||
)
|
||||
|
||||
# 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)
|
||||
# # 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)
|
||||
# # Serialize the pixel data to JSON.
|
||||
# serializer = PixelSerializer(pixel)
|
||||
# return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
||||
# UPPER LIMIT COMPUTATION VIEW
|
||||
# **************************************************************
|
||||
|
||||
|
||||
class UpperLimitView(APIView):
|
||||
"""
|
||||
Calculate and return upper limits (UL, CRUL, FLUL) based on provided RA, DEC, and CL parameters.
|
||||
Calclassic_count_ulate and return upper limits (UL, CRUL, FLUL) based on provided RA, DEC, and CL parameters.
|
||||
|
||||
Args:
|
||||
request: HTTP GET request containing 'ra', 'dec', and 'cl' query parameters.
|
||||
|
||||
Returns:
|
||||
Response object with JSON data containing calculated UL, CRUL, and FLUL values.
|
||||
Response object with JSON data containing calclassic_count_ulated UL, CRUL, and FLUL values.
|
||||
|
||||
Raises:
|
||||
ValueError if provided parameters are invalid.
|
||||
"""
|
||||
def get(self, request):
|
||||
|
||||
# GET PARAMETERS FROM THE REQUEST
|
||||
# **************************************************************
|
||||
|
||||
try:
|
||||
ra = float(request.query_params.get('ra'))
|
||||
dec = float(request.query_params.get('dec'))
|
||||
@@ -59,24 +149,48 @@ class UpperLimitView(APIView):
|
||||
{"error": "Invalud parameters, provide RA, DEC, and CL"},
|
||||
status = status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# ── NEW: pull & parse survey selection ──
|
||||
raw_survey = request.query_params.get('survey')
|
||||
if raw_survey is None:
|
||||
return Response(
|
||||
{"error": "Missing required `survey` parameter (e.g. ?survey=1,3-5)"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
try:
|
||||
survey_numbers = parse_survey_param(raw_survey)
|
||||
except ValueError:
|
||||
return Response(
|
||||
{"error": "Malformed `survey`; use formats like 1, 2-5, or 1,3-4"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# hp = HEALPix(
|
||||
# nside = 4096,
|
||||
# order = 'ring',
|
||||
# frame = 'icrs'
|
||||
# )
|
||||
|
||||
# CREATE SKYCOORD, CONVERT TO GALACTIC BECAUSE THE HEALPIX
|
||||
# MAP ITSELF WAS MADE IN GALACTIC COORDINATES
|
||||
# **************************************************************
|
||||
|
||||
src_coord = SkyCoord(
|
||||
ra, dec, unit = 'deg', frame = 'icrs'
|
||||
)
|
||||
gal = src_coord.galactic
|
||||
src_vec = hp.ang2vec(gal.l.deg, gal.b.deg, lonlat = True)
|
||||
|
||||
aperture_radius = 50 # radius of the aperture in arc seconds
|
||||
# DEFINE APERTURE AND ANNULUS RADII
|
||||
# **************************************************************
|
||||
|
||||
aperture_radius = 71 # radius of the aperture in arc seconds
|
||||
# HPD ~48 arcseconds
|
||||
# 90% ~100 arcseconds
|
||||
annulus_inner = 100 # 2 * aperture_radius
|
||||
annulus_outer = 200 # 4 * aperture_radius
|
||||
annulus_inner = 142 # 2 * aperture_radius
|
||||
annulus_outer = 284 # 4 * aperture_radius
|
||||
|
||||
# FETCH PIXEL DATA DEFINED VIA HP.QUERY_DISC (INCLUSIVE=FALSE)
|
||||
# **************************************************************
|
||||
|
||||
source_pixel_list = hp.query_disc(
|
||||
nside = 4096,
|
||||
@@ -108,10 +222,26 @@ class UpperLimitView(APIView):
|
||||
]
|
||||
|
||||
|
||||
source_pixels = Pixel.objects.filter(hpid__in = source_pixel_list)
|
||||
source_pixels = Pixel.objects.filter(
|
||||
hpid__in = source_pixel_list,
|
||||
survey__number__in = survey_numbers
|
||||
)
|
||||
|
||||
annulus_pixels = Pixel.objects.filter(hpid__in = annulus_pixel_list)
|
||||
annulus_pixels = Pixel.objects.filter(
|
||||
hpid__in = annulus_pixel_list,
|
||||
survey__number__in = survey_numbers
|
||||
)
|
||||
|
||||
if not source_pixels.exists() and not annulus_pixels.exists():
|
||||
return Response(
|
||||
{"detail": "No pixel data for the given survey selection."},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
|
||||
|
||||
# COMPUTE COUNTS, BACKGROUND ESTIMATE, EXPOSURE
|
||||
# **************************************************************
|
||||
|
||||
N = sum(obj.counts for obj in source_pixels)
|
||||
|
||||
@@ -127,26 +257,78 @@ class UpperLimitView(APIView):
|
||||
|
||||
t = tsum / Nnpix
|
||||
|
||||
# CONSTANTS
|
||||
# **************************************************************
|
||||
|
||||
EEF = .9 # eclosed energy fraction, .5 for hpd, .9 for w90
|
||||
ECF = 4e-11 # energy conversion factor
|
||||
|
||||
UL = (
|
||||
sp.gammaincinv(
|
||||
N + 1,
|
||||
confidence_level * sp.gammaincc(N + 1, B) + sp.gammainc(N + 1, B)
|
||||
) - B
|
||||
) # count upper limit
|
||||
|
||||
|
||||
|
||||
CRUL = UL / t / EEF # count rate upper limit
|
||||
# BAYESIAN IMPLEMENTATION VIA POISSON_CONF_INTERVAL
|
||||
# **************************************************************
|
||||
|
||||
low, high = poisson_conf_interval(
|
||||
n = N,
|
||||
background = B,
|
||||
interval = 'kraft-burrows-nousek',
|
||||
confidence_level=confidence_level
|
||||
)
|
||||
|
||||
bayesian_count_ul = high
|
||||
bayesian_count_ll = low
|
||||
|
||||
bayesian_rate_ul = bayesian_count_ul / t / EEF # count rate limits
|
||||
bayesian_rate_ll = bayesian_count_ll / t / EEF
|
||||
|
||||
bayesian_flux_ul = bayesian_rate_ul * ECF # flux limits
|
||||
bayesian_flux_ll = bayesian_rate_ll * ECF
|
||||
|
||||
# CLASSICAL IMPLEMENTATION VIA GAMMAINCCINV
|
||||
# ****************************************************************
|
||||
|
||||
classic_count_ul = sp.gammainccinv(N+1, 1 - confidence_level) - B
|
||||
classic_count_ll = sp.gammainccinv(N, confidence_level) - B
|
||||
|
||||
classic_count_ll = max(classic_count_ll, 0)
|
||||
|
||||
classic_rate_ul = classic_count_ul / t / EEF # count rate limits
|
||||
classic_rate_ll = classic_count_ll / t / EEF
|
||||
|
||||
classic_flux_ul = classic_rate_ul * ECF # flux limits
|
||||
classic_flux_ll = classic_rate_ll * ECF
|
||||
|
||||
# FLUX ESTIMATION
|
||||
# ****************************************************************
|
||||
|
||||
S = N - B # counts as simply counts within aperture
|
||||
# with the background estimate subtracted
|
||||
|
||||
CR = S / t / EEF # count rate
|
||||
|
||||
FL = CR * ECF # conversion to flux
|
||||
|
||||
Flux = max(FL, 0) # flux cannot be lower than zero
|
||||
|
||||
FLUL = CRUL * ECF # flux upper limit
|
||||
|
||||
return Response({
|
||||
'UpperLimit' : UL,
|
||||
'CountRateUpperLimit' : CRUL,
|
||||
'FluxUpperLimit' : FLUL,
|
||||
|
||||
'ClassicUpperLimit' : classic_count_ul,
|
||||
'ClassicLowerLimit' : classic_count_ll,
|
||||
'ClassicCountRateUpperLimit' : classic_rate_ul,
|
||||
'ClassicCountRateLowerLimit' : classic_rate_ll,
|
||||
'ClassicFluxUpperLimit' : classic_flux_ul,
|
||||
'ClassicFluxLowerLimit' : classic_flux_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,
|
||||
|
||||
'FluxEstimate' : Flux,
|
||||
|
||||
'N' : N,
|
||||
'Nnpix' : Nnpix,
|
||||
'Bcounts' : Bcounts,
|
||||
@@ -154,4 +336,5 @@ class UpperLimitView(APIView):
|
||||
'B' : B,
|
||||
'tsum' : tsum,
|
||||
't' : t
|
||||
|
||||
}, status=status.HTTP_200_OK)
|
Reference in New Issue
Block a user