diff --git a/datalab/datalab_session/analysis/line_profile.py b/datalab/datalab_session/analysis/line_profile.py new file mode 100644 index 0000000..d3382f7 --- /dev/null +++ b/datalab/datalab_session/analysis/line_profile.py @@ -0,0 +1,14 @@ +from skimage.measure import profile_line + +from datalab.datalab_session.util import scale_points + +# For creating an array of brightness along a user drawn line +def line_profile(input: dict, sci_hdu: object): + """ + Creates an array of luminosity values and the length of the line in arcseconds + """ + points = scale_points(input['width'], input['height'], sci_hdu.data, [(input["x1"], input["y1"]), (input["x2"], input["y2"])]) + line_profile = profile_line(sci_hdu.data, points[0], points[1], mode="constant", cval=-1) + arcsec = len(line_profile) * sci_hdu.header["PIXSCALE"] + + return {"line_profile": line_profile, "arcsec": arcsec} diff --git a/datalab/datalab_session/util.py b/datalab/datalab_session/util.py index 91ce4b5..19ab1a9 100644 --- a/datalab/datalab_session/util.py +++ b/datalab/datalab_session/util.py @@ -6,6 +6,7 @@ import numpy as np from django.conf import settings +from django.core.cache import cache log = logging.getLogger() log.setLevel(logging.INFO) @@ -97,6 +98,29 @@ def get_archive_from_basename(basename: str) -> dict: return results +def get_hdu(basename: str, extension: str = 'SCI') -> list[fits.HDUList]: + """ + Returns a list of Sci HDUs for the given basenames + Warning: this function returns an opened file that must be closed after use + """ + + # use the basename to fetch and create a list of hdu objects + basename = basename.replace('-large', '').replace('-small', '') + + if cache.get(f'{basename}-{extension}') is not None: + return cache.get(f'{basename}-{extension}') + + archive_record = get_archive_from_basename(basename) + + try: + fits_url = archive_record[0].get('url', 'No URL found') + except IndexError: + RuntimeWarning(f"No image found with specified basename: {basename}") + + hdu = fits.open(fits_url, use_fsspec=True) + cache.set(f'{basename}-{extension}', hdu[extension]) + return hdu[extension] + def create_fits(key: str, image_arr: np.ndarray) -> fits.HDUList: header = fits.Header([('KEY', key)]) @@ -118,3 +142,23 @@ def stack_arrays(array_list: list): stacked = np.stack(cropped_data_list, axis=2) return stacked + +def scale_points(small_img_width: int, small_img_height: int, img_array: list, points: list[tuple[int, int]]): + """ + Scale the coordinates from a smaller image to the full sized fits so we know the positions of the coords on the 2dnumpy array + Returns the list of tuple points with coords scaled for the numpy array + """ + + large_height, large_width = np.shape(img_array) + + # If the aspect ratios don't match we can't be certain where the point was + if small_img_width / small_img_height != large_width / large_height: + raise ValueError("Aspect ratios of the two images must match") + + width_scale = large_width / small_img_width + height_scale = large_height / small_img_height + + points_array = np.array(points) + scaled_points = np.int_(points_array * [width_scale, height_scale]) + + return scaled_points diff --git a/datalab/datalab_session/views.py b/datalab/datalab_session/views.py index a59f33c..40d1994 100644 --- a/datalab/datalab_session/views.py +++ b/datalab/datalab_session/views.py @@ -1,8 +1,11 @@ from rest_framework.generics import RetrieveAPIView +from rest_framework.views import APIView from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from datalab.datalab_session.data_operations.utils import available_operations +from datalab.datalab_session.analysis.line_profile import line_profile +from datalab.datalab_session.util import get_hdu class OperationOptionsApiView(RetrieveAPIView): @@ -15,3 +18,21 @@ def get(self, request): for operation_clazz in operations.values(): operation_details[operation_clazz.name()] = operation_clazz.wizard_description() return Response(operation_details) + +class AnalysisView(APIView): + """ + View to handle analysis actions and return the results + To add a new analysis action, add a case to the switch statement and create a new file in the analysis directory + """ + def post(self, request, action): + input = request.data + + sci_hdu = get_hdu(input['basename']) + + match action: + case 'line-profile': + output = line_profile(input, sci_hdu) + case _: + raise Exception(f'Analysis action {action} not found') + + return Response(output) diff --git a/datalab/urls.py b/datalab/urls.py index 9cb0ff7..234108b 100644 --- a/datalab/urls.py +++ b/datalab/urls.py @@ -20,7 +20,7 @@ import ocs_authentication.auth_profile.urls as authprofile_urls from datalab.datalab_session.viewsets import DataSessionViewSet, DataOperationViewSet -from datalab.datalab_session.views import OperationOptionsApiView +from datalab.datalab_session.views import OperationOptionsApiView, AnalysisView router = routers.SimpleRouter() router.register(r'datasessions', DataSessionViewSet, 'datasessions') @@ -35,6 +35,7 @@ urlpatterns = [ path('admin/', admin.site.urls), re_path(r'^api/', include(api_urlpatterns)), + path(r'api/analysis//', AnalysisView.as_view(), name='analysis'), path('api/available_operations/', OperationOptionsApiView.as_view(), name='available_operations'), re_path(r'^authprofile/', include(authprofile_urls)), ]