From f883309c1ec6ec47cca7305b023e1ac827d93c33 Mon Sep 17 00:00:00 2001 From: Florez Ospina Juan Felipe Date: Wed, 21 Aug 2024 15:26:11 +0200 Subject: [PATCH] Restructured dashboard layout in terms of row and column components from dash_bootstrap_component. Added three bottoms and their callbacks to manage create flag, reset, and commit operations. --- data_flagging_app.py | 271 +++++++++++++++++++++++-------------------- 1 file changed, 145 insertions(+), 126 deletions(-) diff --git a/data_flagging_app.py b/data_flagging_app.py index 3e9865a..ef02561 100644 --- a/data_flagging_app.py +++ b/data_flagging_app.py @@ -1,8 +1,12 @@ -import sys -import os -import h5py -import numpy as np +import sys, os + import pandas as pd +import numpy as np + +import base64 +import dash +import io + # Set up project root directory root_dir = os.path.abspath(os.curdir) sys.path.append(root_dir) @@ -11,165 +15,180 @@ sys.path.append(os.path.join(root_dir,'dima')) import dima.src.hdf5_data_extraction as h5de import dima.src.metadata_review_lib as ma import dima.src.g5505_utils as utils -import base64 -import dash -import io + import data_flagging_utils as data_flagging_utils -#import dash_core_components as dcc -#import dash_html_components as html -#from dash.dependencies import Input, Output, State - from dash import Dash, html, dcc, callback, Output, Input, State import plotly.graph_objs as go from plotly.subplots import make_subplots -import pandas as pd -import numpy as np +import dash_bootstrap_components as dbc -import tempfile +# Initialize Dash app with Bootstrap theme +app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP]) +app.layout = dbc.Container([ + dbc.Row([ -app = dash.Dash(__name__) -app.layout = html.Div([ - html.Div([ - dcc.Upload( - id='upload-image', - children=html.Div([ - 'Drag and Drop or ', - html.A('Select Files') - ]), style={"fontSize": "16px", - 'width': '100%', - 'height': '60px', - 'lineHeight': '60px', - 'borderWidth': '1px', - 'borderStyle': 'dashed', - 'borderRadius': '5px', - 'textAlign': 'center', - 'margin': '10px' - },) - ]), - html.Div([ - dcc.Dropdown( - id='flag-options', - options=[{'label': data_flagging_utils.flags_dict[key]['flag_description'], 'value': key} for key in data_flagging_utils.flags_dict.keys()]#, - #value='000' - ), - html.Button('Flag Selected', id='flag-button') - ]), - html.Div([ - dcc.Graph(id='timeseries-plot', - config={ - 'modeBarButtonsToAdd': ['select2d', 'lasso2d'], # Add box select and lasso select tools - 'displaylogo': False # Optionally remove the Plotly logo from the mode bar - }), + dbc.Col([ + dcc.Upload( + id='upload-image', + children=html.Div(['Drag and Drop or ',html.A('Select Files')]), + style={ + 'fontSize': "16px", + 'width': '100%', + 'height': '60px', + 'lineHeight': '60px', + 'borderWidth': '1px', + 'borderStyle': 'dashed', + 'borderRadius': '5px', + 'textAlign': 'center', + 'margin': '10px' + }), + dcc.Dropdown( + id='flag-options', + options=data_flagging_utils.dropdown_menu_options, + )], + width=12 + )],justify="center", align="center"), + + dbc.Row([ + dbc.Col([dbc.Button('Create Flag', id='flag-button', color="primary", className="mt-2")],width=2), + dbc.Col([dbc.Button('Reset Flag', id='reset-flag-button', color="secondary", className="mt-2")],width=2), + dbc.Col([dbc.Button('Commit Flag', id='commit-flag-button', color="secondary", className="mt-2")],width=2) + ], justify="center", align="center"), - ]), - dcc.Store(id='memory-output'),#, data=fig.to_dict()), # Store the initial figure - html.Div(id='textarea-example-output', style={'whiteSpace': 'pre-line'}) - #html.Div(id='output-container') -]) + dbc.Row([ + dbc.Col([ dcc.Graph(id='timeseries-plot')], width=8), + dbc.Col([html.Div(id='flag-record', style={'whiteSpace': 'pre-line'})], width=4), #config={'modeBarButtons': True, + #'modeBarButtonsToAdd':['select2d','lasso2d'], + #'modeBarButtonsToRemove': ['zoom', 'pan']}),], width=12) + + ],justify="center", align="center"), -@app.callback(Output('memory-output','data'), - Output('timeseries-plot', 'figure'), - [Input('upload-image','filename')], - [Input('upload-image','contents')] + dbc.Row([ # row 3 + dbc.Col([ + dcc.Store(id='memory-output'), + html.Div(id='textarea-example-output', style={'whiteSpace': 'pre-line'}) + ], width=12) + ],justify="center", align="center"), +], ) -def load_data(filename,contents): - data = {'data_loaded_flag':False} + +@app.callback( + Output('memory-output','data'), + Output('timeseries-plot', 'figure'), + [Input('upload-image','filename')], + [Input('upload-image','contents')] +) +def load_data(filename, contents): + data = {'data_loaded_flag': False} if filename and contents and filename.endswith('.h5'): try: - # Decode and read the uploaded file content_type, content_string = contents.split(',') decoded = base64.b64decode(content_string) file_path = io.BytesIO(decoded) - - # Create a temporary file to save the content - #with tempfile.NamedTemporaryFile(delete=False, suffix='.h5') as temp_file: - # temp_file.write(decoded) - # temp_file_path = temp_file.name - fig = data_flagging_utils.create_loaded_file_figure(file_path) - # Return file path and other relevant info data['data_loaded_flag'] = True - - fig.update_layout(dragmode='select', - activeselection=dict(fillcolor='yellow')) + return data, fig except Exception as e: print(f"Error processing file: {e}") return data, dash.no_update - return data, dash.no_update - @app.callback( - Output('timeseries-plot', 'figure',allow_duplicate=True), + Output('timeseries-plot', 'figure', allow_duplicate=True), Output('textarea-example-output','children'), - Input('timeseries-plot', 'relayoutData'), + Input('flag-button', 'n_clicks'), State('timeseries-plot', 'figure'), State('memory-output', 'data'), prevent_initial_call=True ) -def update_graph(relayoutData, fig, data): - - if data is None: - return dash.no_update, dash.no_update - - if data.get('data_loaded_flag',False) is False: - return dash.no_update, dash.no_update - # If fig is None or in its initial state, return no_update - #if fig is None or not fig['data']: # Check if the figure is empty or in an initial state - # return dash.no_update, dash.no_update - - shapes = [] +def create_flag(n_clicks, fig, data): + if not data or not data.get('data_loaded_flag', False): + return dash.no_update, dash.no_update - # Handle case where relayoutData is provided - if relayoutData and 'xaxis.range[0]' in relayoutData: - start = relayoutData['xaxis.range[0]'] - end = relayoutData['xaxis.range[1]'] - else: - start = None # Set to default or previously known values - end = None # Set to default or previously known values - - if start and end: - shapes.append({ - 'type': 'rect', - 'xref': 'x', - 'yref': 'paper', - 'x0': start, - 'y0': 0, - 'x1': end, - 'y1': 1, - 'fillcolor': 'rgba(128, 0, 128, 0.3)', - 'line': {'width': 0} - }) - - # Update the figure with the new shape - fig['layout'].update(shapes=shapes) - - value = 'hola amigos' - #return fig, 'You have entered: \n{}'.format(value) - return dash.no_update, 'You have entered: \n{}'.format(value) - + fig['layout'].update({'dragmode' : 'select', + 'activeselection' : dict(fillcolor='yellow'), + 'doubleClick' : 'reset' + }) + value = '{} amigos'.format(n_clicks) + return fig, f'You have entered: \n{value}' #@app.callback( - #Output('output-container', 'children'), -# [Input('flag-button', 'n_clicks')], -# [State('flag-options', 'value'), -# State('timeseries-plot', 'relayoutData')] +# Output('timeseries-plot', 'figure', allow_duplicate=True), +# Output('timeseries-plot', 'selectedData', allow_duplicate=True), +# #Output('textarea-example-output','children'), +# Input('reset-flag-button', 'n_clicks'), +# State('timeseries-plot', 'figure'), +# #State('memory-output', 'data'), +# prevent_initial_call=True #) -#def flag_data(n_clicks, flag, relayoutData): -# if n_clicks and relayoutData and 'xaxis.range[0]' in relayoutData: -# start = relayoutData['xaxis.range[0]'] -# end = relayoutData['xaxis.range[1]'] -# print(f'Flagged with: {flag} from {start} to {end}') -# elif n_clicks: -# print('Please select an interval to flag.') +#def clear_flag(n_clicks, fig): + #if not data or not data.get('data_loaded_flag', False): + # return dash.no_update, dash.no_update + +# fig['layout'].update({'dragmode': 'zoom', 'activeselection': None}) + #fig.update_layout() + #update_layout(dragmode='select', activeselection=dict(fillcolor='yellow')) + + #shapes = [] + #if relayoutData and 'xaxis.range[0]' in relayoutData: + # start = relayoutData['xaxis.range[0]'] + # end = relayoutData['xaxis.range[1]'] + #else: + # start, end = None, None -#if __name__ == '__main__': -app.run_server(debug=True) + #if start and end: + # shapes.append({ + # 'type': 'rect', + # 'xref': 'x', + # 'yref': 'paper', + # 'x0': start, + # 'y0': 0, + # 'x1': end, + # 'y1': 1, + # 'fillcolor': 'rgba(128, 0, 128, 0.3)', + # 'line': {'width': 0} + # }) + # fig['layout'].update(shapes=shapes) + + #value = '{} amigos'.format(n_clicks) +# return fig, None #, f'You have entered: \n{value}' + +@app.callback( + [Output('timeseries-plot', 'selectedData'), + Output('timeseries-plot', 'figure', allow_duplicate=True)], + [Input('reset-flag-button', 'n_clicks'), + State('timeseries-plot', 'figure'), + State('memory-output', 'data')], + prevent_initial_call = True) +def clear_flag(n_clicks, fig, data): + + + + if n_clicks > 0 and data.get('data_loaded_flag', False): + # Clear selection + selected_data = None + fig['layout'].update({'dragmode': 'zoom', 'activeselection': None, + 'selections':{'line': None}}) + return selected_data, fig + else: + return dash.no_update, dash.no_update + +@callback(Output('flag-record', 'children'), + Input('commit-flag-button','n_clicks'), + State('timeseries-plot','selectedData'), + prevent_initial_call=True) + +def commit_flag(n_clicks,selected_Data): + + value = selected_Data + return f'You have entered: \n{value}' +if __name__ == '__main__': + app.run_server(debug=True)