From 50285fbe712e5c3b65aa47a8110463c693c4237b Mon Sep 17 00:00:00 2001
From: fima <fima@dtu.dk>
Date: Mon, 27 Nov 2023 15:35:25 +0100
Subject: [PATCH] Gui launcher system

---
 qim3d/gui/annotation_tool.py  | 329 ++++++++++++++++++++--------------
 qim3d/gui/data_explorer.py    |  11 +-
 qim3d/gui/iso3d.py            |  10 +-
 qim3d/gui/local_thickness.py  |  11 ++
 qim3d/tests/utils/test_doi.py |  10 +-
 qim3d/utils/internal_tools.py |  54 ++++--
 6 files changed, 260 insertions(+), 165 deletions(-)

diff --git a/qim3d/gui/annotation_tool.py b/qim3d/gui/annotation_tool.py
index ae44f498..c4b121ee 100644
--- a/qim3d/gui/annotation_tool.py
+++ b/qim3d/gui/annotation_tool.py
@@ -2,18 +2,20 @@ import tifffile
 import os
 import numpy as np
 import gradio as gr
-from qim3d.io import load # load or DataLoader?
+from qim3d.io import load  # load or DataLoader?
+from qim3d.utils import internal_tools
+
 
 class Interface:
     def __init__(self):
         self.verbose = False
         self.title = "Annotation tool"
-        #self.plot_height = 768
+        # self.plot_height = 768
         self.height = 1024
-        #self.width = 960
+        # self.width = 960
         self.max_masks = 3
         self.mask_opacity = 0.5
-        self.cmy_hex = ['#00ffff','#ff00ff','#ffff00'] # Colors for max_masks>3?
+        self.cmy_hex = ["#00ffff", "#ff00ff", "#ffff00"]  # Colors for max_masks>3?
 
         # CSS path
         current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -32,13 +34,12 @@ class Interface:
         self.interface.launch(
             quiet=quiet,
             height=self.height,
-            #width=self.width,
+            # width=self.width,
             show_tips=False,
-            **kwargs
+            **kwargs,
         )
 
         return
-    
 
     def get_result(self):
         # Get the temporary files from gradio
@@ -62,239 +63,301 @@ class Interface:
 
         return mask
 
-
-    def create_interface(self, img=None):        
+    def create_interface(self, img=None):
         with gr.Blocks(css=self.css_path) as gradio_interface:
             masks_state = gr.State(value={})
-            counts = gr.Number(value=1,visible=False)
-            
+            counts = gr.Number(value=1, visible=False)
+
             with gr.Row():
-                with gr.Column(scale=1,min_width=320):
+                with gr.Column(scale=1, min_width=320):
                     upload_img_btn = gr.UploadButton(
-                        label='Upload image',
-                        file_types=['image'],
-                        interactive=True if img is None else False
-                        )
+                        label="Upload image",
+                        file_types=["image"],
+                        interactive=True if img is None else False,
+                    )
                     clear_img_btn = gr.Button(
-                        value='Clear image',
-                        interactive=False if img is None else True
-                        )
-                    
+                        value="Clear image", interactive=False if img is None else True
+                    )
+
                     with gr.Row():
-                        with gr.Column(scale=2,min_width=32):
+                        with gr.Column(scale=2, min_width=32):
                             selected_mask = gr.Radio(
-                                    choices = ["Mask 1"], 
-                                    value = "Mask 1",
-                                    label="Choose which mask to draw",
-                                    scale=1
+                                choices=["Mask 1"],
+                                value="Mask 1",
+                                label="Choose which mask to draw",
+                                scale=1,
                             )
-                        with gr.Column(scale=1,min_width=64):
+                        with gr.Column(scale=1, min_width=64):
                             add_mask_btn = gr.Button(
-                                value='Add mask',
+                                value="Add mask",
                                 scale=2,
                             )
                     with gr.Row():
                         prep_dl_btn = gr.Button(
-                            value='Prepare mask for download',
-                            visible=False if img is None else True
-                            )
+                            value="Prepare mask for download",
+                            visible=False if img is None else True,
+                        )
                     with gr.Row():
                         save_output = gr.File(
                             show_label=True,
                             label="Output file",
                             visible=False,
                         )
-                    
+
                 with gr.Column(scale=4):
                     with gr.Row():
                         input_img = gr.Image(
                             label="Input",
-                            tool='sketch',
+                            tool="sketch",
                             value=img,
                             height=600,
                             width=600,
-                            brush_color='#00ffff',
+                            brush_color="#00ffff",
                             mask_opacity=self.mask_opacity,
-                            interactive=False if img is None else True
-                            )
-                    
+                            interactive=False if img is None else True,
+                        )
+
                     output_masks = []
                     for mask_idx in range(self.max_masks):
