Skip to content

Commit

Permalink
Add reproducibility (#177)
Browse files Browse the repository at this point in the history
* Reproducing Screening and Correlation

* Reproducing Hit Validation

* Remove checkbox changning

* Change gitignore

* Add alerts

* Resolve
  • Loading branch information
AndrzejKaj authored Nov 29, 2023
1 parent 0c37bf2 commit 0037dad
Show file tree
Hide file tree
Showing 11 changed files with 456 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ tmp/
**/out/

# Data
data
./data
data/raw/*
!data/raw/.gitkeep
notebooks/data
Expand Down
21 changes: 21 additions & 0 deletions dashboard/data/json_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import base64
import io
import json


def load_data_from_json(content: str | None, name: str | None) -> dict | None:
if content is None:
return None
file = None

_, extension = name.split(".")
if extension == "json":
_, content_string = content.split(",")
decoded = base64.b64decode(content_string)
file = io.StringIO(decoded.decode("utf-8"))

loaded_data = None
if file:
loaded_data = json.load(file)

return loaded_data
3 changes: 3 additions & 0 deletions dashboard/pages/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
dcc.Store(id="report-data-hit-validation-input", storage_type="local"),
dcc.Store(id="report-data-hit-validation-hit-browser", storage_type="local"),
dcc.Store(id="activation-inhibition-screening-options", storage_type="local"),
dcc.Store(id="loaded-setings-screening", storage_type="local"),
dcc.Store(id="loaded-setings-correlation", storage_type="local"),
dcc.Store(id="loaded-setings-hit-validation", storage_type="local"),
],
)

Expand Down
80 changes: 79 additions & 1 deletion dashboard/pages/correlation/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
from dash import Input, Output, State, callback, html, no_update
from plotly import express as px
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
from dashboard.data.preprocess import calculate_concentration
from dashboard.storage import FileStorage
from dashboard.visualization.plots import (
Expand Down Expand Up @@ -111,6 +113,28 @@ def on_both_files_uploaded(
return ICON_OK, False


def upload_settings_data(content: str | None, name: str | None) -> dict:
"""
Callback for file upload. It saves the in local storage for other components.
:param content: base64 encoded file content
:param name: filename
:return: dict with loaded data
"""
if not content:
return no_update
loaded_data = load_data_from_json(content, name)
color = "success"
text = "Settings uploaded successfully"
settings_keys = ["concentration_value", "volume_value"]
if loaded_data == None or not set(settings_keys).issubset(loaded_data.keys()):
color = "danger"
text = (
f"Invalid settings uploaded: the file should contain {settings_keys} keys."
)
return loaded_data, True, html.Span(text), color, no_update


# === STAGE 2 ===


Expand Down Expand Up @@ -167,6 +191,35 @@ def on_visualization_stage_entry(
return feature_fig, concentration_fig, report_data_correlation_plots, False


def on_visualization_stage_entry_load_settings(
current_stage: int,
concentration: float,
volume: float,
saved_data: dict,
) -> tuple[float, float]:
"""
Callback for visualization stage entry.
Loads the data from local storage and update sliders value
:param current_stage: current stage index of the process
:param concentration: concentration slider value
:param volume: volume slider value
:return: value for concentration slider
:return: value for volume slider
"""

if current_stage != 1:
return no_update

concentration_value = concentration
volume_value = volume
if saved_data != None:
concentration_value = saved_data["concentration_value"]
volume_value = saved_data["volume_value"]

return concentration_value, volume_value


# === STAGE 3 ===


Expand All @@ -177,7 +230,11 @@ def on_json_generate_button_click(
filename = (
f"correlation_analysis_settings_{datetime.now().strftime('%Y-%m-%d')}.json"
)
json_object = json.dumps(correlation_plots_report, indent=4)
data_to_save = {
"concentration_value": correlation_plots_report["concentration_value"],
"volume_value": correlation_plots_report["volume_value"],
}
json_object = json.dumps(data_to_save, indent=4)
return dict(content=json_object, filename=filename)


Expand Down Expand Up @@ -229,6 +286,17 @@ def register_callbacks(elements, file_storage: FileStorage):
State("user-uuid", "data"),
)(functools.partial(on_both_files_uploaded, file_storage=file_storage))

callback(
Output("loaded-setings-correlation", "data"),
Output("alert-upload-settings-correlation", "is_open"),
Output("alert-upload-settings-correlation-text", "children"),
Output("alert-upload-settings-correlation", "color"),
Output("dummy-upload-settings-correlation", "children"),
Input("upload-settings-correlation", "contents"),
Input("upload-settings-correlation", "filename"),
prevent_initial_call=True,
)(functools.partial(upload_settings_data))

callback(
Output("inhibition-graph", "figure"),
Output("concentration-graph", "figure"),
Expand All @@ -239,6 +307,16 @@ def register_callbacks(elements, file_storage: FileStorage):
Input("volume-slider", "value"),
State("user-uuid", "data"),
)(functools.partial(on_visualization_stage_entry, file_storage=file_storage))

callback(
Output("concentration-slider", "value"),
Output("volume-slider", "value"),
Input(elements["STAGES_STORE"], "data"),
State("concentration-slider", "value"),
State("volume-slider", "value"),
State("loaded-setings-correlation", "data"),
)(functools.partial(on_visualization_stage_entry_load_settings))

callback(
Output("download-json-settings-correlation", "data"),
Input("generate-json-button", "n_clicks"),
Expand Down
38 changes: 38 additions & 0 deletions dashboard/pages/correlation/stages/s1_correlation_files_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from dashboard.pages.components import annotate_with_tooltip

import dash_bootstrap_components as dbc

DESC = [
html.Span(
"""
Expand Down Expand Up @@ -73,6 +75,42 @@
),
],
),
html.Div(
className="flex-grow-1",
children=[
html.H5("Settings File"),
dcc.Loading(
children=[
dcc.Upload(
id="upload-settings-correlation",
accept=".json",
children=html.Div(
[
"Drag and Drop or ",
html.A("Select", className="select-file"),
" Settings for correlation analysis",
]
),
multiple=False,
className="text-center upload-box",
),
html.Div(
id="dummy-upload-settings-correlation",
className="p-1",
),
],
type="circle",
),
dbc.Alert(
html.Div(id="alert-upload-settings-correlation-text"),
id="alert-upload-settings-correlation",
dismissable=True,
is_open=False,
duration=4000,
className="m-1",
),
],
),
],
)

Expand Down
82 changes: 82 additions & 0 deletions dashboard/pages/hit_validation/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
from dashboard.storage import FileStorage
from dashboard.visualization.plots import plot_ic50, plot_smiles

from dashboard.data.json_reader import load_data_from_json

SCREENING_FILENAME = "{0}_screening_df.pq"
HIT_FILENAME = "{0}_hit_df.pq"

Expand Down Expand Up @@ -177,6 +179,68 @@ def on_file_upload(
)


def upload_settings_data(
content: str | None,
name: str | None,
concentration_lower_bound: float,
concentration_upper_bound: float,
top_lower_bound: float,
top_upper_bound: float,
) -> tuple[float, float, float, float]:
"""
Callback for file upload. It update concentration lower bound,
concentration upper bound, top lower bound, top upper bound
:param content: base64 encoded file content
:param name: filename
:param concentration_lower_bound: concentration lower bound
:param concentration_upper_bound: concentration upper bound
:param top_lower_bound: top lower bound
:param top_upper_bound: top upper bound
:return: concentration lower bound
:return: concentration upper bound
:return: top lower bound
:return: top_upper_bound
"""
if not content:
return no_update
loaded_data = load_data_from_json(content, name)
settings_keys = [
"concentration_lower_bound",
"concentration_upper_bound",
"top_lower_bound",
"top_upper_bound",
]
if loaded_data == None or not set(settings_keys).issubset(loaded_data.keys()):
concentration_lower_bound_value = concentration_lower_bound
concentration_upper_bound_value = concentration_upper_bound
top_lower_bound_value = top_lower_bound
top_upper_bound_value = top_upper_bound
color = "danger"
text = (
f"Invalid settings uploaded: the file should contain {settings_keys} keys."
)

else:
concentration_lower_bound_value = loaded_data["concentration_lower_bound"]
concentration_upper_bound_value = loaded_data["concentration_upper_bound"]
top_lower_bound_value = loaded_data["top_lower_bound"]
top_upper_bound_value = loaded_data["top_upper_bound"]
color = "success"
text = "Settings uploaded successfully"

return (
concentration_lower_bound_value,
concentration_upper_bound_value,
top_lower_bound_value,
top_upper_bound_value,
True,
html.Span(text),
color,
no_update,
)


def on_bounds_change(
lower_bound: float, upper_bound: float
) -> tuple[float, float, html.Div]:
Expand Down Expand Up @@ -451,6 +515,24 @@ def register_callbacks(elements, file_storage: FileStorage):
prevent_initial_call="initial_duplicate",
)(functools.partial(on_file_upload, file_storage=file_storage))

callback(
Output("concentration-lower-bound-input", "value"),
Output("concentration-upper-bound-input", "value"),
Output("top-lower-bound-input", "value"),
Output("top-upper-bound-input", "value"),
Output("alert-upload-settings-hit-validation", "is_open"),
Output("alert-upload-settings-hit-validation-text", "children"),
Output("alert-upload-settings-hit-validation", "color"),
Output("dummy-upload-settings-hit-validation", "children"),
Input("upload-settings-hit-validation", "contents"),
Input("upload-settings-hit-validation", "filename"),
State("concentration-lower-bound-input", "value"),
State("concentration-upper-bound-input", "value"),
State("top-lower-bound-input", "value"),
State("top-upper-bound-input", "value"),
prevent_initial_call=True,
)(functools.partial(upload_settings_data))

callback(
Output("concentration-lower-bound-store", "data"),
Output("concentration-upper-bound-store", "data"),
Expand Down
Loading

0 comments on commit 0037dad

Please sign in to comment.