chore: Update .gitignore and requirements.txt

feat: Add store_settings callback in ascan.py

feat: Refactor ascan.py, filtrage.py, and selection_filtre.py
This commit is contained in:
2024-06-11 10:54:56 +02:00
parent f99eeef6f6
commit dcb316d6b8
6 changed files with 377 additions and 41 deletions

3
.gitignore vendored
View File

@ -2,4 +2,5 @@
/electron-3d-app/
/express-app/
/react-app/
/.vscode/
/.vscode/
/3D_app.old/

5
3D_app/.gitignore vendored
View File

@ -1,2 +1,5 @@
__pycache__/
*.py[cod]
*.py[cod]
/cache/
/Dataset/saves/

View File

@ -1,7 +1,10 @@
import dash
from dash import dcc, html
from dash.dependencies import Input, Output
from dash import dcc, html, ALL, DiskcacheManager
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
from os import listdir, mkdir
from os.path import isfile, join
import diskcache
# on crée l'application
@ -11,8 +14,19 @@ app = dash.Dash(
use_pages=True,
)
cache = diskcache.Cache("./cache")
background_callback_manager = DiskcacheManager(cache)
print("Reloading...")
file_to_load = None
if "saves" not in listdir("Dataset"):
print("No saves folder, creating one for you...")
mkdir("Dataset/saves")
files = listdir("Dataset/saves")
# on lit le fichier modal.md pour le tuto
with open("assets/modal.md", "r") as f:
@ -35,7 +49,13 @@ modal_settings = dbc.Modal(
[
dbc.Switch(
id="use-real-values",
label="Use real values",
label="Use real values (not yet implemented)",
value=False,
className="me-2",
),
dbc.Switch(
id="apply-filters-everywhere",
label="Apply filters everywhere",
value=False,
className="me-2",
),
@ -93,6 +113,64 @@ modal_settings = dbc.Modal(
size="lg",
)
modal_open = dbc.Modal(
[
dbc.ModalHeader("Open a file"),
dbc.ModalBody(
[
dbc.ListGroup(
[
dbc.ListGroupItem(
f"{file}",
action=True,
style={"cursor": "pointer"},
id={"type": "file-item", "index": i},
)
for i, file in enumerate(files)
if isfile(join("Dataset/saves", file))
],
id="file-list",
)
]
),
dbc.ModalFooter(
[
dbc.Button("Refresh", id="open-refresh", color="success"),
dbc.Button("Close", id="open-close", className="open-bn"),
]
),
],
id="open-modal",
size="lg",
)
modal_save = dbc.Modal(
[
dbc.ModalHeader("Save a file"),
dbc.ModalBody(
[
dbc.Input(id="save-input", placeholder="Filename"),
dbc.RadioItems(
id="save-format",
options=[
{"label": "Save raw dataset", "value": "raw"},
{"label": "Save filtered dataset", "value": "filt"},
],
value="raw",
inline=True,
),
]
),
dbc.ModalFooter(
[
dbc.Button("Save", id="save-save", color="success"),
dbc.Button("Close", id="save-close", className="save-bn"),
]
),
],
id="save-modal",
)
# on défini les boutons de la navbar
button_gh = dbc.Button(
"Learn more",
@ -116,6 +194,22 @@ button_settings = dbc.Button(
outline=True,
color="success",
id="settings-open",
style={"textTransform": "none", "marginRight": "25px"},
)
button_open = dbc.Button(
"Open",
outline=True,
color="primary",
id="open-button",
style={"textTransform": "none", "marginRight": "10px"},
)
button_save = dbc.Button(
"Save",
outline=True,
color="success",
id="save-button",
style={"textTransform": "none"},
)
@ -202,6 +296,8 @@ nav_bar = dbc.Navbar(
dbc.NavItem(button_howto),
dbc.NavItem(button_gh),
dbc.NavItem(button_settings),
dbc.NavItem(button_open),
dbc.NavItem(button_save),
],
className="ml-auto",
navbar=True,
@ -214,6 +310,8 @@ nav_bar = dbc.Navbar(
),
modal_overlay,
modal_settings,
modal_open,
modal_save,
navmenu,
],
align="center",
@ -235,11 +333,14 @@ app.layout = dbc.Container(
id="store-settings",
data={
"use_real_values": False,
"use_filters": False,
"echantillonage_x": 1,
"echantillonage_y": 32,
"echantillonage_z": 1,
},
),
dcc.Store(id="store-filters", data={}),
dcc.Store(id="store-files"),
],
fluid=True,
)
@ -280,6 +381,74 @@ def toggle_offcanvas(n, is_open):
return is_open
@app.callback(
Output("open-modal", "is_open"),
[Input("open-button", "n_clicks"), Input("open-close", "n_clicks")],
[dash.dependencies.State("open-modal", "is_open")],
)
def toggle_open(n1, n2, is_open):
if n1 or n2:
return not is_open
return is_open
@app.callback(
Output("save-modal", "is_open"),
[Input("save-button", "n_clicks"), Input("save-close", "n_clicks")],
[dash.dependencies.State("save-modal", "is_open")],
)
def toggle_save(n1, n2, is_open):
if n1 or n2:
return not is_open
return is_open
@app.callback(
Output("file-list", "children"),
[Input("open-refresh", "n_clicks")],
)
def refresh_files(n):
files = listdir("Dataset/saves")
return [
dbc.ListGroupItem(
f"{file}",
action=True,
style={"cursor": "pointer"},
id={"type": "file-item", "index": i},
)
for i, file in enumerate(files)
if isfile(join("Dataset/saves", file))
]
@app.callback(
[Output("open-modal", "is_open", allow_duplicate=True), Output("store-files", "data")],
Input({"type": "file-item", "index": ALL}, "n_clicks"),
State({"type": "file-item", "index": ALL}, "children"),
prevent_initial_call=True,
)
def open_file(n, filenames):
ctx = dash.callback_context
if not ctx.triggered or all(click is None for click in n):
return [None, ""]
file_index = ctx.triggered[0]["prop_id"].split(".")[0]
file_index = eval(file_index)
filename = filenames[file_index['index']]
return [False, filename]
@app.callback(
Output("save-format", "options"),
[Input("store-filters", "data")],
)
def update_save_format(filters):
if filters:
return [
{"label": "Save raw dataset", "value": "raw"},
{"label": "Save filtered dataset", "value": "filt"},
]
return [
{"label": "Save raw dataset", "value": "raw"},
{"label": "Save filtered dataset", "value": "filt", "disabled": True},
]
# on lance l'application
if __name__ == "__main__":
app.run(debug=True, port="8051", threaded=True)

