# Project export: Atlas

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: A new tool to contribute to NASA's Exoplanet Watch citizen science project, Atlas provides a friendly UI for kids to analyze exoplanet transit data and develop a passion for astronomy.
- Devpost: https://devpost.com/software/atlas-7ti0wp
- GitHub: https://github.com/kailashchanel/treehacks-2025-backend
- Demo: https://github.com/kailashchanel/treehacks-2025-frontend
- Video: https://www.youtube.com/embed/fC6evc9-Usc?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — kailashchanel (12 commits), IsaacL05 (5 commits)

## Devpost submission (written by the team)

### Inspiration

NASA's Exoplanet Watch citizen science project allows everyday people to make tangible contributions to extraterrestrial discovery. However, contributors are currently required to work through a lengthy and complex Google Colab notebook, which has proven to be a great deterrent. To alleviate this issue, Atlas automates much of this process in its backend, allowing its users to instead interact with an intuitive, game-like interface. Our hope is that Atlas becomes a space where kids develop a foundational understanding and interest in astronomy while contributing very valuable data for this collective initiative. As an Exoplanet Watch contributor myself (this is Kailash), I wanted an easier way to contribute myself and also a resource to get kids as young as even 3rd grade or younger interested and passionate about astronomy! Functionality Given a selected exoplanet and telescope view, the app generates a star chart and a snapshot of the night sky in the vicinity of where the planet should be. The user is responsible for matching up the corresponding stars in both images; namely, they should identify the "target star" (the one the exoplanet in question is rotating around) and click on the screen where it is located in the night sky snapshot, generating coordinates. Using this information, the backend is able to generate a "lightcurve" of the star that graphs its perceived luminosity as the exoplanet transits in front of it. Although this capability exists in our backend, we were unable to port it to our front end in time. Process First we had to set up a server to host the 24 folders of exoplanet transit image data that our app would reference. Each folder contains between approximately 20 and ~80 images, which we obtained from the MicroObservatory Robotic Telescope Network. We used the Django REST Framework to enable connection between our frontend and backend, due to the flexibility it offered us. Next, we had to write the backend code that facilitated the process of pinpointing stars and identifying light curves. To accomplish this, we used the Colab notebook that contributors are currently required to work with as a starting point and made several substantial changes. Firstly, we removed the notebook's dependency on the user having a folder of photos in their Google Drive, and instead configured it to use photo folders hosted in the server described above. There were also several aspects of the original notebook that were built specifically with Colab in mind (most notably, the display of error messages and user prompts), that we either modified or removed because they would detract from our goal of building an uncluttered user interface for a younger audience. We initially attempted to create our app's frontend using FlutterFlow, but we found it difficult to implement our own code and customize it as we wanted. Thus, we decided to use Expo in conjunction with React Native to facilitate more control over the final appearance of our application. Challenges One major challenge we faced was that our team had very limited experience working with NASA's EXOTIC libraries, which was a driving factor in the backend code. To overcome this, we consulted the libraries' source code and also used LLM assistance in helping us interpret some of the library-specific functionalities. Another considerable hurdle we faced involved reconfiguring the raw astronomical datasets required for the app. Addressing this challenge demanded an in-depth engagement with Python’s astronomy library, as we worked to transform the unprocessed observational data into an applicable format. Lessons Learned Overall, our team gained exposure to the unique intersections between app development, file processing, and web server interaction. More specifically, since many of these concepts were new to us, we gained valuable experience into how we can learn and utilize novel techniques for the purposes of creating a functional project in a short amount of time. We particularly made strategic use of LLM tools like ChatGPT and GitHub Copilot to streamline the process of debugging and identifying mistakes in our code, which we feel is especially relevant given the increasing prominence of AI-assisted software development. We'd also like to credit the authors of the below paper, who contributed to the development of the EXOTIC libraries: [1]Zellem, R. T., “Utilizing Small Telescopes Operated by Citizen Scientists for Transiting Exoplanet Follow-up”, Publications of the Astronomical Society of the Pacific, vol. 132, no. 1011, Art. no. 054401, IOP, 2020. doi:10.1088/1538-3873/ab7ee7.

## README (from the GitHub repository)

# TreeHacks 2025 - Atlas Backend

Built using Python, Django, REST


## Detected evidence (automated analysis)

Indexed codebase: 9 recognized source files, 28 KB.
- Python (language) — detected in the code
- Django (technology) — claimed on Devpost, not found in the code
- JavaScript (language) — claimed on Devpost, not found in the code
- TypeScript (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 407)