-                        with gr.Row(): # make a new row for every mask
-                            output_mask=gr.Image(
+                        with gr.Row():  # make a new row for every mask
+                            output_mask = gr.Image(
                                 label=f"Mask {mask_idx+1}",
-                                visible=True if mask_idx==0 else False,
-                                image_mode='L',
+                                visible=True if mask_idx == 0 else False,
+                                image_mode="L",
                                 height=600,
                                 width=600,
-                                interactive=False if img is None else True, # If statement added bc of bug after Gradio 3.44.x
-                                show_download_button=False
-                                )
+                                interactive=False
+                                if img is None
+                                else True,  # If statement added bc of bug after Gradio 3.44.x
+                                show_download_button=False,
+                            )
                             output_masks.append(output_mask)
-            
+
             # Operations
-            operations = Operations(max_masks=self.max_masks,cmy_hex=self.cmy_hex)
-            
+            operations = Operations(max_masks=self.max_masks, cmy_hex=self.cmy_hex)
+
             # Update component configuration when image is uploaded
-            upload_img_btn.upload(fn=operations.upload_img_update,
-                                inputs=upload_img_btn,
-                                outputs=[input_img,clear_img_btn,upload_img_btn,prep_dl_btn] + output_masks
-                                )
-            
+            upload_img_btn.upload(
+                fn=operations.upload_img_update,
+                inputs=upload_img_btn,
+                outputs=[input_img, clear_img_btn, upload_img_btn, prep_dl_btn]
+                + output_masks,
+            )
+
             # Add mask below when 'add mask' button is clicked
             add_mask_btn.click(
                 fn=operations.increment_mask,
                 inputs=counts,
-                outputs=[counts, selected_mask] + output_masks
-                )
-            
+                outputs=[counts, selected_mask] + output_masks,
+            )
+
             # Draw mask when input image is edited
             input_img.edit(
                 fn=operations.update_masks,
-                inputs=[input_img,selected_mask,masks_state,upload_img_btn],
-                outputs=output_masks
-                ) 
-            
+                inputs=[input_img, selected_mask, masks_state, upload_img_btn],
+                outputs=output_masks,
+            )
+
             # Update brush color according to radio setting
             selected_mask.change(
                 fn=operations.update_brush_color,
-                inputs=selected_mask,outputs=input_img
-                ) 
-            
+                inputs=selected_mask,
+                outputs=input_img,
+            )
+
             # Make file download visible
             prep_dl_btn.click(
                 fn=operations.save_mask,
                 inputs=output_masks,
-                outputs=[save_output,save_output]
-                )
-            
+                outputs=[save_output, save_output],
+            )
+
             # Update 'Add mask' button interactivit according to the current count
             counts.change(
                 fn=operations.set_add_mask_btn_interactivity,
                 inputs=counts,
-                outputs=add_mask_btn
-                )
-            
+                outputs=add_mask_btn,
+            )
+
             # Reset component configuration when image is cleared
             clear_img_btn.click(
                 fn=operations.clear_img_update,
                 inputs=None,
-                outputs=[selected_mask,prep_dl_btn,save_output,counts,input_img,upload_img_btn,clear_img_btn] + output_masks
-                )
-        
+                outputs=[
+                    selected_mask,
+                    prep_dl_btn,
+                    save_output,
+                    counts,
+                    input_img,
+                    upload_img_btn,
+                    clear_img_btn,
+                ]
+                + output_masks,
+            )
+
         return gradio_interface
 
+
 class Operations:
     def __init__(self, max_masks, cmy_hex):
         self.max_masks = max_masks
         self.cmy_hex = cmy_hex
 
-
-    def update_masks(self,input_img,selected_mask,masks_state,file):
+    def update_masks(self, input_img, selected_mask, masks_state, file):
         # Binarize mask (it is not per default due to anti-aliasing)
-        input_mask = input_img['mask']
-        input_mask[input_mask>0]=255
+        input_mask = input_img["mask"]
+        input_mask[input_mask > 0] = 255
 
         try:
             file_name = file.name
         except AttributeError:
-            file_name = 'nb_img'
+            file_name = "nb_img"
 
         # Add new file to state dictionary when this function sees it first time
         if file_name not in masks_state.keys():
