Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

019 functional load page wbaccinelli #23

Merged
merged 14 commits into from
Feb 23, 2024
15 changes: 11 additions & 4 deletions eit_dash/app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import dash_bootstrap_components as dbc
from dash import Dash
from .utils import data_singleton

# this is a shared object to use the data through different pages
# it is initialized here, and imported by the callbacks pages when needed
data_object = data_singleton.get_singleton()

FONT_AWESOME = "https://use.fontawesome.com/releases/v5.13.0/css/all.css"
external_stylesheets = [dbc.themes.BOOTSTRAP, FONT_AWESOME]
app = Dash(__name__,
use_pages=True,
external_stylesheets=external_stylesheets,
suppress_callback_exceptions=True)
app = Dash(
__name__,
use_pages=True,
external_stylesheets=external_stylesheets,
suppress_callback_exceptions=True,
)
278 changes: 235 additions & 43 deletions eit_dash/callbacks/load_callbacks.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,259 @@
import os
from pathlib import Path

import dash_bootstrap_components as dbc
from dash import Input, Output, State, callback, ctx, html
from dash import Input, Output, State, callback, ctx, html, ALL
from dash.exceptions import PreventUpdate

import eit_dash.definitions.element_ids as ids
from eit_dash.app import data_object
from eit_dash.definitions.option_lists import InputFiletypes
from eit_dash.utils.common import create_slider_figure, get_signal_options
from eitprocessing.eit_data import EITData
from eitprocessing.sequence import Sequence

import plotly.graph_objects as go

file_data: Sequence | None = None


def create_info_card(dataset: Sequence, file_type: int, dataset_name: str) -> dbc.Card:
"""
Create the card with the information on the loaded dataset
to be displayed in the Results section

Args:
dataset: Sequence object containing the selected dataset
file_type: index of the selected type of selected
"""
info_data = {
"Name": dataset.eit_data.path.name,
"n_frames": dataset.eit_data.nframes,
"start_time": dataset.eit_data.time[0],
"end_time": dataset.eit_data.time[-1],
"vendor": dataset.eit_data.vendor,
"path": str(dataset.eit_data.path),
}

card_list = [
html.H4(dataset_name, className="card-title"),
html.H6(InputFiletypes(file_type).name, className="card-subtitle"),
]
card_list += [
dbc.Row(f"{data}: {value}", style={"margin-left": 10})
for data, value in info_data.items()
]

card = dbc.Card(dbc.CardBody(card_list), id="card-1")

return card


# managing the file selection. Confirm button clicked
@callback(
[Output(ids.CHOOSE_DATA_POPUP, 'is_open'), Output(ids.NFILES_PLACEHOLDER, 'children')],
[Input(ids.ADD_DATA_BUTTON, 'n_clicks'), Input(ids.SELECT_FILES_BUTTON, 'n_clicks')],
prevent_initial_call=True
Output(ids.CHOOSE_DATA_POPUP, "is_open"),
Output(ids.NFILES_PLACEHOLDER, "children"),
Output(ids.ALERT_LOAD, "is_open"),
Input(ids.SELECT_FILES_BUTTON, "n_clicks"),
Input(ids.SELECT_CONFIRM_BUTTON, "n_clicks"),
State(ids.STORED_CWD, "data"),
State(ids.INPUT_TYPE_SELECTOR, "value"),
prevent_initial_call=True,
)
def open_modal(add_data_click, select_files_click): # pylint: disable=unused-argument
flag = False
# load the information selected from the file (e.g., signals, time span)
def load_selected_data(
select_file, confirm_select, file_path, file_type # pylint: disable=unused-argument
):
open_modal = True
data = None
show_alert = False
read_data_flag = False

trigger = ctx.triggered_id

# when the button for selecting the file has been clicked
if trigger == ids.SELECT_FILES_BUTTON:
flag = True
# if a file has been loaded already, the data should not be cancelled,
# unless a new file is loaded. if `data` is None, the selection is cancelled
data = file_path if file_data else None

return True, flag
# if the callback has not been triggered by the select files button,
# get the information on the selected file and try to read it
if trigger == ids.SELECT_CONFIRM_BUTTON:
path = Path(file_path)
extension = path.suffix if not path.name.startswith(".") else path.name

int_type = int(file_type)

