From a220a48da4a81b5f9c6b3635e3bfdaf4a9ee7fdc Mon Sep 17 00:00:00 2001
From: s233039 <s233039@student.dtu.dk>
Date: Wed, 26 Jun 2024 12:05:28 +0200
Subject: [PATCH] Qim theme

---
 qim3d/css/gradio.css                   |   2 +-
 qim3d/gui/annotation_tool.py           |  27 +++----
 qim3d/gui/data_explorer.py             |  41 +++++-----
 qim3d/gui/images/qim_platform-icon.svg | 104 ++++++++++++++++++++++++
 qim3d/gui/iso3d.py                     |  23 +++---
 qim3d/gui/local_thickness.py           |  27 +++----
 qim3d/gui/qim_theme.py                 | 106 +++++++++++++++++++++++++
 qim3d/utils/cli.py                     |  12 +--
 8 files changed, 272 insertions(+), 70 deletions(-)
 create mode 100644 qim3d/gui/images/qim_platform-icon.svg
 create mode 100644 qim3d/gui/qim_theme.py

diff --git a/qim3d/css/gradio.css b/qim3d/css/gradio.css
index 9c424329..72407d14 100644
--- a/qim3d/css/gradio.css
+++ b/qim3d/css/gradio.css
@@ -351,7 +351,7 @@ input[type="range"]::-webkit-slider-thumb {
 /* Buttons */
 .btn-html {
   position: sticky;
-  display: flex;
+  /* display: flex; */
   justify-content: center;
   background: white !important;
   border-color: #6c757d !important;
diff --git a/qim3d/gui/annotation_tool.py b/qim3d/gui/annotation_tool.py
index 40b3fda8..a554b463 100644
--- a/qim3d/gui/annotation_tool.py
+++ b/qim3d/gui/annotation_tool.py
@@ -28,6 +28,10 @@ import gradio as gr
 import numpy as np
 import qim3d.utils
 from qim3d.io import load, save
+from qim3d.io.logger import log
+from .qim_theme import QimTheme
+from pathlib import Path
+import qim3d
 
 
 class Session:
@@ -50,14 +54,12 @@ class Interface:
         self.temp_dir = os.path.join(tempfile.gettempdir(), f"qim-{self.username}")
         self.name_suffix = None
 
-        # CSS path
-        current_dir = os.path.dirname(os.path.abspath(__file__))
-        self.css_path = os.path.join(current_dir, "..", "css", "gradio.css")
 
-    def launch(self, img=None, **kwargs):
+
+    def launch(self, img=None, force_light_mode:bool = True, **kwargs):
         # Create gradio interfaces
         # img = "/tmp/qim-fima/2dimage.png"
-        self.interface = self.create_interface(img)
+        self.interface = self.create_interface(img, force_light_mode=force_light_mode)
 
         # Set gradio verbose level
         if self.verbose:
@@ -69,6 +71,7 @@ class Interface:
             quiet=quiet,
             height=self.height,
             # width=self.width,
+            favicon_path = Path(qim3d.__file__).parents[0] / "gui/images/qim_platform-icon.svg",
             **kwargs,
         )
 
@@ -94,15 +97,9 @@ class Interface:
     def set_visible(self):
         return gr.update(visible=True)
 
-    def create_interface(self, img=None):
-        from PIL import Image
-
-        if img is not None:
-            custom_css = "annotation-tool"
-        else:
-            custom_css = "annotation-tool no-img"
+    def create_interface(self, img=None, force_light_mode:bool = False):
 
-        with gr.Blocks(css=self.css_path, title=self.title) as gradio_interface:
+        with gr.Blocks(theme = QimTheme(force_light_mode = force_light_mode), title=self.title) as gradio_interface:
 
             brush = gr.Brush(
                 colors=[
@@ -134,7 +131,6 @@ class Interface:
                         show_download_button=True,
                         container=False,
                         transforms=["crop"],
-                        elem_classes=custom_css,
                         layers=False,
                     )
 
@@ -142,13 +138,12 @@ class Interface:
 
                     with gr.Row():
                         overlay_img = gr.Image(
-                            show_download_button=False, show_label=False, visible=False, elem_classes="no-interpolation"
+                            show_download_button=False, show_label=False, visible=False,
                         )
                     with gr.Row():
                         masks_download = gr.File(
                             label="Download masks",
                             visible=False,
-                            elem_classes=custom_css,
                         )
 
             temp_dir = gr.Textbox(value=self.temp_dir, visible=False)
diff --git a/qim3d/gui/data_explorer.py b/qim3d/gui/data_explorer.py
index a8b44701..a786d453 100644
--- a/qim3d/gui/data_explorer.py
+++ b/qim3d/gui/data_explorer.py
@@ -27,10 +27,13 @@ import matplotlib.pyplot as plt
 import numpy as np
 import outputformat as ouf
 import tifffile
+from pathlib import Path
 
+import qim3d
 from qim3d.io import load
 from qim3d.io.logger import log
 from qim3d.utils import internal_tools
+from .qim_theme import QimTheme
 
 
 class Interface:
@@ -49,9 +52,6 @@ class Interface:
             "Intensity histogram",
             "Data summary",
         ]
-        # CSS path
-        current_dir = os.path.dirname(os.path.abspath(__file__))
-        self.css_path = os.path.join(current_dir, "..", "css", "gradio.css")
 
     def clear(self):
         """Used to reset outputs with the clear button"""
@@ -77,14 +77,12 @@ class Interface:
 
     def set_spinner(self, message):
         return gr.update(
-            elem_classes="btn btn-spinner",
             value=f"{message}",
             interactive=False,
         )
 
     def set_relaunch_button(self):
         return gr.update(
-            elem_classes="btn btn-run",
             value=f"Relaunch",
             interactive=True,
         )
@@ -98,8 +96,9 @@ class Interface:
                 update_list.append(gr.update(visible=False))
         return update_list
 
-    def create_interface(self):
-        with gr.Blocks(css=self.css_path) as gradio_interface:
+    def create_interface(self, force_light_mode:bool = True):
+        with gr.Blocks(theme = QimTheme(force_light_mode=force_light_mode), 
+                       title = self.title) as gradio_interface:
             gr.Markdown("# Data Explorer")
 
             # File selection and parameters
@@ -112,12 +111,11 @@ class Interface:
                                 max_lines=1,
                                 container=False,
                                 label="Base path",
-                                elem_classes="h-36",
                                 value=os.getcwd(),
                             )
                         with gr.Column(scale=1, min_width=36):
                             reload_base_path = gr.Button(
-                                value="⟳", elem_classes="btn-html h-36"
+                                value="⟳"
                             )
                     explorer = gr.FileExplorer(
                         ignore_glob="*/.*",  # ignores hidden files
@@ -126,7 +124,7 @@ class Interface:
                         render=True,
                         file_count="single",
                         interactive=True,
-                        elem_classes="h-320 hide-overflow",
+                        height = 320,
                     )
 
                 with gr.Column(scale=1):
