From 2873b0e59a5da9e3d79033d9c328e65739243e08 Mon Sep 17 00:00:00 2001 From: tyrin Date: Tue, 25 Mar 2025 18:18:56 +0300 Subject: [PATCH] initial commit --- __init__.py | 0 admin.py | 3 + apps.py | 6 + management/commands/load_survey.py | 110 ++++++++++++ migrations/0001_initial.py | 35 ++++ .../0002_remove_pixel_uuid_alter_pixel_id.py | 22 +++ migrations/0003_alter_pixel_hpid.py | 18 ++ migrations/0004_survey_nside.py | 18 ++ ..._survey_remove_pixel_rate_delete_survey.py | 24 +++ migrations/0006_pixel_contaminated.py | 18 ++ migrations/__init__.py | 0 models.py | 14 ++ serializers.py | 8 + tests.py | 3 + urls.py | 8 + views.py | 157 ++++++++++++++++++ 16 files changed, 444 insertions(+) create mode 100644 __init__.py create mode 100644 admin.py create mode 100644 apps.py create mode 100644 management/commands/load_survey.py create mode 100644 migrations/0001_initial.py create mode 100644 migrations/0002_remove_pixel_uuid_alter_pixel_id.py create mode 100644 migrations/0003_alter_pixel_hpid.py create mode 100644 migrations/0004_survey_nside.py create mode 100644 migrations/0005_remove_pixel_survey_remove_pixel_rate_delete_survey.py create mode 100644 migrations/0006_pixel_contaminated.py create mode 100644 migrations/__init__.py create mode 100644 models.py create mode 100644 serializers.py create mode 100644 tests.py create mode 100644 urls.py create mode 100644 views.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/admin.py b/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps.py b/apps.py new file mode 100644 index 0000000..86dbda5 --- /dev/null +++ b/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AxcUlConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'axc_ul' diff --git a/management/commands/load_survey.py b/management/commands/load_survey.py new file mode 100644 index 0000000..b5f48c3 --- /dev/null +++ b/management/commands/load_survey.py @@ -0,0 +1,110 @@ +import numpy as np +from astropy.io import fits + +from django.core.management.base import BaseCommand +from django.db import transaction +from axc_ul.models import Pixel + +from itertools import islice + +BATCH_SIZE = 1000000 + +def batch(iterable, size): + """ + Generator that yields successive chunks of size 'size' from 'iterable'. + """ + iterable = iter(iterable) + while True: + chunk = list(islice(iterable, size)) + if not chunk: + break + yield chunk + + +class Command(BaseCommand): + help = "Process FITS files and store the data in the database" + + def add_arguments(self, parser): + parser.add_argument( + '--counts', + type=str, + required=True, + help='Path of the counts file' + ) + parser.add_argument( + '--exposure', + type=str, + required=True, + help='Path of the exposure file' + ) + # parser.add_argument( + # '--survey_number', + # type=int, + # required=True, + # help='Integer ID of the survey being read' + # ) + + def handle(self, *args, **options): + + counts_file = options['counts'] + exposure_file = options['exposure'] + # survey_number = options['survey_number'] + + self.stdout.write(f"Counts file: {counts_file}") + self.stdout.write(f"Exposure file: {exposure_file}") + + with fits.open(counts_file) as hdul: + + column_name = "T" + counts_map = hdul[1].data[column_name] + + counts_data = counts_map.ravel() + + + with fits.open(exposure_file) as hdul: + + column_name = "T" + exposure_map = hdul[1].data[column_name] + + exposure_data = exposure_map.ravel() + + + + self.stdout.write(f"Counts Data Shape: {counts_data.shape}") + self.stdout.write(f"Exposure Data Shape: {exposure_data.shape}") + + assert counts_data.shape == exposure_data.shape, "Counts and exposure maps must have the same shape" + + #rate_data = np.divide(counts_data, exposure_data) + + # with transaction.atomic(): + + # survey,created = Survey.objects.get_or_create(number=survey_number) + + # if created: + # self.stdout.write(f"Created a new survey instance with number: {survey.number}") + # else: + # self.stdout.write(f"Using existing survey instance with the number: {survey.number}") + + + # Create a generator that yields Pixel objects one by one. + pixel_generator = ( + Pixel( + hpid=i, + counts=int(count), + exposure=float(exposure), + #rate=float(rate), + #survey=survey + ) + for i, (count, exposure) in enumerate(zip(counts_data, exposure_data)) + ) + + total_inserted = 0 + # Process the generator in batches. + for pixel_batch in batch(pixel_generator, BATCH_SIZE): + with transaction.atomic(): + Pixel.objects.bulk_create(pixel_batch) + total_inserted += len(pixel_batch) + self.stdout.write(f"Inserted {total_inserted} pixels") + + self.stdout.write(f"Inserted a total of {total_inserted} pixels.") diff --git a/migrations/0001_initial.py b/migrations/0001_initial.py new file mode 100644 index 0000000..3bfc5cb --- /dev/null +++ b/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 5.1.6 on 2025-02-27 12:55 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Survey', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('number', models.IntegerField()), + ], + ), + migrations.CreateModel( + name='Pixel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('hpid', models.IntegerField()), + ('counts', models.IntegerField()), + ('exposure', models.FloatField()), + ('rate', models.FloatField()), + ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pixels', to='axc_ul.survey')), + ], + ), + ] diff --git a/migrations/0002_remove_pixel_uuid_alter_pixel_id.py b/migrations/0002_remove_pixel_uuid_alter_pixel_id.py new file mode 100644 index 0000000..6f22e47 --- /dev/null +++ b/migrations/0002_remove_pixel_uuid_alter_pixel_id.py @@ -0,0 +1,22 @@ +# Generated by Django 5.1.6 on 2025-02-27 19:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('axc_ul', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='pixel', + name='uuid', + ), + migrations.AlterField( + model_name='pixel', + name='id', + field=models.AutoField(primary_key=True, serialize=False), + ), + ] diff --git a/migrations/0003_alter_pixel_hpid.py b/migrations/0003_alter_pixel_hpid.py new file mode 100644 index 0000000..0a25841 --- /dev/null +++ b/migrations/0003_alter_pixel_hpid.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.6 on 2025-03-02 15:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('axc_ul', '0002_remove_pixel_uuid_alter_pixel_id'), + ] + + operations = [ + migrations.AlterField( + model_name='pixel', + name='hpid', + field=models.IntegerField(db_index=True), + ), + ] diff --git a/migrations/0004_survey_nside.py b/migrations/0004_survey_nside.py new file mode 100644 index 0000000..9a072db --- /dev/null +++ b/migrations/0004_survey_nside.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.6 on 2025-03-02 16:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('axc_ul', '0003_alter_pixel_hpid'), + ] + + operations = [ + migrations.AddField( + model_name='survey', + name='nside', + field=models.IntegerField(default=4096), + ), + ] diff --git a/migrations/0005_remove_pixel_survey_remove_pixel_rate_delete_survey.py b/migrations/0005_remove_pixel_survey_remove_pixel_rate_delete_survey.py new file mode 100644 index 0000000..e49a957 --- /dev/null +++ b/migrations/0005_remove_pixel_survey_remove_pixel_rate_delete_survey.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.6 on 2025-03-06 08:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('axc_ul', '0004_survey_nside'), + ] + + operations = [ + migrations.RemoveField( + model_name='pixel', + name='survey', + ), + migrations.RemoveField( + model_name='pixel', + name='rate', + ), + migrations.DeleteModel( + name='Survey', + ), + ] diff --git a/migrations/0006_pixel_contaminated.py b/migrations/0006_pixel_contaminated.py new file mode 100644 index 0000000..5e69a08 --- /dev/null +++ b/migrations/0006_pixel_contaminated.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.6 on 2025-03-13 13:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('axc_ul', '0005_remove_pixel_survey_remove_pixel_rate_delete_survey'), + ] + + operations = [ + migrations.AddField( + model_name='pixel', + name='contaminated', + field=models.BooleanField(default=False), + ), + ] diff --git a/migrations/__init__.py b/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models.py b/models.py new file mode 100644 index 0000000..5ccf9bd --- /dev/null +++ b/models.py @@ -0,0 +1,14 @@ +from django.db import models + +class Pixel(models.Model): + + id = models.AutoField(primary_key=True) + + hpid = models.IntegerField(db_index=True) + + counts = models.IntegerField() + + exposure = models.FloatField() + + contaminated = models.BooleanField(default=False) + diff --git a/serializers.py b/serializers.py new file mode 100644 index 0000000..6f040ee --- /dev/null +++ b/serializers.py @@ -0,0 +1,8 @@ +# serializers.py +from rest_framework import serializers +from axc_ul.models import Pixel + +class PixelSerializer(serializers.ModelSerializer): + class Meta: + model = Pixel + fields = ('hpid', 'counts', 'exposure', 'contaminated') diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/urls.py b/urls.py new file mode 100644 index 0000000..5f340c9 --- /dev/null +++ b/urls.py @@ -0,0 +1,8 @@ +# urls.py +from django.urls import path +from .views import PixelDetailView, UpperLimitView + +urlpatterns = [ + path('pixel//', PixelDetailView.as_view(), name='pixel-detail'), + path('upper-limit/', UpperLimitView.as_view(), name='upper-limit') +] diff --git a/views.py b/views.py new file mode 100644 index 0000000..cd102da --- /dev/null +++ b/views.py @@ -0,0 +1,157 @@ +# axc_ul/views.py + +# from astropy_healpix import HEALPix does not have an option to +# search for pixels non-inclusively +import healpy as hp +import astropy.units as u +from astropy.coordinates import SkyCoord + +import scipy.special as sp + +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status + +from django.shortcuts import get_object_or_404 + +from axc_ul.models import Pixel + +from .serializers import PixelSerializer + +class PixelDetailView(APIView): + """ + API endpoint that returns the pixel data (counts, exposure, rate) + for a given survey number and 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) + + +class UpperLimitView(APIView): + """ + Calculate 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. + + Raises: + ValueError if provided parameters are invalid. + """ + def get(self, request): + + try: + ra = float(request.query_params.get('ra')) + dec = float(request.query_params.get('dec')) + confidence_level = float(request.query_params.get('cl')) + except (TypeError, ValueError): + return Response( + {"error": "Invalud parameters, provide RA, DEC, and CL"}, + status = status.HTTP_400_BAD_REQUEST + ) + + # hp = HEALPix( + # nside = 4096, + # order = 'ring', + # frame = 'icrs' + # ) + + 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 + # HPD ~48 arcseconds + # 90% ~100 arcseconds + annulus_inner = 100 # 2 * aperture_radius + annulus_outer = 200 # 4 * aperture_radius + + source_pixel_list = hp.query_disc( + nside = 4096, + vec = src_vec, + inclusive = False, + nest = False, + radius = (aperture_radius * u.arcsecond).to(u.radian).value + ) + + inner_pixel_list = hp.query_disc( + nside = 4096, + vec = src_vec, + inclusive = False, + nest = False, + radius = (annulus_inner * u.arcsecond).to(u.radian).value + ) + + outer_pixel_list = hp.query_disc( + nside = 4096, + vec = src_vec, + inclusive = False, + nest = False, + radius = (annulus_outer * u.arcsecond).to(u.radian).value + ) + + + annulus_pixel_list = [ + item for item in outer_pixel_list if item not in inner_pixel_list + ] + + + source_pixels = Pixel.objects.filter(hpid__in = source_pixel_list) + + annulus_pixels = Pixel.objects.filter(hpid__in = annulus_pixel_list) + + + N = sum(obj.counts for obj in source_pixels) + + Nnpix = len(source_pixels) + + Bcounts = sum(obj.counts for obj in annulus_pixels) + + Bnpix = len(annulus_pixels) + + B = Bcounts / Bnpix * Nnpix + + tsum = sum(obj.exposure for obj in source_pixels) + + t = tsum / Nnpix + + + 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 + + FLUL = CRUL * ECF # flux upper limit + + return Response({ + 'UpperLimit' : UL, + 'CountRateUpperLimit' : CRUL, + 'FluxUpperLimit' : FLUL, + 'N' : N, + 'Nnpix' : Nnpix, + 'Bcounts' : Bcounts, + 'Bnpix' : Bnpix, + 'B' : B, + 'tsum' : tsum, + 't' : t + }, status=status.HTTP_200_OK) \ No newline at end of file