# check if the file extension is compatible with the file type selected
if (int_type == InputFiletypes.Draeger.value and extension == ".bin") or (
int_type == InputFiletypes.Timpel.value
and extension == ".txt"
or (int_type == InputFiletypes.Sentec.value and extension == ".zri")
):
read_data_flag = True

# if the type check is ok, then close the file selector and read the data
if read_data_flag:
data = file_path
open_modal = False

# if it's not ok, then show an alert
else:
show_alert = True

return open_modal, data, show_alert


@callback(
Output(ids.DATA_SELECTOR_OPTIONS, 'hidden'),
Input(ids.NFILES_PLACEHOLDER, 'children'),
Output(ids.DATA_SELECTOR_OPTIONS, "hidden"),
Output(ids.CHECKBOX_SIGNALS, "options"),
Output(ids.FILE_LENGTH_SLIDER, "figure"),
Input(ids.NFILES_PLACEHOLDER, "children"),
Input(ids.LOAD_CANCEL_BUTTON, "n_clicks"),
State(ids.INPUT_TYPE_SELECTOR, "value"),
prevent_initial_call=True,
)
def load_file(flag):
if flag:
return False
return True
# read the file selected in the file selector
def open_data_selector(data, cancel_load, file_type): # pylint: disable=unused-argument
global file_data

trigger = ctx.triggered_id

# cancelled selection. Reset the data and turn of the data selector
if trigger == ids.LOAD_CANCEL_BUTTON:
data = None
file_data = None

if not data:
# this is needed, because a figure object must be returned for the graph, evn if empty
figure = go.Figure()
return True, [], figure

path = Path(data)
eit_data, continuous_data, sparse_data = EITData.from_path(
path,
vendor=InputFiletypes(int(file_type)).name.lower(),
return_non_eit_data=True,
)

file_data = Sequence(
eit_data=eit_data, continuous_data=continuous_data, sparse_data=sparse_data
)

options = get_signal_options(file_data)

figure = create_slider_figure(file_data)

return False, options, figure


@callback(
Output(ids.DATASET_CONTAINER, 'children'),
Input(ids.LOAD_CONFIRM_BUTTON, 'n_clicks'),
State(ids.DATASET_CONTAINER, 'children'),
State(ids.INPUT_TYPE_SELECTOR, 'value'),
Output(ids.DATASET_CONTAINER, "children"),
Input(ids.LOAD_CONFIRM_BUTTON, "n_clicks"),
State(ids.NFILES_PLACEHOLDER, "children"),
State(ids.DATASET_CONTAINER, "children"),
State(ids.INPUT_TYPE_SELECTOR, "value"),
State(ids.FILE_LENGTH_SLIDER, "relayoutData"),
State(ids.CHECKBOX_SIGNALS, "value"),
prevent_initial_call=True,
)
def show_info(confirm_click, container_state, filetype):
#TODO read data from file
#TODO read secondary input parameters from input selection

dummy_data = {
'N_signals': 3,
'duration': 12,
'n_frames': 234,
'filename': 'file.bin',
'etc': 'etc',
}
def show_info(
btn_click,
loaded_data, # pylint: disable=unused-argument, disable=too-many-arguments
container_state,
filetype,
slidebar_stat,
selected_signals,
):
if file_data:
if slidebar_stat is not None and "xaxis.range" in slidebar_stat:
start_sample = slidebar_stat["xaxis.range"][0]
stop_sample = slidebar_stat["xaxis.range"][1]
else:
start_sample = file_data.eit_data.time[0]
stop_sample = file_data.eit_data.time[-1]

card_list = [
html.H4(f'Dataset {confirm_click}', className="card-title"),
html.H6(InputFiletypes(int(filetype)).name, className="card-subtitle"),
]
card_list += [dbc.Row(f'{data}: {value}', style={'margin-left': 10}) for data, value in dummy_data.items()]

card = dbc.Card(
dbc.CardBody(card_list),
id=f'card-{confirm_click}'
)
if container_state:
container_state += [card]
else:
container_state = [card]
dataset_name = f"Dataset {data_object.get_list_length()}"

# TODO: adapt this to also continuous data
cut_data = Sequence(
label=dataset_name,
eit_data=file_data.eit_data.select_by_time(start_sample, stop_sample),
continuous_data=file_data.continuous_data,
)

# save the selected data in the singleton
data_object.add_sequence(cut_data)

# create the info summary card
card = create_info_card(cut_data, int(filetype), dataset_name)