@@ -180,22 +178,22 @@ class Interface:
                     )
                     with gr.Row():
                         btn_run = gr.Button(
-                            value="Load & Run", elem_classes="btn btn-html btn-run"
+                            value="Load & Run", variant = "primary",
                         )
 
             # Visualization and results
-            with gr.Row(elem_classes="mt-64"):
+            with gr.Row():
 
                 # Z Slicer
                 with gr.Column(visible=False) as result_z_slicer:
-                    zslice_plot = gr.Plot(label="Z slice", elem_classes="rounded")
+                    zslice_plot = gr.Plot(label="Z slice")
                     zpos = gr.Slider(
                         minimum=0, maximum=1, value=0.5, step=0.01, label="Z position"
                     )
 
                 # Y Slicer
                 with gr.Column(visible=False) as result_y_slicer:
-                    yslice_plot = gr.Plot(label="Y slice", elem_classes="rounded")
+                    yslice_plot = gr.Plot(label="Y slice")
 
                     ypos = gr.Slider(
                         minimum=0, maximum=1, value=0.5, step=0.01, label="Y position"
@@ -203,7 +201,7 @@ class Interface:
 
                 # X Slicer
                 with gr.Column(visible=False) as result_x_slicer:
-                    xslice_plot = gr.Plot(label="X slice", elem_classes="rounded")
+                    xslice_plot = gr.Plot(label="X slice")
 
                     xpos = gr.Slider(
                         minimum=0, maximum=1, value=0.5, step=0.01, label="X position"
@@ -211,13 +209,13 @@ class Interface:
                 # Z Max projection
                 with gr.Column(visible=False) as result_z_max_projection:
                     max_projection_plot = gr.Plot(
-                        label="Z max projection", elem_classes="rounded"
+                        label="Z max projection",
                     )
 
                 # Z Min projection
                 with gr.Column(visible=False) as result_z_min_projection:
                     min_projection_plot = gr.Plot(
-                        label="Z min projection", elem_classes="rounded"
+                        label="Z min projection",
                     )
 
                 # Intensity histogram
@@ -230,7 +228,7 @@ class Interface:
                         lines=24,
                         label=None,
                         show_label=False,
-                        elem_classes="monospace-box",
+
                         value="Data summary",
                     )
             ### Gradio objects lists
@@ -379,13 +377,13 @@ class Interface:
 
         return session, gr.update(label=f"X slice: {session.xslice}")
 
-    def launch(self, **kwargs):
+    def launch(self,force_light_mode:bool = True, **kwargs):
         # Show header
         if self.show_header:
             internal_tools.gradio_header(self.title, self.port)
 
-        # Create gradio interfaces
-        interface = self.create_interface()
+        # Create gradio interfaces 
+        interface = self.create_interface(force_light_mode=force_light_mode)
 
         # Set gradio verbose level
         if self.verbose:
@@ -397,6 +395,7 @@ class Interface:
             quiet=quiet,
             height=self.height,
             width=self.width,
+            favicon_path = Path(qim3d.__file__).parents[0] / "gui/images/qim_platform-icon.svg",
             **kwargs,
         )
 