```
.gitignore
eaConf.json
exotic_backend/__init__.py
exotic_backend/.DS_Store
exotic_backend/api/serializers.py
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807035200.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807035416.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807035715.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807040015.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807040316.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807040615.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807040915.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807041215.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807041515.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807041815.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807042115.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807042415.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807042715.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807043016.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807043316.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807043615.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807043916.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807044215.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807044515.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807044817.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807045115.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807045415.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807045716.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807050015.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807050316.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807050616.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807050915.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807051215.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807051516.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807051815.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807052115.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807052415.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807052715.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807053015.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807053315.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807053615.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807053915.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807054217.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807054515.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807054815.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807055117.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807055415.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807055715.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807060015.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807060315.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807060615.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807060916.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807061215.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807061515.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807061815.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807062116.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807062415.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807062715.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807063015.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807063316.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807063615.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807063915.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807064216.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807064516.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807064815.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807065115.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807065415.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807065716.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807070015.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807070315.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807070615.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807070915.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807071215.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807071515.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807071816.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807072115.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807072415.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807072715.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807073015.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807073315.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807073916.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807074216.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807074515.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807074815.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807075115.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807075415.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807075716.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807080016.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807080315.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807080615.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/CoRoT-2240807080916.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/darks/Dark-C-240807081245.FITS
exotic_backend/api/static/CoRoT-2b_20240807_medium/darks/Dark-C-240807081516.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814031324.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814031515.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814031815.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814032116.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814032415.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814032715.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814033015.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814033315.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814033615.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814033915.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814034215.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814034515.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814034815.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814035115.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814035415.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814035715.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814040015.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814040316.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814040616.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814040916.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814041216.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814041516.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814041815.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814042115.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814042415.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814042716.FITS
exotic_backend/api/static/CoRoT-2b_20240814/CoRoT-2240814043015.FITS
[287 more files omitted for size]
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Update README.md
- Update README.md
- Merge branch 'master' of https://github.com/IsaacL05/treehacks-2025
- updated idek
- Update README.md
- code refactor, fits to png complete
- updates; lightcurve successfully generated
- delete old static directory
- Update .gitignore
- changed static file locations
- Merge branch 'master' of https://github.com/IsaacL05/treehacks-2025
- added static directory
- Step 4 first draft
- Step 3 first draft
- Update README.md
- Create README.md
- initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### manage.py

```python
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "exotic_backend.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == "__main__":
    main()

```

### exotic_backend/asgi.py

```python
"""
ASGI config for exotic_backend project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "exotic_backend.settings")

application = get_asgi_application()

```

### exotic_backend/wsgi.py

```python
"""
WSGI config for exotic_backend project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "exotic_backend.settings")

application = get_wsgi_application()

```

### exotic_backend/urls.py

```python
"""
URL configuration for exotic_backend project.

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/5.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""

from django.urls import include, path
from django.conf import settings
from django.conf.urls.static import static

from rest_framework import routers

from exotic_backend.api.views import CoordsInput, Images

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
    path('api/configure', CoordsInput.as_view()),
    path('api/images', Images.as_view())
    # path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
] + static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS[0])

```

### exotic_backend/settings.py

```python
"""
Django settings for exotic_backend project.

Generated by 'django-admin startproject' using Django 5.1.6.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""

from pathlib import Path
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-4hrpea&#w@yh%szgw57r3y$*_4=xu)vuz*g=l_#*@7n^k8=*@("

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    'rest_framework',
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "exotic_backend.urls"

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

WSGI_APPLICATION = "exotic_backend.wsgi.application"


# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}


# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]


# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/

STATIC_URL = "/static/"

STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]  # Directory for static files

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

```

### exotic_backend/api/serializers.py

```python
from django.contrib.auth.models import Group, User
from rest_framework import serializers


class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ['url', 'username', 'email', 'groups']


class GroupSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Group
        fields = ['url', 'name']
```

### exotic_backend/api/views.py

```python
from django.templatetags.static import static

from rest_framework import permissions, status
from rest_framework.views import APIView
from rest_framework.response import Response

import bokeh.io
from bokeh.io import output_notebook

# from exotic.api.colab import *
from exotic.api.plotting import plot_image
from exotic.exotic import NASAExoplanetArchive, get_wcs

import os
import re
import json
import glob
import random

import urllib
from urllib.request import urlopen
from urllib.error import HTTPError

import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from astropy.io import fits
from astropy.visualization import ZScaleInterval, ImageNormalize
from io import BytesIO