# add the card to the current results
if container_state:
container_state += [card]
else:
container_state = [card]

return container_state


# file browser
@callback(
Output(ids.CWD, "children"),
Input(ids.STORED_CWD, "data"),
Input(ids.PARENT_DIR, "n_clicks"),
Input(ids.CWD, "children"),
prevent_initial_call=True,
)
def get_parent_directory(
stored_cwd, n_clicks, currentdir
): # pylint: disable=unused-argument
triggered_id = ctx.triggered_id
if triggered_id == ids.STORED_CWD:
return stored_cwd
parent = Path(currentdir).parent.as_posix()
return parent


@callback(Output(ids.CWD_FILES, "children"), Input(ids.CWD, "children"))
def list_cwd_files(cwd):
path = Path(cwd)

cwd_files = []
if path.is_dir():
files = sorted(os.listdir(path), key=str.lower)
for i, file in enumerate(files):
filepath = Path(file)

full_path = os.path.join(cwd, filepath.as_posix())

is_dir = Path(full_path).is_dir()
link = html.A(
[
html.Span(
file,
id={"type": "listed_file", "index": i},
title=full_path,
style={"fontWeight": "bold"} if is_dir else {},
)
],
href="#",
)
prepend = "" if not is_dir else "📂"
cwd_files.append(prepend)
cwd_files.append(link)
cwd_files.append(html.Br())
return cwd_files


@callback(
Output(ids.STORED_CWD, "data"),
Input({"type": "listed_file", "index": ALL}, "n_clicks"),
State({"type": "listed_file", "index": ALL}, "title"),
)
def store_clicked_file(n_clicks, title):
if not n_clicks or set(n_clicks) == {None}:
raise PreventUpdate
index = ctx.triggered_id["index"]
return title[index]
9 changes: 8 additions & 1 deletion eit_dash/definitions/element_ids.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
# load page
ADD_DATA_BUTTON = 'add-data-button'
ALERT_LOAD = 'alert-load'
CHECKBOX_SIGNALS = 'checkbox-signals'
CHOOSE_DATA_POPUP = 'choose-data-popup'
CWD = 'cwd'
CWD_FILES = 'cwd-files'
INPUT_TYPE_SELECTOR = 'input-type-selector'
SELECT_FILES_BUTTON = 'select-files-button'
METADATA = 'metadata'
CHECKBOX = 'checkbox'
FILE_LENGTH_SLIDER = 'file-length-slider'
DATA_SELECTOR_OPTIONS = 'data-selector-options'
NFILES_PLACEHOLDER = 'nfiles-placeholder'
DATASET_CONTAINER = 'dataset-container'
LOAD_CONFIRM_BUTTON = 'load-confirm-button'
LOAD_CANCEL_BUTTON = 'load-cancel-button'
PARENT_DIR = 'parent-dir'
STORED_CWD = 'stored-cwd'
SELECT_CONFIRM_BUTTON = 'select-confirm-button'

# preprocessing page
CONFIRM_RESAMPLING_BUTTON = 'confirm-resampling-button'
Expand Down
12 changes: 8 additions & 4 deletions eit_dash/definitions/layout_styles.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
CARD_FOOTER = {'textAlign': 'right'}
COLUMN_TITLE = {'textAlign': 'center'}
LOAD_RESULTS = {'textAlign': 'center'}
SUMMARY_ELEMENT = {'textAlign': 'center'}
BUTTONS_ROW = {"textAlign": "center", "margin-top": "30px"}
CARD_FOOTER = {"textAlign": "right"}
CHECKBOX_INPUT = {"margin-right": "10px"}
COLUMN_TITLE = {"textAlign": "center"}
FILE_BROWSER = {"height": 500, "overflow": "scroll"}
LOAD_RESULTS = {"textAlign": "center"}
SUMMARY_ELEMENT = {"textAlign": "center"}
SECTION_TITLE = {"margin-top": "50px"}
10 changes: 6 additions & 4 deletions eit_dash/definitions/option_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ class InputFiletypes(Enum):


class SignalSelections(Enum):
airway_pressure = 0
flow = 1
esophageal_pressure = 2
ignored = 3
raw = 0
airway_pressure = 1
flow = 2
esophageal_pressure = 3
volume = 4
CO2 = 5


class PeriodsSelectMethods(Enum):
Expand Down
Loading
Loading