View File

@ -1,5 +1,5 @@
import dash
from dash import html, callback, Input, Output, dcc
from dash import html, callback, Input, Output, dcc, State
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import numpy as np
@ -308,6 +308,49 @@ layout = html.Div(
style={"padding": "20px"},
)
@callback(
Output("store-filters", "data"),
[
Input("select-ascan-filter1", "value"),
Input("select-ascan-filter2", "value"),
Input("select-ascan-filter3", "value"),
Input("input-ascan-solo-fs", "value"),
Input("input-ascan-solo-cutoff", "value"),
Input("input-ascan-solo-order", "value"),
Input("input-ascan-solo-windowsize", "value"),
Input("input-ascan-solo-fs-2", "value"),
Input("input-ascan-solo-cutoff-2", "value"),
Input("input-ascan-solo-order-2", "value"),
Input("input-ascan-solo-windowsize-2", "value"),
],
)
def store_settings(
select_filtre_1,
select_filtre_2,
select_filtre_3,
fs_filtre_1,
cutoff_filtre_1,
order_filtre_1,
windowsize_filtre_1,
fs_filtre_2,
cutoff_filtre_2,
order_filtre_2,
windowsize_filtre_2,
):
return {
"select_filtre_1": select_filtre_1,
"select_filtre_2": select_filtre_2,
"select_filtre_3": select_filtre_3,
"fs_filtre_1": fs_filtre_1,
"cutoff_filtre_1": cutoff_filtre_1,
"order_filtre_1": order_filtre_1,
"windowsize_filtre_1": windowsize_filtre_1,
"fs_filtre_2": fs_filtre_2,
"cutoff_filtre_2": cutoff_filtre_2,
"order_filtre_2": order_filtre_2,
"windowsize_filtre_2": windowsize_filtre_2,
}
# callback to update filter values
@callback(
@ -368,31 +411,33 @@ def update_filter_values(select_filtre_1, select_filtre_2):
Output("loading", "children"),
],
[
Input("select-ascan-filter1", "value"),
Input("select-ascan-filter2", "value"),
Input("select-ascan-filter3", "value"),
Input("layer-slider-ascan-solo-x", "value"),
Input("layer-slider-ascan-solo-y", "value"),
Input("layer-slider-ascan-solo-z", "value"),
Input("button-validate-filter", "n_clicks"),
Input("input-ascan-solo-fs", "value"),
Input("input-ascan-solo-cutoff", "value"),
Input("input-ascan-solo-order", "value"),
Input("input-ascan-solo-windowsize", "value"),
Input("input-ascan-solo-fs-2", "value"),
Input("input-ascan-solo-cutoff-2", "value"),
Input("input-ascan-solo-order-2", "value"),
Input("input-ascan-solo-windowsize-2", "value"),
],
[
State("select-ascan-filter1", "value"),
State("select-ascan-filter2", "value"),
State("select-ascan-filter3", "value"),
State("input-ascan-solo-fs", "value"),
State("input-ascan-solo-cutoff", "value"),
State("input-ascan-solo-order", "value"),
State("input-ascan-solo-windowsize", "value"),
State("input-ascan-solo-fs-2", "value"),
State("input-ascan-solo-cutoff-2", "value"),
State("input-ascan-solo-order-2", "value"),
State("input-ascan-solo-windowsize-2", "value"),
]
)
def update_heatmap_ascan(
selec_transforme_hilbert,
select_filtre_1,
select_filtre_2,
select_ascan_x,
select_ascan_y,
select_ascan_z,
n_clicks,
selec_transforme_hilbert,
select_filtre_1,
select_filtre_2,
fs_filtre_1,
cutoff_filtre_1,
order_filtre_1,

View File

@ -1,11 +1,13 @@
import dash
from dash import html, callback, Input, Output, dcc
from dash import html, callback, Input, Output, dcc, State, DiskcacheManager
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import numpy as np
import plotly.express as px
import plotly.io as pio
from util import *
from os.path import join
import diskcache
dash.register_page(__name__, path="/")
@ -15,6 +17,9 @@ fichiers_selectionnes = [
"Shear_x001-x101_y{:03d}_Rot00_transform.csv".format(i) for i in range(10, 14)
]
cache = diskcache.Cache("./cache")
background_callback_manager = DiskcacheManager(cache)
# on charge le fichier numpy
# fichiers = np.load("Dataset/npy/export.npy")
@ -465,13 +470,16 @@ layout = html.Div(
# on défini les callbacks
# callback pour le plot 3D
@callback(
[Output("3dplot", "figure"), Output("fade-3dplot", "is_in")],
[Output("3dplot", "figure")],
[
Input("iso-slider", "value"),
Input("y-slider", "value"),
Input("store-settings", "data"),
],
[dash.dependencies.State("fade-3dplot", "is_in")],
running=[
(Output("fade-3dplot", "is_in"), False, True),
],
)
def update_3dplot(iso_value, y_values, settings, is_in):
if settings["use_real_values"]:
@ -503,7 +511,7 @@ def update_3dplot(iso_value, y_values, settings, is_in):
)
)
return [fig, True]
return [fig]
# callback pour le plot 3D en plein écran
@ -539,18 +547,18 @@ def update_3dplot_fullscreen(iso_value, y_values):
# callback pour le A-scan
@callback(
[
Output("heatmap-ascan", "figure"),
Output("fade-ascan", "is_in"),
],
[Output("heatmap-ascan", "figure")],
[Input("layer-slider-bscan-zx", "value"), Input("layer-slider-bscan-xy", "value")],
[dash.dependencies.State("fade-ascan", "is_in")],
running=[
(Output("fade-ascan", "is_in"), False, True),
],
prevent_initial_call=True,
)
def update_heatmap_ascan(layer, layer1, is_in):
fig = px.line(y=volume[layer - 1, :, layer1], title="A-scan")
return [fig, True]
return [fig]
# callback pour le A-scan en plein écran
@ -568,10 +576,12 @@ def update_heatmap_ascan_fullscreen(layer):
[
Output("heatmap-bscan-zx", "figure"),
Output("store-bscan-zx-layer", "data"),
Output("fade-bscan-zx", "is_in"),
],
[Input("layer-slider-bscan-zx", "value")],
[dash.dependencies.State("fade-bscan-zx", "is_in")],
running=[
(Output("fade-bscan-xy", "is_in"), False, True),
],
prevent_initial_call=True,
)
def update_heatmap_bscan_zx(layer, is_in):
@ -582,7 +592,7 @@ def update_heatmap_bscan_zx(layer, is_in):
title="B-scan ZX",
)
return [fig, layer, True]
return [fig, layer]
# callback pour les B-scan ZX en plein écran
@ -603,20 +613,19 @@ def update_heatmap_bscan_zx_fullscreen(layer):
# callback pour les B-scan ZX
@callback(
[
Output("heatmap-bscan-xy", "figure"),
Output("store-bscan-xy-layer", "data"),
Output("fade-bscan-xy", "is_in"),
],
[Output("heatmap-bscan-xy", "figure"), Output("store-bscan-xy-layer", "data")],
[Input("layer-slider-bscan-xy", "value")],
[dash.dependencies.State("fade-bscan-xy", "is_in")],
running=[
(Output("fade-bscan-zx", "is_in"), False, True),
],
prevent_initial_call=True,
)
def update_heatmap_bscan_xy(layer, is_in):
fig = go.Figure(data=go.Heatmap(z=volume[:, :, layer], colorscale="Jet"))
fig.update_layout(title="B-scan XY")
return [fig, layer, True]
return [fig, layer]
# callback pour les B-scan ZX en plein écran
@ -772,25 +781,30 @@ def update_bscan_layers(bscan_xy, bscan_zx):
@callback(
[Output("store-settings", "data"), Output("settings-apply", "n_clicks")],
[
Input("use-real-values", "value"),
Input("echantillonage-x", "value"),
Input("echantillonage-y", "value"),
Input("echantillonage-z", "value"),
Input("settings-apply", "n_clicks"),
],
[
State("use-real-values", "value"),
State("apply-filters-everywhere", "value"),
State("echantillonage-x", "value"),
State("echantillonage-y", "value"),
State("echantillonage-z", "value"),
],
prevent_initial_call=True,
)
def update_settings(
clicks,
use_real_values,
apply_filters,
echantillonage_x_value,
echantillonage_y_value,
echantillonage_z_value,
clicks,
):
if clicks != None and clicks == 1:
return [
{
"use_real_values": use_real_values,
"use_filters": apply_filters,
"echantillonage_x": echantillonage_x_value,
"echantillonage_y": echantillonage_y_value,
"echantillonage_z": echantillonage_z_value,
@ -805,6 +819,11 @@ def update_settings(
Output("layer-slider-bscan-zx", "marks"),
Output("layer-slider-bscan-xy", "max"),
Output("layer-slider-bscan-xy", "marks"),
Output("iso-slider", "min"),
Output("iso-slider", "max"),
Output("iso-slider", "marks"),
Output("y-slider", "max"),
Output("y-slider", "marks"),
Output("settings-spinner", "children"),
],
Input("store-settings", "data"),
@ -825,5 +844,100 @@ def redef_data(data):
{str(i): str(i) for i in range(0, dim_x, max(1, int(dim_x / 20)))},
dim_z - 1,
{str(i): str(i) for i in range(1, dim_z + 1, max(1, int(dim_z / 20)))},
volume.min(),
volume.max() / 2,
{
str(i): str(i)
for i in range(
int(volume.min()),
int(volume.max() / 2) + 1,
int((volume.max() / 2 - volume.min()) / 10),
)
},
dim_y,
{str(i): str(i) for i in range(0, int(dim_y) + 1, max(1, int(dim_y / 20)))},
"Apply",
]
@callback(
[Input("store-filters", "data"), Input("store-settings", "data")],
)
def apply_filters(data, settings):
global volume
if settings["use_filters"]:
select_filtre_1 = data["select_filtre_2"]
select_filtre_2 = data["select_filtre_3"]
fs_filtre_1 = data["fs_filtre_1"]
cutoff_filtre_1 = data["cutoff_filtre_1"]
order_filtre_1 = data["order_filtre_1"]
windowsize_filtre_1 = data["windowsize_filtre_1"]
fs_filtre_2 = data["fs_filtre_2"]
cutoff_filtre_2 = data["cutoff_filtre_2"]
order_filtre_2 = data["order_filtre_2"]
windowsize_filtre_2 = data["windowsize_filtre_2"]
selec_transforme_hilbert = data["select_filtre_1"]
volume = pre_volume[
:: settings["echantillonage_x"],
:: settings["echantillonage_y"],
:: settings["echantillonage_z"],
]
data_avec_traitement = switch_case(volume, int(selec_transforme_hilbert))
data_avec_traitement = switch_case(
data_avec_traitement,
int(select_filtre_1),
int(fs_filtre_1),
int(cutoff_filtre_1),
int(order_filtre_1),
int(windowsize_filtre_1),
)
data_avec_traitement = switch_case(
data_avec_traitement,
int(select_filtre_2),
int(fs_filtre_2),
int(cutoff_filtre_2),
int(order_filtre_2),
int(windowsize_filtre_2),
)
volume = data_avec_traitement
else:
volume = pre_volume[
:: settings["echantillonage_x"],
:: settings["echantillonage_y"],
:: settings["echantillonage_z"],
]
return None
@callback(
Input("store-files", "data"),
State("store-settings", "data"),
prevent_initial_call=True,
)
def update_files(data, settings):
global pre_volume, dim_y
if data is None or data == "":
return None
pre_volume = np.load(join("Dataset/saves", data))
redef_data(settings)
update_3dplot(0, [0, dim_y / 2], settings, False)
update_heatmap_ascan(0, 0, False)
update_heatmap_bscan_zx(0, False)
update_heatmap_bscan_xy(0, False)
return None
@callback(
Input("save-save", "n_clicks"),
[State("save-input", "value"), State("save-format", "value")],
)
def save_data(n_clicks, filename, format):
if n_clicks is None:
return None
if format == "raw":
np.save(join("Dataset/saves", filename), pre_volume)
else:
np.save(join("Dataset/saves", filename), volume)
return None

View File

@ -1,8 +1,12 @@
dash==2.17.0
dash_bootstrap_components==1.6.0
diskcache==5.6.3
matplotlib==3.8.4
numpy==1.26.4
pandas==2.2.2
plotly==5.22.0
progress==1.6
python_igraph==0.11.5
scikit_learn==1.5.0
scipy==1.13.1
tqdm==4.66.4