-            masks_state[file_name]=[[] for _ in range(self.max_masks)]
+            masks_state[file_name] = [[] for _ in range(self.max_masks)]
 
         # Get index of currently selected and non-selected masks
-        sel_mask_idx = int(selected_mask[-1])-1
-        nonsel_mask_idxs = [mask_idx for mask_idx in list(range(self.max_masks)) if mask_idx != sel_mask_idx]
+        sel_mask_idx = int(selected_mask[-1]) - 1
+        nonsel_mask_idxs = [
+            mask_idx
+            for mask_idx in list(range(self.max_masks))
+            if mask_idx != sel_mask_idx
+        ]
 
         # Add background to state first time function is invoked in current session
-        if len(masks_state[file_name][0])==0:
+        if len(masks_state[file_name][0]) == 0:
             for i in range(len(masks_state[file_name])):
                 masks_state[file_name][i].append(input_mask)
-        
+
         # Check for discrepancy between what is drawn and what is shown as output masks
         masks_state_combined = 0
         for i in range(len(masks_state[file_name])):
-            masks_state_combined+=masks_state[file_name][i][-1] 
-        discrepancy = masks_state_combined!=input_mask
-        if np.any(discrepancy): # Correct discrepancy in output masks
+            masks_state_combined += masks_state[file_name][i][-1]
+        discrepancy = masks_state_combined != input_mask
+        if np.any(discrepancy):  # Correct discrepancy in output masks
             for i in range(self.max_masks):
-                masks_state[file_name][i][-1][discrepancy]=0
-        
+                masks_state[file_name][i][-1][discrepancy] = 0
+
         # Add most recent change in input to currently selected mask
         mask2append = input_mask
         for mask_idx in nonsel_mask_idxs:
-            mask2append -= masks_state[file_name][mask_idx][-1] 
+            mask2append -= masks_state[file_name][mask_idx][-1]
         masks_state[file_name][sel_mask_idx].append(mask2append)
-        
+
         return [masks_state[file_name][i][-1] for i in range(self.max_masks)]
 
-    def save_mask(self,*masks):
+    def save_mask(self, *masks):
         # Go from multi-channel to single-channel mask
-        stacked_masks = np.stack(masks,axis=-1)
+        stacked_masks = np.stack(masks, axis=-1)
         final_mask = np.zeros_like(masks[0])
-        final_mask[np.where(stacked_masks==255)[:2]]=np.where(stacked_masks==255)[-1]+1
+        final_mask[np.where(stacked_masks == 255)[:2]] = (
+            np.where(stacked_masks == 255)[-1] + 1
+        )
 
         # Save output image in a temp space (and to current directory which is a bug)
         filename = "mask.tif"
-        tifffile.imwrite(filename,final_mask)
+        tifffile.imwrite(filename, final_mask)
 
         save_output_update = gr.File(visible=True)
-        
+
         return save_output_update, filename
 
-    def increment_mask(self,counts):
+    def increment_mask(self, counts):
         # increment count by 1
-        counts+=1
-        counts=int(counts)
+        counts += 1
+        counts = int(counts)
 
         counts_update = gr.Number(value=counts)
-        selected_mask_update = gr.Radio(value = f"Mask {counts}", choices = [f"Mask {i+1}" for i in range(counts)])
-        output_masks_update = [gr.Image(visible=True)]*counts + [gr.Image(visible=False)]*(self.max_masks-counts)
+        selected_mask_update = gr.Radio(
+            value=f"Mask {counts}", choices=[f"Mask {i+1}" for i in range(counts)]
+        )
+        output_masks_update = [gr.Image(visible=True)] * counts + [
+            gr.Image(visible=False)
+        ] * (self.max_masks - counts)
 
         return [counts_update, selected_mask_update] + output_masks_update
 
-    def update_brush_color(self,selected_mask):
-        sel_mask_idx = int(selected_mask[-1])-1
-        if sel_mask_idx<len(self.cmy_hex):
+    def update_brush_color(self, selected_mask):
+        sel_mask_idx = int(selected_mask[-1]) - 1
+        if sel_mask_idx < len(self.cmy_hex):
             input_img_update = gr.Image(brush_color=self.cmy_hex[sel_mask_idx])
         else:
-            input_img_update = gr.Image(brush_color='#000000') # Return black brush
-        
+            input_img_update = gr.Image(brush_color="#000000")  # Return black brush
+
         return input_img_update
 
