From 51233b12e3ce995e3ea259fd815ce8a474984e82 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Tue, 8 Oct 2024 15:58:35 -0400 Subject: [PATCH 1/5] WIP. Adding re-extraction backend logic. --- banzai_floyds_ui/gui/app.py | 119 ++++++++++++++++++++++- banzai_floyds_ui/gui/plots.py | 23 +---- banzai_floyds_ui/gui/utils/plot_utils.py | 23 +++++ pyproject.toml | 3 +- 4 files changed, 144 insertions(+), 24 deletions(-) diff --git a/banzai_floyds_ui/gui/app.py b/banzai_floyds_ui/gui/app.py index ae74682..f50d25e 100644 --- a/banzai_floyds_ui/gui/app.py +++ b/banzai_floyds_ui/gui/app.py @@ -10,15 +10,33 @@ from banzai_floyds_ui.gui.plots import make_profile_plot from banzai_floyds_ui.gui.utils.plot_utils import extraction_region_traces from dash.exceptions import PreventUpdate -from banzai_floyds_ui.gui.utils.plot_utils import json_to_polynomial +from banzai_floyds_ui.gui.utils.plot_utils import json_to_polynomial, plot_extracted_data from banzai_floyds_ui.gui.utils.plot_utils import EXTRACTION_REGION_LINE_ORDER - +from banzai.utils import import_utils +from banzai.utils.stage_utils import get_stages_for_individual_frame +from banzai_floyds.frames import FLOYDSObservationFrame +import dash_bootstrap_components as dbc +from banzai_floyds import settings +import os +import banzai.main logger = logging.getLogger(__name__) dashboard_name = 'banzai-floyds' app = DjangoDash(name=dashboard_name) +# set up the context object for banzai + +parser.add_argument('--post-to-archive', dest='post_to_archive', action='store_true', default=False) +parser.add_argument('--no-file-cache', dest='no_file_cache', action='store_true', default=False, + help='Turn off saving files to disk') +parser.add_argument('--post-to-opensearch', dest='post_to_opensearch', action='store_true', + default=False) + +settings.fpack=True +settings.db_address = os.environ['DB_ADDRESS'] +RUNTIME_CONTEXT = banzai.main.parse_args(settings, parse_system_args=False) + def layout(): last_week = datetime.date.today() - datetime.timedelta(days=7) @@ -74,6 +92,7 @@ def layout(): dcc.Store(id='initial-extraction-info'), dcc.Store(id='extraction-positions'), dcc.Store(id='extraction-traces'), + dcc.Store(id='extractions'), dcc.Loading( id='loading-arc-2d-plot-container', type='default', @@ -111,6 +130,7 @@ def layout(): config={'edits': {'shapePosition': True}}), ] ), + html.Div('Extraction Type:'), dcc.Loading( id='loading-extraction-plot-container', type='default', @@ -119,7 +139,9 @@ def layout(): style={'display': 'inline-block', 'width': '100%', 'height': '550px;'}), ] - ) + ), + dcc.radioItems(['Optimal', 'Unweighted'], 'Optimal', inline=True, id='extraction-type'), + dcc.button('Save Extraction', id='extract-button'), ] ) ] @@ -278,6 +300,8 @@ def callback_make_plots(*args, **kwargs): sci_2d_frame, sci_2d_filename = get_related_frame(frame_id, archive_header, 'L1ID2D') sci_2d_plot, extraction_data = make_2d_sci_plot(sci_2d_frame, sci_2d_filename) + kwargs['session_state'][sci_2d_filename] = sci_2d_frame + profile_plot, initial_extraction_info = make_profile_plot(sci_2d_frame) for key in extraction_data: @@ -337,3 +361,92 @@ def update_extraction_positions(initial_extraction_info, relayout_data, current_ current_extraction_positions[str(order)][line_id] = relayout_data[key_with_update] print(current_extraction_positions) return current_extraction_positions + + +def reextract(hdu, filename, centers, extraction_positions, runtime_context, extraction_type='optimal'): + # Convert 2d science hdu to banzai-floyds frame object + frame = FLOYDSObservationFrame(hdu, ) + # reset the weights and the background region + _, original_widths = frame.profile_fits + frame.profile_fits = centers, original_widths, frame['PROFILEFITS'].data + frame.profile = load_profile_data(frame.profile_fits) + frame.extraction_window = bar + if extraction_type == 'unweighted': + frame.binned_data['weights'] = 1.0 + frame.background_window = foo + stages_to_do = get_stages_for_individual_frame(runtime_context.ORDERED_STAGES, + last_stage=runtime_context.LAST_STAGE[frame.obstype.upper()], + extra_stages=runtime_context.EXTRA_STAGES[frame.obstype.upper()]) + + # Starting at the extraction weights stage + start_index = stages_to_do.index('banzai_floyds.extract.Extractor') + stages_to_do = stages_to_do[start_index:] + + for stage_name in stages_to_do: + stage_constructor = import_utils.import_attribute(stage_name) + stage = stage_constructor(runtime_context) + frames = stage.run([frame]) + if not frames: + logger.error('Reduction stopped', extra_tags={'filename': filename}) + return + return frame + + +@app.expanded_callback(dash.dependencies.Output('extractions', 'data'), + [dash.dependencies.Input('extraction-positions', 'data'), + dash.dependencies.Input('extraction-type', 'value')], + prevent_initial_call=True) +def trigger_reextract(extraction_positions, extraction_type, **kwargs): + + science_frame = kwargs['session_state'].get('science_2d_frame') + if science_frame is None: + raise PreventUpdate + + frame = reextract(science_frame, centers, extraction_positions, + RUNTIME_CONTEXT, extraction_type=extraction_type.lower()) + return plot_extracted_data(frame.extracted) + + +app.clientside_callback( + """ + function(extraction_data) { + if (typeof extraction_data === "undefined") { + return window.dash_clientside.no_update; + } + + var dccGraph = document.getElementById('sci-1d-plot'); + var jsFigure = dccGraph.querySelector('.js-plotly-plot'); + var trace_ids = []; + for (let i = 1; i <= extraction_data.x.length; i++) { + trace_ids.push(i); + } + Plotly.restyle(jsFigure, extraction_data, trace_ids); + return window.dash_clientside.no_update; + } + """, + dash.dependencies.Input('extractions', 'data'), + prevent_initial_call=True) + + +@app.expanded_callback(dash.dependencies.Input('extract-button', 'n_clicks'), + [dash.dependencies.State('extraction-type', 'value'), + dash.dependencies.State('extraction-positions', 'data')], prevent_initial_call=True) +def save_extraction(n_clicks, extraction_type, extraction_positions, **kwargs): + if not n_clicks: + raise PreventUpdate + science_frame = kwargs['session_state'].get('science_2d_frame') + if science_frame is None: + raise PreventUpdate + + # If not logged in, open a modal saying you can only save if you are. + username = kwargs['session_state'].get('username') + if username is None: + throw error + + # Run the reextraction + extracted_frame = reextract(science_frame, filename, centers, extraction_positions, + RUNTIME_CONTEXT, extraction_type=extraction_type.lower()) + # Save the reducer into the header + extracted_frame.meta['REDUCER'] = username + # Push the results to the archive + extracted_frame.write(RUNTIME_CONTEXT) diff --git a/banzai_floyds_ui/gui/plots.py b/banzai_floyds_ui/gui/plots.py index 52e99ff..e87ec1b 100644 --- a/banzai_floyds_ui/gui/plots.py +++ b/banzai_floyds_ui/gui/plots.py @@ -11,7 +11,7 @@ from banzai_floyds.utils.wavelength_utils import WavelengthSolution from scipy.interpolate import LinearNDInterpolator from banzai_floyds_ui.gui.utils.plot_utils import unfilled_histogram, EXTRACTION_REGION_LINE_ORDER -from banzai_floyds_ui.gui.utils.plot_utils import extraction_region_traces +from banzai_floyds_ui.gui.utils.plot_utils import extraction_region_traces, plot_extracted_data import importlib import json @@ -483,24 +483,7 @@ def make_1d_sci_plot(frame_id, archive_header): }, 'yaxis6': {'anchor': 'x6', 'domain': [0.0, 0.32], 'exponentformat': 'power'} } - figure_data = [] - plot_column = {2: 1, 1: 2} - for order in [2, 1]: - where_order = frame_data['order'] == order - top_row_axis = '' if plot_column[order] == 1 else plot_column[order] - figure_data.append( - dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['flux'][where_order], - line_color='#023858', mode='lines', xaxis=f'x{top_row_axis}', yaxis=f'y{top_row_axis}') - ) - mid_row_axis = plot_column[order] + 2 - figure_data.append( - dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['fluxraw'][where_order], - line_color='#023858', mode='lines', xaxis=f'x{mid_row_axis}', yaxis=f'y{mid_row_axis}') - ) - bottom_row_axis = plot_column[order] + 4 - figure_data.append( - dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['background'][where_order], - line_color='#023858', mode='lines', xaxis=f'x{bottom_row_axis}', yaxis=f'y{bottom_row_axis}') - ) + + figure_data = plot_extracted_data(frame_data) return {'data': figure_data, 'layout': layout} diff --git a/banzai_floyds_ui/gui/utils/plot_utils.py b/banzai_floyds_ui/gui/utils/plot_utils.py index c684f52..afc29a0 100644 --- a/banzai_floyds_ui/gui/utils/plot_utils.py +++ b/banzai_floyds_ui/gui/utils/plot_utils.py @@ -55,3 +55,26 @@ def extraction_region_traces(order_polynomial, center_polynomial, width_polynoma background_upper_end = extract_center + bkg_right_outer * extract_sigma return x, [extract_center, extract_low, extract_high, background_lower_start, background_upper_start, background_lower_end, background_upper_end] + + +def plot_extracted_data(frame_data): + figure_data = [] + plot_column = {2: 1, 1: 2} + for order in [2, 1]: + where_order = frame_data['order'] == order + top_row_axis = '' if plot_column[order] == 1 else plot_column[order] + figure_data.append( + dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['flux'][where_order], + line_color='#023858', mode='lines', xaxis=f'x{top_row_axis}', yaxis=f'y{top_row_axis}') + ) + mid_row_axis = plot_column[order] + 2 + figure_data.append( + dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['fluxraw'][where_order], + line_color='#023858', mode='lines', xaxis=f'x{mid_row_axis}', yaxis=f'y{mid_row_axis}') + ) + bottom_row_axis = plot_column[order] + 4 + figure_data.append( + dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['background'][where_order], + line_color='#023858', mode='lines', xaxis=f'x{bottom_row_axis}', yaxis=f'y{bottom_row_axis}') + ) + return figure_data diff --git a/pyproject.toml b/pyproject.toml index da9f083..f06698f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ include = ["banzai_floyds_ui*"] [tool.setuptools.package-data] "banzai_floyds_ui" = ["static/*"] -"banzai_floyds_ui.gui" = ["templates/*"] +"banzai_floyds_ui.gui" = ["templates/*", "data/*"] [project] dynamic = ["version"] @@ -24,6 +24,7 @@ dependencies = [ "django-rest-framework", "django-bootstrap4", "dpd_static_support", + "dash-bootstrap-components", "fontawesomefree", "requests", "django_redis", From daa657dd275fd93083788f4833f428b3615f1f39 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Tue, 15 Oct 2024 08:17:04 -0400 Subject: [PATCH 2/5] WIP: Getting re-extraction to work in real time. --- banzai_floyds_ui/gui/app.py | 133 ++++++++++++++++------- banzai_floyds_ui/gui/plots.py | 43 +++++--- banzai_floyds_ui/gui/urls.py | 4 +- banzai_floyds_ui/gui/utils/file_utils.py | 26 +++++ banzai_floyds_ui/gui/utils/plot_utils.py | 12 +- 5 files changed, 154 insertions(+), 64 deletions(-) diff --git a/banzai_floyds_ui/gui/app.py b/banzai_floyds_ui/gui/app.py index 8270424..a9f2956 100644 --- a/banzai_floyds_ui/gui/app.py +++ b/banzai_floyds_ui/gui/app.py @@ -1,5 +1,6 @@ import dash from dash import dcc, html +import dash_bootstrap_components as dbc from django_plotly_dash import DjangoDash import logging import datetime @@ -8,32 +9,33 @@ from banzai_floyds_ui.gui.utils.file_utils import fetch_all, get_related_frame from banzai_floyds_ui.gui.plots import make_1d_sci_plot, make_2d_sci_plot, make_arc_2d_plot, make_arc_line_plots from banzai_floyds_ui.gui.plots import make_profile_plot +from banzai_floyds_ui.gui.utils.file_utils import get_filename, cache_fits, get_cached_fits from banzai_floyds_ui.gui.utils.plot_utils import extraction_region_traces from dash.exceptions import PreventUpdate from banzai_floyds_ui.gui.utils.plot_utils import json_to_polynomial, plot_extracted_data from banzai_floyds_ui.gui.utils.plot_utils import EXTRACTION_REGION_LINE_ORDER from banzai.utils import import_utils from banzai.utils.stage_utils import get_stages_for_individual_frame -from banzai_floyds.frames import FLOYDSObservationFrame -import dash_bootstrap_components as dbc +from banzai_floyds.frames import FLOYDSFrameFactory from banzai_floyds import settings +from banzai_floyds.utils.profile_utils import profile_fits_to_data import os import banzai.main +import io +from banzai.logs import get_logger + -logger = logging.getLogger(__name__) +logger = get_logger() dashboard_name = 'banzai-floyds' app = DjangoDash(name=dashboard_name) # set up the context object for banzai -parser.add_argument('--post-to-archive', dest='post_to_archive', action='store_true', default=False) -parser.add_argument('--no-file-cache', dest='no_file_cache', action='store_true', default=False, - help='Turn off saving files to disk') -parser.add_argument('--post-to-opensearch', dest='post_to_opensearch', action='store_true', - default=False) - -settings.fpack=True +settings.fpack = True +settings.post_to_open_search = bool(os.environ.get('POST_TO_OPENSEARCH', False)) +settings.post_to_archive = bool(os.environ.get('POST_TO_ARCHIVE', False)) +settings.no_file_cache = True settings.db_address = os.environ['DB_ADDRESS'] RUNTIME_CONTEXT = banzai.main.parse_args(settings, parse_system_args=False) @@ -47,6 +49,16 @@ def layout(): html.Div( id='options-container', children=[ + dbc.Modal([ + dbc.ModalHeader(dbc.ModalTitle("Header"), className='bg-danger text-white'), + dbc.ModalBody("You must be logged in to save an extraction."), + dbc.ModalFooter( + dbc.Button("Close", id="close", className="ms-auto", n_clicks=0) + ), + ], + id="error-logged-in-modal", + is_open=False, + ), html.Div( children=[ dcc.DatePickerRange( @@ -90,6 +102,7 @@ def layout(): id='plot-container', children=[ dcc.Store(id='initial-extraction-info'), + dcc.Store(id='file-list-metadata'), dcc.Store(id='extraction-positions'), dcc.Store(id='extraction-traces'), dcc.Store(id='extractions'), @@ -140,8 +153,8 @@ def layout(): 'width': '100%', 'height': '550px;'}), ] ), - dcc.radioItems(['Optimal', 'Unweighted'], 'Optimal', inline=True, id='extraction-type'), - dcc.button('Save Extraction', id='extract-button'), + dcc.RadioItems(['Optimal', 'Unweighted'], 'Optimal', inline=True, id='extraction-type'), + dbc.Button('Save Extraction', id='extract-button'), ] ) ] @@ -151,7 +164,8 @@ def layout(): app.layout = layout -@app.expanded_callback(dash.dependencies.Output('file-list-dropdown', 'options'), +@app.expanded_callback([dash.dependencies.Output('file-list-dropdown', 'options'), + dash.dependencies.Output('file-list-metadata', 'data')], [dash.dependencies.Input('date-range-picker', 'start_date'), dash.dependencies.Input('date-range-picker', 'end_date'), dash.dependencies.Input('site-picker', 'value')]) @@ -184,7 +198,7 @@ def callback_dropdown_files(*args, **kwargs): data = response.json()['results'] results += [{'label': row['filename'], 'value': row['id']} for row in data] results.sort(key=lambda x: x['label']) - return results + return results, results # This callback snaps the lines back to the same y-position so they don't wander @@ -243,7 +257,7 @@ def callback_dropdown_files(*args, **kwargs): } """, dash.dependencies.Input('extraction-traces', 'data'), - prevet_initial_call=True + prevent_initial_call=True ) @@ -262,7 +276,7 @@ def on_extraction_region_update(extraction_positions, initial_extraction_info): for line in EXTRACTION_REGION_LINE_ORDER[1:]: center = extraction_positions[str(order)]['center'] position = extraction_positions[str(order)][line] - positions_sigma[line] = abs(position - center) + positions_sigma[line] = position - center positions_sigma[line] /= initial_extraction_info['refsigma'][str(order)] center_delta = extraction_positions[str(order)]['center'] - initial_extraction_info['refcenter'][str(order)] x, traces = extraction_region_traces(order_center_polynomial, center_polynomial, width_polynomal, @@ -272,7 +286,7 @@ def on_extraction_region_update(extraction_positions, initial_extraction_info): xs.append(x) ys.append(trace) - return {'x': xs, 'y': ys} + return {'x': xs, 'y': ys} @app.expanded_callback( @@ -282,7 +296,7 @@ def on_extraction_region_update(extraction_positions, initial_extraction_info): dash.dependencies.Output('profile-plot', 'figure'), dash.dependencies.Output('extraction-plot', 'figure'), dash.dependencies.Output('initial-extraction-info', 'data')], - dash.dependencies.Input('file-list-dropdown', 'value'), prevent_intial_call=True) + dash.dependencies.Input('file-list-dropdown', 'value'), prevent_initial_call=True) def callback_make_plots(*args, **kwargs): frame_id = args[0] if frame_id is None: @@ -300,7 +314,7 @@ def callback_make_plots(*args, **kwargs): sci_2d_frame, sci_2d_filename = get_related_frame(frame_id, archive_header, 'L1ID2D') sci_2d_plot, extraction_data = make_2d_sci_plot(sci_2d_frame, sci_2d_filename) - kwargs['session_state'][sci_2d_filename] = sci_2d_frame + cache_fits('science_2d_frame', sci_2d_frame) profile_plot, initial_extraction_info = make_profile_plot(sci_2d_frame) @@ -361,21 +375,51 @@ def update_extraction_positions(initial_extraction_info, relayout_data, current_ return current_extraction_positions -def reextract(hdu, filename, centers, extraction_positions, runtime_context, extraction_type='optimal'): +def reextract(hdu, filename, extraction_positions, initial_extraction_info, runtime_context, extraction_type='optimal'): # Convert 2d science hdu to banzai-floyds frame object - frame = FLOYDSObservationFrame(hdu, ) + factory = FLOYDSFrameFactory() + buffer = io.BytesIO() + hdu.writeto(buffer) + buffer.seek(0) + file_info = {'filename': filename, 'data_buffer': buffer} + frame = factory.open(file_info, runtime_context) # reset the weights and the background region - _, original_widths = frame.profile_fits - frame.profile_fits = centers, original_widths, frame['PROFILEFITS'].data - frame.profile = load_profile_data(frame.profile_fits) - frame.extraction_window = bar + centers, widths = frame.profile_fits + for order in [1, 2]: + center_delta = extraction_positions[str(order)]['center'] - \ + initial_extraction_info['positions'][str(order)]['center'] + centers[order-1].coef[0] += center_delta + frame.profile_fits = centers, widths, frame['PROFILEFITS'].data + frame.profile = profile_fits_to_data(frame.data.shape, centers, widths, + frame.orders, frame.wavelengths.data) + extraction_windows = [] + for order in [1, 2]: + lower = extraction_positions[str(order)]['extract_lower'] / initial_extraction_info['refsigma'][str(order)] + upper = extraction_positions[str(order)]['extract_upper'] / initial_extraction_info['refsigma'][str(order)] + extraction_windows.append([lower, upper]) + + frame.extraction_windows = extraction_windows + # Override the default optimal extraction weights from the profile if extraction_type == 'unweighted': frame.binned_data['weights'] = 1.0 - frame.background_window = foo + + background_windows = [] + for order in [1, 2]: + order_background = [] + for region in ['left', 'right']: + inner = extraction_positions[str(order)][f'bkg_{region}_inner'] + inner /= initial_extraction_info['refsigma'][str(order)] + outer = extraction_positions[str(order)][f'bkg_{region}_outer'] + outer /= initial_extraction_info['refsigma'][str(order)] + this_background = [inner, outer] + this_background.sort() + order_background.append(this_background) + background_windows.append(order_background) + frame.background_windows = background_windows stages_to_do = get_stages_for_individual_frame(runtime_context.ORDERED_STAGES, last_stage=runtime_context.LAST_STAGE[frame.obstype.upper()], extra_stages=runtime_context.EXTRA_STAGES[frame.obstype.upper()]) - + # Starting at the extraction weights stage start_index = stages_to_do.index('banzai_floyds.extract.Extractor') stages_to_do = stages_to_do[start_index:] @@ -392,15 +436,21 @@ def reextract(hdu, filename, centers, extraction_positions, runtime_context, ext @app.expanded_callback(dash.dependencies.Output('extractions', 'data'), [dash.dependencies.Input('extraction-positions', 'data'), - dash.dependencies.Input('extraction-type', 'value')], - prevent_initial_call=True) -def trigger_reextract(extraction_positions, extraction_type, **kwargs): + dash.dependencies.Input('extraction-type', 'value')], + [dash.dependencies.State('initial-extraction-info', 'data'), + dash.dependencies.State('file-list-dropdown', 'value'), + dash.dependencies.State('file-list-metadata', 'data')], + prevent_initial_call=True) +def trigger_reextract(extraction_positions, extraction_type, initial_extraction_info, + frame_id, frame_data, **kwargs): + + science_frame = get_cached_fits('science_2d_frame') - science_frame = kwargs['session_state'].get('science_2d_frame') if science_frame is None: raise PreventUpdate - frame = reextract(science_frame, centers, extraction_positions, + filename = get_filename(frame_id, frame_data) + frame = reextract(science_frame, filename, extraction_positions, initial_extraction_info, RUNTIME_CONTEXT, extraction_type=extraction_type.lower()) return plot_extracted_data(frame.extracted) @@ -426,25 +476,32 @@ def trigger_reextract(extraction_positions, extraction_type, **kwargs): prevent_initial_call=True) -@app.expanded_callback(dash.dependencies.Input('extract-button', 'n_clicks'), +@app.expanded_callback(dash.dependencies.Output("error-logged-in-modal", "is_open"), + dash.dependencies.Input('extract-button', 'n_clicks'), [dash.dependencies.State('extraction-type', 'value'), - dash.dependencies.State('extraction-positions', 'data')], prevent_initial_call=True) -def save_extraction(n_clicks, extraction_type, extraction_positions, **kwargs): + dash.dependencies.State('extraction-positions', 'data'), + dash.dependencies.State('initial-extraction-info', 'data')], + prevent_initial_call=True) +def save_extraction(n_clicks, extraction_type, extraction_positions, initial_extraction_info, + frame_id, frame_data, **kwargs): if not n_clicks: raise PreventUpdate - science_frame = kwargs['session_state'].get('science_2d_frame') + science_frame = get_cached_fits('science_2d_frame') if science_frame is None: raise PreventUpdate # If not logged in, open a modal saying you can only save if you are. username = kwargs['session_state'].get('username') if username is None: - throw error + return True + filename = get_filename(frame_id, frame_data) # Run the reextraction - extracted_frame = reextract(science_frame, filename, centers, extraction_positions, + extracted_frame = reextract(science_frame, filename, extraction_positions, initial_extraction_info, RUNTIME_CONTEXT, extraction_type=extraction_type.lower()) # Save the reducer into the header extracted_frame.meta['REDUCER'] = username # Push the results to the archive extracted_frame.write(RUNTIME_CONTEXT) + # Return false to keep the error modal closed + return False diff --git a/banzai_floyds_ui/gui/plots.py b/banzai_floyds_ui/gui/plots.py index 07e832d..ad9b973 100644 --- a/banzai_floyds_ui/gui/plots.py +++ b/banzai_floyds_ui/gui/plots.py @@ -85,13 +85,20 @@ def make_2d_sci_plot(frame, filename): # We really should just use the extraction_window values in the binned data # We currently use N sigma from the profile center to set the background region # TODO: use a background_window in the binned data analogous to the extraction_window - n_extract_sigma = frame['SCI'].header['EXTRTWIN'] - bkg_lower_n_sigma = frame['SCI'].header['BKWINDW0'] - bkg_upper_n_sigma = frame['SCI'].header['BKWINDW1'] + + extract_lower_n_sigma = frame['SCI'].header[f'XTRTW{order}0'] + upper_lower_n_sigma = frame['SCI'].header[f'XTRTW{order}1'] + + bkg_left_lower_n_sigma = frame['SCI'].header[f'BKWO{order}00'] + bkg_left_upper_n_sigma = frame['SCI'].header[f'BKWO{order}01'] + + bkg_right_lower_n_sigma = frame['SCI'].header[f'BKWO{order}10'] + bkg_right_upper_n_sigma = frame['SCI'].header[f'BKWO{order}11'] x, traces = extraction_region_traces(order_polynomial, center_polynomial, width_polynomal, - wavelengths_polynomial, n_extract_sigma, n_extract_sigma, - bkg_lower_n_sigma, bkg_lower_n_sigma, bkg_upper_n_sigma, bkg_upper_n_sigma) + wavelengths_polynomial, extract_lower_n_sigma, upper_lower_n_sigma, + bkg_left_lower_n_sigma, bkg_right_lower_n_sigma, bkg_left_upper_n_sigma, + bkg_right_upper_n_sigma) if order == 2: center_name = 'Extraction Center' @@ -388,24 +395,25 @@ def make_profile_plot(sci_2d_frame): width_polynomial = header_to_polynomial(sci_2d_frame['PROFILEFITS'].header, 'WID', order) extract_sigma = width_polynomial(order_center[order]) initial_extraction_info['refsigma'][str(order)] = extract_sigma - n_extract_sigma = sci_2d_frame['SCI'].header['EXTRTWIN'] - bkg_lower_n_sigma = sci_2d_frame['SCI'].header['BKWINDW0'] - bkg_upper_n_sigma = sci_2d_frame['SCI'].header['BKWINDW1'] + + extract_lower_n_sigma = sci_2d_frame['SCI'].header[f'XTRTW{order}0'] + extract_upper_n_sigma = sci_2d_frame['SCI'].header[f'XTRTW{order}1'] + + bkg_left_lower_n_sigma = sci_2d_frame['SCI'].header[f'BKWO{order}00'] + bkg_left_upper_n_sigma = sci_2d_frame['SCI'].header[f'BKWO{order}01'] + + bkg_right_lower_n_sigma = sci_2d_frame['SCI'].header[f'BKWO{order}10'] + bkg_right_upper_n_sigma = sci_2d_frame['SCI'].header[f'BKWO{order}11'] colors = [LAVENDER, LAVENDER, LAVENDER, DARK_SALMON, DARK_SALMON, DARK_SALMON, DARK_SALMON] dashed = ['solid', 'dash', 'dash', 'dash', 'dash', 'dash', 'dash'] names = ['Extraction Center', 'Extraction Region', 'Extraction Region', 'Background Region', 'Background Region', 'Background Region', 'Background Region'] - xs = [ - extract_center, - extract_center - n_extract_sigma * extract_sigma, - extract_center + n_extract_sigma * extract_sigma, - extract_center - bkg_lower_n_sigma * extract_sigma, - extract_center + bkg_lower_n_sigma * extract_sigma, - extract_center - bkg_upper_n_sigma * extract_sigma, - extract_center + bkg_upper_n_sigma * extract_sigma - ] + bkg_right_lower_n_sigma = sci_2d_frame['SCI'].header[f'BKWO{order}10'] + xs = [extract_center + n_sigma * extract_sigma for n_sigma in + [0, extract_lower_n_sigma, extract_upper_n_sigma, bkg_left_lower_n_sigma, bkg_left_upper_n_sigma, + bkg_right_lower_n_sigma, bkg_right_upper_n_sigma]] for position, key in zip(xs, EXTRACTION_REGION_LINE_ORDER): initial_extraction_info['positions'][str(order)][key] = position @@ -436,7 +444,6 @@ def make_1d_sci_plot(frame_id, archive_header): frame_1d = download_frame(url=f'{settings.ARCHIVE_URL}{frame_id}/', headers=archive_header) frame_data = frame_1d[1].data - # We again make the layout dict manually. Below is equivilent to # make_subplots(rows=3, cols=2, vertical_spacing=0.02, horizontal_spacing=0.07, shared_xaxes=True, # subplot_titles=['Blue Order (order=2)', 'Red Order (order=1)', None, None, None, None]) diff --git a/banzai_floyds_ui/gui/urls.py b/banzai_floyds_ui/gui/urls.py index f09444f..31599a9 100644 --- a/banzai_floyds_ui/gui/urls.py +++ b/banzai_floyds_ui/gui/urls.py @@ -20,8 +20,8 @@ urlpatterns = [ path('admin/', admin.site.urls), - path('', include('banzai_floyds_ui.api.urls')), - path('banzai-floyds', banzai_floyds_view, name="banzai-floyds"), + path('api', include('banzai_floyds_ui.api.urls')), + path('', banzai_floyds_view, name="banzai-floyds"), path('login', login_view, name="login"), path('logout', logout_view, name="logout"), path('django_plotly_dash', include('django_plotly_dash.urls')), diff --git a/banzai_floyds_ui/gui/utils/file_utils.py b/banzai_floyds_ui/gui/utils/file_utils.py index 977616d..e1a2476 100644 --- a/banzai_floyds_ui/gui/utils/file_utils.py +++ b/banzai_floyds_ui/gui/utils/file_utils.py @@ -4,6 +4,8 @@ import httpx import io import requests +from django.core.cache import cache +from io import BytesIO async def fetch(url, params, headers): @@ -39,3 +41,27 @@ def get_related_frame(frame_id, archive_header, related_frame_key): related_frame_filename = response.json()['data'][related_frame_key] params = {'basename_exact': related_frame_filename} return download_frame(archive_header, params=params, list_endpoint=True), related_frame_filename + + +def get_filename(frame_id, frame_data): + for row in frame_data: + if row['value'] == frame_id: + filename = row['label'] + break + return filename + + +def cache_fits(key_name, hdulist): + buffer = BytesIO() + hdulist.writeto(buffer) + buffer.seek(0) + cache.set(key_name, buffer.read(), timeout=None) + + +def get_cached_fits(key_name): + cached_value = cache.get(key_name) + if cached_value is None: + return None + buffer = BytesIO(cached_value) + buffer.seek(0) + return fits.open(buffer) diff --git a/banzai_floyds_ui/gui/utils/plot_utils.py b/banzai_floyds_ui/gui/utils/plot_utils.py index afc29a0..b3935c1 100644 --- a/banzai_floyds_ui/gui/utils/plot_utils.py +++ b/banzai_floyds_ui/gui/utils/plot_utils.py @@ -47,11 +47,11 @@ def extraction_region_traces(order_polynomial, center_polynomial, width_polynoma extract_center += order_polynomial(x) extract_center += center_delta extract_high = extract_center + extract_upper * extract_sigma - extract_low = extract_center - extract_lower * extract_sigma + extract_low = extract_center + extract_lower * extract_sigma background_upper_start = extract_center + bkg_right_inner * extract_sigma - background_lower_start = extract_center - bkg_left_inner * extract_sigma + background_lower_start = extract_center + bkg_left_inner * extract_sigma # Let's start with 10 sigma for now as the edge rather than using the whole slit - background_lower_end = extract_center - bkg_left_outer * extract_sigma + background_lower_end = extract_center + bkg_left_outer * extract_sigma background_upper_end = extract_center + bkg_right_outer * extract_sigma return x, [extract_center, extract_low, extract_high, background_lower_start, background_upper_start, background_lower_end, background_upper_end] @@ -65,16 +65,16 @@ def plot_extracted_data(frame_data): top_row_axis = '' if plot_column[order] == 1 else plot_column[order] figure_data.append( dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['flux'][where_order], - line_color='#023858', mode='lines', xaxis=f'x{top_row_axis}', yaxis=f'y{top_row_axis}') + line=dict(color='#023858'), mode='lines', xaxis=f'x{top_row_axis}', yaxis=f'y{top_row_axis}') ) mid_row_axis = plot_column[order] + 2 figure_data.append( dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['fluxraw'][where_order], - line_color='#023858', mode='lines', xaxis=f'x{mid_row_axis}', yaxis=f'y{mid_row_axis}') + line=dict(color='#023858'), mode='lines', xaxis=f'x{mid_row_axis}', yaxis=f'y{mid_row_axis}') ) bottom_row_axis = plot_column[order] + 4 figure_data.append( dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['background'][where_order], - line_color='#023858', mode='lines', xaxis=f'x{bottom_row_axis}', yaxis=f'y{bottom_row_axis}') + line=dict(color='#023858'), mode='lines', xaxis=f'x{bottom_row_axis}', yaxis=f'y{bottom_row_axis}') ) return figure_data From ed5499028daee4f914824521d11268533ec5f707 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Wed, 30 Oct 2024 14:44:21 -0400 Subject: [PATCH 3/5] Fixes based on comissioning. --- banzai_floyds_ui/gui/app.py | 68 ++++++++++++++++++++++++++--------- banzai_floyds_ui/gui/plots.py | 18 +++++----- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/banzai_floyds_ui/gui/app.py b/banzai_floyds_ui/gui/app.py index 125612e..2227fa1 100644 --- a/banzai_floyds_ui/gui/app.py +++ b/banzai_floyds_ui/gui/app.py @@ -2,7 +2,6 @@ from dash import dcc, html import dash_bootstrap_components as dbc from django_plotly_dash import DjangoDash -import logging import datetime import requests import asyncio @@ -12,7 +11,7 @@ from banzai_floyds_ui.gui.utils.file_utils import get_filename, cache_fits, get_cached_fits from banzai_floyds_ui.gui.utils.plot_utils import extraction_region_traces from dash.exceptions import PreventUpdate -from banzai_floyds_ui.gui.utils.plot_utils import json_to_polynomial, plot_extracted_data +from banzai_floyds_ui.gui.utils.plot_utils import json_to_polynomial from banzai_floyds_ui.gui.utils.plot_utils import EXTRACTION_REGION_LINE_ORDER from banzai.utils import import_utils from banzai.utils.stage_utils import get_stages_for_individual_frame @@ -50,15 +49,35 @@ def layout(): id='options-container', children=[ dbc.Modal([ - dbc.ModalHeader(dbc.ModalTitle("Header"), className='bg-danger text-white'), + dbc.ModalHeader(dbc.ModalTitle("Error"), className='bg-danger text-white'), dbc.ModalBody("You must be logged in to save an extraction."), dbc.ModalFooter( - dbc.Button("Close", id="close", className="ms-auto", n_clicks=0) + dbc.Button("Close", id="logged-in-close", className="ms-auto", n_clicks=0) ), ], id="error-logged-in-modal", is_open=False, ), + dbc.Modal([ + dbc.ModalHeader(dbc.ModalTitle("Error"), className='bg-danger text-white'), + dbc.ModalBody("Error extracting spectrum. Plots may not reflect extraction paramters."), + dbc.ModalFooter( + dbc.Button("Close", id="extract-fail-close", className="ms-auto", n_clicks=0) + ), + ], + id="error-extract-failed-modal", + is_open=False, + ), + dbc.Modal([ + dbc.ModalHeader(dbc.ModalTitle("Error"), className='bg-danger text-white'), + dbc.ModalBody("Error extracting spectrum. Spectrum will not be saved."), + dbc.ModalFooter( + dbc.Button("Close", id="save-fail-close", className="ms-auto", n_clicks=0) + ), + ], + id="error-extract-failed-on-save-modal", + is_open=False, + ), html.Div( children=[ dcc.DatePickerRange( @@ -394,8 +413,10 @@ def reextract(hdu, filename, extraction_positions, initial_extraction_info, runt frame.orders, frame.wavelengths.data) extraction_windows = [] for order in [1, 2]: - lower = extraction_positions[str(order)]['extract_lower'] / initial_extraction_info['refsigma'][str(order)] - upper = extraction_positions[str(order)]['extract_upper'] / initial_extraction_info['refsigma'][str(order)] + lower = extraction_positions[str(order)]['extract_lower'] - extraction_positions[str(order)]['center'] + lower /= initial_extraction_info['refsigma'][str(order)] + upper = extraction_positions[str(order)]['extract_upper'] - extraction_positions[str(order)]['center'] + upper /= initial_extraction_info['refsigma'][str(order)] extraction_windows.append([lower, upper]) frame.extraction_windows = extraction_windows @@ -407,9 +428,9 @@ def reextract(hdu, filename, extraction_positions, initial_extraction_info, runt for order in [1, 2]: order_background = [] for region in ['left', 'right']: - inner = extraction_positions[str(order)][f'bkg_{region}_inner'] + inner = extraction_positions[str(order)][f'bkg_{region}_inner'] - extraction_positions[str(order)]['center'] inner /= initial_extraction_info['refsigma'][str(order)] - outer = extraction_positions[str(order)][f'bkg_{region}_outer'] + outer = extraction_positions[str(order)][f'bkg_{region}_outer'] - extraction_positions[str(order)]['center'] outer /= initial_extraction_info['refsigma'][str(order)] this_background = [inner, outer] this_background.sort() @@ -431,10 +452,12 @@ def reextract(hdu, filename, extraction_positions, initial_extraction_info, runt if not frames: logger.error('Reduction stopped', extra_tags={'filename': filename}) return + logger.info('Reduction complete', extra_tags={'filename': filename}) return frame -@app.expanded_callback(dash.dependencies.Output('extractions', 'data'), +@app.expanded_callback([dash.dependencies.Output('extractions', 'data'), + dash.dependencies.Output('error-extract-failed-modal', 'is_open')], [dash.dependencies.Input('extraction-positions', 'data'), dash.dependencies.Input('extraction-type', 'value')], [dash.dependencies.State('initial-extraction-info', 'data'), @@ -452,7 +475,18 @@ def trigger_reextract(extraction_positions, extraction_type, initial_extraction_ filename = get_filename(frame_id, frame_data) frame = reextract(science_frame, filename, extraction_positions, initial_extraction_info, RUNTIME_CONTEXT, extraction_type=extraction_type.lower()) - return plot_extracted_data(frame.extracted) + + if frame is None: + return dash.no_update, True + + x = [] + y = [] + for order in [2, 1]: + where_order = frame.extracted['order'] == order + for flux in ['flux', 'fluxraw', 'background']: + x.append(frame.extracted['wavelength'][where_order]) + y.append(frame.extracted[flux][where_order]) + return {'x': x, 'y': y}, False app.clientside_callback( @@ -461,11 +495,10 @@ def trigger_reextract(extraction_positions, extraction_type, initial_extraction_ if (typeof extraction_data === "undefined") { return window.dash_clientside.no_update; } - - var dccGraph = document.getElementById('sci-1d-plot'); + var dccGraph = document.getElementById('extraction-plot'); var jsFigure = dccGraph.querySelector('.js-plotly-plot'); var trace_ids = []; - for (let i = 1; i <= extraction_data.x.length; i++) { + for (let i = 0; i < extraction_data.x.length; i++) { trace_ids.push(i); } Plotly.restyle(jsFigure, extraction_data, trace_ids); @@ -476,7 +509,8 @@ def trigger_reextract(extraction_positions, extraction_type, initial_extraction_ prevent_initial_call=True) -@app.expanded_callback(dash.dependencies.Output("error-logged-in-modal", "is_open"), +@app.expanded_callback([dash.dependencies.Output("error-logged-in-modal", "is_open"), + dash.dependencies.Output('error-extract-failed-on-save-modal', 'is_open')], dash.dependencies.Input('extract-button', 'n_clicks'), [dash.dependencies.State('extraction-type', 'value'), dash.dependencies.State('extraction-positions', 'data'), @@ -493,15 +527,17 @@ def save_extraction(n_clicks, extraction_type, extraction_positions, initial_ext # If not logged in, open a modal saying you can only save if you are. username = kwargs['session_state'].get('username') if username is None: - return True + return True, dash.no_update filename = get_filename(frame_id, frame_data) # Run the reextraction extracted_frame = reextract(science_frame, filename, extraction_positions, initial_extraction_info, RUNTIME_CONTEXT, extraction_type=extraction_type.lower()) + if extracted_frame is None: + return dash.no_update, True # Save the reducer into the header extracted_frame.meta['REDUCER'] = username # Push the results to the archive extracted_frame.write(RUNTIME_CONTEXT) # Return false to keep the error modal closed - return False + return dash.no_update, dash.no_update diff --git a/banzai_floyds_ui/gui/plots.py b/banzai_floyds_ui/gui/plots.py index 5e7734b..4475d0e 100644 --- a/banzai_floyds_ui/gui/plots.py +++ b/banzai_floyds_ui/gui/plots.py @@ -71,9 +71,9 @@ def make_2d_sci_plot(frame, filename): frame['SCI'].data.shape) order_polynomial = np.polynomial.Legendre(orders.coeffs[order - 1], domain=orders.domains[order - 1]) - wavelength_solution = WavelengthSolution.from_header(frame['WAVELENGTHS'].header, orders) - wavelengths_polynomial = Legendre(coef=wavelength_solution.coefficients[order - 1], - domain=wavelength_solution.domains[order - 1]) + wavelenth_solution = WavelengthSolution.from_header(frame['WAVELENGTHS'].header, orders) + wavelengths_polynomial = Legendre(coef=wavelenth_solution.coefficients[order - 1], + domain=wavelenth_solution.domains[order - 1]) center_polynomial = header_to_polynomial(frame['PROFILEFITS'].header, 'CTR', order) width_polynomal = header_to_polynomial(frame['PROFILEFITS'].header, 'WID', order) for polynomial, key in zip([order_polynomial, center_polynomial, width_polynomal, wavelengths_polynomial], @@ -97,9 +97,9 @@ def make_2d_sci_plot(frame, filename): x, traces = extraction_region_traces(order_polynomial, center_polynomial, width_polynomal, wavelengths_polynomial, extract_lower_n_sigma, upper_lower_n_sigma, - bkg_left_lower_n_sigma, bkg_right_lower_n_sigma, bkg_left_upper_n_sigma, + bkg_left_upper_n_sigma, bkg_right_lower_n_sigma, bkg_left_lower_n_sigma, bkg_right_upper_n_sigma) - + print('Got here', f'{extract_lower_n_sigma=}', f'{upper_lower_n_sigma=}', f'{bkg_left_upper_n_sigma=}', f'{bkg_right_lower_n_sigma=}', f'{bkg_left_lower_n_sigma=}', f'{bkg_right_upper_n_sigma=}') if order == 2: center_name = 'Extraction Center' center_legend = True @@ -330,13 +330,13 @@ def make_profile_plot(sci_2d_frame): 'range': [-0.1, 1.5], 'domain': [0.565, 1.0]}, 'yaxis2': {'anchor': 'x2', 'title': {'text': 'y (pixel)'}, 'domain': [0.565, 1.0]}, 'yaxis3': {'anchor': 'x3', 'title': {'text': 'Normalized Flux'}, - 'range': [-0.1, 1.5],'domain': [0.0, 0.435]}, + 'range': [-0.1, 1.5], 'domain': [0.0, 0.435]}, 'yaxis4': {'anchor': 'x4', 'title': {'text': 'y (pixel)'}, 'domain': [0.0, 0.435]} } figure_data = [] plot_row = {2: 1, 1: 2} - # Define the coordinate reference plot manually per order + # Define the coordinate refernce plot manually per order reference_axes = {2: 1, 1: 3} # Approximate wavelength center to plot the profile order_center = {1: 7000, 2: 4500} @@ -412,8 +412,8 @@ def make_profile_plot(sci_2d_frame): bkg_right_lower_n_sigma = sci_2d_frame['SCI'].header[f'BKWO{order}10'] xs = [extract_center + n_sigma * extract_sigma for n_sigma in - [0, extract_lower_n_sigma, extract_upper_n_sigma, bkg_left_lower_n_sigma, bkg_left_upper_n_sigma, - bkg_right_lower_n_sigma, bkg_right_upper_n_sigma]] + [0, extract_lower_n_sigma, extract_upper_n_sigma, bkg_left_upper_n_sigma, + bkg_right_lower_n_sigma, bkg_left_lower_n_sigma, bkg_right_upper_n_sigma]] for position, key in zip(xs, EXTRACTION_REGION_LINE_ORDER): initial_extraction_info['positions'][str(order)][key] = position From 47df98f3c95747f1616b230d29571586ae250e47 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Thu, 31 Oct 2024 16:28:43 -0400 Subject: [PATCH 4/5] Minor changes to make the UX better given how long the reextraction takes. --- banzai_floyds_ui/gui/app.py | 53 ++++++++++-------------- banzai_floyds_ui/gui/utils/file_utils.py | 25 +++++++---- banzai_floyds_ui/gui/views.py | 3 ++ 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/banzai_floyds_ui/gui/app.py b/banzai_floyds_ui/gui/app.py index 2227fa1..77ff835 100644 --- a/banzai_floyds_ui/gui/app.py +++ b/banzai_floyds_ui/gui/app.py @@ -8,7 +8,7 @@ from banzai_floyds_ui.gui.utils.file_utils import fetch_all, get_related_frame from banzai_floyds_ui.gui.plots import make_1d_sci_plot, make_2d_sci_plot, make_arc_2d_plot, make_arc_line_plots from banzai_floyds_ui.gui.plots import make_profile_plot -from banzai_floyds_ui.gui.utils.file_utils import get_filename, cache_fits, get_cached_fits +from banzai_floyds_ui.gui.utils import file_utils from banzai_floyds_ui.gui.utils.plot_utils import extraction_region_traces from dash.exceptions import PreventUpdate from banzai_floyds_ui.gui.utils.plot_utils import json_to_polynomial @@ -22,12 +22,13 @@ import banzai.main import io from banzai.logs import get_logger +from django.core.cache import cache logger = get_logger() dashboard_name = 'banzai-floyds' -app = DjangoDash(name=dashboard_name) +app = DjangoDash(name=dashboard_name, csrf_token_name='csrftoken') # set up the context object for banzai @@ -51,9 +52,6 @@ def layout(): dbc.Modal([ dbc.ModalHeader(dbc.ModalTitle("Error"), className='bg-danger text-white'), dbc.ModalBody("You must be logged in to save an extraction."), - dbc.ModalFooter( - dbc.Button("Close", id="logged-in-close", className="ms-auto", n_clicks=0) - ), ], id="error-logged-in-modal", is_open=False, @@ -61,19 +59,16 @@ def layout(): dbc.Modal([ dbc.ModalHeader(dbc.ModalTitle("Error"), className='bg-danger text-white'), dbc.ModalBody("Error extracting spectrum. Plots may not reflect extraction paramters."), - dbc.ModalFooter( - dbc.Button("Close", id="extract-fail-close", className="ms-auto", n_clicks=0) - ), ], id="error-extract-failed-modal", is_open=False, ), dbc.Modal([ dbc.ModalHeader(dbc.ModalTitle("Error"), className='bg-danger text-white'), - dbc.ModalBody("Error extracting spectrum. Spectrum will not be saved."), - dbc.ModalFooter( - dbc.Button("Close", id="save-fail-close", className="ms-auto", n_clicks=0) - ), + dbc.ModalBody(""" + Error saving spectrum. + Note you need to have clicked the re-extract button at least before saving. + """), ], id="error-extract-failed-on-save-modal", is_open=False, @@ -123,8 +118,6 @@ def layout(): dcc.Store(id='initial-extraction-info'), dcc.Store(id='file-list-metadata'), dcc.Store(id='extraction-positions'), - dcc.Store(id='extraction-traces'), - dcc.Store(id='extractions'), dcc.Loading( id='loading-arc-2d-plot-container', type='default', @@ -162,17 +155,21 @@ def layout(): config={'edits': {'shapePosition': True}}), ] ), - html.Div('Extraction Type:'), + html.Div(['Extraction Type:', + dcc.RadioItems(['Optimal', 'Unweighted'], 'Optimal', inline=True, id='extraction-type', + style={"margin-right": "10px"})], + dbc.Button('Re-Extract', id='extract-button')), dcc.Loading( id='loading-extraction-plot-container', type='default', children=[ + dcc.Store(id='extraction-traces'), + dcc.Store(id='extractions'), dcc.Graph(id='extraction-plot', style={'display': 'inline-block', 'width': '100%', 'height': '550px;'}), ] ), - dcc.RadioItems(['Optimal', 'Unweighted'], 'Optimal', inline=True, id='extraction-type'), dbc.Button('Save Extraction', id='extract-button'), ] ) @@ -215,7 +212,7 @@ def callback_dropdown_files(*args, **kwargs): logger.error(f"Failed to fetch data from archive: {e}. {response.content}") return data = response.json()['results'] - results += [{'label': row['filename'], 'value': row['id']} for row in data] + results += [{'label': f'{row["filename"]} {row["OBJECT"]} {row["PROPID"]}', 'value': row['id']} for row in data] results.sort(key=lambda x: x['label']) return results, results @@ -333,7 +330,8 @@ def callback_make_plots(*args, **kwargs): sci_2d_frame, sci_2d_filename = get_related_frame(frame_id, archive_header, 'L1ID2D') sci_2d_plot, extraction_data = make_2d_sci_plot(sci_2d_frame, sci_2d_filename) - cache_fits('science_2d_frame', sci_2d_frame) + file_utils.cache_fits('science_2d_frame', sci_2d_frame) + cache.set('filename', sci_2d_frame['SCI'].header['ORIGNAME'] + '.fits') profile_plot, initial_extraction_info = make_profile_plot(sci_2d_frame) @@ -467,18 +465,19 @@ def reextract(hdu, filename, extraction_positions, initial_extraction_info, runt def trigger_reextract(extraction_positions, extraction_type, initial_extraction_info, frame_id, frame_data, **kwargs): - science_frame = get_cached_fits('science_2d_frame') + science_frame = file_utils.get_cached_fits('science_2d_frame') if science_frame is None: raise PreventUpdate - filename = get_filename(frame_id, frame_data) + filename = cache.get('filename') frame = reextract(science_frame, filename, extraction_positions, initial_extraction_info, RUNTIME_CONTEXT, extraction_type=extraction_type.lower()) if frame is None: return dash.no_update, True + file_utils.cache_frame('reextracted_frame', frame) x = [] y = [] for order in [2, 1]: @@ -512,29 +511,21 @@ def trigger_reextract(extraction_positions, extraction_type, initial_extraction_ @app.expanded_callback([dash.dependencies.Output("error-logged-in-modal", "is_open"), dash.dependencies.Output('error-extract-failed-on-save-modal', 'is_open')], dash.dependencies.Input('extract-button', 'n_clicks'), - [dash.dependencies.State('extraction-type', 'value'), - dash.dependencies.State('extraction-positions', 'data'), - dash.dependencies.State('initial-extraction-info', 'data')], prevent_initial_call=True) -def save_extraction(n_clicks, extraction_type, extraction_positions, initial_extraction_info, - frame_id, frame_data, **kwargs): +def save_extraction(n_clicks, **kwargs): if not n_clicks: raise PreventUpdate - science_frame = get_cached_fits('science_2d_frame') - if science_frame is None: - raise PreventUpdate # If not logged in, open a modal saying you can only save if you are. username = kwargs['session_state'].get('username') if username is None: return True, dash.no_update - filename = get_filename(frame_id, frame_data) # Run the reextraction - extracted_frame = reextract(science_frame, filename, extraction_positions, initial_extraction_info, - RUNTIME_CONTEXT, extraction_type=extraction_type.lower()) + extracted_frame = file_utils.get_cached_frame('reextracted_frame') if extracted_frame is None: return dash.no_update, True + # Save the reducer into the header extracted_frame.meta['REDUCER'] = username # Push the results to the archive diff --git a/banzai_floyds_ui/gui/utils/file_utils.py b/banzai_floyds_ui/gui/utils/file_utils.py index 6670074..48237a5 100644 --- a/banzai_floyds_ui/gui/utils/file_utils.py +++ b/banzai_floyds_ui/gui/utils/file_utils.py @@ -6,6 +6,7 @@ import requests from django.core.cache import cache from io import BytesIO +import pickle async def fetch(url, params, headers): @@ -45,14 +46,6 @@ def get_related_frame(frame_id, archive_header, related_frame_key): return download_frame(archive_header, params=params, list_endpoint=True), related_frame_filename -def get_filename(frame_id, frame_data): - for row in frame_data: - if row['value'] == frame_id: - filename = row['label'] - break - return filename - - def cache_fits(key_name, hdulist): buffer = BytesIO() hdulist.writeto(buffer) @@ -67,3 +60,19 @@ def get_cached_fits(key_name): buffer = BytesIO(cached_value) buffer.seek(0) return fits.open(buffer) + + +def get_cached_frame(key_name): + cached_value = cache.get(key_name) + if cached_value is None: + return None + buffer = BytesIO(cached_value) + buffer.seek(0) + return pickle.load(buffer) + + +def cache_frame(key_name, frame): + buffer = BytesIO() + pickle.dump(frame, buffer) + buffer.seek(0) + cache.set(key_name, buffer.read(), timeout=None) diff --git a/banzai_floyds_ui/gui/views.py b/banzai_floyds_ui/gui/views.py index 4804715..2e79260 100644 --- a/banzai_floyds_ui/gui/views.py +++ b/banzai_floyds_ui/gui/views.py @@ -1,6 +1,7 @@ from django.shortcuts import render from django.views.decorators.http import require_http_methods from banzai_floyds_ui.gui.forms import LoginForm +from django.views.decorators.csrf import csrf_protect from django.conf import settings import requests @@ -16,6 +17,7 @@ def banzai_floyds_view(request, template_name="floyds.html", **kwargs): return render(request, template_name=template_name, context=context) +@csrf_protect @require_http_methods(["POST"]) def login_view(request): form = LoginForm(request.POST) @@ -35,6 +37,7 @@ def login_view(request): return render(request, 'floyds.html') +@csrf_protect @require_http_methods(["POST"]) def logout_view(request): if 'auth_token' in request.session: From b46e95f37ba6fea45579b862acbde073e522b26f Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Tue, 5 Nov 2024 12:05:13 -0500 Subject: [PATCH 5/5] Fixes based on comments and added changelog. --- CHANGES.md | 7 ++++++ banzai_floyds_ui/gui/plots.py | 30 +++++++----------------- banzai_floyds_ui/gui/utils/plot_utils.py | 22 ++++++++++++++--- pyproject.toml | 2 +- 4 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 CHANGES.md diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..acf384d --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,7 @@ +0.2.0 (2024-11-05) +------------------ +- Added the ability to re-extract via the GUI + +0.1.0 (2024-06-06) +------------------ +- Initial Release \ No newline at end of file diff --git a/banzai_floyds_ui/gui/plots.py b/banzai_floyds_ui/gui/plots.py index 4475d0e..43a25a0 100644 --- a/banzai_floyds_ui/gui/plots.py +++ b/banzai_floyds_ui/gui/plots.py @@ -12,24 +12,11 @@ from scipy.interpolate import LinearNDInterpolator from banzai_floyds_ui.gui.utils.plot_utils import unfilled_histogram, EXTRACTION_REGION_LINE_ORDER from banzai_floyds_ui.gui.utils.plot_utils import extraction_region_traces, plot_extracted_data +from banzai_floyds_ui.gui.utils.plot_utils import DARK_BLUE, COLORMAP, DARK_SALMON, LAVENDER import importlib import json -COLORMAP = [ - [0, '#fff7fb'], - [0.125, '#ece7f2'], - [0.25, '#d0d1e6'], - [0.375, '#a6bddb'], - [0.5, '#74a9cf'], - [0.625, '#3690c0'], - [0.75, '#0570b0'], - [0.875, '#045a8d'], - [1, '#023858'] -] -DARK_SALMON = '#8F0B0B' -LAVENDER = '#BB69F5' - template_path = importlib.resources.files('banzai_floyds_ui.gui').joinpath('data/plotly_template.json') PLOTLY_TEMPLATE = json.loads(template_path.read_text()) @@ -71,9 +58,9 @@ def make_2d_sci_plot(frame, filename): frame['SCI'].data.shape) order_polynomial = np.polynomial.Legendre(orders.coeffs[order - 1], domain=orders.domains[order - 1]) - wavelenth_solution = WavelengthSolution.from_header(frame['WAVELENGTHS'].header, orders) - wavelengths_polynomial = Legendre(coef=wavelenth_solution.coefficients[order - 1], - domain=wavelenth_solution.domains[order - 1]) + wavelength_solution = WavelengthSolution.from_header(frame['WAVELENGTHS'].header, orders) + wavelengths_polynomial = Legendre(coef=wavelength_solution.coefficients[order - 1], + domain=wavelength_solution.domains[order - 1]) center_polynomial = header_to_polynomial(frame['PROFILEFITS'].header, 'CTR', order) width_polynomal = header_to_polynomial(frame['PROFILEFITS'].header, 'WID', order) for polynomial, key in zip([order_polynomial, center_polynomial, width_polynomal, wavelengths_polynomial], @@ -99,7 +86,6 @@ def make_2d_sci_plot(frame, filename): wavelengths_polynomial, extract_lower_n_sigma, upper_lower_n_sigma, bkg_left_upper_n_sigma, bkg_right_lower_n_sigma, bkg_left_lower_n_sigma, bkg_right_upper_n_sigma) - print('Got here', f'{extract_lower_n_sigma=}', f'{upper_lower_n_sigma=}', f'{bkg_left_upper_n_sigma=}', f'{bkg_right_lower_n_sigma=}', f'{bkg_left_lower_n_sigma=}', f'{bkg_right_upper_n_sigma=}') if order == 2: center_name = 'Extraction Center' center_legend = True @@ -270,7 +256,7 @@ def make_arc_line_plots(arc_frame_hdu): figure_data.append( dict( type='scatter', x=residuals_wavelengths.tolist(), y=residuals.tolist(), - mode='markers', marker=dict(color='#023858'), + mode='markers', marker=dict(color=DARK_BLUE), hovertext=residual_hover_text, hovertemplate='%{y}\u212B: %{hovertext}', xaxis=f'x{plot_column[order] + 2}', yaxis=f'y{plot_column[order] + 2}' @@ -336,7 +322,7 @@ def make_profile_plot(sci_2d_frame): figure_data = [] plot_row = {2: 1, 1: 2} - # Define the coordinate refernce plot manually per order + # Define the coordinate reference plot manually per order reference_axes = {2: 1, 1: 3} # Approximate wavelength center to plot the profile order_center = {1: 7000, 2: 4500} @@ -359,7 +345,7 @@ def make_profile_plot(sci_2d_frame): model_name = None data_name = None figure_data.append( - unfilled_histogram(data['y_order'], data['data'], '#023858', name=data_name, legend='legend2', + unfilled_histogram(data['y_order'], data['data'], DARK_BLUE, name=data_name, legend='legend2', axis=2 * plot_row[order] - 1), ) @@ -373,7 +359,7 @@ def make_profile_plot(sci_2d_frame): dict( type='scatter', x=traced_points['wavelength'], y=traced_points['center'], - mode='markers', marker={'color': '#023858'}, + mode='markers', marker={'color': DARK_BLUE}, hoverinfo='skip', showlegend=False, xaxis=f'x{plot_row[order] * 2}', yaxis=f'y{plot_row[order] * 2}' ) diff --git a/banzai_floyds_ui/gui/utils/plot_utils.py b/banzai_floyds_ui/gui/utils/plot_utils.py index 4ab14bf..83f9957 100644 --- a/banzai_floyds_ui/gui/utils/plot_utils.py +++ b/banzai_floyds_ui/gui/utils/plot_utils.py @@ -1,6 +1,22 @@ import numpy as np +DARK_BLUE = '#023858' +COLORMAP = [ + [0, '#fff7fb'], + [0.125, '#ece7f2'], + [0.25, '#d0d1e6'], + [0.375, '#a6bddb'], + [0.5, '#74a9cf'], + [0.625, '#3690c0'], + [0.75, '#0570b0'], + [0.875, '#045a8d'], + [1, DARK_BLUE] +] +DARK_SALMON = '#8F0B0B' +LAVENDER = '#BB69F5' + + def xy_to_svg_path(x, y): # We don't want a Z at the end because we're not closing the path return 'M ' + ' L '.join(f'{i},{j}' for i, j in zip(x, y)) @@ -65,16 +81,16 @@ def plot_extracted_data(frame_data): top_row_axis = '' if plot_column[order] == 1 else plot_column[order] figure_data.append( dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['flux'][where_order], - line=dict(color='#023858'), mode='lines', xaxis=f'x{top_row_axis}', yaxis=f'y{top_row_axis}') + line=dict(color=DARK_BLUE), mode='lines', xaxis=f'x{top_row_axis}', yaxis=f'y{top_row_axis}') ) mid_row_axis = plot_column[order] + 2 figure_data.append( dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['fluxraw'][where_order], - line=dict(color='#023858'), mode='lines', xaxis=f'x{mid_row_axis}', yaxis=f'y{mid_row_axis}') + line=dict(color=DARK_BLUE), mode='lines', xaxis=f'x{mid_row_axis}', yaxis=f'y{mid_row_axis}') ) bottom_row_axis = plot_column[order] + 4 figure_data.append( dict(type='scatter', x=frame_data['wavelength'][where_order], y=frame_data['background'][where_order], - line=dict(color='#023858'), mode='lines', xaxis=f'x{bottom_row_axis}', yaxis=f'y{bottom_row_axis}') + line=dict(color=DARK_BLUE), mode='lines', xaxis=f'x{bottom_row_axis}', yaxis=f'y{bottom_row_axis}') ) return figure_data diff --git a/pyproject.toml b/pyproject.toml index 3d2bdbb..5aac77b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "django_redis", "whitenoise", "httpx", - "banzai_floyds @ git+https://github.com/lcogt/banzai-floyds@reextract", + "banzai_floyds @ git+https://github.com/lcogt/banzai-floyds@0.10.0", "scipy" ]