diff --git a/qim3d/gui/images/qim_platform-icon.svg b/qim3d/gui/images/qim_platform-icon.svg
new file mode 100644
index 00000000..b40bab5f
--- /dev/null
+++ b/qim3d/gui/images/qim_platform-icon.svg
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="8.3444281mm"
+   height="7.7738566mm"
+   viewBox="0 0 8.344428 7.7738567"
+   version="1.1"
+   id="svg1"
+   inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
+   sodipodi:docname="qim_platform-icon.svg"
+   inkscape:export-filename="qim_platform-icon.png"
+   inkscape:export-xdpi="779.25055"
+   inkscape:export-ydpi="779.25055"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:document-units="mm"
+     inkscape:zoom="3.1345226"
+     inkscape:cx="90.603908"
+     inkscape:cy="61.253347"
+     inkscape:window-width="1266"
+     inkscape:window-height="631"
+     inkscape:window-x="3830"
+     inkscape:window-y="56"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs1" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-49.212495,-93.140004)">
+    <g
+       id="g95"
+       transform="matrix(1.0725239,0,0,1.0727401,-76.182898,-88.386314)"
+       style="stroke-width:0.932287">
+      <path
+         id="path94"
+         style="fill:#ff9900;fill-opacity:1;stroke:none;stroke-width:0.443769;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+         d="m 117.13806,171.16039 v -1.08393 c 0,-0.35299 0.2857,-0.63715 0.64059,-0.63715 v 0 h 6.05523 c 0.35489,0 0.64059,0.28416 0.64059,0.63715 v 1.08393"
+         sodipodi:nodetypes="csscssc" />
+      <path
+         id="rect75"
+         style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.443769;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+         d="m 117.13806,172.211 v -2.13454 c 0,-0.35299 0.2857,-0.63715 0.64059,-0.63715 v 0 h 6.05523 c 0.35489,0 0.64059,0.28416 0.64059,0.63715 v 3.9123"
+         sodipodi:nodetypes="csscssc" />
+      <path
+         id="path76"
+         style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.443769;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+         d="m 124.47447,174.94459 v 0.66053 c 0,0.35299 -0.2857,0.63715 -0.64059,0.63715 h -6.05523 c -0.35489,0 -0.64059,-0.28416 -0.64059,-0.63715 v -2.43526"
+         sodipodi:nodetypes="cssssc" />
+      <path
+         style="fill:#cd4d00;fill-opacity:1;stroke:#000000;stroke-width:0.443769;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+         d="m 118.00096,170.34725 h 0.28389"
+         id="path72"
+         sodipodi:nodetypes="cc" />
+      <path
+         style="fill:#cd4d00;fill-opacity:1;stroke:#000000;stroke-width:0.443769;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+         d="m 120.63414,170.34725 h 2.93385"
+         id="path74"
+         sodipodi:nodetypes="cc" />
+      <path
+         style="fill:#cd4d00;fill-opacity:1;stroke:#000000;stroke-width:0.443769;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+         d="m 119.04851,170.34725 h 0.28389"
+         id="path75"
+         sodipodi:nodetypes="cc" />
+      <g
+         id="g93"
+         transform="matrix(0.36505828,0,0,0.35972642,83.784309,128.37775)"
+         style="stroke-width:2.57263">
+        <path
+           id="path91"
+           d="m 100.83326,125.49607 -5.27832,2.18097 c -0.37814,0.14893 -0.41489,0.7496 -0.0409,0.91436 l 5.17239,2.13082 c 0.4877,0.18116 1.00239,0.18129 1.45479,0 l 5.17239,-2.13082 c 0.37397,-0.16476 0.33723,-0.76543 -0.0409,-0.91436 l -5.27831,-2.18097 c -0.44426,-0.18069 -0.70409,-0.18273 -1.1611,0 z"
+           fill="#ffab61"
+           paint-order="normal"
+           style="fill:#990000;fill-opacity:1;stroke:#000000;stroke-width:1.2246;stroke-dasharray:none;stroke-opacity:1" />
+        <path
+           id="path92"
+           d="m 100.83326,123.02262 -5.27832,2.18097 c -0.37814,0.14893 -0.41489,0.7496 -0.0409,0.91436 l 5.17239,2.13082 c 0.4877,0.18116 1.00239,0.18129 1.45479,0 l 5.17239,-2.13082 c 0.37397,-0.16476 0.33723,-0.76543 -0.0409,-0.91436 l -5.27831,-2.18097 c -0.44426,-0.18069 -0.70409,-0.18272 -1.1611,0 z"
+           fill="#ffc861"
+           paint-order="normal"
+           style="fill:#cd4d00;fill-opacity:1;stroke:#000000;stroke-width:1.2246;stroke-dasharray:none;stroke-opacity:1" />
+        <path
+           id="path93"
+           d="m 100.83325,120.5494 -5.27832,2.18098 c -0.37814,0.14893 -0.41489,0.7496 -0.0409,0.91436 l 5.17239,2.13082 c 0.4877,0.18115 1.00239,0.18128 1.45479,0 l 5.17239,-2.13082 c 0.37397,-0.16477 0.33723,-0.76544 -0.0409,-0.91436 l -5.27831,-2.18098 c -0.44426,-0.18069 -0.70409,-0.18271 -1.1611,0 z"
+           fill="#55a1ff"
+           paint-order="normal"
+           style="fill:#ff9900;fill-opacity:1;stroke:#000000;stroke-width:1.2246;stroke-dasharray:none;stroke-opacity:1" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/qim3d/gui/iso3d.py b/qim3d/gui/iso3d.py
index f6b0870d..ff154400 100644
--- a/qim3d/gui/iso3d.py
+++ b/qim3d/gui/iso3d.py
@@ -22,8 +22,11 @@ import os
 from qim3d.utils import internal_tools
 from qim3d.io import DataLoader
 from qim3d.io.logger import log