-    def set_add_mask_btn_interactivity(self,counts):
-        add_mask_btn_update = gr.Button(interactive=True) if counts<self.max_masks else gr.Button(interactive=False)
+    def set_add_mask_btn_interactivity(self, counts):
+        add_mask_btn_update = (
+            gr.Button(interactive=True)
+            if counts < self.max_masks
+            else gr.Button(interactive=False)
+        )
         return add_mask_btn_update
 
     def clear_img_update(self):
-        selected_mask_update = gr.Radio(choices = ["Mask 1"], value = "Mask 1") # Reset radio component to only show 'Mask 1'
-        prep_dl_btn_update = gr.Button(visible=False) # Make 'Prepare mask for download' button invisible
-        save_output_update = gr.File(visible=False) # Make File save box invisible
-        counts_update = gr.Number(value=1) # Reset invisible counter to 1
-        input_img_update = gr.Image(value=None,interactive=False) # Set input image component to non-interactive (so a new image cannot be uploaded directly in the component) 
-        upload_img_btn_update = gr.Button(interactive=True) # Make 'Upload image' button interactive 
-        clear_img_btn_update = gr.Button(interactive=False) # Make 'Clear image' button non-interactive
-        output_masks_update = [gr.Image(value=None,visible=True if i==0 else False,interactive=False) for i in range(self.max_masks)] # Remove drawn masks and set as invisible except mask 1. 'interactive=False' added bc of bug after Gradio 3.44.x
-        
-        return [selected_mask_update,
-                prep_dl_btn_update,
-                save_output_update,
-                counts_update,
-                input_img_update,
-                upload_img_btn_update,
-                clear_img_btn_update] + output_masks_update
-
-    def upload_img_update(self,file):
-        input_img_update = gr.Image(value=load(file.name),interactive=True) # Upload image from button to Image components
-        clear_img_btn_update = gr.Button(interactive=True) # Make 'Clear image' button interactive
-        upload_img_btn_update = gr.Button(interactive=False) # Make 'Upload image' button non-interactive
-        prep_dl_btn_update = gr.Button(visible=True) # Make 'Prepare mask for download' button visible
-        output_masks_update = [gr.Image(interactive=True)]*self.max_masks # This line is added bc of bug in Gradio after 3.44.x
-
-        return [input_img_update,
-                clear_img_btn_update,
-                upload_img_btn_update,
-                prep_dl_btn_update] + output_masks_update
-        
\ No newline at end of file
+        selected_mask_update = gr.Radio(
+            choices=["Mask 1"], value="Mask 1"
+        )  # Reset radio component to only show 'Mask 1'
+        prep_dl_btn_update = gr.Button(
+            visible=False
+        )  # Make 'Prepare mask for download' button invisible
+        save_output_update = gr.File(visible=False)  # Make File save box invisible
+        counts_update = gr.Number(value=1)  # Reset invisible counter to 1
+        input_img_update = gr.Image(
+            value=None, interactive=False
+        )  # Set input image component to non-interactive (so a new image cannot be uploaded directly in the component)
+        upload_img_btn_update = gr.Button(
+            interactive=True
+        )  # Make 'Upload image' button interactive
+        clear_img_btn_update = gr.Button(
+            interactive=False
+        )  # Make 'Clear image' button non-interactive
+        output_masks_update = [
+            gr.Image(value=None, visible=True if i == 0 else False, interactive=False)
+            for i in range(self.max_masks)
+        ]  # Remove drawn masks and set as invisible except mask 1. 'interactive=False' added bc of bug after Gradio 3.44.x
+
+        return [
+            selected_mask_update,
+            prep_dl_btn_update,
+            save_output_update,
+            counts_update,
+            input_img_update,
+            upload_img_btn_update,
+            clear_img_btn_update,
+        ] + output_masks_update
+
+    def upload_img_update(self, file):
+        input_img_update = gr.Image(
+            value=load(file.name), interactive=True
+        )  # Upload image from button to Image components
+        clear_img_btn_update = gr.Button(
+            interactive=True
+        )  # Make 'Clear image' button interactive
+        upload_img_btn_update = gr.Button(
+            interactive=False
+        )  # Make 'Upload image' button non-interactive
+        prep_dl_btn_update = gr.Button(
+            visible=True
+        )  # Make 'Prepare mask for download' button visible
+        output_masks_update = [
+            gr.Image(interactive=True)
+        ] * self.max_masks  # This line is added bc of bug in Gradio after 3.44.x
+
+        return [
+            input_img_update,
+            clear_img_btn_update,
+            upload_img_btn_update,
+            prep_dl_btn_update,
+        ] + output_masks_update
+
+
+if __name__ == "__main__":
+    # Get port using the QIM API
+    port_dict = internal_tools.get_port_dict()
+    internal_tools.gradio_header(Interface().title, port_dict["port"])
+
+    # Creates interface
+    app = Interface().create_interface()
+    app.launch(server_name="0.0.0.0", server_port=int(port_dict["port"]))
diff --git a/qim3d/gui/data_explorer.py b/qim3d/gui/data_explorer.py
index 7ac43a16..76ad85ef 100644
--- a/qim3d/gui/data_explorer.py
+++ b/qim3d/gui/data_explorer.py
@@ -252,7 +252,6 @@ class Session:
         ).strftime("%Y-%m-%d %H:%M")
         self.file_size = os.path.getsize(self.data_path)
 
