From 560c6fad0ea95c79474339c94bcd22c75ce899bc Mon Sep 17 00:00:00 2001 From: Le Stagiaire Date: Fri, 24 May 2024 14:58:18 +0200 Subject: [PATCH] V3 de la 3D App --- 3D_app/README.md | 6 + 3D_app/main.py | 325 ++------- .../pages/__pycache__/ascan.cpython-312.pyc | Bin 0 -> 2696 bytes 3D_app/pages/__pycache__/home.cpython-312.pyc | Bin 0 -> 15896 bytes 3D_app/pages/ascan.py | 78 +++ 3D_app/pages/home.py | 620 ++++++++++++++++++ 6 files changed, 769 insertions(+), 260 deletions(-) create mode 100644 3D_app/pages/__pycache__/ascan.cpython-312.pyc create mode 100644 3D_app/pages/__pycache__/home.cpython-312.pyc create mode 100644 3D_app/pages/ascan.py create mode 100644 3D_app/pages/home.py diff --git a/3D_app/README.md b/3D_app/README.md index 9bda7f9..fa7d93d 100644 --- a/3D_app/README.md +++ b/3D_app/README.md @@ -10,3 +10,9 @@ * Correction des dépendances obselètes * Ajustement des sliders avec des valeurs commençant par 1 et non 0 * Changement du thème en dark + +## V3 + +* Ajout d'un menu pour naviguer entre les pages +* Ajout des popups pour afficher les graphiques en plein écran +* Ajout de la page avec les filtres pour la A-scan diff --git a/3D_app/main.py b/3D_app/main.py index 1552013..0b62c24 100644 --- a/3D_app/main.py +++ b/3D_app/main.py @@ -1,196 +1,14 @@ import dash from dash import dcc, html from dash.dependencies import Input, Output -import plotly.graph_objects as go -import numpy as np -from util import * import dash_bootstrap_components as dbc -import plotly.express as px -import plotly.io as pio - -# on définit le dossier et les fichiers à lire -dossier = "Dataset/Shear_transform" -fichiers_selectionnes = [ - "Shear_x001-x101_y{:03d}_Rot00_transform.csv".format(i) for i in range(10, 14) -] - -# dossier = "Dataset/Shear_Wave_Rot00_CSV_Data" -# fichiers_selectionnes = ['Shear_x001-x101_y{:03d}_Rot00.csv'.format(i) for i in range(10, 62)] - -# on lit les fichiers et on les met dans un tableau -pre_volume = np.array(lire_fichier_csv(dossier, fichiers_selectionnes)) -volume = pre_volume[:, ::32, :] -dim_x, dim_y, dim_z = volume.shape - -X, Y, Z = np.mgrid[0:dim_x, 0:dim_y, 0:dim_z] - -# on défini le thème de l'application -pio.templates.default = "plotly_dark" - -# on crée le plot 3D -fig = go.Figure( - data=go.Volume( - x=X.flatten(), - y=Y.flatten(), - z=Z.flatten(), - value=volume.flatten(), - isomin=5000, - isomax=volume.max(), - opacity=0.1, # needs to be small to see through all surfaces - surface_count=20, # needs to be a large number for good volume rendering - colorscale="Jet" - ) -) - -# on défini les configurations des plots -config3DPlot = { - "toImageButtonOptions": { - "format": "svg", # one of png, svg, jpeg, webp - "filename": "3D-Plot", - "height": 1000, - "width": 1400, - "scale": 1, # Multiply title/legend/axis/canvas sizes by this factor - }, - "displaylogo": False -} - -configAScan = { - "toImageButtonOptions": { - "format": "svg", # one of png, svg, jpeg, webp - "filename": "A-Scan", - "height": 1000, - "width": 1400, - "scale": 1, # Multiply title/legend/axis/canvas sizes by this factor - }, - "displaylogo": False -} - -configBScanXY = { - "toImageButtonOptions": { - "format": "svg", # one of png, svg, jpeg, webp - "filename": "B-Scan XY", - "height": 1000, - "width": 1400, - "scale": 1, # Multiply title/legend/axis/canvas sizes by this factor - }, - "displaylogo": False -} - -configBScanZX = { - "toImageButtonOptions": { - "format": "svg", # one of png, svg, jpeg, webp - "filename": "B-Scan ZX", - "height": 1000, - "width": 1400, - "scale": 1, # Multiply title/legend/axis/canvas sizes by this factor - }, - "displaylogo": False -} # on crée l'application -app = dash.Dash(__name__, external_stylesheets=[dbc.themes.DARKLY]) +app = dash.Dash(__name__, external_stylesheets=[dbc.themes.DARKLY, dbc.icons.BOOTSTRAP], use_pages=True) -# on crée les cartes -# carte pour le plot 3D -mesh_card = dbc.Card( - [ - dbc.CardBody( - [ - dcc.Graph(id="3dplot", figure=fig, config=config3DPlot, style={"height": "411px", "marginBottom": "15px"}), # 'fig' is your 3D plotly figure - dcc.Slider( - id="iso-slider", - 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()) / 10), - ) - }, - step=1, - ), - 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, - ), - ] - ) - ] -) +print('Reloading...') -# carte pour le A-scan -Ascan_card = dbc.Card( - [ - dbc.CardBody( - [ - dcc.Graph(id="heatmap-ascan", config=configAScan, style={"marginBottom": "15px"}), # 'fig' is your 2D plotly figure - dcc.Slider( - id="layer-slider-ascan", - min=1, - max=dim_x, - value=1, - step=1, - marks={ - str(i): str(i) for i in range(1, dim_x + 1, max(1, int(dim_x / 20))) - }, - ), - ] - ) - ] -) - -# carte pour le B-scan XY -Bscan_card_xy = dbc.Card( - [ - dbc.CardBody( - [ - dcc.Graph(id="heatmap-bscan-xy", config=configBScanXY, style={"marginBottom": "15px"}), # 'fig' is your 2D plotly figure - dcc.Slider( - id="layer-slider-bscan-xy", - min=1, - max=dim_x, - value=1, - step=1, - marks={ - str(i): str(i) for i in range(1, dim_x + 1, max(1, int(dim_x / 20))) - }, - ), - ] - ) - ] -) - -# carte pour le B-scan ZX -Bscan_card_zx = dbc.Card( - [ - dbc.CardBody( - [ - dcc.Graph(id="heatmap-bscan-zx", config=configBScanZX, style={"marginBottom": "15px"}), # 'fig' is your 2D plotly figure - dcc.Slider( - id="layer-slider-bscan-zx", - 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))) - }, - ), - ] - ) - ] -) # on lit le fichier modal.md pour le tuto with open("assets/modal.md", "r") as f: @@ -224,6 +42,32 @@ button_howto = dbc.Button( style={"textTransform": "none", "marginRight": "10px"}, ) +navmenu = html.Div( + dbc.Offcanvas( + dbc.ListGroup( + [ + dbc.Nav( + [ + dbc.NavLink( + html.Div(page["name"], className="ms-2"), + href=page["path"], + active="exact", + ) + for page in dash.page_registry.values() + if not page["path"].startswith("/fullscreen") + ], + vertical=True, + pills=True, + className="bg-dark", + ) + ] + ), + id="offcanvas-menu", + title="3D App", + is_open=False, + ), +) + # on défini la navbar nav_bar = dbc.Navbar( dbc.Container( @@ -234,15 +78,27 @@ nav_bar = dbc.Navbar( dbc.Row( [ dbc.Col( - html.A( - html.Img( - src=app.get_asset_url( - "logo_IJL couleur.png" - ), - height="100px", + [ + dbc.Button( + html.Span(className="navbar-toggler-icon"), + outline=True, + color="secondary", + id="navbar-toggler", + style={ + "marginRight": "10px", + "marginLeft": "10px", + }, ), - href="https://ijl.univ-lorraine.fr/", - ), + html.A( + html.Img( + src=app.get_asset_url( + "logo_IJL couleur.png" + ), + height="64px", + ), + href="https://ijl.univ-lorraine.fr/", + ), + ], style={"width": "min-content"}, ), dbc.Col( @@ -263,7 +119,6 @@ nav_bar = dbc.Navbar( ), dbc.Col( [ - dbc.NavbarToggler(id="navbar-toggler"), dbc.Collapse( dbc.Nav( [dbc.NavItem(button_howto), dbc.NavItem(button_gh)], @@ -273,91 +128,29 @@ nav_bar = dbc.Navbar( id="navbar-collapse", navbar=True, ), - ], align="right" + ], + align="right", ), modal_overlay, + navmenu, ], align="center", style={"width": "100%"}, ), ], fluid=True, - style={"--bs-gutter-x": "0"} + style={"--bs-gutter-x": "0"}, ), dark=True, ) # on défini le layout de l'application app.layout = dbc.Container( - [ - nav_bar, - dbc.Row([dbc.Col(Ascan_card, width=6), dbc.Col(mesh_card, width=6)], style={"margin": "8px"}), - dbc.Row([dbc.Col(Bscan_card_xy, width=6), dbc.Col(Bscan_card_zx, width=6)], style={"margin": "8px"}) - ], + [nav_bar, dash.page_container], fluid=True, ) -# on défini les callbacks -# callback pour le plot 3D -@app.callback( - Output("3dplot", "figure"), - [Input("iso-slider", "value"), Input("y-slider", "value")], -) -def update_3dplot(iso_value, y_values): - y_min, y_max = y_values - 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], - 0 : selected_volume.shape[1], - 0 : selected_volume.shape[2], - ] - - fig = go.Figure( - data=go.Volume( - x=X.flatten(), - y=Y.flatten(), - z=Z.flatten(), - value=selected_volume.flatten(), - isomin=iso_value, - isomax=selected_volume.max(), - opacity=0.1, - surface_count=20, - colorscale="Jet", - ) - ) - - return fig - -# callback pour le A-scan -@app.callback(Output("heatmap-ascan", "figure"), [Input("layer-slider-ascan", "value")]) -def update_heatmap_ascan(layer): - fig = px.line(y=volume[layer - 1, :, 5], title="A-scan") - return fig - -# callback pour les B-scan XY -@app.callback( - Output("heatmap-bscan-xy", "figure"), [Input("layer-slider-bscan-xy", "value")] -) -def update_heatmap_bscan_xy(layer): - fig = px.imshow( - volume[layer - 1, :, :], - color_continuous_scale="Jet", - aspect="auto", - title="B-scan XY", - ) - - return fig - -# callback pour les B-scan ZX -@app.callback( - Output("heatmap-bscan-zx", "figure"), [Input("layer-slider-bscan-zx", "value")] -) -def update_heatmap_bscan_zx(layer): - fig = go.Figure(data=go.Heatmap(z=volume[:, :, layer], colorscale="Jet")) - - return fig - # callback pour le modal @app.callback( Output("modal", "is_open"), @@ -370,6 +163,18 @@ def toggle_modal(n1, n2, is_open): return is_open +# callback pour le navmenu +@app.callback( + Output("offcanvas-menu", "is_open"), + [Input("navbar-toggler", "n_clicks")], + [dash.dependencies.State("offcanvas-menu", "is_open")], +) +def toggle_offcanvas(n, is_open): + if n: + return not is_open + return is_open + + # on lance l'application if __name__ == "__main__": app.run(debug=True, port="8051") diff --git a/3D_app/pages/__pycache__/ascan.cpython-312.pyc b/3D_app/pages/__pycache__/ascan.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c0fa33af93093da81dfdff721e9480724247da7 GIT binary patch literal 2696 zcmY)uTTC3+_0DUb?6PmbhOiW3!1lr})GbY|IM^7=Ze=GXZk!R(YS=q0LuPhHGqaf8 z+R|34B_mk^no1_EKUV#6g)60h>0hk$!%}}1Q7fD4T6NT)@`nwolzjEvon5az(%kbt z_uTXPM|4&Xy|l2KXq$2HP4;e_2kl!(>;YdL z%Rg@Ev?1`1$_crrbw|rq?3H^lgPGebJ+c2b=TM7W4;YJma^LdlwwD$*51ADE#iTd@ zx6QP;ZHSTF4<3=6Y@7qPS;t?)ZcrQ&-^Lv{fP=Ubhj945fFn41pB0Zml+5zwwx1RT zeE0JEEvgN1499T-cim@kH}0V@&*CHasKPw#-G&(H2e!iURR@3l#C`HH{8mG^{|(&_ zU)OSQoWvoyA7TxGw=M@Sg9xY-4cbl2tipry6fgId3g{9MxooFd1+osM3P05@AJBi}EYs1Ysu1 z0ZlUVN+GqN!wnkYGpCGI$P}w!;?|q@36@<_<#GfvWNVVqyvov5opd*;MdW*fu&O!Yyn<22=gMuPjW7=wvVy0x$j-jbDRtc{v1t{x^ zt`;>J($aVpQ~@$UIEdV$5hjz#AEwT|Gk?v3?wnuD>qTpR&W36yFJAiPyhX7r#U^L6 zl3~mnKwI;T1dbUc5-S>5vgK^EDA|UB|xbj{EUa>2G>_!e>`W~jQYmd5fRT*~ST%g~k<3A{9H$NhYe_O>B49X}mtu)83k z#0Bl47wtv~=^|4s+PZ0T0?r>ir!^fHRry_W9!M&%V1uK5j$xRm$p3FN`2rej2 zu{~`4z3CdC`G%j`;itX~KjDA=l5%S(z8^xt#GR|_*VnG^B#u>Y)Y9j76SKR43l(48 z8~xq%ro9!}u>bJE#-$x^f5lZ#^xji9)g2VA2$kX;`MIxSePnH9BeQXBIc8>xrF^-#PXi9!tDjieq(U&Ox*|F!3D=e}Yd zO@38;Y(6S~eP;Lc-0sMSkhf^CJ~*_=ZA^dW`kdRE{-bL%2ziSJ4+CCs+}AC*_9swB zd}FcZ>pMJk)W=p_FWyD0?_1ti^Y>S~clqPr3V1!;dNZ6EULq-rlqia#B+9Z5>aq^Y^6lubWGlYqLuQ<1Db9!@!^=a`mKb}N z+1+%dW((VGfk@c^k(&h~)hePi1>&G+;UER-9zRIXfhV)s*2T8n`k!nGizfZk?+rO5 zB~h{+C-Ju3f%N9R?|s+%zVChS82+PLt;FD|_`~BP|8)|>euWz1V@_3;deVOu&_U6;@>mmS%}>ELc?+ys zp-y;oe=fl>3C3XVI#GP)%@on(Jato&gaC!qst4R+T1Lx7XfJjZb4Z-<|FRClu3)sn z-J0Pb)-Gc=@nrqFU|+$kCBRGN-X`K?-T*IU^h}8;L!`Bu7!!@t`0El>(#qF~ zWNU;d0b*+zQ|8{2;Ud<~6VO*-HCrn|TCWJrylIT70FD@A7Rt)kB}sk)U6plFfe(tTWPW!?fD{JZ*Ls@$hn!!Yb-3dC&Lu(x`{-m;B)X%*P$?D~>v zHLaoZ=QrI{Byr{o(zb7;wX}{dq>E@hUHmph8|ad^CDtZ@8rMLZ0b05Sx)q?NHP9BY zk<2~3Rwn^YuLB=h2R@n;j=SA!;dB{YPFK)o7d~Hk6Ji8C^yc|0G3o^Qx%tW5o+N&D z3HZ3%mkmkQ=`Hisq7~^Hx|Xh^Ep$CdZMaD#F~nY{M63ZX${onTtC4ABn&>9Fo^F0y zGQagEnZyzB*&O&S;P3EScskNHj)vAq9)@kq0VEg0|L%>kvc`olH;O<(rif4LBcObW z|6{2()*%8JmzJRsiQGl6Cv%t!<*)*j!$97iOvYN-`eVsYb;
  • *w2UO0(>bR9*GZ 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 Rzryu_rt*I`>jQ0#{{#7vTweeH literal 0 HcmV?d00001 diff --git a/3D_app/pages/ascan.py b/3D_app/pages/ascan.py new file mode 100644 index 0000000..6e263b1 --- /dev/null +++ b/3D_app/pages/ascan.py @@ -0,0 +1,78 @@ +import dash +from dash import html, callback, Input, Output, dcc +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 * + +dash.register_page(__name__, path="/ascan", title='A-Scan filters', name='A-Scan filters') + +# on définit le dossier et les fichiers à lire +dossier = "Dataset/Shear_transform" +fichiers_selectionnes = [ + "Shear_x001-x101_y{:03d}_Rot00_transform.csv".format(i) for i in range(10, 14) +] + +# dossier = "Dataset/Shear_Wave_Rot00_CSV_Data" +# fichiers_selectionnes = ['Shear_x001-x101_y{:03d}_Rot00.csv'.format(i) for i in range(10, 62)] + +# on lit les fichiers et on les met dans un tableau +pre_volume = np.array(lire_fichier_csv(dossier, fichiers_selectionnes)) +volume = pre_volume[:, ::32, :] +dim_x, dim_y, dim_z = volume.shape + +X, Y, Z = np.mgrid[0:dim_x, 0:dim_y, 0:dim_z] + +configAScan = { + "toImageButtonOptions": { + "format": "svg", # one of png, svg, jpeg, webp + "filename": "A-Scan", + "height": 1000, + "width": 1400, + "scale": 1, # Multiply title/legend/axis/canvas sizes by this factor + }, + "displaylogo": False, +} + +layout = html.Div( + [ + dbc.Select( + id="select-ascan-filter", + options=[ + {"label": "Option 1", "value": "1"}, + {"label": "Option 2", "value": "2"}, + ], + style={"margin-bottom": "15px"}, + ), + dcc.Graph( + id="heatmap-ascan-solo", + config=configAScan, + style={"marginBottom": "15px"}, + ), # 'fig' is your 2D plotly figure + dcc.Slider( + id="layer-slider-ascan-solo", + min=1, + max=dim_x, + value=1, + step=1, + marks={ + str(i): str(i) for i in range(1, dim_x + 1, max(1, int(dim_x / 20))) + }, + ), + ], + style={"padding": "20px"}, +) + + +# callback to update the heatmap +@callback( + Output("heatmap-ascan-solo", "figure"), + [Input("select-ascan-filter", "value"), Input("layer-slider-ascan-solo", "value")], +) +def update_heatmap_ascan(value, layer): + # TODO: implement the filter + + fig = px.line(y=volume[layer - 1, :, 5], title="A-scan") + return fig diff --git a/3D_app/pages/home.py b/3D_app/pages/home.py new file mode 100644 index 0000000..66b687f --- /dev/null +++ b/3D_app/pages/home.py @@ -0,0 +1,620 @@ +import dash +from dash import html, callback, Input, Output, dcc +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 * + +dash.register_page(__name__, path="/") + +# on définit le dossier et les fichiers à lire +dossier = "Dataset/Shear_transform" +fichiers_selectionnes = [ + "Shear_x001-x101_y{:03d}_Rot00_transform.csv".format(i) for i in range(10, 14) +] + +# dossier = "Dataset/Shear_Wave_Rot00_CSV_Data" +# fichiers_selectionnes = ['Shear_x001-x101_y{:03d}_Rot00.csv'.format(i) for i in range(10, 62)] + +# on lit les fichiers et on les met dans un tableau +pre_volume = np.array(lire_fichier_csv(dossier, fichiers_selectionnes)) +volume = pre_volume[:, ::32, :] +dim_x, dim_y, dim_z = volume.shape + +X, Y, Z = np.mgrid[0:dim_x, 0:dim_y, 0:dim_z] + +# on défini le thème de l'application +pio.templates.default = "plotly_dark" + +# on crée le plot 3D +fig = go.Figure( + data=go.Volume( + x=X.flatten(), + y=Y.flatten(), + z=Z.flatten(), + value=volume.flatten(), + isomin=5000, + isomax=volume.max(), + opacity=0.1, # needs to be small to see through all surfaces + surface_count=20, # needs to be a large number for good volume rendering + colorscale="Jet", + ) +) + +# on défini les configurations des plots +config3DPlot = { + "toImageButtonOptions": { + "format": "svg", # one of png, svg, jpeg, webp + "filename": "3D-Plot", + "height": 1000, + "width": 1400, + "scale": 1, # Multiply title/legend/axis/canvas sizes by this factor + }, + "displaylogo": False, +} + +configAScan = { + "toImageButtonOptions": { + "format": "svg", # one of png, svg, jpeg, webp + "filename": "A-Scan", + "height": 1000, + "width": 1400, + "scale": 1, # Multiply title/legend/axis/canvas sizes by this factor + }, + "displaylogo": False, +} + +configBScanXY = { + "toImageButtonOptions": { + "format": "svg", # one of png, svg, jpeg, webp + "filename": "B-Scan XY", + "height": 1000, + "width": 1400, + "scale": 1, # Multiply title/legend/axis/canvas sizes by this factor + }, + "displaylogo": False, +} + +configBScanZX = { + "toImageButtonOptions": { + "format": "svg", # one of png, svg, jpeg, webp + "filename": "B-Scan ZX", + "height": 1000, + "width": 1400, + "scale": 1, # Multiply title/legend/axis/canvas sizes by this factor + }, + "displaylogo": False, +} + + +# on crée les cartes +# carte pour le plot 3D +mesh_card = dbc.Card( + [ + dbc.CardBody( + [ + dbc.Row( + [ + dbc.Col( + html.H2( + "3D Plot", + className="card-title", + style={"textAlign": "left"}, + ), + width="4", + ), + dbc.Col( + dbc.Button( + html.I(className="bi bi-arrows-fullscreen"), + id="fullscreen-button-3dplot", + className="mb-3", + color="primary", + style={"marginBottom": "15px"}, + ), + ), + ], + ), + dcc.Graph( + id="3dplot", + figure=fig, + config=config3DPlot, + style={"height": "411px", "marginBottom": "15px"}, + ), # 'fig' is your 3D plotly figure + dcc.Slider( + id="iso-slider", + 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()) / 10), + ) + }, + step=1, + ), + 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, + ), + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("3D Plot")), + dbc.ModalBody( + [ + dcc.Graph( + id="3dplot-fullscreen", + figure=fig, + 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()) / 10), + ) + }, + step=1, + ), + 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 / 20)) + ) + }, + step=1, + ), + ] + ), + ], + id="modal-3dplot", + fullscreen=True, + ), + ] + ) + ] +) + +# carte pour le A-scan +Ascan_card = dbc.Card( + [ + dbc.CardBody( + [ + dbc.Row( + [ + dbc.Col( + html.H2( + "A-scan", + className="card-title", + style={"textAlign": "left"}, + ), + width="4", + ), + dbc.Col( + dbc.Button( + html.I(className="bi bi-arrows-fullscreen"), + id="fullscreen-button-ascan", + className="mb-3", + color="primary", + style={"marginBottom": "15px"}, + ), + ), + ], + ), + dcc.Graph( + id="heatmap-ascan", + config=configAScan, + style={"marginBottom": "15px"}, + ), # 'fig' is your 2D plotly figure + dcc.Slider( + id="layer-slider-ascan", + min=1, + max=dim_x, + value=1, + step=1, + marks={ + str(i): str(i) + for i in range(1, dim_x + 1, max(1, int(dim_x / 20))) + }, + ), + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("A-Scan")), + dbc.ModalBody( + [ + dcc.Graph( + id="heatmap-ascan-fullscreen", + config=configAScan, + style={"marginBottom": "15px"}, + ), # 'fig' is your 2D plotly figure + dcc.Slider( + id="layer-slider-ascan-fullscreen", + min=1, + max=dim_x, + value=1, + step=1, + marks={ + str(i): str(i) + for i in range( + 1, dim_x + 1, max(1, int(dim_x / 20)) + ) + }, + ), + ] + ), + ], + id="modal-ascan", + fullscreen=True, + ), + ] + ) + ] +) + +# carte pour le B-scan XY +Bscan_card_xy = dbc.Card( + [ + dbc.CardBody( + [ + dbc.Row( + [ + dbc.Col( + html.H2( + "B-scan XY", + className="card-title", + style={"textAlign": "left"}, + ), + width="4", + ), + dbc.Col( + dbc.Button( + html.I(className="bi bi-arrows-fullscreen"), + id="fullscreen-button-bscan-xy", + className="mb-3", + color="primary", + style={"marginBottom": "15px"}, + ), + ), + ], + ), + dcc.Graph( + id="heatmap-bscan-xy", + config=configBScanXY, + style={"marginBottom": "15px"}, + ), # 'fig' is your 2D plotly figure + dcc.Slider( + id="layer-slider-bscan-xy", + min=1, + max=dim_x, + value=1, + step=1, + marks={ + str(i): str(i) + for i in range(1, dim_x + 1, max(1, int(dim_x / 20))) + }, + ), + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("B-Scan XY")), + dbc.ModalBody( + [ + dcc.Graph( + id="heatmap-bscan-xy-fullscreen", + config=configBScanXY, + style={"marginBottom": "15px"}, + ), # 'fig' is your 2D plotly figure + dcc.Slider( + id="layer-slider-bscan-xy-fullscreen", + min=1, + max=dim_x, + value=1, + step=1, + marks={ + str(i): str(i) + for i in range( + 1, dim_x + 1, max(1, int(dim_x / 20)) + ) + }, + ), + ] + ), + ], + id="modal-bscan-xy", + fullscreen=True, + ), + ] + ) + ] +) + +# carte pour le B-scan ZX +Bscan_card_zx = dbc.Card( + [ + dbc.CardBody( + [ + dbc.Row( + [ + dbc.Col( + html.H2( + "B-scan ZX", + className="card-title", + style={"textAlign": "left"}, + ), + width="4", + ), + dbc.Col( + dbc.Button( + html.I(className="bi bi-arrows-fullscreen"), + id="fullscreen-button-bscan-zx", + className="mb-3", + color="primary", + style={"marginBottom": "15px"}, + ), + ), + ], + ), + dcc.Graph( + id="heatmap-bscan-zx", + config=configBScanZX, + style={"marginBottom": "15px"}, + ), # 'fig' is your 2D plotly figure + dcc.Slider( + id="layer-slider-bscan-zx", + 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))) + }, + ), + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("B-Scan XY")), + dbc.ModalBody( + [ + dcc.Graph( + id="heatmap-bscan-zx-fullscreen", + config=configBScanZX, + style={"marginBottom": "15px"}, + ), # 'fig' is your 2D plotly figure + dcc.Slider( + id="layer-slider-bscan-zx-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 / 20)) + ) + }, + ), + ], + ), + ], + id="modal-bscan-xy", + fullscreen=True, + ), + ] + ) + ] +) + +layout = html.Div( + [ + dbc.Row( + [dbc.Col(Ascan_card, width=6), dbc.Col(mesh_card, width=6)], + style={"margin": "8px"}, + ), + dbc.Row( + [dbc.Col(Bscan_card_xy, width=6), dbc.Col(Bscan_card_zx, width=6)], + style={"margin": "8px"}, + ), + ] +) + + +# on défini les callbacks +# callback pour le plot 3D +@callback( + Output("3dplot", "figure"), + [Input("iso-slider", "value"), Input("y-slider", "value")], +) +def update_3dplot(iso_value, y_values): + y_min, y_max = y_values + 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], + 0 : selected_volume.shape[1], + 0 : selected_volume.shape[2], + ] + + fig = go.Figure( + data=go.Volume( + x=X.flatten(), + y=Y.flatten(), + z=Z.flatten(), + value=selected_volume.flatten(), + isomin=iso_value, + isomax=selected_volume.max(), + opacity=0.1, + surface_count=20, + colorscale="Jet", + ) + ) + + return fig + + +# callback pour le plot 3D en plein écran +@callback( + Output("3dplot-fullscreen", "figure"), + [Input("iso-slider-fullscreen", "value"), Input("y-slider-fullscreen", "value")], +) +def update_3dplot_fullscreen(iso_value, y_values): + y_min, y_max = y_values + 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], + 0 : selected_volume.shape[1], + 0 : selected_volume.shape[2], + ] + + fig = go.Figure( + data=go.Volume( + x=X.flatten(), + y=Y.flatten(), + z=Z.flatten(), + value=selected_volume.flatten(), + isomin=iso_value, + isomax=selected_volume.max(), + opacity=0.1, + surface_count=20, + colorscale="Jet", + ) + ) + + return fig + + +# callback pour le A-scan +@callback( + Output("heatmap-ascan", "figure"), + Input("layer-slider-ascan", "value"), +) +def update_heatmap_ascan(layer): + fig = px.line(y=volume[layer - 1, :, 5], title="A-scan") + return fig + + +# callback pour le A-scan en plein écran +@callback( + Output("heatmap-ascan-fullscreen", "figure"), + Input("layer-slider-ascan-fullscreen", "value"), +) +def update_heatmap_ascan_fullscreen(layer): + fig = px.line(y=volume[layer - 1, :, 5], title="A-scan") + return fig + + +# callback pour les B-scan XY +@callback(Output("heatmap-bscan-xy", "figure"), Input("layer-slider-bscan-xy", "value")) +def update_heatmap_bscan_xy(layer): + fig = px.imshow( + volume[layer - 1, :, :], + color_continuous_scale="Jet", + aspect="auto", + title="B-scan XY", + ) + + return fig + + +# callback pour les B-scan XY en plein écran +@callback( + Output("heatmap-bscan-xy-fullscreen", "figure"), + Input("layer-slider-bscan-xy-fullscreen", "value"), +) +def update_heatmap_bscan_xy_fullscreen(layer): + fig = px.imshow( + volume[layer - 1, :, :], + color_continuous_scale="Jet", + aspect="auto", + title="B-scan XY", + ) + + return fig + + +# callback pour les B-scan ZX +@callback(Output("heatmap-bscan-zx", "figure"), Input("layer-slider-bscan-zx", "value")) +def update_heatmap_bscan_zx(layer): + fig = go.Figure(data=go.Heatmap(z=volume[:, :, layer], colorscale="Jet")) + + return fig + + +# callback pour les B-scan ZX en plein écran +@callback( + Output("heatmap-bscan-zx-fullscreen", "figure"), + Input("layer-slider-bscan-zx-fullscreen", "value"), +) +def update_heatmap_bscan_zx_fullscreen(layer): + fig = go.Figure(data=go.Heatmap(z=volume[:, :, layer], colorscale="Jet")) + + 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"), + [Input("fullscreen-button-bscan-xy", "n_clicks")], + [dash.dependencies.State("modal-bscan-xy", "is_open")], +) +def toggle_fullscreen_bscan_xy(n1, is_open): + if n1: + 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 +