+from .qim_theme import QimTheme
 import plotly.graph_objects as go
 from scipy import ndimage
+from pathlib import Path
+import qim3d
 
 
 class Interface:
@@ -54,9 +57,6 @@ class Interface:
                 [os.path.join(current_dir, *examples_dir, example)]
             )
 
-        # CSS path
-        self.css_path = os.path.join(current_dir, "..", "css", "gradio.css")
-
     def clear(self):
         """Used to reset the plot with the clear button"""
         return None
@@ -210,10 +210,10 @@ class Interface:
         # as it otherwise is not deleted
         os.remove("iso3d.html")
 
-    def create_interface(self):
+    def create_interface(self, force_light_mode:bool = True):
         # Create gradio app
 
-        with gr.Blocks(css=self.css_path) as gradio_interface:
+        with gr.Blocks(theme = QimTheme(force_light_mode = force_light_mode), title = self.title) as gradio_interface:
             if self.show_header:
                 gr.Markdown(
                     """
@@ -229,7 +229,7 @@ class Interface:
                     with gr.Tab("Input"):
                         # File loader
                         gradio_file = gr.File(
-                            show_label=False, elem_classes="file-input h-128"
+                            show_label=False
                         )
                     with gr.Tab("Examples"):
                         gr.Examples(examples=self.img_examples, inputs=gradio_file)
@@ -238,11 +238,11 @@ class Interface:
                     with gr.Row():
                         with gr.Column(scale=3, min_width=64):
                             btn_run = gr.Button(
-                                value="Run 3D visualization", elem_classes="btn btn-run"
+                                value="Run 3D visualization", variant = "primary"
                             )
                         with gr.Column(scale=1, min_width=64):
                             btn_clear = gr.Button(
-                                value="Clear", elem_classes="btn btn-clear"
+                                value="Clear", variant = "stop"
                             )
 
                     with gr.Tab("Display"):
@@ -255,7 +255,6 @@ class Interface:
                             label="Display resolution",
                             info="Number of voxels for the largest dimension",
                             value=64,
-                            elem_classes="",
                         )
                         surface_count = gr.Slider(
                             2, 16, step=1, label="Total iso-surfaces", value=6
@@ -373,7 +372,6 @@ class Interface:
                         label="Download interactive plot",
                         show_label=True,
                         visible=False,
-                        elem_classes="w-256",
                     )
 
                 outputs = [volvizplot, plot_download]
@@ -399,13 +397,13 @@ class Interface:
     def make_visible(self):
         return gr.update(visible=True)
 
-    def launch(self, **kwargs):
+    def launch(self, force_light_mode:bool = True, **kwargs):
         # Show header
         if self.show_header:
             internal_tools.gradio_header(self.title, self.port)
 
         # Create gradio interface
-        self.interface = self.create_interface()
+        self.interface = self.create_interface(force_light_mode=force_light_mode)
 
         # Set gradio verbose level
         if self.verbose:
@@ -417,6 +415,7 @@ class Interface:
             quiet=quiet,
             height=self.height,
             width=self.width,
+            favicon_path = Path(qim3d.__file__).parents[0] / "gui/images/qim_platform-icon.svg",
             **kwargs,
         )
 
diff --git a/qim3d/gui/local_thickness.py b/qim3d/gui/local_thickness.py
index 4dcc9113..44af9791 100644
--- a/qim3d/gui/local_thickness.py
+++ b/qim3d/gui/local_thickness.py
@@ -45,10 +45,13 @@ from scipy import ndimage
 import outputformat as ouf
 import plotly.graph_objects as go
 import localthickness as lt
-import matplotlib
+
 
 # matplotlib.use("Agg")
 import matplotlib.pyplot as plt
+from pathlib import Path
+import qim3d
+from .qim_theme import QimTheme
 
 
 class Interface:
@@ -75,8 +78,6 @@ class Interface:
                 [os.path.join(current_dir, *examples_dir, example)]
             )
 
-        # CSS path
-        self.css_path = os.path.join(current_dir, "..", "css", "gradio.css")
 
     def clear(self):
         """Used to reset the plot with the clear button"""
@@ -106,14 +107,14 @@ class Interface:
         session.zpos = zpos
         return session
 
-    def launch(self, img=None,**kwargs):
+    def launch(self, img=None, force_light_mode:bool = True, **kwargs):
         # Show header
         if self.show_header:
             internal_tools.gradio_header(self.title, self.port)
 
         # Create gradio interfaces
 
-        self.interface = self.create_interface(img=img)
+        self.interface = self.create_interface(img=img, force_light_mode=force_light_mode)
 
         # Set gradio verbose level
         if self.verbose:
@@ -125,6 +126,7 @@ class Interface:
             quiet=quiet,
             height=self.height,
             width=self.width,
+            favicon_path = Path(qim3d.__file__).parents[0] / "gui/images/qim_platform-icon.svg",
             **kwargs
         )
 
@@ -152,8 +154,8 @@ class Interface:
 
         return vol_lt
 
-    def create_interface(self, img=None):
-        with gr.Blocks(css=self.css_path) as gradio_interface:
+    def create_interface(self, img=None, force_light_mode:bool = True):
+        with gr.Blocks(theme = QimTheme(force_light_mode=force_light_mode), title = self.title) as gradio_interface:
             gr.Markdown(
                 "# 3D Local thickness \n Interface for _Fast local thickness in 3D and 2D_ (https://github.com/vedranaa/local-thickness)"
             )
@@ -166,7 +168,6 @@ class Interface:
                         with gr.Tab("Input"):
                             data = gr.File(
                                 show_label=False,
-                                elem_classes="file-input h-128",
                                 value=img,
                             )
                         with gr.Tab("Examples"):
@@ -228,10 +229,10 @@ class Interface:
                     with gr.Row():
                         with gr.Column(scale=3, min_width=64):
                             btn = gr.Button(
-                                "Run local thickness", elem_classes="btn btn-run"
+                                "Run local thickness", variant = "primary"
                             )
                         with gr.Column(scale=1, min_width=64):
-                            btn_clear = gr.Button("Clear", elem_classes="btn btn-clear")
+                            btn_clear = gr.Button("Clear", variant = "stop")
 
                     inputs = [
                         data,
@@ -250,21 +251,18 @@ class Interface:
                             show_label=True,
                             label="Original",
                             visible=True,
-                            elem_classes="plot",
                         )
 
                         binary_vol = gr.Plot(
                             show_label=True,
                             label="Binary",
                             visible=True,
-                            elem_classes="plot",
                         )
 
                         output_vol = gr.Plot(
                             show_label=True,
                             label="Local thickness",
                             visible=True,
-                            elem_classes="plot",
                         )
                     with gr.Row():
                         histogram = gr.Plot(
@@ -278,7 +276,6 @@ class Interface:
                             show_label=True,
                             label="Output file",
                             visible=False,
-                            elem_classes="",
                         )
 
             # Pipelines
@@ -316,7 +313,7 @@ class Interface:
                 fn=pipeline.input_viz, inputs=session, outputs=input_vol, show_progress=False).success(
                 fn=pipeline.binary_viz, inputs=session, outputs=binary_vol,show_progress=False).success(
                 fn=pipeline.output_viz, inputs=session, outputs=output_vol,show_progress=False)
-            # fmt: on
+                # fmt: on
 
         return gradio_interface
 
diff --git a/qim3d/gui/qim_theme.py b/qim3d/gui/qim_theme.py
new file mode 100644
index 00000000..4dd19054
--- /dev/null
+++ b/qim3d/gui/qim_theme.py
@@ -0,0 +1,106 @@
+import gradio as gr
+
+class QimTheme(gr.themes.Default):
+    """
+    QIM3D Theme for gradio interface
+    The theming options are quite broad. However if there is something you can not achieve with this theme
+    there is a possibility to add some more css if you override _get_css_theme function as shown at the bottom
+    in comments.
+    """
+    def __init__(self, force_light_mode:bool = True):
+        """
+        Parameters:
+        -----------
+        - force_light_mode (bool, optional): Gradio themes have dark mode by default. 
+                Qim platform is not ready for dark mode yet, thus the tools should also be in light mode.
+                This sets the darkmode values to be the same as light mode values.
+        """
+        super().__init__()
+        self.force_light_mode = force_light_mode
+        self.general_values() # Not color related
+        self.set_light_mode_values()
+        self.set_dark_mode_values() # Checks the light mode setting inside
+
+    def general_values(self):
+        self.set_button()
+        self.set_h1()
+        
+    def set_light_mode_values(self):
+        self.set_light_primary_button()
+        self.set_light_secondary_button()
+        self.set_light_checkbox()
+        self.set_light_cancel_button()
+        self.set_light_example()
+
+    def set_dark_mode_values(self):
+        if self.force_light_mode:
+            for attr in [dark_attr for dark_attr in dir(self) if not dark_attr.startswith("_") and dark_attr.endswith("dark")]:
+                self.__dict__[attr] = self.__dict__[attr[:-5]] # ligth and dark attributes have same names except for '_dark' at the end
+        else:
+            self.set_dark_primary_button()
+            # Secondary button looks good by default in dark mode
+            self.set_dark_checkbox()
+            self.set_dark_cancel_button()
+            # Example looks good by default in dark mode
+
+    def set_button(self):
+        self.button_transition = "0.15s"
+        self.button_large_text_weight = "normal"
+
+    def set_light_primary_button(self):
+        self.run_color = "#198754"
+        self.button_primary_background_fill = "#FFFFFF"
+        self.button_primary_background_fill_hover = self.run_color
+        self.button_primary_border_color = self.run_color
+        self.button_primary_text_color = self.run_color
+        self.button_primary_text_color_hover = "#FFFFFF"
+
+    def set_dark_primary_button(self):
+        self.bright_run_color = "#299764"
+        self.button_primary_background_fill_dark = self.button_primary_background_fill_hover
+        self.button_primary_background_fill_hover_dark = self.bright_run_color
+        self.button_primary_border_color_dark = self.button_primary_border_color
+        self.button_primary_border_color_hover_dark = self.bright_run_color
+
+    def set_light_secondary_button(self):
+        self.button_secondary_background_fill = "white"
+
+    def set_light_example(self):
+        """
+        This sets how the examples in gradio.Examples look like. Used in iso3d.
+        """
+        self.border_color_accent = self.neutral_100
+        self.color_accent_soft = self.neutral_100
+
+    def set_h1(self):
+        self.text_xxl = "2.5rem"
+
+    def set_light_checkbox(self):
+        light_blue = "#60a5fa"
+        self.checkbox_background_color_selected = light_blue
+        self.checkbox_border_color_selected = light_blue
+        self.checkbox_border_color_focus = light_blue
+
+    def set_dark_checkbox(self):
+        self.checkbox_border_color_dark = self.neutral_500
+        self.checkbox_border_color_focus_dark = self.checkbox_border_color_focus_dark
+
+    def set_light_cancel_button(self):
+        self.cancel_color = "#dc3545"
+        self.button_cancel_background_fill = "white"
+        self.button_cancel_background_fill_hover = self.cancel_color
+        self.button_cancel_border_color = self.cancel_color
+        self.button_cancel_text_color = self.cancel_color
+        self.button_cancel_text_color_hover = "white"
+
+    def set_dark_cancel_button(self):
+        self.button_cancel_background_fill_dark = self.cancel_color
+        self.button_cancel_background_fill_hover_dark = "red"
+        self.button_cancel_border_color_dark = self.cancel_color
+        self.button_cancel_border_color_hover_dark = "red"
+        self.button_cancel_text_color_dark = "white"
+
+    # def _get_theme_css(self):
+    #     sup = super()._get_theme_css()
+    #     return "\n.svelte-182fdeq {\nbackground: rgba(255, 0, 0, 0.5) !important;\n}\n" + sup # You have to use !important, so it overrides other css
+        
\ No newline at end of file
diff --git a/qim3d/utils/cli.py b/qim3d/utils/cli.py
index e4a64b9a..4b057142 100644
--- a/qim3d/utils/cli.py
+++ b/qim3d/utils/cli.py
@@ -97,26 +97,28 @@ def main():
                 data_explorer.run_interface(arghost)
             else:
                 interface = data_explorer.Interface()
-                interface.launch(inbrowser=inbrowser)
+                interface.launch(inbrowser=inbrowser, force_light_mode=False)
         elif args.iso3d:
             if args.platform:
                 iso3d.run_interface(arghost)
             else:
                 interface = iso3d.Interface()
-                interface.launch(inbrowser=inbrowser)
-
+                interface.launch(inbrowser=inbrowser, force_light_mode=False)
+        
         elif args.annotation_tool:
             if args.platform:
                 annotation_tool.run_interface(arghost)
             else:
                 interface = annotation_tool.Interface()
-                interface.launch(inbrowser=inbrowser)
+                interface.launch(inbrowser=inbrowser, force_light_mode=False)        
         elif args.local_thickness:
             if args.platform:
                 local_thickness.run_interface(arghost)
             else:
                 interface = local_thickness.Interface()
-                interface.launch(inbrowser=inbrowser)
+                interface.launch(inbrowser=inbrowser, force_light_mode=False)
+
+
     elif args.subcommand == "viz":
         if not args.source:
             print("Please specify a source file using the argument --source")
-- 
GitLab