-
     def create_summary_dict(self):
         # Create dictionary
         if self.error_message:
@@ -477,6 +476,10 @@ class Pipeline:
 
 
 if __name__ == "__main__":
-    app = Interface()
-    app.show_header = True
-    app.launch(server_name="0.0.0.0", show_error=True)
+    # Get port using the QIM API
+    port_dict = internal_tools.get_port_dict()
+    internal_tools.gradio_header(Interface().title, port_dict["port"])
+
+    # Creates interface
+    app = Interface().create_interface()
+    app.launch(server_name="0.0.0.0", server_port=int(port_dict["port"]))
diff --git a/qim3d/gui/iso3d.py b/qim3d/gui/iso3d.py
index 64e760d5..60526920 100644
--- a/qim3d/gui/iso3d.py
+++ b/qim3d/gui/iso3d.py
@@ -398,6 +398,10 @@ class Interface:
 
 
 if __name__ == "__main__":
-    app = Interface()
-    app.show_header = True
-    app.launch(server_name="0.0.0.0", show_error=True, default_port=True)
+    # Get port using the QIM API
+    port_dict = internal_tools.get_port_dict()
+    internal_tools.gradio_header(Interface().title, port_dict["port"])
+
+    # Creates interface
+    app = Interface().create_interface()
+    app.launch(server_name="0.0.0.0", server_port=int(port_dict["port"]))
diff --git a/qim3d/gui/local_thickness.py b/qim3d/gui/local_thickness.py
index faf5cc62..fa4dda33 100644
--- a/qim3d/gui/local_thickness.py
+++ b/qim3d/gui/local_thickness.py
@@ -12,6 +12,7 @@ import plotly.graph_objects as go
 import localthickness as lt
 import matplotlib
 
+
 # matplotlib.use("Agg")
 import matplotlib.pyplot as plt
 
@@ -420,3 +421,13 @@ class Pipeline:
         tifffile.imwrite(filename, session.vol_thickness)
 
         return filename
+
+
+if __name__ == "__main__":
+    # Get port using the QIM API
+    port_dict = internal_tools.get_port_dict()
+    internal_tools.gradio_header(Interface().title, port_dict["port"])
+
+    # Creates interface
+    app = Interface().create_interface()
+    app.launch(server_name="0.0.0.0", server_port=int(port_dict["port"]))
diff --git a/qim3d/tests/utils/test_doi.py b/qim3d/tests/utils/test_doi.py
index a4181088..0db54357 100644
--- a/qim3d/tests/utils/test_doi.py
+++ b/qim3d/tests/utils/test_doi.py
@@ -6,16 +6,10 @@ doi = "https://doi.org/10.1007/s10851-021-01041-3"
 def test_get_bibtex():
     bibtext = qim3d.utils.doi.get_bibtex(doi)
 
-    assert (
-        bibtext
-        == "@article{Stephensen_2021,\n\tdoi = {10.1007/s10851-021-01041-3},\n\turl = {https://doi.org/10.1007%2Fs10851-021-01041-3},\n\tyear = 2021,\n\tmonth = {jun},\n\tpublisher = {Springer Science and Business Media {LLC}},\n\tvolume = {63},\n\tnumber = {8},\n\tpages = {1069--1083},\n\tauthor = {Hans J. T. Stephensen and Anne Marie Svane and Carlos B. Villanueva and Steven A. Goldman and Jon Sporring},\n\ttitle = {Measuring Shape Relations Using r-Parallel Sets},\n\tjournal = {Journal of Mathematical Imaging and Vision}\n}"
-    )
+    assert "Measuring Shape Relations Using r-Parallel Sets" in bibtext
 
 
 def test_get_reference():
     reference = qim3d.utils.doi.get_reference(doi)
 
-    assert (
-        reference
-        == "Stephensen, H. J. T., Svane, A. M., Villanueva, C. B., Goldman, S. A., & Sporring, J. (2021). Measuring Shape Relations Using r-Parallel Sets. Journal of Mathematical Imaging and Vision, 63(8), 1069รข\x80\x931083. https://doi.org/10.1007/s10851-021-01041-3\n"
-    )
+    assert "Stephensen" in reference
diff --git a/qim3d/utils/internal_tools.py b/qim3d/utils/internal_tools.py
index bc06e5f9..eb18c11b 100644
--- a/qim3d/utils/internal_tools.py
+++ b/qim3d/utils/internal_tools.py
@@ -9,7 +9,8 @@ import numpy as np
 import socket
 import os
 import shutil
-
+import requests
+import getpass
 from PIL import Image
 from pathlib import Path
 from qim3d.io.logger import log
@@ -29,7 +30,7 @@ def mock_plot():
     """
 
     # TODO: Check if using Agg backend conflicts with other pipelines
-    
+
     matplotlib.use("Agg")
 
     fig = plt.figure(figsize=(5, 4))
@@ -173,6 +174,7 @@ def sizeof(num, suffix="B"):
         num /= 1024.0
     return f"{num:.1f} Y{suffix}"
 
+
 def is_server_running(ip, port):
     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     try:
@@ -182,9 +184,10 @@ def is_server_running(ip, port):
     except:
         return False
 
-def temp_data(folder,remove = False,n = 3,img_shape = (32,32)):
+
+def temp_data(folder, remove=False, n=3, img_shape=(32, 32)):
     """Creates a temporary folder to test deep learning tools.
-    
+
     Creates two folders, 'train' and 'test', who each also have two subfolders 'images' and 'labels'.
     n random images are then added to all four subfolders.
     If the 'remove' variable is True, the folders and their content are removed.
@@ -198,8 +201,8 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)):
     Example:
         >>> tempdata('temporary_folder',n = 10, img_shape = (16,16))
     """
-    folder_trte = ['train','test']
-    sub_folders = ['images','labels']
+    folder_trte = ["train", "test"]
+    sub_folders = ["images", "labels"]
 
     # Creating train/test folder
     path_train = Path(folder) / folder_trte[0]
@@ -212,7 +215,7 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)):
     path_test_lab = path_test / sub_folders[1]
 
     # Random image
-    img = np.random.randint(2,size = img_shape,dtype = np.uint8)
+    img = np.random.randint(2, size=img_shape, dtype=np.uint8)
     img = Image.fromarray(img)
 
     if not os.path.exists(path_train):
@@ -221,10 +224,10 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)):
         os.makedirs(path_train_lab)
         os.makedirs(path_test_lab)
         for i in range(n):
-            img.save(path_train_im / f'img_train{i}.png')
-            img.save(path_train_lab / f'img_train{i}.png')
-            img.save(path_test_im / f'img_test{i}.png')
-            img.save(path_test_lab / f'img_test{i}.png')
+            img.save(path_train_im / f"img_train{i}.png")
+            img.save(path_train_lab / f"img_train{i}.png")
+            img.save(path_test_im / f"img_test{i}.png")
+            img.save(path_test_lab / f"img_test{i}.png")
 
     if remove:
         for filename in os.listdir(folder):
@@ -235,13 +238,30 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)):
                 elif os.path.isdir(file_path):
                     shutil.rmtree(file_path)
             except Exception as e:
-                log.warning('Failed to delete %s. Reason: %s' % (file_path, e))
-        
+                log.warning("Failed to delete %s. Reason: %s" % (file_path, e))
+
         os.rmdir(folder)
-    
+
+
 def stringify_path(path):
-    """Converts an os.PathLike object to a string
-    """
-    if isinstance(path,os.PathLike):
+    """Converts an os.PathLike object to a string"""
+    if isinstance(path, os.PathLike):
         path = path.__fspath__()
     return path
+
+
+def get_port_dict():
+    # Gets user and port
+    username = getpass.getuser()
+    url = f"https://platform.qim.dk/qim-api/get-port/{username}"
+
+    response = requests.get(url, timeout=10)
+    # Check if the request was successful (status code 200)
+    if response.status_code == 200:
+        # Parse the JSON response into a Python dictionary
+        port_dict = response.json()
+    else:
+        # Print an error message if the request was not successful
+        raise (f"Error: {response.status_code}")
+
+    return port_dict
-- 
GitLab