Skip to content

Commit ddcf14e

Browse files
authored
Adding Settings Menu (#552)
* added basic construct of settings page * added selection of sections and forms for plot settings * moved settings file to own folder, added navigation to sidebar and small enhancements * Added footer and returning to last view after saving or canceling * small bugfix and refactoring * Fixed alignment * enabled saving and loading settings, split settings into sections and moved some methods * connected and enhanced plotly template, added small ui changes * minor changes * moved plot_template to settings folder * restructured plot settings html, enabled choosing a custom font * included file format from settings in plot download * added plot height and width into plot template * added plot settings preview * moved js code from <script> into own js file * moved plot preview in column, some formatting * adjusted spacing, added convertion from mm to px * fixed behaviour of unsaved changes modal * added default yamls as fallback templates * removed plot.yaml * adjusted gitignore for ignoring individual settings * small bugfix * added check if list of exported plots is empty * enhanced plot download by including scaling to experienced size, added some documentation * fixed broken tiff & eps download with plotly figures * removed general section, added temporary databases section * added font scaling to displayed plots * added requested changes * added tests * attempt to fix tests that were successful locally * removed unused imports * added requested changes
1 parent 02a7f08 commit ddcf14e

27 files changed

Lines changed: 803 additions & 98 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
user_data/runs/*
44
user_data/workflows/*
55
user_data/external_data/*
6+
user_data/settings/*
7+
!user_data/settings/plots_default.yaml
8+
!user_data/settings/databases_default.yaml
69
user_data/debug/*
710
ui/static/admin/*
811
ui/uploads/*

protzilla/constants/paths.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
USER_DATA_PATH = Path(PROJECT_PATH, "user_data")
55
RUNS_PATH = USER_DATA_PATH / "runs"
66
WORKFLOWS_PATH = USER_DATA_PATH / "workflows"
7+
SETTINGS_PATH = USER_DATA_PATH / "settings"
78
EXTERNAL_DATA_PATH = Path(PROJECT_PATH, "user_data/external_data")
89
WORKFLOW_META_PATH = Path(PROJECT_PATH, "protzilla/constants/workflow_meta.json")
910
UI_PATH = Path(PROJECT_PATH, "ui")

protzilla/data_preprocessing/plots.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
from protzilla.data_preprocessing.plots_helper import generate_tics
99
from protzilla.utilities import default_intensity_column
10-
from protzilla.utilities.plot_template import *
1110
from protzilla.constants.colors import *
1211

1312
def create_pie_plot(

protzilla/steps.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from typing import Literal
1111

1212
import pandas as pd
13-
import plotly
13+
import plotly.io as pio
14+
import plotly.graph_objects as go
1415
from PIL import Image
1516

1617
from protzilla.utilities import format_trace
@@ -284,33 +285,47 @@ def __repr__(self):
284285
def empty(self) -> bool:
285286
return len(self.plots) == 0
286287

287-
def export(self, format_):
288+
def export(self, settings: dict) -> list:
289+
"""
290+
Converts all plots from this step to files according to the format and size in the Plotly template.
291+
An exported plot is represented as BytesIO object containing binary image data.
292+
:param settings: Dict containing the plot settings.
293+
:return: List of all exported plots.
294+
"""
295+
from ui.settings.plot_template import get_scale_factor
288296
exports = []
297+
format_ = settings["file_format"]
298+
289299
for plot in self.plots:
290-
if isinstance(plot, plotly.graph_objs.Figure):
291-
if format_ in ["eps", "tiff"]:
292-
png_binary = plotly.io.to_image(plot, format="png", scale=4)
293-
img = Image.open(BytesIO(png_binary)).convert("RGB")
300+
scale_factor = get_scale_factor(plot, settings)
301+
# For Plotly GO Figure
302+
if isinstance(plot, go.Figure):
303+
if format_ in ["tiff", "eps"]:
304+
binary_png = pio.to_image(plot, format="png", scale=scale_factor)
305+
img = Image.open(BytesIO(binary_png)).convert("RGB")
294306
binary = BytesIO()
295307
if format_ == "tiff":
296308
img.save(binary, format="tiff", compression="tiff_lzw")
297-
else:
309+
elif format_ == "eps":
298310
img.save(binary, format=format_)
311+
binary.seek(0)
299312
exports.append(binary)
300313
else:
301-
binary_string = plotly.io.to_image(plot, format=format_, scale=4)
302-
exports.append(BytesIO(binary_string))
314+
binary_png = pio.to_image(plot, format=format_, scale=scale_factor)
315+
exports.append(BytesIO(binary_png))
303316
elif isinstance(plot, dict) and "plot_base64" in plot:
304317
plot = plot["plot_base64"]
305318

306-
if isinstance(plot, bytes): # base64 encoded plots
307-
if format_ in ["eps", "tiff"]:
319+
# TO DO: Include scale_factor here
320+
# For base64 encoded plot
321+
if isinstance(plot, bytes):
322+
if format_ in ["tiff", "eps"]:
308323
img = Image.open(BytesIO(base64.b64decode(plot))).convert("RGB")
309324
binary = BytesIO()
310325
if format_ == "tiff":
311326
img.save(binary, format="tiff", compression="tiff_lzw")
312-
else:
313-
img.save(binary, format=format_)
327+
elif format_ == "eps":
328+
img.save(binary, format="eps")
314329
binary.seek(0)
315330
exports.append(binary)
316331
elif format_ in ["png", "jpg"]:

protzilla/utilities/plot_template.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

protzilla/utilities/utilities.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import pandas as pd
1212
import psutil
1313

14+
from django.http import QueryDict
1415

1516
# recipie from https://docs.python.org/3/library/itertools.html
1617
def unique_justseen(iterable, key=None):
@@ -136,3 +137,47 @@ def get_file_name_from_upload_path(upload_path: str) -> str:
136137
base_name = file_name_randomized.split("_")[0]
137138
file_extension = file_name_randomized.split(".")[-1]
138139
return f"{base_name}.{file_extension}"
140+
141+
142+
def parameters_from_post(post: QueryDict) -> dict:
143+
"""
144+
Removes token from dict and converts the remaining entries into suitable data formats.
145+
:param post: Django dict containing POST data.
146+
:return: Dict containing the parameters in suitable formats.
147+
"""
148+
d = dict(post)
149+
if "csrfmiddlewaretoken" in d:
150+
del d["csrfmiddlewaretoken"]
151+
parameters = {}
152+
for k, v in d.items():
153+
if len(v) > 1:
154+
# only used for named_output parameters and multiselect fields
155+
parameters[k] = v
156+
else:
157+
parameters[k] = convert_str_if_possible(v[0])
158+
return parameters
159+
160+
161+
def convert_str_if_possible(s):
162+
"""
163+
Converts an input value into suitable representation as a string.
164+
:param s: Input value.
165+
:return: Converted input value.
166+
"""
167+
try:
168+
f = float(s)
169+
return int(f) if int(f) == f else f
170+
except ValueError:
171+
if s == "checked":
172+
# s is a checkbox
173+
return True
174+
if re.fullmatch(r"\d+(\.\d+)?(\|\d+(\.\d+)?)*", s):
175+
# s is a multi-numeric input e.g. 1-0.12-5
176+
numbers_str = re.findall(r"\d+(?:\.\d+)?", s)
177+
numbers = []
178+
for num in numbers_str:
179+
num = float(num)
180+
num = int(num) if int(num) == num else num
181+
numbers.append(num)
182+
return numbers
183+
return s

tests/ui/test_settings.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import pytest
2+
import plotly.graph_objects as go
3+
4+
from protzilla.constants.colors import PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR
5+
from protzilla.constants.paths import SETTINGS_PATH
6+
from ui.settings.plot_template import (
7+
determine_font,
8+
resize_for_display,
9+
get_scale_factor
10+
)
11+
12+
13+
@pytest.fixture
14+
def sample_params():
15+
return {
16+
"section_id": "plots",
17+
"file_format": "png",
18+
"width": 85,
19+
"height": 60,
20+
"custom_font": "Ubuntu Mono",
21+
"font": "Arial",
22+
"heading_size": 11,
23+
"text_size": 8
24+
}
25+
26+
def test_determine_font(sample_params):
27+
assert determine_font(sample_params) == "Arial"
28+
sample_params["font"] = "Custom"
29+
assert determine_font(sample_params) == "Ubuntu Mono"
30+
31+
def test_resize_for_display(sample_params):
32+
result = resize_for_display(sample_params)
33+
assert result["display_width"] == 600 # SCALED_WIDTH
34+
assert result["display_heading_size"] == 27
35+
36+
def test_get_scale_factor(sample_params):
37+
fig = go.Figure()
38+
fig.update_layout(width=600)
39+
scale = get_scale_factor(fig, sample_params)
40+
assert isinstance(scale, float)
41+
assert round(scale, 3) == 1.673

ui/main/settings.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"django.contrib.messages",
4848
"django.contrib.staticfiles",
4949
"runs",
50+
"settings",
5051
]
5152

5253
MIDDLEWARE = [
@@ -127,7 +128,10 @@
127128

128129
STATIC_URL = "static/"
129130

130-
STATICFILES_DIRS = [BASE_DIR / "static"]
131+
STATICFILES_DIRS = [
132+
BASE_DIR / "static",
133+
BASE_DIR / "settings/static"
134+
]
131135

132136
# Default primary key field type
133137
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field

ui/main/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
urlpatterns = [
2222
path("", views.index),
2323
path("runs/", include("runs.urls")),
24+
path("settings/", include("settings.urls")),
2425
path("databases", views.databases, name="databases"),
2526
path("databases/upload", views.database_upload, name="database_upload"),
2627
path("databases/delete", views.database_delete, name="database_delete"),

ui/main/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def index(request):
1919

2020

2121
def databases(request):
22+
request.session['last_view'] = "databases"
2223
databases = uniprot_databases()
2324
df_infos = {}
2425
if database_metadata_path.exists():

0 commit comments

Comments
 (0)