Skip to content
Snippets Groups Projects
Commit 50285fbe authored by fima's avatar fima :beers:
Browse files

Gui launcher system

parent 4c17c5eb
Branches
Tags
1 merge request!32Gui launcher system
...@@ -3,6 +3,8 @@ import os ...@@ -3,6 +3,8 @@ import os
import numpy as np import numpy as np
import gradio as gr 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: class Interface:
def __init__(self): def __init__(self):
...@@ -13,7 +15,7 @@ class Interface: ...@@ -13,7 +15,7 @@ class Interface:
# self.width = 960 # self.width = 960
self.max_masks = 3 self.max_masks = 3
self.mask_opacity = 0.5 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 # CSS path
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
...@@ -34,12 +36,11 @@ class Interface: ...@@ -34,12 +36,11 @@ class Interface:
height=self.height, height=self.height,
# width=self.width, # width=self.width,
show_tips=False, show_tips=False,
**kwargs **kwargs,
) )
return return
def get_result(self): def get_result(self):
# Get the temporary files from gradio # Get the temporary files from gradio
temp_sets = self.interface.temp_file_sets temp_sets = self.interface.temp_file_sets
...@@ -62,7 +63,6 @@ class Interface: ...@@ -62,7 +63,6 @@ class Interface:
return mask return mask
def create_interface(self, img=None): def create_interface(self, img=None):
with gr.Blocks(css=self.css_path) as gradio_interface: with gr.Blocks(css=self.css_path) as gradio_interface:
masks_state = gr.State(value={}) masks_state = gr.State(value={})
...@@ -71,13 +71,12 @@ class Interface: ...@@ -71,13 +71,12 @@ class Interface:
with gr.Row(): with gr.Row():
with gr.Column(scale=1, min_width=320): with gr.Column(scale=1, min_width=320):
upload_img_btn = gr.UploadButton( upload_img_btn = gr.UploadButton(
label='Upload image', label="Upload image",
file_types=['image'], file_types=["image"],
interactive=True if img is None else False interactive=True if img is None else False,
) )
clear_img_btn = gr.Button( clear_img_btn = gr.Button(
value='Clear image', value="Clear image", interactive=False if img is None else True
interactive=False if img is None else True
) )
with gr.Row(): with gr.Row():
...@@ -86,17 +85,17 @@ class Interface: ...@@ -86,17 +85,17 @@ class Interface:
choices=["Mask 1"], choices=["Mask 1"],
value="Mask 1", value="Mask 1",
label="Choose which mask to draw", label="Choose which mask to draw",
scale=1 scale=1,
) )
with gr.Column(scale=1, min_width=64): with gr.Column(scale=1, min_width=64):
add_mask_btn = gr.Button( add_mask_btn = gr.Button(
value='Add mask', value="Add mask",
scale=2, scale=2,
) )
with gr.Row(): with gr.Row():
prep_dl_btn = gr.Button( prep_dl_btn = gr.Button(
value='Prepare mask for download', value="Prepare mask for download",
visible=False if img is None else True visible=False if img is None else True,
) )
with gr.Row(): with gr.Row():
save_output = gr.File( save_output = gr.File(
...@@ -109,13 +108,13 @@ class Interface: ...@@ -109,13 +108,13 @@ class Interface:
with gr.Row(): with gr.Row():
input_img = gr.Image( input_img = gr.Image(
label="Input", label="Input",
tool='sketch', tool="sketch",
value=img, value=img,
height=600, height=600,
width=600, width=600,
brush_color='#00ffff', brush_color="#00ffff",
mask_opacity=self.mask_opacity, mask_opacity=self.mask_opacity,
interactive=False if img is None else True interactive=False if img is None else True,
) )
output_masks = [] output_masks = []
...@@ -124,11 +123,13 @@ class Interface: ...@@ -124,11 +123,13 @@ class Interface:
output_mask = gr.Image( output_mask = gr.Image(
label=f"Mask {mask_idx+1}", label=f"Mask {mask_idx+1}",
visible=True if mask_idx == 0 else False, visible=True if mask_idx == 0 else False,
image_mode='L', image_mode="L",
height=600, height=600,
width=600, width=600,
interactive=False if img is None else True, # If statement added bc of bug after Gradio 3.44.x interactive=False
show_download_button=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) output_masks.append(output_mask)
...@@ -136,69 +137,81 @@ class Interface: ...@@ -136,69 +137,81 @@ class Interface:
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 # Update component configuration when image is uploaded
upload_img_btn.upload(fn=operations.upload_img_update, upload_img_btn.upload(
fn=operations.upload_img_update,
inputs=upload_img_btn, inputs=upload_img_btn,
outputs=[input_img,clear_img_btn,upload_img_btn,prep_dl_btn] + output_masks 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 below when 'add mask' button is clicked
add_mask_btn.click( add_mask_btn.click(
fn=operations.increment_mask, fn=operations.increment_mask,
inputs=counts, inputs=counts,
outputs=[counts, selected_mask] + output_masks outputs=[counts, selected_mask] + output_masks,
) )
# Draw mask when input image is edited # Draw mask when input image is edited
input_img.edit( input_img.edit(
fn=operations.update_masks, fn=operations.update_masks,
inputs=[input_img, selected_mask, masks_state, upload_img_btn], inputs=[input_img, selected_mask, masks_state, upload_img_btn],
outputs=output_masks outputs=output_masks,
) )
# Update brush color according to radio setting # Update brush color according to radio setting
selected_mask.change( selected_mask.change(
fn=operations.update_brush_color, fn=operations.update_brush_color,
inputs=selected_mask,outputs=input_img inputs=selected_mask,
outputs=input_img,
) )
# Make file download visible # Make file download visible
prep_dl_btn.click( prep_dl_btn.click(
fn=operations.save_mask, fn=operations.save_mask,
inputs=output_masks, inputs=output_masks,
outputs=[save_output,save_output] outputs=[save_output, save_output],
) )
# Update 'Add mask' button interactivit according to the current count # Update 'Add mask' button interactivit according to the current count
counts.change( counts.change(
fn=operations.set_add_mask_btn_interactivity, fn=operations.set_add_mask_btn_interactivity,
inputs=counts, inputs=counts,
outputs=add_mask_btn outputs=add_mask_btn,
) )
# Reset component configuration when image is cleared # Reset component configuration when image is cleared
clear_img_btn.click( clear_img_btn.click(
fn=operations.clear_img_update, fn=operations.clear_img_update,
inputs=None, 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 return gradio_interface
class Operations: class Operations:
def __init__(self, max_masks, cmy_hex): def __init__(self, max_masks, cmy_hex):
self.max_masks = max_masks self.max_masks = max_masks
self.cmy_hex = cmy_hex 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) # Binarize mask (it is not per default due to anti-aliasing)
input_mask = input_img['mask'] input_mask = input_img["mask"]
input_mask[input_mask > 0] = 255 input_mask[input_mask > 0] = 255
try: try:
file_name = file.name file_name = file.name
except AttributeError: except AttributeError:
file_name = 'nb_img' file_name = "nb_img"
# Add new file to state dictionary when this function sees it first time # Add new file to state dictionary when this function sees it first time
if file_name not in masks_state.keys(): if file_name not in masks_state.keys():
...@@ -206,7 +219,11 @@ class Operations: ...@@ -206,7 +219,11 @@ class Operations:
# Get index of currently selected and non-selected masks # Get index of currently selected and non-selected masks
sel_mask_idx = int(selected_mask[-1]) - 1 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] 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 # 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:
...@@ -234,7 +251,9 @@ class Operations: ...@@ -234,7 +251,9 @@ class Operations:
# Go from multi-channel to single-channel mask # 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.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) # Save output image in a temp space (and to current directory which is a bug)
filename = "mask.tif" filename = "mask.tif"
...@@ -250,8 +269,12 @@ class Operations: ...@@ -250,8 +269,12 @@ class Operations:
counts = int(counts) counts = int(counts)
counts_update = gr.Number(value=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)]) selected_mask_update = gr.Radio(
output_masks_update = [gr.Image(visible=True)]*counts + [gr.Image(visible=False)]*(self.max_masks-counts) 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 return [counts_update, selected_mask_update] + output_masks_update
...@@ -260,41 +283,81 @@ class Operations: ...@@ -260,41 +283,81 @@ class Operations:
if sel_mask_idx < len(self.cmy_hex): if sel_mask_idx < len(self.cmy_hex):
input_img_update = gr.Image(brush_color=self.cmy_hex[sel_mask_idx]) input_img_update = gr.Image(brush_color=self.cmy_hex[sel_mask_idx])
else: 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 return input_img_update
def set_add_mask_btn_interactivity(self, counts): 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) add_mask_btn_update = (
gr.Button(interactive=True)
if counts < self.max_masks
else gr.Button(interactive=False)
)
return add_mask_btn_update return add_mask_btn_update
def clear_img_update(self): def clear_img_update(self):
selected_mask_update = gr.Radio(choices = ["Mask 1"], value = "Mask 1") # Reset radio component to only show 'Mask 1' selected_mask_update = gr.Radio(
prep_dl_btn_update = gr.Button(visible=False) # Make 'Prepare mask for download' button invisible 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 save_output_update = gr.File(visible=False) # Make File save box invisible
counts_update = gr.Number(value=1) # Reset invisible counter to 1 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) input_img_update = gr.Image(
upload_img_btn_update = gr.Button(interactive=True) # Make 'Upload image' button interactive value=None, interactive=False
clear_img_btn_update = gr.Button(interactive=False) # Make 'Clear image' button non-interactive ) # Set input image component to non-interactive (so a new image cannot be uploaded directly in the component)
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 upload_img_btn_update = gr.Button(
interactive=True
return [selected_mask_update, ) # 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, prep_dl_btn_update,
save_output_update, save_output_update,
counts_update, counts_update,
input_img_update, input_img_update,
upload_img_btn_update, upload_img_btn_update,
clear_img_btn_update] + output_masks_update clear_img_btn_update,
] + output_masks_update
def upload_img_update(self, file): def upload_img_update(self, file):
input_img_update = gr.Image(value=load(file.name),interactive=True) # Upload image from button to Image components input_img_update = gr.Image(
clear_img_btn_update = gr.Button(interactive=True) # Make 'Clear image' button interactive value=load(file.name), interactive=True
upload_img_btn_update = gr.Button(interactive=False) # Make 'Upload image' button non-interactive ) # Upload image from button to Image components
prep_dl_btn_update = gr.Button(visible=True) # Make 'Prepare mask for download' button visible clear_img_btn_update = gr.Button(
output_masks_update = [gr.Image(interactive=True)]*self.max_masks # This line is added bc of bug in Gradio after 3.44.x interactive=True
) # Make 'Clear image' button interactive
return [input_img_update, 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, clear_img_btn_update,
upload_img_btn_update, upload_img_btn_update,
prep_dl_btn_update] + output_masks_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"]))
...@@ -252,7 +252,6 @@ class Session: ...@@ -252,7 +252,6 @@ class Session:
).strftime("%Y-%m-%d %H:%M") ).strftime("%Y-%m-%d %H:%M")
self.file_size = os.path.getsize(self.data_path) self.file_size = os.path.getsize(self.data_path)
def create_summary_dict(self): def create_summary_dict(self):
# Create dictionary # Create dictionary
if self.error_message: if self.error_message:
...@@ -477,6 +476,10 @@ class Pipeline: ...@@ -477,6 +476,10 @@ class Pipeline:
if __name__ == "__main__": if __name__ == "__main__":
app = Interface() # Get port using the QIM API
app.show_header = True port_dict = internal_tools.get_port_dict()
app.launch(server_name="0.0.0.0", show_error=True) 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"]))
...@@ -398,6 +398,10 @@ class Interface: ...@@ -398,6 +398,10 @@ class Interface:
if __name__ == "__main__": if __name__ == "__main__":
app = Interface() # Get port using the QIM API
app.show_header = True port_dict = internal_tools.get_port_dict()
app.launch(server_name="0.0.0.0", show_error=True, default_port=True) 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"]))
...@@ -12,6 +12,7 @@ import plotly.graph_objects as go ...@@ -12,6 +12,7 @@ import plotly.graph_objects as go
import localthickness as lt import localthickness as lt
import matplotlib import matplotlib
# matplotlib.use("Agg") # matplotlib.use("Agg")
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
...@@ -420,3 +421,13 @@ class Pipeline: ...@@ -420,3 +421,13 @@ class Pipeline:
tifffile.imwrite(filename, session.vol_thickness) tifffile.imwrite(filename, session.vol_thickness)
return filename 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"]))
...@@ -6,16 +6,10 @@ doi = "https://doi.org/10.1007/s10851-021-01041-3" ...@@ -6,16 +6,10 @@ doi = "https://doi.org/10.1007/s10851-021-01041-3"
def test_get_bibtex(): def test_get_bibtex():
bibtext = qim3d.utils.doi.get_bibtex(doi) bibtext = qim3d.utils.doi.get_bibtex(doi)
assert ( assert "Measuring Shape Relations Using r-Parallel Sets" in bibtext
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}"
)
def test_get_reference(): def test_get_reference():
reference = qim3d.utils.doi.get_reference(doi) reference = qim3d.utils.doi.get_reference(doi)
assert ( assert "Stephensen" in reference
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"
)
...@@ -9,7 +9,8 @@ import numpy as np ...@@ -9,7 +9,8 @@ import numpy as np
import socket import socket
import os import os
import shutil import shutil
import requests
import getpass
from PIL import Image from PIL import Image
from pathlib import Path from pathlib import Path
from qim3d.io.logger import log from qim3d.io.logger import log
...@@ -173,6 +174,7 @@ def sizeof(num, suffix="B"): ...@@ -173,6 +174,7 @@ def sizeof(num, suffix="B"):
num /= 1024.0 num /= 1024.0
return f"{num:.1f} Y{suffix}" return f"{num:.1f} Y{suffix}"
def is_server_running(ip, port): def is_server_running(ip, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
...@@ -182,6 +184,7 @@ def is_server_running(ip, port): ...@@ -182,6 +184,7 @@ def is_server_running(ip, port):
except: except:
return False 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 a temporary folder to test deep learning tools.
...@@ -198,8 +201,8 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)): ...@@ -198,8 +201,8 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)):
Example: Example:
>>> tempdata('temporary_folder',n = 10, img_shape = (16,16)) >>> tempdata('temporary_folder',n = 10, img_shape = (16,16))
""" """
folder_trte = ['train','test'] folder_trte = ["train", "test"]
sub_folders = ['images','labels'] sub_folders = ["images", "labels"]
# Creating train/test folder # Creating train/test folder
path_train = Path(folder) / folder_trte[0] path_train = Path(folder) / folder_trte[0]
...@@ -221,10 +224,10 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)): ...@@ -221,10 +224,10 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)):
os.makedirs(path_train_lab) os.makedirs(path_train_lab)
os.makedirs(path_test_lab) os.makedirs(path_test_lab)
for i in range(n): for i in range(n):
img.save(path_train_im / f'img_train{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_train_lab / f"img_train{i}.png")
img.save(path_test_im / f'img_test{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_test_lab / f"img_test{i}.png")
if remove: if remove:
for filename in os.listdir(folder): for filename in os.listdir(folder):
...@@ -235,13 +238,30 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)): ...@@ -235,13 +238,30 @@ def temp_data(folder,remove = False,n = 3,img_shape = (32,32)):
elif os.path.isdir(file_path): elif os.path.isdir(file_path):
shutil.rmtree(file_path) shutil.rmtree(file_path)
except Exception as e: 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) os.rmdir(folder)
def stringify_path(path): def stringify_path(path):
"""Converts an os.PathLike object to a string """Converts an os.PathLike object to a string"""
"""
if isinstance(path, os.PathLike): if isinstance(path, os.PathLike):
path = path.__fspath__() path = path.__fspath__()
return path 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment