diff --git a/dashboard/data/preprocess.py b/dashboard/data/preprocess.py index 4eca1560..6884b264 100644 --- a/dashboard/data/preprocess.py +++ b/dashboard/data/preprocess.py @@ -162,5 +162,5 @@ def calculate_concentration( :param summary_assay_volume: to divide by :return: dataframe """ - df["Concentration"] = df["Actual Volume_y"] * concetration / summary_assay_volume + df["Concentration"] = df["Actual Volume_1"] * concetration / summary_assay_volume return df diff --git a/dashboard/pages/correlation/callbacks.py b/dashboard/pages/correlation/callbacks.py index c459e021..a88e9f46 100644 --- a/dashboard/pages/correlation/callbacks.py +++ b/dashboard/pages/correlation/callbacks.py @@ -4,13 +4,12 @@ import json import uuid from datetime import datetime +from typing import Tuple import pandas as pd import pyarrow as pa -from dash import Input, Output, State, callback, html, no_update -from plotly import express as px +from dash import Input, Output, State, callback, dcc, html, no_update from plotly import graph_objects as go -import dash_bootstrap_components as dbc from dashboard.data import validation from dashboard.data.json_reader import load_data_from_json @@ -42,7 +41,7 @@ def on_file_upload( content: str | None, stored_uuid: str, file_storage: FileStorage, store_suffix: str -) -> tuple[html.I, str]: +) -> Tuple[html.I, str]: """ Callback for file upload. It saves the file to the storage and returns an icon indicating the status of the upload. @@ -144,7 +143,7 @@ def on_visualization_stage_entry( volume_value: int, stored_uuid: str, file_storage: FileStorage, -) -> tuple[go.Figure, go.Figure]: +) -> Tuple[go.Figure, go.Figure]: """ Callback for visualization stage entry. It loads the data from the storage and returns the figures. @@ -166,15 +165,19 @@ def on_visualization_stage_entry( df_secondary = pd.read_parquet( pa.BufferReader(file_storage.read_file(saved_name_2)) ) - df_merged = pd.merge(df_primary, df_secondary, on="EOS", how="inner") + df_merged = pd.merge( + df_primary, df_secondary, on="EOS", how="inner", suffixes=["_0", "_1"] + ) + df_merged.drop(["Unnamed: 0_0", "Unnamed: 0_1"], axis=1, inplace=True) df = calculate_concentration(df_merged, concentration_value, volume_value) + file_storage.save_file(f"{stored_uuid}_correlation_df.pq", df.to_parquet()) - feature = "% ACTIVATION" if "% ACTIVATION_x" in df.columns else "% INHIBITION" - concentration_fig = concentration_plot(df, feature[2:]) + feature = "% ACTIVATION" if "% ACTIVATION_0" in df.columns else "% INHIBITION" + concentration_fig = concentration_plot(df, feature[2:]) feature_fig = concentration_confirmatory_plot( - df[f"{feature}_x"], - df[f"{feature}_y"], + df[f"{feature}_0"], + df[f"{feature}_1"], df["Concentration"], f"{feature[2:]}", ) @@ -187,16 +190,75 @@ def on_visualization_stage_entry( full_html=False, include_plotlyjs="cdn" ), } - return feature_fig, concentration_fig, report_data_correlation_plots, False +def on_threshold_change( + threshold_1: float, + threshold_2: float, + stored_uuid: str, + file_storage: FileStorage, +): + """ + Callback for threshold update, updates the plot + + :param threshold_1: first threshold + :param threshold_2: second threshold + :param stored_uuid: session uuid + :param file_storage: file storage + :return: figures + """ + saved_name = f"{stored_uuid}_correlation_df.pq" + df = pd.read_parquet(pa.BufferReader(file_storage.read_file(saved_name))) + feature = "% ACTIVATION" if "% ACTIVATION_0" in df.columns else "% INHIBITION" + + new_fig = concentration_plot( + df, + feature[2:], + threshold_1, + threshold_2, + ) + return new_fig + + +def on_save_filtering_clicked( + n_clicks: int, + threshold_1: float, + threshold_2: float, + stored_uuid: str, + file_storage: FileStorage, +) -> None: + """ + Callback for the save filtered button + + :param n_clicks: number of clicks + :param threshold_1: first threshold + :param threshold_2: second threshold + :param stored_uuid: uuid of the stored data + :param file_storage: storage object + :return: None + """ + saved_name = f"{stored_uuid}_correlation_df.pq" + df = pd.read_parquet(pa.BufferReader(file_storage.read_file(saved_name))) + feature = "% ACTIVATION" if "% ACTIVATION_0" in df.columns else "% INHIBITION" + + filename = f"correlation_threshold_{datetime.now().strftime('%Y-%m-%d')}.csv" + df["> threshold_one"] = False + df.loc[df[f"{feature}_0"] > threshold_1, "> threshold_one"] = True + df["> threshold_two"] = False + df.loc[df[f"{feature}_0"] > threshold_2, "> threshold_two"] = True + + file_storage.save_file(f"{stored_uuid}_filtered_correlation_df.pq", df.to_parquet()) + + return dcc.send_data_frame(df.to_csv, filename) + + def on_visualization_stage_entry_load_settings( current_stage: int, concentration: float, volume: float, saved_data: dict, -) -> tuple[float, float]: +) -> Tuple[float, float]: """ Callback for visualization stage entry. Loads the data from local storage and update sliders value @@ -298,7 +360,7 @@ def register_callbacks(elements, file_storage: FileStorage): )(functools.partial(upload_settings_data)) callback( - Output("inhibition-graph", "figure"), + Output("feature-graph", "figure"), Output("concentration-graph", "figure"), Output("report-data-correlation-plots", "data"), Output({"type": elements["BLOCKER"], "index": 1}, "data"), @@ -308,6 +370,23 @@ def register_callbacks(elements, file_storage: FileStorage): State("user-uuid", "data"), )(functools.partial(on_visualization_stage_entry, file_storage=file_storage)) + callback( + Output("concentration-graph", "figure", allow_duplicate=True), + Input("activity-threshold-bottom-input", "value"), + Input("activity-threshold-top-input", "value"), + State("user-uuid", "data"), + prevent_initial_call=True, + )(functools.partial(on_threshold_change, file_storage=file_storage)) + + callback( + Output("download-filtered-csv", "data"), + Input("save-filtered-button", "n_clicks"), + State("activity-threshold-bottom-input", "value"), + State("activity-threshold-top-input", "value"), + State("user-uuid", "data"), + prevent_initial_call=True, + )(functools.partial(on_save_filtering_clicked, file_storage=file_storage)) + callback( Output("concentration-slider", "value"), Output("volume-slider", "value"), diff --git a/dashboard/pages/correlation/stages/s2_correlation_plots.py b/dashboard/pages/correlation/stages/s2_correlation_plots.py index 20f0da4e..73742c6e 100644 --- a/dashboard/pages/correlation/stages/s2_correlation_plots.py +++ b/dashboard/pages/correlation/stages/s2_correlation_plots.py @@ -1,6 +1,8 @@ from dash import html, dcc from dashboard.pages.components import annotate_with_tooltip +from dashboard.visualization.text_tables import make_download_button_text + CONCENTRATION_SLIDER_DESC = """ Choose the concentration to be used for the final compound concentration calculation that will be @@ -20,8 +22,14 @@ className="col", children=[ dcc.Loading( - id="loading-inhibition-graph", - children=[dcc.Graph(id="inhibition-graph")], + id="loading-feature-graph", + children=[ + dcc.Graph( + id="feature-graph", + className="six columns", + style={"width": "100%"}, + ) + ], type="circle", ) ], @@ -31,9 +39,75 @@ children=[ dcc.Loading( id="loading-concentration-graph", - children=[dcc.Graph(id="concentration-graph")], + children=[ + dcc.Graph( + id="concentration-graph", + className="six columns", + style={"width": "100%"}, + ) + ], type="circle", - ) + ), + html.Div( + className="row", + children=[ + html.Div( + className="col", + children=[ + html.Span( + children=[ + html.Label( + children="Set the first threshold", + className="form-label", + ), + dcc.Input( + id="activity-threshold-bottom-input", + type="number", + value=0, + min=-50, + className="form-control", + ), + ], + className="flex-grow-1", + ), + ], + ), + html.Div( + className="col", + children=[ + html.Span( + children=[ + html.Label( + children="Set the second threshold", + className="form-label", + ), + dcc.Input( + id="activity-threshold-top-input", + type="number", + value=100, + min=0, + className="form-control", + ), + ], + className="flex-grow-1", + ), + ], + ), + html.Div( + className="mt-3 mb-1 d-flex justify-content-center", + children=[ + html.Button( + make_download_button_text( + "Save filtered dataframe" + ), + className="btn btn-primary btn-lg btn-block btn-report", + id="save-filtered-button", + ), + dcc.Download(id="download-filtered-csv"), + ], + ), + ], + ), ], ), ], diff --git a/dashboard/visualization/plots.py b/dashboard/visualization/plots.py index f44f3318..654f1099 100644 --- a/dashboard/visualization/plots.py +++ b/dashboard/visualization/plots.py @@ -598,7 +598,9 @@ def concentration_confirmatory_plot( return fig -def concentration_plot(df: pd.DataFrame, reaction_type: str) -> go.Figure: +def concentration_plot( + df: pd.DataFrame, reaction_type: str, line_1: float = 0, line_2: float = 100 +) -> go.Figure: """ Plot activation/inhibition values for each compound by concentration @@ -608,7 +610,7 @@ def concentration_plot(df: pd.DataFrame, reaction_type: str) -> go.Figure: """ fig = go.Figure() # NOTE: to clarify - value_by_conc = df.pivot_table(f"% {reaction_type}_x", "EOS", "Concentration") + value_by_conc = df.pivot_table(f"% {reaction_type}_0", "EOS", "Concentration") for _, row in value_by_conc.iterrows(): fig.add_trace( go.Scatter( @@ -621,6 +623,8 @@ def concentration_plot(df: pd.DataFrame, reaction_type: str) -> go.Figure: text=[str(row.name), str(row.name), str(row.name)], ) ) + fig.add_hline(y=line_1, line_dash="dash") + fig.add_hline(y=line_2, line_dash="dash") fig.update_layout( title_text="Concentrations", xaxis_title="Concentration [uM]",