diff --git a/app.py b/app.py index c8514d9..efda09c 100644 --- a/app.py +++ b/app.py @@ -2,7 +2,7 @@ import dash_auth import dash_mantine_components as dmc -from dash import Dash, dcc +from dash import Dash from callbacks.control_bar import * # noqa: F403, F401 from callbacks.image_viewer import * # noqa: F403, F401 @@ -30,9 +30,8 @@ children=[ control_bar_layout(), image_viewer_layout(), - dcc.Store(id="current-class-selection", data="#FFA200"), ], ) if __name__ == "__main__": - app.run_server(debug=True) + app.run_server(host="0.0.0.0", port=8075, debug=True) diff --git a/assets/models.json b/assets/models.json new file mode 100755 index 0000000..3b22cb1 --- /dev/null +++ b/assets/models.json @@ -0,0 +1,1176 @@ +{ + "contents": [ + { + "model_name": "DSLIA MSDNet", + "version": "0.0.1", + "type": "supervised", + "user": "mlexchange team", + "uri": "ghcr.io/mlexchange/mlex_dlsia_segmentation:main", + "application": [ + "segmentation" + ], + "description": "MSDNets in DLSIA for image segmentation", + "gui_parameters": [ + { + "type": "int", + "name": "layer_width", + "title": "Layer Width", + "param_key": "layer_width", + "value": 1, + "comp_group": "train_model" + }, + { + "type": "int", + "name": "num_layers", + "title": "# Layers", + "param_key": "num_layers", + "value": 3, + "comp_group": "train_model" + }, + { + "type": "bool", + "name": "custom_dilation", + "title": "Custom Dilation", + "param_key": "custom_dilation", + "checked": false, + "comp_group": "train_model" + }, + { + "type": "int", + "name": "max_dilation", + "title": "Maximum Dilation", + "param_key": "max_dilation", + "value": 5, + "comp_group": "train_model" + }, + { + "type": "str", + "name": "dilation_array", + "title": "Dilation Array", + "param_key": "dilation_array", + "value": "[1, 2, 4]", + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "num_epochs", + "title": "# Epochs", + "param_key": "num_epochs", + "min": 1, + "max": 1000, + "value": 30, + "marks": [ + { + "value": 1, + "label": "1" + }, + { + "value": 100, + "label": "100" + }, + { + "value": 1000, + "label": "1000" + } + ], + "comp_group": "train_model" + }, + { + "type": "dropdown", + "name": "optimizer", + "title": "Optimizer", + "param_key": "optimizer", + "value": "Adam", + "data": [ + { + "label": "Adadelta", + "value": "Adadelta" + }, + { + "label": "Adagrad", + "value": "Adagrad" + }, + { + "label": "Adam", + "value": "Adam" + }, + { + "label": "AdamW", + "value": "AdamW" + }, + { + "label": "SparseAdam", + "value": "SparseAdam" + }, + { + "label": "Adamax", + "value": "Adamax" + }, + { + "label": "ASGD", + "value": "ASGD" + }, + { + "label": "LBFGS", + "value": "LBFGS" + }, + { + "label": "RMSprop", + "value": "RMSprop" + }, + { + "label": "Rprop", + "value": "Rprop" + }, + { + "label": "SGD", + "value": "SGD" + } + ], + "comp_group": "train_model" + }, + { + "type": "dropdown", + "name": "criterion", + "title": "Criterion", + "param_key": "criterion", + "value": "MSELoss", + "data": [ + { + "label": "L1Loss", + "value": "L1Loss" + }, + { + "label": "MSELoss", + "value": "MSELoss" + }, + { + "label": "CrossEntropyLoss", + "value": "CrossEntropyLoss" + }, + { + "label": "CTCLoss", + "value": "CTCLoss" + }, + { + "label": "NLLLoss", + "value": "NLLLoss" + }, + { + "label": "PoissonNLLLoss", + "value": "PoissonNLLLoss" + }, + { + "label": "GaussianNLLLoss", + "value": "GaussianNLLLoss" + }, + { + "label": "KLDivLoss", + "value": "KLDivLoss" + }, + { + "label": "BCELoss", + "value": "BCELoss" + }, + { + "label": "BCEWithLogitsLoss", + "value": "BCEWithLogitsLoss" + }, + { + "label": "MarginRankingLoss", + "value": "MarginRankingLoss" + }, + { + "label": "HingeEnbeddingLoss", + "value": "HingeEnbeddingLoss" + }, + { + "label": "MultiLabelMarginLoss", + "value": "MultiLabelMarginLoss" + }, + { + "label": "HuberLoss", + "value": "HuberLoss" + }, + { + "label": "SmoothL1Loss", + "value": "SmoothL1Loss" + }, + { + "label": "SoftMarginLoss", + "value": "SoftMarginLoss" + }, + { + "label": "MutiLabelSoftMarginLoss", + "value": "MutiLabelSoftMarginLoss" + }, + { + "label": "CosineEmbeddingLoss", + "value": "CosineEmbeddingLoss" + }, + { + "label": "MultiMarginLoss", + "value": "MultiMarginLoss" + }, + { + "label": "TripletMarginLoss", + "value": "TripletMarginLoss" + }, + { + "label": "TripletMarginWithDistanceLoss", + "value": "TripletMarginWithDistanceLoss" + } + ], + "comp_group": "train_model" + }, + { + "type": "str", + "name": "weights", + "title": "Class Weights", + "param_key": "weights", + "value": "[1.0, 1.0, 1.0]", + "comp_group": "train_model" + }, + { + "type": "float", + "name": "learning_rate", + "title": "Learning Rate", + "param_key": "learning_rate", + "value": 0.001, + "min": 0, + "max": 0.1, + "step": 0.0001, + "precision": 4, + "comp_group": "train_model" + }, + { + "type": "dropdown", + "name": "activation", + "title": "Activation", + "param_key": "activation", + "value": "Sigmoid", + "data": [ + { + "label": "ReLU", + "value": "ReLU" + }, + { + "label": "Sigmoid", + "value": "Sigmoid" + }, + { + "label": "Tanh", + "value": "Tanh" + }, + { + "label": "Softmax", + "value": "Softmax" + } + ], + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "val_pct", + "title": "Validation %", + "param_key": "val_pct", + "min": 0, + "max": 100, + "step": 5, + "value": 20, + "marks": [ + { + "value": 0, + "label": "0%" + }, + { + "value": 50, + "label": "50%" + }, + { + "value": 100, + "label": "100%" + } + ], + "comp_group": "train_model" + }, + { + "type": "bool", + "name": "shuffle_train", + "title": "Shuffle Training", + "param_key": "shuffle_train", + "checked": true, + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "batch_size_train", + "title": "Batch Size Training", + "param_key": "batch_size_train", + "min": 16, + "max": 128, + "step": 16, + "value": 32, + "marks": [ + { + "value": 16, + "label": "16" + }, + { + "value": 32, + "label": "32" + }, + { + "value": 64, + "label": "64" + }, + { + "value": 128, + "label": "128" + } + ], + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "batch_size_val", + "title": "Batch Size Validation", + "param_key": "batch_size_val", + "min": 16, + "max": 128, + "step": 16, + "value": 32, + "marks": [ + { + "value": 16, + "label": "16" + }, + { + "value": 32, + "label": "32" + }, + { + "value": 64, + "label": "64" + }, + { + "value": 128, + "label": "128" + } + ], + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "batch_size_inference", + "title": "Batch Size Inference", + "param_key": "batch_size_inference", + "min": 16, + "max": 128, + "step": 16, + "value": 32, + "marks": [ + { + "value": 16, + "label": "16" + }, + { + "value": 32, + "label": "32" + }, + { + "value": 64, + "label": "64" + }, + { + "value": 128, + "label": "128" + } + ], + "comp_group": "prediction_model" + } + ], + "cmd": [ + "python3 src/train_model.py", + "python3 src/segment.py" + ], + "reference": "https://dlsia.readthedocs.io/en/latest/" + }, + { + "model_name": "DSLIA TUNet", + "version": "0.0.1", + "type": "supervised", + "user": "mlexchange team", + "uri": "ghcr.io/mlexchange/mlex_dlsia_segmentation:main", + "application": [ + "segmentation" + ], + "description": "TUNet in DLSIA for image segmentation", + "gui_parameters": [ + { + "type": "int", + "name": "depth", + "title": "Depth", + "param_key": "depth", + "value": 4, + "comp_group": "train_model" + }, + { + "type": "int", + "name": "base_channels", + "title": "Base Channels", + "param_key": "base_channels", + "value": 32, + "comp_group": "train_model" + }, + { + "type": "int", + "name": "growth_rate", + "title": "Growth Rate", + "param_key": "growth_rate", + "value": 2, + "comp_group": "train_model" + }, + { + "type": "int", + "name": "hidden_rate", + "title": "Hidden Rate", + "param_key": "hidden_rate", + "value": 1, + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "num_epochs", + "title": "# Epochs", + "param_key": "num_epochs", + "min": 1, + "max": 1000, + "value": 30, + "marks": [ + { + "value": 1, + "label": "1" + }, + { + "value": 100, + "label": "100" + }, + { + "value": 1000, + "label": "1000" + } + ], + "comp_group": "train_model" + }, + { + "type": "dropdown", + "name": "optimizer", + "title": "Optimizer", + "param_key": "optimizer", + "value": "Adam", + "data": [ + { + "label": "Adadelta", + "value": "Adadelta" + }, + { + "label": "Adagrad", + "value": "Adagrad" + }, + { + "label": "Adam", + "value": "Adam" + }, + { + "label": "AdamW", + "value": "AdamW" + }, + { + "label": "SparseAdam", + "value": "SparseAdam" + }, + { + "label": "Adamax", + "value": "Adamax" + }, + { + "label": "ASGD", + "value": "ASGD" + }, + { + "label": "LBFGS", + "value": "LBFGS" + }, + { + "label": "RMSprop", + "value": "RMSprop" + }, + { + "label": "Rprop", + "value": "Rprop" + }, + { + "label": "SGD", + "value": "SGD" + } + ], + "comp_group": "train_model" + }, + { + "type": "dropdown", + "name": "criterion", + "title": "Criterion", + "param_key": "criterion", + "value": "MSELoss", + "data": [ + { + "label": "L1Loss", + "value": "L1Loss" + }, + { + "label": "MSELoss", + "value": "MSELoss" + }, + { + "label": "CrossEntropyLoss", + "value": "CrossEntropyLoss" + }, + { + "label": "CTCLoss", + "value": "CTCLoss" + }, + { + "label": "NLLLoss", + "value": "NLLLoss" + }, + { + "label": "PoissonNLLLoss", + "value": "PoissonNLLLoss" + }, + { + "label": "GaussianNLLLoss", + "value": "GaussianNLLLoss" + }, + { + "label": "KLDivLoss", + "value": "KLDivLoss" + }, + { + "label": "BCELoss", + "value": "BCELoss" + }, + { + "label": "BCEWithLogitsLoss", + "value": "BCEWithLogitsLoss" + }, + { + "label": "MarginRankingLoss", + "value": "MarginRankingLoss" + }, + { + "label": "HingeEnbeddingLoss", + "value": "HingeEnbeddingLoss" + }, + { + "label": "MultiLabelMarginLoss", + "value": "MultiLabelMarginLoss" + }, + { + "label": "HuberLoss", + "value": "HuberLoss" + }, + { + "label": "SmoothL1Loss", + "value": "SmoothL1Loss" + }, + { + "label": "SoftMarginLoss", + "value": "SoftMarginLoss" + }, + { + "label": "MutiLabelSoftMarginLoss", + "value": "MutiLabelSoftMarginLoss" + }, + { + "label": "CosineEmbeddingLoss", + "value": "CosineEmbeddingLoss" + }, + { + "label": "MultiMarginLoss", + "value": "MultiMarginLoss" + }, + { + "label": "TripletMarginLoss", + "value": "TripletMarginLoss" + }, + { + "label": "TripletMarginWithDistanceLoss", + "value": "TripletMarginWithDistanceLoss" + } + ], + "comp_group": "train_model" + }, + { + "type": "str", + "name": "weights", + "title": "Class Weights", + "param_key": "weights", + "value": "[1.0, 1.0, 1.0]", + "comp_group": "train_model" + }, + { + "type": "float", + "name": "learning_rate", + "title": "Learning Rate", + "param_key": "learning_rate", + "value": 0.001, + "min": 0, + "max": 0.1, + "step": 0.0001, + "precision": 4, + "comp_group": "train_model" + }, + { + "type": "dropdown", + "name": "activation", + "title": "Activation", + "param_key": "activation", + "value": "Sigmoid", + "data": [ + { + "label": "ReLU", + "value": "ReLU" + }, + { + "label": "Sigmoid", + "value": "Sigmoid" + }, + { + "label": "Tanh", + "value": "Tanh" + }, + { + "label": "Softmax", + "value": "Softmax" + } + ], + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "val_pct", + "title": "Validation %", + "param_key": "val_pct", + "min": 0, + "max": 100, + "step": 5, + "value": 20, + "marks": [ + { + "value": 0, + "label": "0%" + }, + { + "value": 100, + "label": "100%" + } + ], + "comp_group": "train_model" + }, + { + "type": "bool", + "name": "shuffle_train", + "title": "Shuffle Training", + "param_key": "shuffle_train", + "checked": true, + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "batch_size_train", + "title": "Training Batch Size", + "param_key": "batch_size_train", + "min": 16, + "max": 128, + "step": 16, + "value": 32, + "marks": [ + { + "value": 16, + "label": "16" + }, + { + "value": 32, + "label": "32" + }, + { + "value": 64, + "label": "64" + }, + { + "value": 128, + "label": "128" + } + ], + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "batch_size_val", + "title": "Validation Batch Size", + "param_key": "batch_size_val", + "min": 16, + "max": 128, + "step": 16, + "value": 32, + "marks": [ + { + "value": 16, + "label": "16" + }, + { + "value": 32, + "label": "32" + }, + { + "value": 64, + "label": "64" + }, + { + "value": 128, + "label": "128" + } + ], + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "batch_size_inference", + "title": "Inference Batch Size", + "param_key": "batch_size_inference", + "min": 16, + "max": 128, + "step": 16, + "value": 32, + "marks": [ + { + "value": 16, + "label": "16" + }, + { + "value": 32, + "label": "32" + }, + { + "value": 64, + "label": "64" + }, + { + "value": 128, + "label": "128" + } + ], + "comp_group": "prediction_model" + } + ], + "cmd": [ + "python3 src/train_model.py", + "python3 src/segment.py" + ], + "reference": "https://dlsia.readthedocs.io/en/latest/" + }, + { + "model_name": "DSLIA TUNet3+", + "version": "0.0.1", + "type": "supervised", + "user": "mlexchange team", + "uri": "ghcr.io/mlexchange/mlex_dlsia_segmentation:main", + "application": [ + "segmentation" + ], + "description": "TUNet3+ DLSIA for image segmentation", + "gui_parameters": [ + { + "type": "int", + "name": "depth", + "title": "Depth", + "param_key": "depth", + "value": 4, + "comp_group": "train_model" + }, + { + "type": "int", + "name": "base_channels", + "title": "Base Channels", + "param_key": "base_channels", + "value": 32, + "comp_group": "train_model" + }, + { + "type": "int", + "name": "growth_rate", + "title": "Growth Rate", + "param_key": "growth_rate", + "value": 2, + "comp_group": "train_model" + }, + { + "type": "int", + "name": "hidden_rate", + "title": "Hidden Rate", + "param_key": "hidden_rate", + "value": 1, + "comp_group": "train_model" + }, + { + "type": "int", + "name": "carryover_channels", + "title": "Carryover Channels", + "param_key": "carryover_channels", + "value": 32, + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "num_epochs", + "title": "# Epochs", + "param_key": "num_epochs", + "min": 1, + "max": 1000, + "value": 30, + "marks": [ + { + "value": 1, + "label": "1" + }, + { + "value": 100, + "label": "100" + }, + { + "value": 1000, + "label": "1000" + } + ], + "comp_group": "train_model" + }, + { + "type": "dropdown", + "name": "optimizer", + "title": "Optimizer", + "param_key": "optimizer", + "value": "Adam", + "data": [ + { + "label": "Adadelta", + "value": "Adadelta" + }, + { + "label": "Adagrad", + "value": "Adagrad" + }, + { + "label": "Adam", + "value": "Adam" + }, + { + "label": "AdamW", + "value": "AdamW" + }, + { + "label": "SparseAdam", + "value": "SparseAdam" + }, + { + "label": "Adamax", + "value": "Adamax" + }, + { + "label": "ASGD", + "value": "ASGD" + }, + { + "label": "LBFGS", + "value": "LBFGS" + }, + { + "label": "RMSprop", + "value": "RMSprop" + }, + { + "label": "Rprop", + "value": "Rprop" + }, + { + "label": "SGD", + "value": "SGD" + } + ], + "comp_group": "train_model" + }, + { + "type": "dropdown", + "name": "criterion", + "title": "Criterion", + "param_key": "criterion", + "value": "MSELoss", + "data": [ + { + "label": "L1Loss", + "value": "L1Loss" + }, + { + "label": "MSELoss", + "value": "MSELoss" + }, + { + "label": "CrossEntropyLoss", + "value": "CrossEntropyLoss" + }, + { + "label": "CTCLoss", + "value": "CTCLoss" + }, + { + "label": "NLLLoss", + "value": "NLLLoss" + }, + { + "label": "PoissonNLLLoss", + "value": "PoissonNLLLoss" + }, + { + "label": "GaussianNLLLoss", + "value": "GaussianNLLLoss" + }, + { + "label": "KLDivLoss", + "value": "KLDivLoss" + }, + { + "label": "BCELoss", + "value": "BCELoss" + }, + { + "label": "BCEWithLogitsLoss", + "value": "BCEWithLogitsLoss" + }, + { + "label": "MarginRankingLoss", + "value": "MarginRankingLoss" + }, + { + "label": "HingeEnbeddingLoss", + "value": "HingeEnbeddingLoss" + }, + { + "label": "MultiLabelMarginLoss", + "value": "MultiLabelMarginLoss" + }, + { + "label": "HuberLoss", + "value": "HuberLoss" + }, + { + "label": "SmoothL1Loss", + "value": "SmoothL1Loss" + }, + { + "label": "SoftMarginLoss", + "value": "SoftMarginLoss" + }, + { + "label": "MutiLabelSoftMarginLoss", + "value": "MutiLabelSoftMarginLoss" + }, + { + "label": "CosineEmbeddingLoss", + "value": "CosineEmbeddingLoss" + }, + { + "label": "MultiMarginLoss", + "value": "MultiMarginLoss" + }, + { + "label": "TripletMarginLoss", + "value": "TripletMarginLoss" + }, + { + "label": "TripletMarginWithDistanceLoss", + "value": "TripletMarginWithDistanceLoss" + } + ], + "comp_group": "train_model" + }, + { + "type": "str", + "name": "weights", + "title": "Class Weights", + "param_key": "weights", + "value": "[1.0, 1.0, 1.0]", + "comp_group": "train_model" + }, + { + "type": "float", + "name": "learning_rate", + "title": "Learning Rate", + "param_key": "learning_rate", + "value": 0.001, + "min": 0, + "max": 0.1, + "step": 0.0001, + "precision": 4, + "comp_group": "train_model" + }, + { + "type": "dropdown", + "name": "activation", + "title": "Activation", + "param_key": "activation", + "value": "Sigmoid", + "data": [ + { + "label": "ReLU", + "value": "ReLU" + }, + { + "label": "Sigmoid", + "value": "Sigmoid" + }, + { + "label": "Tanh", + "value": "Tanh" + }, + { + "label": "Softmax", + "value": "Softmax" + } + ], + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "val_pct", + "title": "Validation %", + "param_key": "val_pct", + "min": 0, + "max": 100, + "step": 5, + "value": 20, + "marks": [ + { + "value": 0, + "label": "0%" + }, + { + "value": 100, + "label": "100%" + } + ], + "comp_group": "train_model" + }, + { + "type": "bool", + "name": "shuffle_train", + "title": "Shuffle Training", + "param_key": "shuffle_train", + "checked": true, + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "batch_size_train", + "title": "Training Batch Size", + "param_key": "batch_size_train", + "min": 16, + "max": 128, + "step": 16, + "value": 32, + "marks": [ + { + "value": 16, + "label": "16" + }, + { + "value": 32, + "label": "32" + }, + { + "value": 64, + "label": "64" + }, + { + "value": 128, + "label": "128" + } + ], + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "batch_size_val", + "title": "Validation Batch Size", + "param_key": "batch_size_val", + "min": 16, + "max": 128, + "step": 16, + "value": 32, + "marks": [ + { + "value": 16, + "label": "16" + }, + { + "value": 32, + "label": "32" + }, + { + "value": 64, + "label": "64" + }, + { + "value": 128, + "label": "128" + } + ], + "comp_group": "train_model" + }, + { + "type": "slider", + "name": "batch_size_inference", + "title": "Inference Batch Size", + "param_key": "batch_size_inference", + "min": 16, + "max": 128, + "step": 16, + "value": 32, + "marks": [ + { + "value": 16, + "label": "16" + }, + { + "value": 32, + "label": "32" + }, + { + "value": 64, + "label": "64" + }, + { + "value": 128, + "label": "128" + } + ], + "comp_group": "prediction_model" + } + ], + "cmd": [ + "python3 src/train_model.py", + "python3 src/segment.py" + ], + "reference": "https://dlsia.readthedocs.io/en/latest/" + } + ] +} diff --git a/callbacks/control_bar.py b/callbacks/control_bar.py index d0da110..b3086ed 100644 --- a/callbacks/control_bar.py +++ b/callbacks/control_bar.py @@ -2,6 +2,7 @@ import os import random import time +import uuid import dash_mantine_components as dmc import plotly.express as px @@ -24,9 +25,10 @@ from dash_iconify import DashIconify from components.annotation_class import annotation_class_item +from components.parameter_items import ParameterItems from constants import ANNOT_ICONS, ANNOT_NOTIFICATION_MSGS, KEY_MODES, KEYBINDS from utils.annotations import Annotations -from utils.data_utils import tiled_datasets, tiled_masks, tiled_results +from utils.data_utils import models, tiled_datasets, tiled_masks, tiled_results from utils.plot_utils import generate_notification, generate_notification_bg_icon_col # TODO - temporary local file path and user for annotation saving and exporting @@ -917,3 +919,19 @@ def update_current_annotated_slices_values(all_classes): ] disabled = True if len(dropdown_values) == 0 else False return dropdown_values, disabled + + +@callback( + Output("model-parameters", "children"), + Input("model-list", "value"), +) +def update_model_parameters(model_name): + model = models[model_name] + if model["gui_parameters"]: + # TODO: Retain old parameters if they exist + item_list = ParameterItems( + _id={"type": str(uuid.uuid4())}, json_blob=model["gui_parameters"] + ) + return item_list + else: + return html.Div("Model has no parameters") diff --git a/callbacks/segmentation.py b/callbacks/segmentation.py index c7c8d40..dca9537 100644 --- a/callbacks/segmentation.py +++ b/callbacks/segmentation.py @@ -7,7 +7,7 @@ from dash import ALL, Input, Output, State, callback, no_update from dash.exceptions import PreventUpdate -from utils.data_utils import tiled_masks +from utils.data_utils import extract_parameters_from_html, tiled_masks MODE = os.getenv("MODE", "") @@ -58,12 +58,14 @@ @callback( Output("output-details", "children"), Output("submitted-job-id", "data"), + Output("model-parameter-values", "data"), Input("run-model", "n_clicks"), State("annotation-store", "data"), State({"type": "annotation-class-store", "index": ALL}, "data"), State("project-name-src", "value"), + State("model-parameters", "children"), ) -def run_job(n_clicks, global_store, all_annotations, project_name): +def run_job(n_clicks, global_store, all_annotations, project_name, model_parameters): """ This callback collects parameters from the UI and submits a job to the computing api. If the app is run from "dev" mode, then only a placeholder job_uid will be created. @@ -72,7 +74,11 @@ def run_job(n_clicks, global_store, all_annotations, project_name): # TODO: Appropriately paramaterize the DEMO_WORKFLOW json depending on user inputs and relevant file paths """ + input_params = {} if n_clicks: + input_params = extract_parameters_from_html(model_parameters) + # return the input values in dictionary and save to the model parameter store + print(f"input_param:\n{input_params}") if MODE == "dev": mask_uri = tiled_masks.save_annotations_data( global_store, all_annotations, project_name @@ -84,6 +90,7 @@ def run_job(n_clicks, global_store, all_annotations, project_name): size="sm", ), job_uid, + input_params, ) else: mask_uri = tiled_masks.save_annotations_data( @@ -100,6 +107,7 @@ def run_job(n_clicks, global_store, all_annotations, project_name): size="sm", ), job_uid, + input_params, ) else: return ( @@ -108,8 +116,9 @@ def run_job(n_clicks, global_store, all_annotations, project_name): size="sm", ), job_uid, + input_params, ) - return no_update, no_update + return no_update, no_update, input_params @callback( diff --git a/components/control_bar.py b/components/control_bar.py index 007f856..4809e5f 100644 --- a/components/control_bar.py +++ b/components/control_bar.py @@ -5,8 +5,9 @@ from dash_iconify import DashIconify from components.annotation_class import annotation_class_item +from components.parameter_items import ControlItem from constants import ANNOT_ICONS, KEYBINDS -from utils.data_utils import tiled_datasets +from utils.data_utils import models, tiled_datasets def _tooltip(text, children): @@ -18,24 +19,6 @@ def _tooltip(text, children): ) -def _control_item(title, title_id, item): - """ - Returns a customized layout for a control item - """ - return dmc.Grid( - [ - dmc.Text( - title, - id=title_id, - size="sm", - style={"width": "100px", "margin": "auto", "paddingRight": "5px"}, - align="right", - ), - html.Div(item, style={"width": "265px", "margin": "auto"}), - ] - ) - - def _accordion_item(title, icon, value, children, id): """ Returns a customized layout for an accordion item @@ -78,7 +61,7 @@ def layout(): id="data-selection-controls", children=[ dmc.Space(h=5), - _control_item( + ControlItem( "Dataset", "image-selector", dmc.Grid( @@ -114,7 +97,7 @@ def layout(): ), ), dmc.Space(h=25), - _control_item( + ControlItem( "Slice 1", "image-selection-text", [ @@ -177,7 +160,7 @@ def layout(): ], ), dmc.Space(h=25), - _control_item( + ControlItem( _tooltip( "Jump to your annotated slices", "Annotated slices", @@ -207,7 +190,7 @@ def layout(): children=html.Div( [ dmc.Space(h=5), - _control_item( + ControlItem( "Brightness", "bightness-text", [ @@ -251,7 +234,7 @@ def layout(): ], ), dmc.Space(h=20), - _control_item( + ControlItem( "Contrast", "contrast-text", dmc.Grid( @@ -452,6 +435,9 @@ def layout(): }, className="add-class-btn", ), + dcc.Store( + id="current-class-selection", data="#FFA200" + ), dmc.Space(h=20), ], ), @@ -603,6 +589,24 @@ def layout(): "run-model", id="model-configuration", children=[ + ControlItem( + "Model", + "model-selector", + dmc.Select( + id="model-list", + data=models.modelname_list, + value=( + models.modelname_list[0] + if models.modelname_list[0] + else None + ), + placeholder="Select a model...", + ), + ), + dmc.Space(h=15), + html.Div(id="model-parameters"), + dcc.Store(id="model-parameter-values", data={}), + dmc.Space(h=25), dmc.Center( dmc.Button( "Run model", @@ -624,7 +628,7 @@ def layout(): styles={"trackLabel": {"cursor": "pointer"}}, ), dmc.Space(h=25), - _control_item( + ControlItem( "Results", "", dmc.Select( @@ -633,7 +637,7 @@ def layout(): ), ), dmc.Space(h=25), - _control_item( + ControlItem( "Opacity", "", dmc.Slider( diff --git a/components/parameter_items.py b/components/parameter_items.py new file mode 100644 index 0000000..f165125 --- /dev/null +++ b/components/parameter_items.py @@ -0,0 +1,271 @@ +import dash_bootstrap_components as dbc +import dash_mantine_components as dmc +from dash import html + + +class ControlItem(dmc.Grid): + """ + Customized layout for a control item + """ + + def __init__(self, title, title_id, item, style={}): + super(ControlItem, self).__init__( + children=[ + dmc.Text( + title, + id=title_id, + size="sm", + style={"width": "100px", "margin": "auto", "paddingRight": "5px"}, + align="right", + ), + html.Div(item, style={"width": "265px", "margin": "auto"}), + ], + style=style, + ) + + +class NumberItem(ControlItem): + def __init__( + self, + name, + base_id, + title=None, + param_key=None, + visible=True, + **kwargs, + ): + if param_key is None: + param_key = name + self.input = dmc.NumberInput( + id={**base_id, "name": name, "param_key": param_key, "layer": "input"}, + **kwargs, + ) + + style = {"padding": "15px 0px 0px 0px"} + if not visible: + style["display"] = "none" + + super(NumberItem, self).__init__( + title=title, + title_id={ + **base_id, + "name": name, + "param_key": param_key, + "layer": "label", + }, + item=self.input, + style=style, + ) + + +class StrItem(ControlItem): + def __init__( + self, + name, + base_id, + title=None, + param_key=None, + visible=True, + **kwargs, + ): + if param_key is None: + param_key = name + self.input = dmc.TextInput( + id={**base_id, "name": name, "param_key": param_key, "layer": "input"}, + **kwargs, + ) + + style = {"padding": "15px 0px 0px 0px"} + if not visible: + style["display"] = "none" + + super(StrItem, self).__init__( + title=title, + title_id={ + **base_id, + "name": name, + "param_key": param_key, + "layer": "label", + }, + item=self.input, + style=style, + ) + + +class SliderItem(ControlItem): + def __init__( + self, + name, + base_id, + title=None, + param_key=None, + visible=True, + **kwargs, + ): + if param_key is None: + param_key = name + self.input = dmc.Slider( + id={**base_id, "name": name, "param_key": param_key, "layer": "input"}, + labelAlwaysOn=False, + **kwargs, + ) + + style = {"padding": "15px 0px 15px 0px"} + if not visible: + style["display"] = "none" + + super(SliderItem, self).__init__( + title=title, + title_id={ + **base_id, + "name": name, + "param_key": param_key, + "layer": "label", + }, + item=self.input, + style=style, + ) + + +class DropdownItem(ControlItem): + def __init__( + self, + name, + base_id, + title=None, + param_key=None, + visible=True, + **kwargs, + ): + if param_key is None: + param_key = name + self.input = dmc.Select( + id={**base_id, "name": name, "param_key": param_key, "layer": "input"}, + **kwargs, + ) + + style = {"padding": "15px 0px 0px 0px"} + if not visible: + style["display"] = "none" + + super(DropdownItem, self).__init__( + title=title, + title_id={ + **base_id, + "name": name, + "param_key": param_key, + "layer": "label", + }, + item=self.input, + style=style, + ) + + +class RadioItem(ControlItem): + def __init__( + self, name, base_id, title=None, param_key=None, visible=True, **kwargs + ): + if param_key is None: + param_key = name + + options = [ + dmc.Radio(option["label"], value=option["value"]) + for option in kwargs["options"] + ] + kwargs.pop("options", None) + self.input = dmc.RadioGroup( + options, + id={**base_id, "name": name, "param_key": param_key, "layer": "input"}, + **kwargs, + ) + + style = {"padding": "15px 0px 0px 0px"} + if not visible: + style["display"] = "none" + + super(RadioItem, self).__init__( + title=title, + title_id={ + **base_id, + "name": name, + "param_key": param_key, + "layer": "label", + }, + item=self.input, + style=style, + ) + + +class BoolItem(ControlItem): + def __init__( + self, name, base_id, title=None, param_key=None, visible=True, **kwargs + ): + if param_key is None: + param_key = name + + self.input = dmc.Switch( + id={**base_id, "name": name, "param_key": param_key, "layer": "input"}, + label=title, + **kwargs, + ) + + style = {"padding": "15px 0px 0px 0px"} + if not visible: + style["display"] = "none" + + super(BoolItem, self).__init__( + title="", # title is already in the switch + title_id={ + **base_id, + "name": name, + "param_key": param_key, + "layer": "label", + }, + item=self.input, + style=style, + ) + + +class ParameterItems(dbc.Form): + type_map = { + "float": NumberItem, + "int": NumberItem, + "str": StrItem, + "slider": SliderItem, + "dropdown": DropdownItem, + "radio": RadioItem, + "bool": BoolItem, + } + + def __init__(self, _id, json_blob, values=None): + super(ParameterItems, self).__init__(id=_id, children=[]) + self._json_blob = json_blob + self.children = self.build_children(values=values) + + def _determine_type(self, parameter_dict): + if "type" in parameter_dict: + if parameter_dict["type"] in self.type_map: + return parameter_dict["type"] + elif parameter_dict["type"].__name__ in self.type_map: + return parameter_dict["type"].__name__ + elif type(parameter_dict["value"]) in self.type_map: + return type(parameter_dict["value"]) + raise TypeError( + f"No item type could be determined for this parameter: {parameter_dict}" + ) + + def build_children(self, values=None): + children = [] + for json_record in self._json_blob: + # Build a parameter dict from self.json_blob + type = json_record.get("type", self._determine_type(json_record)) + json_record = json_record.copy() + if values and json_record["name"] in values: + json_record["value"] = values[json_record["name"]] + json_record.pop("type", None) + if "comp_group" in json_record: + json_record.pop("comp_group", None) + item = self.type_map[type](**json_record, base_id=self.id) + children.append(item) + + return children diff --git a/docker-compose.yml b/docker-compose.yml index c0a7f2b..5f1a7d5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,12 @@ services: environment: DATA_TILED_URI: '${DATA_TILED_URI}' DATA_TILED_API_KEY: '${DATA_TILED_API_KEY}' + MASK_TILED_URI: '${MASK_TILED_URI}' + MASK_TILED_API_KEY: '${TILED_API_KEY}' + SEG_TILED_URI: '${SEG_TILED_URI}' + SEG_TILED_API_KEY: '${SEG_TILED_API_KEY}' + USER_NAME: '${USER_NAME}' + USER_PASSWORD: '${USER_PASSWORD}' volumes: - ./app.py:/app/app.py - ./constants.py:/app/constants.py diff --git a/utils/annotations.py b/utils/annotations.py index c2eafcd..e7875f1 100644 --- a/utils/annotations.py +++ b/utils/annotations.py @@ -11,7 +11,7 @@ class Annotations: - def __init__(self, annotation_store, global_store): + def __init__(self, annotation_store, image_shape): if annotation_store: slices = [] for annotation_class in annotation_store: @@ -49,7 +49,7 @@ def __init__(self, annotation_store, global_store): self.annotation_classes = annotation_classes self.annotations = annotations self.annotations_hash = self.get_annotations_hash() - self.image_shape = global_store["image_shapes"][0] + self.image_shape = image_shape def get_annotations(self): return self.annotations diff --git a/utils/data_utils.py b/utils/data_utils.py index a60b919..3311019 100644 --- a/utils/data_utils.py +++ b/utils/data_utils.py @@ -168,7 +168,18 @@ def save_annotations_data(self, global_store, all_annotations, project_name): """ Transforms annotations data to a pixelated mask and outputs to the Tiled server """ - annotations = Annotations(all_annotations, global_store) + if "image_shapes" in global_store: + image_shape = global_store["image_shapes"][0] + else: + print("Global store was not filled.") + data_shape = ( + tiled_datasets.get_data_shape_by_name(project_name) + if project_name + else None + ) + image_shape = (data_shape[1], data_shape[2]) + + annotations = Annotations(all_annotations, image_shape) # TODO: Check sparse status, it may be worthwhile to store the mask as a sparse array # if our machine learning models can handle sparse arrays annotations.create_annotation_mask(sparse=False) @@ -223,3 +234,45 @@ def save_annotations_data(self, global_store, all_annotations, project_name): tiled_results = TiledDataLoader( data_tiled_uri=SEG_TILED_URI, data_tiled_api_key=SEG_TILED_API_KEY ) + + +class Models: + def __init__(self, modelfile_path="./assets/models.json"): + self.path = modelfile_path + f = open(self.path) + + contents = json.load(f)["contents"] + self.modelname_list = [content["model_name"] for content in contents] + self.models = {} + + for i, n in enumerate(self.modelname_list): + self.models[n] = contents[i] + + def __getitem__(self, key): + try: + return self.models[key] + except KeyError: + raise KeyError(f"A model with name {key} does not exist.") + + +models = Models() + + +def extract_parameters_from_html(model_parameters_html): + """ + Extracts parameters from the children component of a + """ + input_params = {} + for param in model_parameters_html["props"]["children"]: + # param["props"]["children"][0] is the label + # param["props"]["children"][1] is the input + parameter_container = param["props"]["children"][1] + # The achtual parameter item is the first and only child of the parameter container + parameter_item = parameter_container["props"]["children"]["props"] + key = parameter_item["id"]["param_key"] + if "value" in parameter_item: + value = parameter_item["value"] + elif "checked" in parameter_item: + value = parameter_item["checked"] + input_params[key] = value + return input_params