dashboard
pacsanini.dashboard.app
#
The main application dashboard.
control_card()
#
Return the div that contains all the control buttons/inputs to filter data.
Source code in pacsanini/dashboard/app.py
def control_card() -> html.Div:
"""Return the div that contains all the control buttons/inputs to filter data."""
return html.Div(
id="control-card",
children=[
html.P("Select Manufacturer"),
dcc.Dropdown(
id="manufacturer-select",
options=[{"label": i, "value": i} for i in app_env.manufacturers],
value=0,
multi=True,
),
html.Br(),
html.P("Select Date Range"),
dcc.DatePickerRange(
id="date-range-picker",
start_date=date(2010, 1, 1),
end_date=datetime.utcnow().date(),
display_format="YYYY MMM DD",
clearable=True,
),
html.Br(),
html.Br(),
html.P("Select Study Image Count"),
dcc.RangeSlider(
id="image-range-picker",
min=0,
max=20,
step=1,
value=[0, 5],
marks={i: f"{i} images" for i in range(0, 21, 10)},
tooltip={"placement": "top", "always_visible": True},
),
html.P("Select Patient Age"),
dcc.RangeSlider(
id="patient-age-range-picker",
min=0,
max=100,
step=5,
value=[0, 80],
marks={i: str(i) for i in range(0, 100, 20)},
tooltip={"placement": "top", "always_visible": True},
),
html.Br(),
],
)
counts_card()
#
Return the div that contains the overall count of patients/studies/images.
Source code in pacsanini/dashboard/app.py
def counts_card() -> html.Div:
"""Return the div that contains the overall count of patients/studies/images."""
return html.Div(
className="row",
children=[
html.Div(
className="four columns",
children=[
html.Div(
className="card gold-left-border",
children=html.Div(
className="container",
children=[
html.H4(id="patient-count", children=""),
html.P(children="patients"),
],
),
)
],
),
html.Div(
className="four columns",
children=[
html.Div(
className="card green-left-border",
children=html.Div(
className="container",
children=[
html.H4(id="study-count", children=""),
html.P(children="studies"),
],
),
)
],
),
html.Div(
className="four columns",
children=[
html.Div(
className="card purple-left-border",
children=html.Div(
className="container",
children=[
html.H4(id="image-count", children=""),
html.P(children="images"),
],
),
)
],
),
],
)
description_card()
#
Return a div containing the dashboard title and descriptions.
Source code in pacsanini/dashboard/app.py
def description_card() -> html.Div:
"""Return a div containing the dashboard title and descriptions."""
return html.Div(
id="description-card",
children=[
html.H5("Pacsanini Dashboard 🎻"),
html.Div(
id="intro",
children=(
"Welcome to the pacsanini dashboard."
" Explore the available DICOM data according to the filters you come up with."
),
),
],
)
generate_study_metadata_plot(data)
#
Recompute and return the bar plot.
Source code in pacsanini/dashboard/app.py
def generate_study_metadata_plot(data: pd.DataFrame) -> Any:
"""Recompute and return the bar plot."""
data["study_year"] = data["study_date"].apply(lambda x: x.year)
year_grp = data.groupby(["study_year", "manufacturer"])
yearly_patients = year_grp["patient_id"].nunique().reset_index()
yearly_studies = year_grp["study_uid"].nunique().reset_index()
yearly_images = year_grp["image_count"].sum().reset_index()
years = yearly_images["study_year"]
fig = go.Figure(
data=[
go.Bar(
name="patients",
x=years,
y=yearly_patients["patient_id"],
textposition="none",
text=yearly_studies["manufacturer"],
hovertemplate="%{x} %{text} patients: %{y}",
marker_color="#DA9422",
),
go.Bar(
name="studies",
x=years,
y=yearly_studies["study_uid"],
textposition="none",
text=yearly_studies["manufacturer"],
hovertemplate="%{x} %{text} studies: %{y}",
marker_color="#22D892",
),
go.Bar(
name="images",
x=years,
y=yearly_images["image_count"],
textposition="none",
text=yearly_images["manufacturer"],
hovertemplate="%{x} %{text} images: %{y}",
marker_color="#9222D8",
),
]
)
fig.update_layout(
title="Patient, Study and Image counts per year",
legend_title_text="Count type",
barmode="group",
xaxis_title="year",
yaxis_title="counts",
xaxis=dict(tick0=data["study_year"].min(), dtick=1),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
)
return fig
graph_card()
#
Return an empty bar plot that will be initialized on startup.
Source code in pacsanini/dashboard/app.py
def graph_card() -> html.Div:
"""Return an empty bar plot that will be initialized on startup."""
fig = px.bar()
return html.Div(children=[dcc.Graph(id="data-overview", figure=fig)])
load_manufacturer_options(_)
#
Load the different available manufacturers on application startup.
Source code in pacsanini/dashboard/app.py
@app.callback(
Output("manufacturer-select", "options"), Input("control-card", "children")
)
def load_manufacturer_options(_):
"""Load the different available manufacturers on application startup."""
if not app_env.manufacturers:
DBSession = sessionmaker(bind=app_env.engine)
session = DBSession()
available_manufacturers = [
res[0] for res in session.query(ManufacturerView.manufacturer).all()
]
app_env.manufacturers = available_manufacturers
else:
available_manufacturers = app_env.manufacturers
return [{"label": manu, "value": manu} for manu in available_manufacturers]
run_server(config, port=8050, debug=False)
#
Run the dashboard server using the provided configuration to access
the database backend.
Running the server will block the current thread until it is terminated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config |
PacsaniniConfig |
The pacsanini configuration to use for spawning the dashboard server. |
required |
port |
int |
The port to use for the server. Defaults to 8050. |
8050 |
debug |
bool |
Whether the launch the dashboard in debug mode or not. The default is False. |
False |
Source code in pacsanini/dashboard/app.py
def run_server(config: PacsaniniConfig, port: int = 8050, debug: bool = False):
"""Run the dashboard server using the provided configuration to access
the database backend.
Running the server will block the current thread until it is terminated.
Parameters
----------
config : PacsaniniConfig
The pacsanini configuration to use for spawning the dashboard server.
port : int
The port to use for the server. Defaults to 8050.
debug : bool
Whether the launch the dashboard in debug mode or not. The default
is False.
"""
engine = None
session = None
try:
engine = create_engine(config.storage.resources)
app_env.engine = engine
DBSession = sessionmaker(bind=app_env.engine)
session = DBSession()
available_manufacturers = [
res[0] for res in session.query(ManufacturerView.manufacturer).all()
]
app_env.manufacturers = available_manufacturers
app.run_server(port=port, debug=debug)
finally:
if session:
session.close()
if engine:
engine.dispose()
update_query_results(manufacturer, start_date, end_date, image_range, patient_age)
#
Update the patient, study, and image counts based on the provided
control card input values. Regenerate the graph as well.
Source code in pacsanini/dashboard/app.py
@app.callback(
[
Output("patient-count", "children"),
Output("study-count", "children"),
Output("image-count", "children"),
Output("data-overview", "figure"),
],
[
Input("manufacturer-select", "value"),
Input("date-range-picker", "start_date"),
Input("date-range-picker", "end_date"),
Input("image-range-picker", "value"),
Input("patient-age-range-picker", "value"),
],
)
def update_query_results(manufacturer, start_date, end_date, image_range, patient_age):
"""Update the patient, study, and image counts based on the provided
control card input values. Regenerate the graph as well.
"""
DBSession = sessionmaker(bind=app_env.engine)
session = DBSession()
query = session.query(StudyMetaView)
if manufacturer:
if isinstance(manufacturer, str):
query = query.filter(StudyMetaView.manufacturer == manufacturer)
else:
query = query.filter(StudyMetaView.manufacturer.in_(manufacturer))
if start_date and end_date:
start_date_obj = date.fromisoformat(start_date)
end_date_obj = date.fromisoformat(end_date)
query = query.filter(
and_(
StudyMetaView.study_date >= start_date_obj,
StudyMetaView.study_date <= end_date_obj,
)
)
elif start_date:
start_date_obj = date.fromisoformat(start_date)
query = query.filter(StudyMetaView.study_date >= start_date_obj)
elif end_date:
end_date_obj = date.fromisoformat(end_date)
query = query.filter(StudyMetaView.study_date <= end_date_obj)
patient_age_lower = patient_age[0]
patient_age_upper = patient_age[1]
if patient_age_lower == patient_age_upper:
query = query.filter(StudyMetaView.patient_age == patient_age_upper)
elif patient_age_lower == 0:
query = query.filter(StudyMetaView.patient_age <= patient_age_upper)
else:
query = query.filter(
and_(
StudyMetaView.patient_age >= patient_age_lower,
StudyMetaView.patient_age <= patient_age_upper,
)
)
image_count_lower = image_range[0]
image_count_upper = image_range[1]
if image_count_lower == image_count_upper:
query = query.filter(StudyMetaView.image_count == image_count_upper)
elif image_count_lower == 0:
query = query.filter(StudyMetaView.image_count <= image_count_upper)
else:
query = query.filter(
and_(
StudyMetaView.image_count >= image_count_lower,
StudyMetaView.image_count <= image_count_upper,
)
)
results = pd.read_sql_query(query.statement, app_env.engine.connect())
return (
f'{results["patient_id"].nunique():,}',
f'{results["study_uid"].nunique():,}',
f'{results["image_count"].sum():,}',
generate_study_metadata_plot(results),
)