From bdbb75cca8db46e10947061a0a5e323f8784e814 Mon Sep 17 00:00:00 2001 From: Le Stagiaire Date: Wed, 26 Jun 2024 13:27:34 +0200 Subject: [PATCH] V4.2 chore: Refactor home callbacks to be more readable. feat: Implementation of slider control --- .gitignore | 1 + 3D_app/GNG_3D.py | 258 ++++++++----- 3D_app/README.md | 5 + 3D_app/callbacks/home.py | 775 +++++++++++++++++++++++++++++++++++---- 3D_app/import_sqlite.py | 31 +- 3D_app/main.py | 22 +- 3D_app/pages/ascan.py | 2 +- 3D_app/pages/home.py | 621 ++++++++++++++++++++++++------- 3D_app/requirements.txt | 3 +- 9 files changed, 1408 insertions(+), 310 deletions(-) diff --git a/.gitignore b/.gitignore index 6d559b8..ffe4c83 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /.vscode/ /3D_app.old/ 3D_app/data.sqlite +1acquisition-ultrasons-cale-tofd_2024-06-07_0851/ diff --git a/3D_app/GNG_3D.py b/3D_app/GNG_3D.py index 99fceca..9fd0e73 100644 --- a/3D_app/GNG_3D.py +++ b/3D_app/GNG_3D.py @@ -1,31 +1,29 @@ import matplotlib.pyplot as plt -from mpl_toolkits.mplot3d import Axes3D import numpy as np -from sklearn.datasets import make_swiss_roll -import igraph as ig -from numpy.linalg import norm -from sklearn.datasets import make_moons +import igraph as ig +from numpy.linalg import norm from tqdm import tqdm -from sys import stdout + +# import cupy as np + class GrowingNeuralGas: - + def __init__(self, max_neurons, max_iter, max_age, eb, en, alpha, beta, l, dataset): - - ''' + """ --------------------------------------------- Growing Neural Gas' Parameter Declarations --------------------------------------------- - 1. max_iter ; Maximum # of iterations the - 2. max_age ; Maximum # of age + 1. max_iter ; Maximum # of iterations the + 2. max_age ; Maximum # of age 3. eb ; fraction of distance betweeen nearest neuron and input signal 4. en ; fraction of distance betweeen neighboring neurons and input signal 5. alpha ; multiplying scalar for local error 6. beta ; multiplying scalar for global error - 7. l + 7. l 8. dataset - ''' - + """ + # Parameters declared by user self.max_neurons = max_neurons self.max_iter = max_iter @@ -37,15 +35,15 @@ class GrowingNeuralGas: self.l = l self.dataset_original = dataset.copy() self.dataset = dataset.copy() - + # Variable for tracking learning evolution self.verts_evolve = [] self.edges_evolve = [] - + def initialize_gng(self): - ''' + """ Initialize Growing Neural Gas - ''' + """ # Get random datapoints from target dataset t0 = np.random.randint(0, int(self.dataset.shape[0] / 2)) t1 = np.random.randint(int(self.dataset.shape[0] / 2), self.dataset.shape[0]) @@ -54,45 +52,60 @@ class GrowingNeuralGas: self.gng.add_vertex(weight=self.dataset[t0, :].astype(np.float64), error=0) self.gng.add_vertex(weight=self.dataset[t1, :].astype(np.float64), error=0) self.gng.add_edge(0, 1, age=0) - + def learning_position(self): for _ in range(self.l): # Step 1. Get a random datapoint from target dataset t = np.random.randint(0, self.dataset.shape[0]) random_input = self.dataset[t, :] - + # Step 2. Find 2 nearest neuron from random_input - nearest_index = np.array([norm(weight[:2] - random_input[:2])**2 for weight in self.gng.vs['weight']]).argsort() + nearest_index = np.array( + [ + norm(weight[:2] - random_input[:2]) ** 2 + for weight in self.gng.vs["weight"] + ] + ).argsort() neuron_s1 = self.gng.vs[nearest_index[0]] - neuron_s2 = self.gng.vs[nearest_index[1]] + neuron_s2 = self.gng.vs[nearest_index[1]] # Step 3. Increase the age of all neighboring edges from nearest neuron (neuron_s1) for edge_id in self.gng.incident(neuron_s1.index): - self.gng.es[edge_id]['age'] += 1 + self.gng.es[edge_id]["age"] += 1 # Step 4. Add error to the nearest neuron - self.gng.vs[neuron_s1.index]['error'] += norm(neuron_s1['weight'][:2] - random_input[:2]) + self.gng.vs[neuron_s1.index]["error"] += norm( + neuron_s1["weight"][:2] - random_input[:2] + ) # Step 5.1. Update position of nearest neuron - neuron_s1['weight'][:2] += (self.eb * (random_input[:2] - neuron_s1['weight'][:2])) - neuron_s1['weight'][2] += (self.eb * (random_input[2] - neuron_s1['weight'][2])) + neuron_s1["weight"][:2] += self.eb * ( + random_input[:2] - neuron_s1["weight"][:2] + ) + neuron_s1["weight"][2] += self.eb * ( + random_input[2] - neuron_s1["weight"][2] + ) # Step 5.2. Update position of nearest neuron's neighbors for neuron in self.gng.vs[self.gng.neighbors(neuron_s1.index)]: - neuron['weight'][:2] += (self.en * (random_input[:2] - neuron['weight'][:2])) - neuron['weight'][2] += (self.en * (random_input[2] - neuron['weight'][2])) + neuron["weight"][:2] += self.en * ( + random_input[:2] - neuron["weight"][:2] + ) + neuron["weight"][2] += self.en * (random_input[2] - neuron["weight"][2]) # Step 6. Update edge of nearest neurons - EDGE_FLAG = self.gng.get_eid(neuron_s1.index, neuron_s2.index, directed=False, error=False) + EDGE_FLAG = self.gng.get_eid( + neuron_s1.index, neuron_s2.index, directed=False, error=False + ) if EDGE_FLAG == -1: self.gng.add_edge(neuron_s1.index, neuron_s2.index, age=0) else: - self.gng.es[EDGE_FLAG]['age'] = 0 + self.gng.es[EDGE_FLAG]["age"] = 0 # Step 7.1. Delete aging edge for edge in self.gng.es: src = edge.source tgt = edge.target - if edge['age'] > self.max_age: + if edge["age"] > self.max_age: self.gng.delete_edges(edge.index) # Step 7.2. Delete isolated neuron @@ -102,7 +115,7 @@ class GrowingNeuralGas: # Step 8. Reduce global error for neuron in self.gng.vs: - neuron['error'] *= self.beta + neuron["error"] *= self.beta # Step 9.1. Remove generated random input from target dataset self.dataset = np.delete(self.dataset, t, axis=0) @@ -114,13 +127,20 @@ class GrowingNeuralGas: # Adding new neuron from previous learning # Get neuron q and f if len(self.gng.vs) <= self.max_neurons: - error_index = np.array([error for error in self.gng.vs['error']]).argsort() + error_index = np.array([error for error in self.gng.vs["error"]]).argsort() neuron_q = self.gng.vs[error_index[-1]] - error = np.array([(neuron['error'], neuron.index) for neuron in self.gng.vs[self.gng.neighbors(neuron_q.index)]]) + error = np.array( + [ + (neuron["error"], neuron.index) + for neuron in self.gng.vs[self.gng.neighbors(neuron_q.index)] + ] + ) error = np.sort(error, axis=0) neuron_f = self.gng.vs[int(error[-1, 1])] # Add neuron between neuron q and f - self.gng.add_vertex(weight=(neuron_q['weight'] + neuron_f['weight']) / 2, error=0) + self.gng.add_vertex( + weight=(neuron_q["weight"] + neuron_f["weight"]) / 2, error=0 + ) neuron_r = self.gng.vs[len(self.gng.vs) - 1] # Delete edge between neuron q and f self.gng.delete_edges(self.gng.get_eid(neuron_q.index, neuron_f.index)) @@ -128,24 +148,30 @@ class GrowingNeuralGas: self.gng.add_edge(neuron_q.index, neuron_r.index, age=0) self.gng.add_edge(neuron_r.index, neuron_f.index, age=0) # Update neuron error - neuron_q['error'] *= self.alpha - neuron_f['error'] *= self.alpha - neuron_r['error'] = neuron_q['error'] - + neuron_q["error"] *= self.alpha + neuron_f["error"] *= self.alpha + neuron_r["error"] = neuron_q["error"] + def learn(self): # Initialize GNG self.initialize_gng() # GNG learning iteration for iter, _ in zip(range(0, self.max_iter), tqdm(range(self.max_iter))): # Track evolution - self.verts_evolve.append(np.array([neuron['weight'] for neuron in self.gng.vs])) - self.edges_evolve.append(np.array([(neuron.source + 1, neuron.target + 1) for neuron in self.gng.es])) + self.verts_evolve.append( + np.array([neuron["weight"] for neuron in self.gng.vs]) + ) + self.edges_evolve.append( + np.array( + [(neuron.source + 1, neuron.target + 1) for neuron in self.gng.es] + ) + ) # Learn new posititon self.learning_position() - self.update_neuron() + self.update_neuron() return self.gng - - def plot_gng(self, active1,active2, data_point_size=1, neuron_size=30): + + def plot_gng(self, active1, active2, data_point_size=1, neuron_size=30): """ Function to plot the neurons and connections of the GNG in 3D space, with color representing the intensity. Parameters: @@ -153,67 +179,103 @@ class GrowingNeuralGas: - neuron_size: Size of the neurons in the plot. """ fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - + ax = fig.add_subplot(111, projection="3d") + # Plot the original data points - if (active1==True): - scatter = ax.scatter(self.dataset_original[:, 0], self.dataset_original[:, 1], self.dataset_original[:, 2], c=self.dataset_original[:, 3], s=data_point_size, cmap='viridis', alpha=0.5, label='Data') - + if active1 == True: + scatter = ax.scatter( + self.dataset_original[:, 0], + self.dataset_original[:, 1], + self.dataset_original[:, 2], + c=self.dataset_original[:, 3], + s=data_point_size, + cmap="viridis", + alpha=0.5, + label="Data", + ) + # Plot the neurons - neuron_positions = np.array([neuron['weight'] for neuron in self.gng.vs]) - scatter=ax.scatter(neuron_positions[:, 0], neuron_positions[:, 1], neuron_positions[:, 2], c=neuron_positions[:, 3], s=neuron_size, cmap='viridis', label='Neurons') - fig.colorbar(scatter, ax=ax, label='Intensity') + neuron_positions = np.array([neuron["weight"] for neuron in self.gng.vs]) + scatter = ax.scatter( + neuron_positions[:, 0], + neuron_positions[:, 1], + neuron_positions[:, 2], + c=neuron_positions[:, 3], + s=neuron_size, + cmap="viridis", + label="Neurons", + ) + fig.colorbar(scatter, ax=ax, label="Intensity") # Plot the connections - if(active2==True): + if active2 == True: for edge in self.gng.es: src, tgt = edge.source, edge.target - src_pos, tgt_pos = self.gng.vs[src]['weight'], self.gng.vs[tgt]['weight'] - ax.plot([src_pos[0], tgt_pos[0]], [src_pos[1], tgt_pos[1]], [src_pos[2], tgt_pos[2]], 'k-', alpha=0.5) + src_pos, tgt_pos = ( + self.gng.vs[src]["weight"], + self.gng.vs[tgt]["weight"], + ) + ax.plot( + [src_pos[0], tgt_pos[0]], + [src_pos[1], tgt_pos[1]], + [src_pos[2], tgt_pos[2]], + "k-", + alpha=0.5, + ) - ax.set_title('Growing Neural Gas Result') - ax.legend() - plt.show() - - - - -def plot_gng(x1,x2,active2, neuron_size=30): - """ - Function to plot the neurons and connections of the GNG in 3D space, with color representing the intensity. - Parameters: - - data_point_size: Size of the data points in the plot. - - neuron_size: Size of the neurons in the plot. - """ - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - - # Plot the original data points - #if (active1==True): - #scatter = ax.scatter(self.dataset_original[:, 0], self.dataset_original[:, 1], self.dataset_original[:, 2], c=self.dataset_original[:, 3], s=data_point_size, cmap='viridis', alpha=0.5, label='Data') - - # Plot the neurons - neuron_positions = x1 - scatter=ax.scatter(neuron_positions[:, 0], neuron_positions[:, 1], neuron_positions[:, 2], c=neuron_positions[:, 3], s=neuron_size, cmap='viridis', label='Neurons') - fig.colorbar(scatter, ax=ax, label='Intensity') - # Plot the connections - if(active2==True): - for edge in x2: - src, tgt = edge[0], edge[1] - src_pos, tgt_pos = x1[src][:], x1[tgt][:] - ax.plot([src_pos[0], tgt_pos[0]], [src_pos[1], tgt_pos[1]], [src_pos[2], tgt_pos[2]], 'k-', alpha=0.5) - - - - ax.set_title('Growing Neural Gas Result') + ax.set_title("Growing Neural Gas Result") ax.legend() plt.show() -def position_point_espace(data, seuil,y): - tab = [] - size_data = np.shape(data) - for i in range(size_data[0]): - for j in range(size_data[1]): - if data[i, j] > seuil: - tab.append([y,i, j, data[i, j]]) - return tab \ No newline at end of file +def plot_gng(x1, x2, active2, neuron_size=30): + """ + Function to plot the neurons and connections of the GNG in 3D space, with color representing the intensity. + Parameters: + - data_point_size: Size of the data points in the plot. + - neuron_size: Size of the neurons in the plot. + """ + fig = plt.figure() + ax = fig.add_subplot(111, projection="3d") + + # Plot the original data points + # if (active1==True): + # scatter = ax.scatter(self.dataset_original[:, 0], self.dataset_original[:, 1], self.dataset_original[:, 2], c=self.dataset_original[:, 3], s=data_point_size, cmap='viridis', alpha=0.5, label='Data') + + # Plot the neurons + neuron_positions = x1 + scatter = ax.scatter( + neuron_positions[:, 0], + neuron_positions[:, 1], + neuron_positions[:, 2], + c=neuron_positions[:, 3], + s=neuron_size, + cmap="viridis", + label="Neurons", + ) + fig.colorbar(scatter, ax=ax, label="Intensity") + # Plot the connections + if active2 == True: + for edge in x2: + src, tgt = edge[0], edge[1] + src_pos, tgt_pos = x1[src][:], x1[tgt][:] + ax.plot( + [src_pos[0], tgt_pos[0]], + [src_pos[1], tgt_pos[1]], + [src_pos[2], tgt_pos[2]], + "k-", + alpha=0.5, + ) + + ax.set_title("Growing Neural Gas Result") + ax.legend() + plt.show() + + +def position_point_espace(data, seuil, y): + tab = [] + size_data = np.shape(data) + for i in range(size_data[0]): + for j in range(size_data[1]): + if data[i, j] > seuil: + tab.append([y, i, j, data[i, j]]) + return tab diff --git a/3D_app/README.md b/3D_app/README.md index cc0aa84..877f7e2 100644 --- a/3D_app/README.md +++ b/3D_app/README.md @@ -35,3 +35,8 @@ * Ajustement de la fonction d'enregistrement et d'ouverture des datasets * Ajout d'un bouton de sauvegarde pour les datasets filtrés + +# V4.2 + +- Réorganisation des callbacks de `home.py` +- Ajout de popups pour contrôler l'affichage des sliders diff --git a/3D_app/callbacks/home.py b/3D_app/callbacks/home.py index 7ab715b..47da7f7 100644 --- a/3D_app/callbacks/home.py +++ b/3D_app/callbacks/home.py @@ -7,6 +7,8 @@ from os.path import join from util import lire_fichier_csv from selection_filtre import switch_case from callbacks.ascan import data_traits +from dash import dcc, html +import dash_bootstrap_components as dbc # on définit le dossier et les fichiers à lire dossier = "Dataset/Shear_transform" @@ -27,6 +29,11 @@ X, Y, Z = np.mgrid[0:dim_x, 0:dim_y, 0:dim_z] def get_callbacks(): + # ----------------------------------------------------------------------- + # ---------------------- Callbacks pour le 3D plot ---------------------- + # ----------------------------------------------------------------------- + + # callback pour le plot 3D @callback( [Output("3dplot", "figure"), Output("fade-3dplot", "is_in")], [ @@ -38,13 +45,13 @@ def get_callbacks(): ) def update_3dplot(iso_value, y_values, settings, is_in): if settings["use_real_values"]: - y_min, y_max = y_values + y_min, y_max = y_values or [0, dim_y] selected_volume = volume[0:dim_x, int(y_min) : int(y_max), 0:dim_z] X, Y, Z = [ np.load("Dataset/npy/{}-values.npy".format(i)) for i in ["x", "y", "z"] ] else: - y_min, y_max = y_values + y_min, y_max = y_values or [0, dim_y] selected_volume = volume[0:dim_x, int(y_min) : int(y_max), 0:dim_z] X, Y, Z = np.mgrid[ 0 : selected_volume.shape[0], @@ -65,7 +72,6 @@ def get_callbacks(): colorscale="Jet", ) ) - return [fig, True] # callback pour le plot 3D en plein écran @@ -75,6 +81,7 @@ def get_callbacks(): Input("iso-slider-fullscreen", "value"), Input("y-slider-fullscreen", "value"), ], + prevent_initial_call=True, ) def update_3dplot_fullscreen(iso_value, y_values): y_min, y_max = y_values @@ -101,6 +108,166 @@ def get_callbacks(): return fig + # callback pour le plein écran du plot 3D + @callback( + Output("modal-3dplot", "is_open"), + [Input("fullscreen-button-3dplot", "n_clicks")], + [dash.dependencies.State("modal-3dplot", "is_open")], + ) + def toggle_fullscreen_3dplot(n1, is_open): + if n1: + return not is_open + return is_open + + # callback pour les paramètres du plot 3D + @callback( + Output("modal-settings-3dplot", "is_open"), + [ + Input("settings-button-3dplot", "n_clicks"), + Input("save-settings-3dplot", "n_clicks"), + ], + [dash.dependencies.State("modal-settings-3dplot", "is_open")], + ) + def toggle_settings_3dplot(n1, n2, is_open): + if n1 or n2: + return not is_open + return is_open + + @callback( + Output("3dplot-settings", "data"), + Input("save-settings-3dplot", "n_clicks"), + [ + State("checklist-3dplot-card-settings", "value"), + State("checklist-3dplot-fullscreen-settings", "value"), + ], + prevent_initial_call=True, + ) + def save_settings_3dplot(n_clicks, card_settings, fullscreen_settings): + return { + "card_settings": card_settings, + "fullscreen_settings": fullscreen_settings, + } + + @callback( + [ + Output("iso-slider-container", "children"), + Output("y-slider-container", "children"), + Output("iso-slider-fullscreen-container", "children"), + Output("y-slider-fullscreen-container", "children"), + ], + Input("3dplot-settings", "data"), + ) + def update_3dplot_sliders(data): + iso_slider = dbc.Row( + [ + dbc.Col(html.Span("Iso value: "), width="auto"), + dbc.Col( + dcc.Slider( + id="iso-slider", + min=0, + max=volume.max() / 2, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range( + 0, + int(volume.max() / 2) + 1, + int((volume.max() / 2) / 20), + ) + }, + ) + ), + ] + ) + y_slider = dbc.Row( + [ + dbc.Col(html.Span("Y crop: "), width="auto"), + dbc.Col( + dcc.RangeSlider( + id="y-slider", + min=0, + max=dim_y, + value=[0, dim_y], + marks={ + str(i): str(i) + for i in range(0, dim_y + 1, max(1, int(dim_y / 20))) + }, + ) + ), + ] + ) + iso_slider_fullscreen = dbc.Row( + [ + dbc.Col(html.Span("Iso value: "), width="auto"), + dbc.Col( + dcc.Slider( + id="iso-slider-fullscreen", + min=0, + max=volume.max() / 2, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range( + 0, + int(volume.max() / 2) + 1, + int((volume.max() / 2) / 20), + ) + }, + ) + ), + ] + ) + y_slider_fullscreen = dbc.Row( + [ + dbc.Col(html.Span("Y crop: "), width="auto"), + dbc.Col( + dcc.RangeSlider( + id="y-slider-fullscreen", + min=0, + max=dim_y, + value=[0, dim_y], + marks={ + str(i): str(i) + for i in range(0, dim_y + 1, max(1, int(dim_y / 20))) + }, + ) + ), + ] + ) + if data is None: + return [None, None, None, None] + else: + if ( + data["card_settings"] is None + or "iso_slider" not in data["card_settings"] + ): + iso_slider = None + if data["card_settings"] is None or "y_slider" not in data["card_settings"]: + y_slider = None + if ( + data["fullscreen_settings"] is None + or "iso_slider" not in data["fullscreen_settings"] + ): + iso_slider_fullscreen = None + if ( + data["fullscreen_settings"] is None + or "y_slider" not in data["fullscreen_settings"] + ): + y_slider_fullscreen = None + + return [ + iso_slider, + y_slider, + iso_slider_fullscreen, + y_slider_fullscreen, + ] + + # ---------------------------------------------------------------------- + # ---------------------- Callbacks pour le A-Scan ---------------------- + # ---------------------------------------------------------------------- + # callback pour le A-scan @callback( [Output("heatmap-ascan", "figure"), Output("fade-ascan", "is_in")], @@ -108,33 +275,213 @@ def get_callbacks(): Input("layer-slider-bscan-zx", "value"), Input("layer-slider-bscan-xy", "value"), ], - [State("fade-ascan", "is_in"), State("store-settings", "data")], + [ + State("crop-slider", "value"), + State("fade-ascan", "is_in"), + State("store-settings", "data"), + ], ) - def update_heatmap_ascan(layer, layer1, is_in, settings): + def update_heatmap_ascan(layer, layer1, crop_values, is_in, settings): + y_min, y_max = crop_values or [0, dim_y] + layer = layer or 1 if settings["apply_sampling_everywhere"]: data = volume else: data = pre_volume - - fig = px.line(y=data[layer - 1, :, layer1], title="A-scan") + + fig = px.line( + y=data[layer - 1, int(y_min) : int(y_max), layer1], title="A-scan" + ) return [fig, True] # callback pour le A-scan en plein écran @callback( Output("heatmap-ascan-fullscreen", "figure"), - Input("layer-slider-ascan-fullscreen", "value"), - State("store-settings", "data") + [ + Input("layer-slider-x-ascan-fullscreen", "value"), + Input("layer-slider-z-ascan-fullscreen", "value"), + Input("crop-slider-fullscreen", "value"), + ], + State("store-settings", "data"), ) - def update_heatmap_ascan_fullscreen(layer, settings): + def update_heatmap_ascan_fullscreen(layer_x, layer_z, crop, settings): + y_min, y_max = crop or [0, dim_y] + layer_x = layer_x or 0 + layer_z = layer_z or 0 if settings["apply_sampling_everywhere"]: data = volume else: data = pre_volume - fig = px.line(y=data[layer - 1, :, 5], title="A-scan") + fig = px.line( + y=data[layer_x - 1, int(y_min) : int(y_max), layer_z], title="A-scan" + ) return fig + # callback pour le plein écran du A-scan + @callback( + Output("modal-ascan", "is_open"), + [Input("fullscreen-button-ascan", "n_clicks")], + [dash.dependencies.State("modal-ascan", "is_open")], + ) + def toggle_fullscreen_ascan(n1, is_open): + if n1: + return not is_open + return is_open + + # callback pour les paramètres du A-scan + @callback( + Output("modal-settings-ascan", "is_open"), + [ + Input("settings-button-ascan", "n_clicks"), + Input("save-settings-ascan", "n_clicks"), + ], + [dash.dependencies.State("modal-settings-ascan", "is_open")], + ) + def toggle_settings_ascan(n1, n2, is_open): + if n1 or n2: + return not is_open + return is_open + + @callback( + Output("ascan-settings", "data"), + Input("save-settings-ascan", "n_clicks"), + [ + State("checklist-ascan-card-settings", "value"), + State("checklist-ascan-fullscreen-settings", "value"), + ], + prevent_initial_call=True, + ) + def save_settings_ascan(n_clicks, card_settings, fullscreen_settings): + return { + "card_settings": card_settings, + "fullscreen_settings": fullscreen_settings, + } + + @callback( + [ + Output("crop-slider-container", "children"), + Output("layer-slider-x-ascan-fullscreen-container", "children"), + Output("layer-slider-z-ascan-fullscreen-container", "children"), + Output("crop-slider-fullscreen-container", "children"), + ], + Input("ascan-settings", "data"), + ) + def update_ascan_sliders(data): + crop_slider = dbc.Row( + [ + dbc.Col( + html.Span("Crop: ", className="mr-2"), + width="auto", + ), + dbc.Col( + dcc.RangeSlider( + id="crop-slider", + min=0, + max=dim_y, + step=1, + value=[0, dim_y], + marks={ + str(i): str(i) + for i in range(0, dim_y + 1, max(1, int(dim_y / 20))) + }, + ), + ), + ] + ) + slider_x_fullscreen = dbc.Row( + [ + dbc.Col(html.Span("Layer X: "), width="auto"), + dbc.Col( + dcc.Slider( + id="layer-slider-x-ascan-fullscreen", + min=0, + max=dim_x, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range(0, dim_x, max(1, int(dim_x / 50))) + }, + ) + ), + ] + ) + slider_z_fullscreen = dbc.Row( + [ + dbc.Col(html.Span("Layer Z: "), width="auto"), + dbc.Col( + dcc.Slider( + id="layer-slider-z-ascan-fullscreen", + min=1, + max=dim_z - 1, + value=1, + marks={ + str(i): str(i) + for i in range(1, dim_z - 1, max(1, int(dim_z / 20))) + }, + step=1, + ) + ), + ] + ) + crop_slider_fullscreen = dbc.Row( + [ + dbc.Col(html.Span("Crop: "), width="auto"), + dbc.Col( + dcc.RangeSlider( + id="crop-slider-fullscreen", + min=0, + max=dim_y, + value=[0, dim_y], + marks={ + str(i): str(i) + for i in range( + 0, + dim_y + 1, + max(1, int(dim_y / 20)), + ) + }, + step=1, + ) + ), + ] + ) + if data is None: + return [None, None, None, None] + else: + if ( + data["card_settings"] is None + or "crop_slider" not in data["card_settings"] + ): + crop_slider = None + if ( + data["fullscreen_settings"] is None + or "layer_slider_x" not in data["fullscreen_settings"] + ): + slider_x_fullscreen = None + if ( + data["fullscreen_settings"] is None + or "layer_slider_z" not in data["fullscreen_settings"] + ): + slider_z_fullscreen = None + if ( + data["fullscreen_settings"] is None + or "crop_slider" not in data["fullscreen_settings"] + ): + crop_slider_fullscreen = None + return [ + crop_slider, + slider_x_fullscreen, + slider_z_fullscreen, + crop_slider_fullscreen, + ] + + # ------------------------------------------------------------------------- + # ---------------------- Callbacks pour le B-Scan ZX ---------------------- + # ------------------------------------------------------------------------- + # callback pour les B-scan XY @callback( [ @@ -143,16 +490,21 @@ def get_callbacks(): Output("fade-bscan-xy", "is_in"), ], [Input("layer-slider-bscan-zx", "value")], - [State("fade-bscan-zx", "is_in"), State("store-settings", "data")], + [ + State("crop-slider", "value"), + State("fade-bscan-zx", "is_in"), + State("store-settings", "data"), + ], ) - def update_heatmap_bscan_zx(layer, is_in, settings): + def update_heatmap_bscan_zx(layer, crop_values, is_in, settings): + y_min, y_max = crop_values or [0, dim_y] if settings["apply_sampling_everywhere"]: data = volume else: data = pre_volume fig = px.imshow( - data[layer - 1, :, :], + data[layer - 1, int(y_min) : int(y_max), :], color_continuous_scale="Jet", aspect="auto", title="B-scan ZX", @@ -164,7 +516,7 @@ def get_callbacks(): @callback( Output("heatmap-bscan-zx-fullscreen", "figure"), Input("layer-slider-bscan-zx-fullscreen", "value"), - State("store-settings", "data") + State("store-settings", "data"), ) def update_heatmap_bscan_zx_fullscreen(layer, settings): if settings["apply_sampling_everywhere"]: @@ -181,6 +533,128 @@ def get_callbacks(): return fig + # callback pour le plein écran du B-scan ZX + @callback( + Output("modal-bscan-zx", "is_open"), + [Input("fullscreen-button-bscan-zx", "n_clicks")], + [dash.dependencies.State("modal-bscan-zx", "is_open")], + ) + def toggle_fullscreen_bscan_zx(n1, is_open): + if n1: + return not is_open + return is_open + + # callback pour les paramètres du B-scan ZX + @callback( + Output("modal-settings-bscan-zx", "is_open"), + [ + Input("settings-button-bscan-zx", "n_clicks"), + Input("save-settings-bscan-zx", "n_clicks"), + ], + [dash.dependencies.State("modal-settings-bscan-zx", "is_open")], + ) + def toggle_settings_bscan_zx(n1, n2, is_open): + if n1 or n2: + return not is_open + return is_open + + @callback( + Output("bscan-zx-settings", "data"), + Input("save-settings-bscan-zx", "n_clicks"), + [ + State("checklist-bscan-zx-card-settings", "value"), + State("checklist-bscan-zx-fullscreen-settings", "value"), + ], + prevent_initial_call=True, + ) + def save_settings_bscan_zx(n_clicks, card_settings, fullscreen_settings): + return { + "card_settings": card_settings, + "fullscreen_settings": fullscreen_settings, + } + + @callback( + [ + Output("layer-slider-bscan-zx-container", "children"), + Output("layer-slider-bscan-zx-fullscreen-container", "children"), + ], + Input("bscan-zx-settings", "data"), + ) + def update_bscan_zx_sliders(data): + slider = dbc.Row( + [ + dbc.Col(html.Span("Layer: "), width="auto"), + dbc.Col( + dcc.Slider( + id="layer-slider-bscan-zx", + min=0, + max=dim_x - 1, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range(0, dim_x, max(1, int(dim_x / 20))) + }, + ) + ), + ] + ) + x_slider_fullscreen = dbc.Row( + [ + dbc.Col(html.Span("X layer: "), width="auto"), + dbc.Col( + dcc.Slider( + id="layer-slider-bscan-zx-fullscreen", + min=0, + max=dim_z, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range(0, dim_z, max(1, int(dim_z / 50))) + }, + ) + ), + ] + ) + y_slider_fullscreen = dbc.Row( + [ + dbc.Col(html.Span("Y layer: "), width="auto"), + dbc.Col( + dcc.Slider( + id="layer-slider-bscan-xy-fullscreen", + min=0, + max=dim_z, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range(0, dim_z, max(1, int(dim_z / 50))) + }, + ) + ), + ] + ) + if data is None: + return [None, None] + else: + if data["card_settings"] is None or "x_slider" not in data["card_settings"]: + slider = None + if ( + data["fullscreen_settings"] is None + or "layer_slider" not in data["fullscreen_settings"] + ): + slider_fullscreen = None + + return [ + slider, + slider_fullscreen, + ] + + # ------------------------------------------------------------------------- + # ---------------------- Callbacks pour le B-Scan XY ---------------------- + # ------------------------------------------------------------------------- + # callback pour les B-scan ZX @callback( [ @@ -189,15 +663,22 @@ def get_callbacks(): Output("fade-bscan-zx", "is_in"), ], [Input("layer-slider-bscan-xy", "value")], - [State("fade-bscan-xy", "is_in"), State("store-settings", "data")], + [ + State("crop-slider", "value"), + State("fade-bscan-xy", "is_in"), + State("store-settings", "data"), + ], ) - def update_heatmap_bscan_xy(layer, is_in, settings): + def update_heatmap_bscan_xy(layer, crop_values, is_in, settings): + y_min, y_max = crop_values or [0, dim_y] if settings["apply_sampling_everywhere"]: data = volume else: data = pre_volume - fig = go.Figure(data=go.Heatmap(z=data[:, :, layer], colorscale="Jet")) + fig = go.Figure( + data=go.Heatmap(z=data[:, int(y_min) : int(y_max), layer], colorscale="Jet") + ) fig.update_layout(title="B-scan XY") return [fig, layer, True] @@ -219,28 +700,6 @@ def get_callbacks(): return fig - # callback pour le plein écran du plot 3D - @callback( - Output("modal-3dplot", "is_open"), - [Input("fullscreen-button-3dplot", "n_clicks")], - [dash.dependencies.State("modal-3dplot", "is_open")], - ) - def toggle_fullscreen_3dplot(n1, is_open): - if n1: - return not is_open - return is_open - - # callback pour le plein écran du A-scan - @callback( - Output("modal-ascan", "is_open"), - [Input("fullscreen-button-ascan", "n_clicks")], - [dash.dependencies.State("modal-ascan", "is_open")], - ) - def toggle_fullscreen_ascan(n1, is_open): - if n1: - return not is_open - return is_open - # callback pour le plein écran du B-scan XY @callback( Output("modal-bscan-xy", "is_open"), @@ -252,17 +711,6 @@ def get_callbacks(): return not is_open return is_open - # callback pour le plein écran du B-scan ZX - @callback( - Output("modal-bscan-zx", "is_open"), - [Input("fullscreen-button-bscan-zx", "n_clicks")], - [dash.dependencies.State("modal-bscan-zx", "is_open")], - ) - def toggle_fullscreen_bscan_zx(n1, is_open): - if n1: - return not is_open - return is_open - @callback( [ Output("heatmap-bscan-xy", "clickData"), @@ -278,6 +726,118 @@ def get_callbacks(): bscan_zx = clickData["points"][0]["y"] return [clickData, bscan_zx] + # callback pour les paramètres du B-scan XY + @callback( + Output("modal-settings-bscan-xy", "is_open"), + [ + Input("settings-button-bscan-xy", "n_clicks"), + Input("save-settings-bscan-xy", "n_clicks"), + ], + [dash.dependencies.State("modal-settings-bscan-xy", "is_open")], + ) + def toggle_settings_bscan_xy(n1, n2, is_open): + if n1 or n2: + return not is_open + return is_open + + @callback( + Output("bscan-xy-settings", "data"), + Input("save-settings-bscan-xy", "n_clicks"), + [ + State("checklist-bscan-xy-card-settings", "value"), + State("checklist-bscan-xy-fullscreen-settings", "value"), + ], + prevent_initial_call=True, + ) + def save_settings_bscan_xy(n_clicks, card_settings, fullscreen_settings): + return { + "card_settings": card_settings, + "fullscreen_settings": fullscreen_settings, + } + + @callback( + [ + Output("layer-slider-bscan-xy-container", "children"), + Output("layer-slider-bscan-xy-fullscreen-container", "children"), + ], + Input("bscan-xy-settings", "data"), + ) + def update_bscan_xy_sliders(data): + slider = dbc.Row( + [ + dbc.Col(html.Span("Layer: "), width="auto"), + dbc.Col( + dcc.Slider( + id="layer-slider-bscan-xy", + min=1, + max=dim_z - 1, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range(1, dim_z, max(1, int(dim_z / 20))) + }, + ) + ), + ] + ) + x_slider_fullscreen = dbc.Row( + [ + dbc.Col(html.Span("X layer: "), width="auto"), + dbc.Col( + dcc.Slider( + id="layer-slider-bscan-xy-fullscreen", + min=0, + max=dim_x, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range(0, dim_x, max(1, int(dim_x / 50))) + }, + ) + ), + ] + ) + y_slider_fullscreen = dbc.Row( + [ + dbc.Col(html.Span("Y layer: "), width="auto"), + dbc.Col( + dcc.Slider( + id="layer-slider-bscan-zx-fullscreen", + min=0, + max=dim_x, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range(0, dim_x, max(1, int(dim_x / 50))) + }, + ) + ), + ] + ) + if data is None: + return [None, None] + else: + if data["card_settings"] is None or "x_slider" not in data["card_settings"]: + slider = None + if ( + data["fullscreen_settings"] is None + or "layer_slider" not in data["fullscreen_settings"] + ): + slider_fullscreen = None + + return [ + slider, + slider_fullscreen, + ] + + # --------------------------------------------------------------------------------- + # ---------------------- Callbacks pour la selection de data ---------------------- + # --------------------------------------------------------------------------------- + + # callback pour la selection de data sur le B-scan ZX @callback( [ Output("heatmap-bscan-zx", "clickData"), @@ -293,6 +853,7 @@ def get_callbacks(): bscan_xy = clickData["points"][0]["x"] return [clickData, bscan_xy] + # callback pour la selection de data sur le B-scan XY @callback( [ Output("heatmap-bscan-xy", "figure", allow_duplicate=True), @@ -308,7 +869,7 @@ def get_callbacks(): type="line", x0=0 - 1, y0=bscan_zx, - x1=dim_y, + x1=pre_volume.shape[1], y1=bscan_zx, line=dict(color="white", width=1), ) @@ -325,7 +886,7 @@ def get_callbacks(): x0=bscan_xy, y0=0, x1=bscan_xy, - y1=dim_y, + y1=pre_volume.shape[1], line=dict(color="white", width=1), ) @@ -333,6 +894,10 @@ def get_callbacks(): return [fig, fig2, fig3] + # -------------------------------------------------------------------------------------- + # ---------------------- Callbacks pour la gestion des parametres ---------------------- + # -------------------------------------------------------------------------------------- + @callback( [Output("store-settings", "data"), Output("settings-apply", "n_clicks")], [ @@ -372,6 +937,8 @@ def get_callbacks(): @callback( [ + Output("crop-slider", "max"), + Output("crop-slider", "marks"), Output("layer-slider-bscan-zx", "max"), Output("layer-slider-bscan-zx", "marks"), Output("layer-slider-bscan-xy", "max"), @@ -381,8 +948,8 @@ def get_callbacks(): Output("iso-slider", "marks"), Output("y-slider", "max"), Output("y-slider", "marks"), - Output("layer-slider-ascan-fullscreen", "max"), - Output("layer-slider-ascan-fullscreen", "marks"), + Output("layer-slider-x-ascan-fullscreen", "max"), + Output("layer-slider-x-ascan-fullscreen", "marks"), Output("layer-slider-bscan-xy-fullscreen", "max"), Output("layer-slider-bscan-xy-fullscreen", "marks"), Output("layer-slider-bscan-zx-fullscreen", "max"), @@ -399,15 +966,21 @@ def get_callbacks(): ) def redef_data(data): global volume, pre_volume, dim_x, dim_y, dim_z, X, Y, Z - volume = pre_volume[ - :: data["echantillonage_x"], - :: data["echantillonage_y"], - :: data["echantillonage_z"], - ] + if data["apply_sampling_everywhere"]: + volume = pre_volume[ + :: data["echantillonage_x"], + :: data["echantillonage_y"], + :: data["echantillonage_z"], + ] + else: + volume = pre_volume + dim_x, dim_y, dim_z = volume.shape X, Y, Z = np.mgrid[0:dim_x, 0:dim_y, 0:dim_z] return [ + dim_y - 1, + {str(i): str(i) for i in range(1, dim_y + 1, max(1, int(dim_y / 20)))}, dim_x - 1, {str(i): str(i) for i in range(0, dim_x, max(1, int(dim_x / 20)))}, dim_z - 1, @@ -445,6 +1018,10 @@ def get_callbacks(): "Apply", ] + # ------------------------------------------------------------------------ + # ---------------------- Callbacks pour les filtres ---------------------- + # ------------------------------------------------------------------------ + @callback( Output("store-filters", "data", allow_duplicate=True), [Input("store-filters", "data"), Input("store-settings", "data")], @@ -499,6 +1076,10 @@ def get_callbacks(): ] return data + # ------------------------------------------------------------------------------------ + # ---------------------- Callbacks pour la gestion des fichiers ---------------------- + # ------------------------------------------------------------------------------------ + @callback( Output("store-files", "data", allow_duplicate=True), Input("store-files", "data"), @@ -516,9 +1097,9 @@ def get_callbacks(): # Appliquer les nouveaux paramètres d'échantillonnage volume = pre_volume[ - ::settings["echantillonage_x"], - ::settings["echantillonage_y"], - ::settings["echantillonage_z"], + :: settings["echantillonage_x"], + :: settings["echantillonage_y"], + :: settings["echantillonage_z"], ] dim_x, dim_y, dim_z = volume.shape X, Y, Z = np.mgrid[0:dim_x, 0:dim_y, 0:dim_z] @@ -526,9 +1107,9 @@ def get_callbacks(): # Mettre à jour les graphiques 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) + update_heatmap_ascan(0, 0, False, settings) + update_heatmap_bscan_zx(0, False, settings) + update_heatmap_bscan_xy(0, False, settings) return None @@ -548,3 +1129,65 @@ def get_callbacks(): else: np.save(join("Dataset/saves", filename), data_traits) return True + + # ------------------------------------------------------------------------------------- + # ---------------------- Callbacks pour la gestion des données ------------------------ + # ------------------------------------------------------------------------------------- + + @callback( + [ + Output("heatmap-ascan", "figure", allow_duplicate=True), + Output("heatmap-bscan-xy", "figure", allow_duplicate=True), + Output("heatmap-bscan-zx", "figure", allow_duplicate=True), + ], + Input("crop-slider", "value"), + [ + State("store-bscan-zx-layer", "data"), + State("store-bscan-xy-layer", "data"), + State("store-settings", "data"), + ], + prevent_initial_call=True, + ) + def update_scans(layer, layer1, layer2, settings): + y_min, y_max = layer + if settings["apply_sampling_everywhere"]: + data = volume + else: + data = pre_volume + + fig = px.line( + y=data[layer1 - 1, int(y_min) : int(y_max), layer2], title="A-scan" + ) + fig2 = go.Figure( + data=go.Heatmap( + z=data[:, int(y_min) : int(y_max), layer2], colorscale="Jet" + ) + ) + fig2.add_shape( + type="line", + x0=0 - 1, + y0=layer1, + x1=y_max, + y1=layer1, + line=dict(color="white", width=1), + ) + fig2.update_layout( + title="B-scan XY", autosize=True, dragmode="zoom", clickmode="event+select" + ) + fig3 = px.imshow( + data[layer1 - 1, int(y_min) : int(y_max), :], + color_continuous_scale="Jet", + aspect="auto", + title="B-scan ZX", + ) + fig3.add_shape( + type="line", + x0=layer2, + y0=0, + x1=layer2, + y1=y_max, + line=dict(color="white", width=1), + ) + fig3.update_layout(autosize=True, dragmode="zoom", clickmode="event+select") + + return [fig, fig2, fig3] diff --git a/3D_app/import_sqlite.py b/3D_app/import_sqlite.py index 3d3fd7b..98df30c 100644 --- a/3D_app/import_sqlite.py +++ b/3D_app/import_sqlite.py @@ -49,4 +49,33 @@ db.close() timestamp_end = datetime.datetime.now() totaltime = timestamp_end - timestamp_start -print('Done in', totaltime.microseconds / 1000, 'ms!') \ No newline at end of file +print('Done in', totaltime.microseconds / 1000, 'ms!') + +if input("Do you want to display the 3D volume? (y/n) ") == "y": + import plotly.graph_objects as go + + # on récupère les données de la base de données pour les afficher + db = sqlite3.connect('data.sqlite') + cursor = db.cursor() + cursor.execute('SELECT * FROM volume') + rows = cursor.fetchall() + db.close() + + volume = np.zeros((dim_x, dim_y, dim_z)) + for row in rows: + x, y, z, value = row + volume[x, y, z] = value + + fig = go.Figure(data=go.Volume( + x=X.flatten(), + y=Y.flatten(), + z=Z.flatten(), + value=volume.flatten(), + isomin=0, + isomax=volume.max(), + opacity=0.1, # needs to be small to see through all surfaces + surface_count=21, # needs to be a large number for good volume rendering + )) + fig.show() + + input("Press Enter to continue...") \ No newline at end of file diff --git a/3D_app/main.py b/3D_app/main.py index cdca6fc..e542d10 100644 --- a/3D_app/main.py +++ b/3D_app/main.py @@ -1,7 +1,7 @@ import dash from dash import dcc, html, ALL, DiskcacheManager import dash_bootstrap_components as dbc -from os import listdir, mkdir +from os import listdir, mkdir, path from os.path import isfile, join import diskcache from json import load @@ -351,7 +351,13 @@ nav_bar = dbc.Navbar( dbc.NavItem(button_settings), dbc.NavItem(button_open), dbc.NavItem(button_save), - dbc.Label(id="opened-file", style={"marginLeft": "10px", "padding": "0 auto"}), + dbc.Label( + id="opened-file", + style={ + "marginLeft": "10px", + "padding": "0 auto", + }, + ), ], className="ml-auto", navbar=True, @@ -367,7 +373,7 @@ nav_bar = dbc.Navbar( modal_open, modal_save, navmenu, - save_toast + save_toast, ], align="center", style={"width": "100%"}, @@ -382,6 +388,7 @@ nav_bar = dbc.Navbar( # on défini le layout de l'application app.layout = dbc.Container( [ + dcc.Location(id="loc", refresh=False), nav_bar, dash.page_container, dcc.Store( @@ -397,7 +404,12 @@ app.layout = dbc.Container( ), dcc.Store(id="store-filters", data={}), dcc.Store(id="store-files"), + dcc.Store(id="ascan-settings"), + dcc.Store(id="3dplot-settings"), + dcc.Store(id="bscan-zx-settings"), + dcc.Store(id="bscan-xy-settings"), ], + id="app-container", fluid=True, ) @@ -405,4 +417,6 @@ get_callbacks() # on lance l'application if __name__ == "__main__": - app.run(debug=config["debug"] or False, port=config["port"] or "8051", threaded=True) \ No newline at end of file + app.run( + debug=config["debug"] or False, port=config["port"] or "8051", threaded=True + ) diff --git a/3D_app/pages/ascan.py b/3D_app/pages/ascan.py index 92cad54..407fedd 100644 --- a/3D_app/pages/ascan.py +++ b/3D_app/pages/ascan.py @@ -74,7 +74,7 @@ layout = html.Div( dbc.Select( id="select-ascan-filter1", options=[ - {"label": "Transformer du Hilbert", "value": "1"}, + {"label": "Transformée de Hilbert", "value": "1"}, ], value=1, style={"margin-bottom": "15px"}, diff --git a/3D_app/pages/home.py b/3D_app/pages/home.py index 12fc8c2..ad9a1ef 100644 --- a/3D_app/pages/home.py +++ b/3D_app/pages/home.py @@ -112,12 +112,26 @@ mesh_card = dbc.Fade( className="card-title", style={"textAlign": "left"}, ), - dbc.Button( - html.I(className="bi bi-arrows-fullscreen"), - id="fullscreen-button-3dplot", - className="mb-3", - color="primary", - style={"marginBottom": "15px"}, + html.Div( + [ + dbc.Button( + html.I(className="bi bi-gear-wide"), + id="settings-button-3dplot", + className="mb-3", + color="secondary", + style={ + "marginBottom": "15px", + "marginRight": "5px", + }, + ), + dbc.Button( + html.I(className="bi bi-arrows-fullscreen"), + id="fullscreen-button-3dplot", + className="mb-3", + color="primary", + style={"marginBottom": "15px"}, + ), + ] ), ], style={"display": "flex", "justifyContent": "space-between"}, @@ -128,31 +142,39 @@ mesh_card = dbc.Fade( config=config3DPlot, style={"marginBottom": "15px"}, ), # 'fig' is your 3D plotly figure - dcc.Slider( - id="iso-slider", - min=0, - max=volume.max() / 2, - value=0, - marks={ - str(i): str(i) - for i in range( - 0, - int(volume.max() / 2) + 1, - int(volume.max() / 20), - ) - }, - step=1, + html.Div( + dcc.Slider( + id="iso-slider", + min=0, + max=volume.max() / 2, + value=0, + marks={ + str(i): str(i) + for i in range( + 0, + int(volume.max() / 2) + 1, + int(volume.max() / 20), + ) + }, + step=1, + ), + id="iso-slider-container", ), - dcc.RangeSlider( - id="y-slider", - min=0, - max=dim_y, - value=[0, dim_y / 2], - marks={ - str(i): str(i) - for i in range(0, int(dim_y) + 1, max(1, int(dim_y / 20))) - }, - step=1, + html.Div( + dcc.RangeSlider( + id="y-slider", + min=0, + max=dim_y, + value=[0, dim_y / 2], + marks={ + str(i): str(i) + for i in range( + 0, int(dim_y) + 1, max(1, int(dim_y / 20)) + ) + }, + step=1, + ), + id="y-slider-container", ), dbc.Modal( [ @@ -165,38 +187,47 @@ mesh_card = dbc.Fade( config=config3DPlot, style={"marginBottom": "15px", "height": "90%"}, ), # 'fig' is your 3D plotly figure - dcc.Slider( - id="iso-slider-fullscreen", - min=volume.min(), - max=volume.max() / 2, - value=volume.min(), - marks={ - str(i): str(i) - for i in range( - int(volume.min()), - int(volume.max() / 2) + 1, - int( - (volume.max() / 2 - volume.min()) - / 20 - ), - ) - }, - step=1, + html.Div( + dcc.Slider( + id="iso-slider-fullscreen", + min=volume.min(), + max=volume.max() / 2, + value=volume.min(), + marks={ + str(i): str(i) + for i in range( + int(volume.min()), + int(volume.max() / 2) + 1, + int( + ( + volume.max() / 2 + - volume.min() + ) + / 20 + ), + ) + }, + step=1, + ), + id="iso-slider-fullscreen-container", ), - dcc.RangeSlider( - id="y-slider-fullscreen", - min=0, - max=dim_y, - value=[0, dim_y / 2], - marks={ - str(i): str(i) - for i in range( - 0, - int(dim_y) + 1, - max(1, int(dim_y / 50)), - ) - }, - step=1, + html.Div( + dcc.RangeSlider( + id="y-slider-fullscreen", + min=0, + max=dim_y, + value=[0, dim_y / 2], + marks={ + str(i): str(i) + for i in range( + 0, + int(dim_y) + 1, + max(1, int(dim_y / 50)), + ) + }, + step=1, + ), + id="y-slider-fullscreen-container", ), ] ), @@ -204,7 +235,57 @@ mesh_card = dbc.Fade( id="modal-3dplot", fullscreen=True, ), - ] + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("3D plot settings")), + dbc.ModalBody( + [ + html.Span("Card display: "), + dbc.Checklist( + options=[ + { + "label": "Show iso slider", + "value": "iso_slider", + }, + { + "label": "Show Y slider", + "value": "y_slider", + }, + ], + id="checklist-3dplot-card-settings", + ), + html.Span("Fullscreen display: "), + dbc.Checklist( + options=[ + { + "label": "Show iso slider", + "value": "iso_slider", + }, + { + "label": "Show Y slider", + "value": "y_slider", + }, + ], + id="checklist-3dplot-fullscreen-settings", + ), + ] + ), + dbc.ModalFooter( + [ + dbc.Button( + "Save", + id="save-settings-3dplot", + className="ml-auto", + color="primary", + ), + ] + ), + ], + id="modal-settings-3dplot", + fullscreen=False, + ), + ], + style={"height": "610px"}, ) ] ), @@ -226,12 +307,26 @@ Ascan_card = dbc.Fade( className="card-title", style={"textAlign": "left"}, ), - dbc.Button( - html.I(className="bi bi-arrows-fullscreen"), - id="fullscreen-button-ascan", - className="mb-3", - color="primary", - style={"marginBottom": "15px"}, + html.Div( + [ + dbc.Button( + html.I(className="bi bi-gear-wide"), + id="settings-button-ascan", + className="mb-3", + color="secondary", + style={ + "marginBottom": "15px", + "marginRight": "5px", + }, + ), + dbc.Button( + html.I(className="bi bi-arrows-fullscreen"), + id="fullscreen-button-ascan", + className="mb-3", + color="primary", + style={"marginBottom": "15px"}, + ), + ] ), ], style={"display": "flex", "justifyContent": "space-between"}, @@ -241,6 +336,20 @@ Ascan_card = dbc.Fade( config=configAScan, style={"marginBottom": "15px"}, ), # 'fig' is your 2D plotly figure + html.Div( + dcc.RangeSlider( + id="crop-slider", + min=0, + max=dim_y, + value=[0, dim_y], + marks={ + str(i): str(i) + for i in range(0, dim_y + 1, max(1, int(dim_y / 20))) + }, + step=1, + ), + id="crop-slider-container", + ), dbc.Modal( [ dbc.ModalHeader(dbc.ModalTitle("A-Scan")), @@ -251,18 +360,59 @@ Ascan_card = dbc.Fade( config=configAScan, style={"marginBottom": "15px"}, ), # 'fig' is your 2D plotly figure - dcc.Slider( - id="layer-slider-ascan-fullscreen", - min=0, - max=dim_x, - value=0, - step=1, - marks={ - str(i): str(i) - for i in range( - 0, dim_x, max(1, int(dim_x / 50)) - ) - }, + html.Div( + dcc.Slider( + id="layer-slider-x-ascan-fullscreen", + min=0, + max=dim_x, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range( + 0, + dim_x, + max(1, int(dim_x / 50)), + ) + }, + ), + id="layer-slider-x-ascan-fullscreen-container", + ), + html.Div( + dcc.Slider( + id="layer-slider-z-ascan-fullscreen", + min=1, + max=dim_z, + value=1, + step=1, + marks={ + str(i): str(i) + for i in range( + 1, + dim_z, + max(1, int(dim_z / 20)), + ) + }, + ), + id="layer-slider-z-ascan-fullscreen-container", + ), + html.Div( + dcc.RangeSlider( + id="crop-slider-fullscreen", + min=0, + max=dim_y, + value=[0, dim_y], + marks={ + str(i): str(i) + for i in range( + 0, + dim_y + 1, + max(1, int(dim_y / 20)), + ) + }, + step=1, + ), + id="crop-slider-fullscreen-container", ), ] ), @@ -270,8 +420,57 @@ Ascan_card = dbc.Fade( id="modal-ascan", fullscreen=True, ), + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("A-Scan settings")), + dbc.ModalBody( + [ + html.Span("Card display: "), + dbc.Checklist( + options=[ + { + "label": "Show crop slider", + "value": "crop_slider", + }, + ], + id="checklist-ascan-card-settings", + ), + html.Span("Fullscreen display: "), + dbc.Checklist( + options=[ + { + "label": "Show layer slider X", + "value": "layer_slider_x", + }, + { + "label": "Show layer slider Z", + "value": "layer_slider_z", + }, + { + "label": "Show crop slider", + "value": "crop_slider", + }, + ], + id="checklist-ascan-fullscreen-settings", + ), + ] + ), + dbc.ModalFooter( + [ + dbc.Button( + "Save", + id="save-settings-ascan", + className="ml-auto", + color="primary", + ), + ] + ), + ], + id="modal-settings-ascan", + fullscreen=False, + ), ], - style={"height": "599px"}, + style={"height": "610px"}, ) ], ), @@ -293,12 +492,26 @@ Bscan_card_xy = dbc.Fade( className="card-title", style={"textAlign": "left"}, ), - dbc.Button( - html.I(className="bi bi-arrows-fullscreen"), - id="fullscreen-button-bscan-zx", - className="mb-3", - color="primary", - style={"marginBottom": "15px"}, + html.Div( + [ + dbc.Button( + html.I(className="bi bi-gear-wide"), + id="settings-button-bscan-zx", + className="mb-3", + color="secondary", + style={ + "marginBottom": "15px", + "marginRight": "5px", + }, + ), + dbc.Button( + html.I(className="bi bi-arrows-fullscreen"), + id="fullscreen-button-bscan-zx", + className="mb-3", + color="primary", + style={"marginBottom": "15px"}, + ), + ] ), ], style={"display": "flex", "justifyContent": "space-between"}, @@ -308,16 +521,19 @@ Bscan_card_xy = dbc.Fade( config=configBScanZX, style={"marginBottom": "15px"}, ), # 'fig' is your 2D plotly figure - dcc.Slider( - id="layer-slider-bscan-zx", - min=0, - max=dim_x - 1, - value=0, - step=1, - marks={ - str(i): str(i) - for i in range(0, dim_x, max(1, int(dim_x / 20))) - }, + html.Div( + dcc.Slider( + id="layer-slider-bscan-zx", + min=0, + max=dim_x - 1, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range(0, dim_x, max(1, int(dim_x / 20))) + }, + ), + id="layer-slider-bscan-zx-container", ), dbc.Modal( [ @@ -329,18 +545,23 @@ Bscan_card_xy = dbc.Fade( config=configBScanXY, style={"marginBottom": "15px", "height": "90%"}, ), # 'fig' is your 2D plotly figure - dcc.Slider( - id="layer-slider-bscan-zx-fullscreen", - min=0, - max=dim_x, - value=0, - step=1, - marks={ - str(i): str(i) - for i in range( - 0, dim_x + 1, max(1, int(dim_x / 50)) - ) - }, + html.Div( + dcc.Slider( + id="layer-slider-bscan-zx-fullscreen", + min=0, + max=dim_x, + value=0, + step=1, + marks={ + str(i): str(i) + for i in range( + 0, + dim_x + 1, + max(1, int(dim_x / 50)), + ) + }, + ), + id="layer-slider-bscan-zx-fullscreen-container", ), ] ), @@ -348,6 +569,55 @@ Bscan_card_xy = dbc.Fade( id="modal-bscan-zx", fullscreen=True, ), + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("B-Scan ZX settings")), + dbc.ModalBody( + [ + html.Span("Card display: "), + dbc.Checklist( + options=[ + { + "label": "Show X slider", + "value": "x_slider", + } + ], + id="checklist-bscan-zx-card-settings", + ), + html.Span("Fullscreen display: "), + dbc.Checklist( + options=[ + { + "label": "Show X slider", + "value": "x_slider", + }, + { + "label": "Show Y slider", + "value": "y_slider", + }, + { + "label": "Show crop slider", + "value": "crop_slider", + }, + ], + id="checklist-bscan-zx-fullscreen-settings", + ), + ] + ), + dbc.ModalFooter( + [ + dbc.Button( + "Save", + id="save-settings-bscan-zx", + className="ml-auto", + color="primary", + ), + ] + ), + ], + id="modal-settings-bscan-zx", + fullscreen=False, + ), ] ) ] @@ -370,12 +640,30 @@ Bscan_card_zx = dbc.Fade( className="card-title", style={"textAlign": "left"}, ), - dbc.Button( - html.I(className="bi bi-arrows-fullscreen"), - id="fullscreen-button-bscan-xy", - className="mb-3", - color="primary", - style={"marginBottom": "15px"}, + html.Div( + [ + dbc.Button( + html.I(className="bi bi-gear-wide"), + id="settings-button-bscan-xy", + className="mb-3", + color="secondary", + style={ + "marginBottom": "15px", + "marginRight": "5px", + }, + ), + dbc.Button( + html.I(className="bi bi-arrows-fullscreen"), + id="fullscreen-button-bscan-xy", + className="mb-3", + color="primary", + style={"marginBottom": "15px"}, + ), + ], + style={ + "display": "flex", + "justifyContent": "space-between", + }, ), ], style={"display": "flex", "justifyContent": "space-between"}, @@ -385,16 +673,19 @@ Bscan_card_zx = dbc.Fade( config=configBScanXY, style={"marginBottom": "15px"}, ), # 'fig' is your 2D plotly figure - dcc.Slider( - id="layer-slider-bscan-xy", - min=1, - max=dim_z - 1, - value=1, - step=1, - marks={ - str(i): str(i) - for i in range(1, dim_z + 1, max(1, int(dim_z / 20))) - }, + html.Div( + dcc.Slider( + id="layer-slider-bscan-xy", + min=1, + max=dim_z - 1, + value=1, + step=1, + marks={ + str(i): str(i) + for i in range(1, dim_z + 1, max(1, int(dim_z / 20))) + }, + ), + id="layer-slider-bscan-xy-container", ), dbc.Modal( [ @@ -406,18 +697,23 @@ Bscan_card_zx = dbc.Fade( config=configBScanXY, style={"marginBottom": "15px", "height": "90%"}, ), # 'fig' is your 2D plotly figure - dcc.Slider( - id="layer-slider-bscan-xy-fullscreen", - min=1, - max=dim_z - 1, - value=1, - step=1, - marks={ - str(i): str(i) - for i in range( - 1, dim_z + 1, max(1, int(dim_z / 50)) - ) - }, + html.Div( + dcc.Slider( + id="layer-slider-bscan-xy-fullscreen", + min=1, + max=dim_z - 1, + value=1, + step=1, + marks={ + str(i): str(i) + for i in range( + 1, + dim_z + 1, + max(1, int(dim_z / 50)), + ) + }, + ), + id="layer-slider-bscan-xy-fullscreen-container", ), ], ), @@ -425,6 +721,55 @@ Bscan_card_zx = dbc.Fade( id="modal-bscan-xy", fullscreen=True, ), + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("B-Scan XY settings")), + dbc.ModalBody( + [ + html.Span("Card display: "), + dbc.Checklist( + options=[ + { + "label": "Show X slider", + "value": "x_slider", + } + ], + id="checklist-bscan-xy-card-settings", + ), + html.Span("Fullscreen display: "), + dbc.Checklist( + options=[ + { + "label": "Show X slider", + "value": "x_slider", + }, + { + "label": "Show Y slider", + "value": "y_slider", + }, + { + "label": "Show crop slider", + "value": "crop_slider", + }, + ], + id="checklist-bscan-xy-fullscreen-settings", + ), + ] + ), + dbc.ModalFooter( + [ + dbc.Button( + "Save", + id="save-settings-bscan-xy", + className="ml-auto", + color="primary", + ), + ] + ), + ], + id="modal-settings-bscan-xy", + fullscreen=False, + ), ] ) ] @@ -449,4 +794,4 @@ layout = html.Div( ] ) -get_callbacks() \ No newline at end of file +get_callbacks() diff --git a/3D_app/requirements.txt b/3D_app/requirements.txt index efb2f55..7acc10c 100644 --- a/3D_app/requirements.txt +++ b/3D_app/requirements.txt @@ -2,11 +2,10 @@ dash==2.17.0 dash_bootstrap_components==1.6.0 diskcache==5.6.3 matplotlib==3.8.4 -numpy==1.26.4 +numpy==2.0.0 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