API_STATIC = "exotic_backend/api/static/"
PUBLIC_STATIC = "/static/"

class CoordsInput(APIView):
    def get(self, request):
        convert_fits_to_png = True
        main_run = False

        sorted_files = sorted(os.listdir(f"{API_STATIC}TRES-3b_20100911"))
        verified_filepath = f"{API_STATIC}TRES-3b_20100911"
        output_dir = verified_filepath + "_output/"

        def get_folders_names(directory):
            # List all folders in the directory
            folders = [folder for folder in os.listdir(directory) 
            if os.path.isdir(os.path.join(directory, folder)) and not folder.endswith('_output')] 
            return folders

        if convert_fits_to_png:
            names = get_folders_names(API_STATIC)
            # names = ["CoRoT-2b_20240807_medium"]
            for name in names:
                filepath = f"{API_STATIC}{name}"
                sorted_files_g = sorted(os.listdir(f"{API_STATIC}{name}"))
                uploaded_files = [f for f in sorted_files_g if os.path.isfile(os.path.join(filepath, f))]

                fits_list = []
                first_image = ""
                for f in uploaded_files:
                    # Look for .fits images and keep count
                    if f.lower().endswith(('.fits', '.fits.gz', '.fit')):
                        fits_list.append(f)
                        if first_image == "":
                            first_image = os.path.join(filepath, f)

                # # Process FITS files
                # fits_files = [f for f in os.listdir(verified_filepath) if f.endswith('.fits')]

                total_images = len(fits_list)

                output_dir = filepath + "_output/"
                os.makedirs(output_dir, exist_ok=True)

                for i, fits_file in enumerate(fits_list):
                    with fits.open(os.path.join(filepath, fits_file)) as hdul:
                        image_data = hdul[0].data
                        
                        norm = ImageNormalize(image_data, interval=ZScaleInterval(), 
                                            vmin=np.nanpercentile(image_data, 5),
                                            vmax=np.nanpercentile(image_data, 99))
                        
                        fig, ax = plt.subplots()
                        ax.imshow(image_data, cmap='viridis', norm=norm)
                        ax.axis('off')
                        # ax.set_title(f"Image {i + 1}/{total_images}")
                        
                        # Save the figure as PNG
                        output_path = os.path.join(output_dir, f"image_{i+1}.png")
                        fig.savefig(output_path, dpi=100, bbox_inches='tight', pad_inches=0, 
                                    format='png', transparent=True)
                        plt.close(fig)
                        
                        print(f"Saved: {output_path}")

        if main_run:
            # Directory full of files found, look for .fits and inits.json
            uploaded_files = [f for f in sorted_files if os.path.isfile(os.path.join(verified_filepath, f))]
            fits_count, inits_count, first_image = 0, 0, ""

            # Identify .FITS and inits.json files in user-submitted folder
            inits = []    # array of paths to any inits files found in the directory
            fits_list = []
            for f in uploaded_files:
                # Look for .fits images and keep count
                if f.lower().endswith(('.fits', '.fits.gz', '.fit')):
                    fits_list.append(f)
                if first_image == "":
                    first_image = os.path.join(verified_filepath, f)
                fits_count += 1
                # Look for inits.json file(s)
                if re.search(r"\.json$", f, re.IGNORECASE):
                    inits.append(os.path.join(verified_filepath, f))

            inits_count = len(inits)
            print("fits count" + str(fits_count))
            # display(HTML(f'<p class="output"><br />Found {fits_count} image files and {inits_count} initialization files in the directory.</p>'))

            # Determine if folder has enough .FITS folders to move forward
            if fits_count >= 1:
                fits_files_found = True # exit outer loop and continue

                # Make the output directory if it does not exist already.
                if not os.path.isdir(output_dir):
                    os.mkdir(output_dir)
                # display(HTML(f'<p class="output">Creating output_dir at {output_dir}</p>'))
                output_dir_for_shell = output_dir.replace(" ", "\ ")
            else:
                # display(HTML(f'<p class="error">Failed to find a significant number of .FITS files at {verified_filepath}</p>'))
                input_filepath = input('Enter path to .FITS images(e.g. "EXOTIC/HatP32Dec202017") and press return:  ')

            # Read configuration from inits.json, if available
            inits_file_path = ""
            if inits_count == 1:                 # one inits file exists
            # Deal with inits.json file
                inits_file_path = os.path.join(inits[0])
                inits_file_exists = True
                print("Previous inits.json file exists and will be used.")
            #
[truncated — 16196 more characters]
```