Skip to content

Commit

Permalink
Merge pull request #6 from sebi06/work-in-progress
Browse files Browse the repository at this point in the history
Work in progress
  • Loading branch information
sebi06 authored Jul 22, 2022
2 parents 3c73e8e + 1b7a44f commit 37c8bd8
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 275 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ Semantic Segmentation using DeepLearning ONNX models packaged as *.czann files.

This [napari] plugin was generated with [Cookiecutter] using [@napari]'s [cookiecutter-napari-plugin] template.

<!--
Don't miss the full getting started guide to set up your new package:
https://github.com/napari/cookiecutter-napari-plugin#getting-started
and review the napari docs for plugin developers:
https://napari.org/plugins/index.html
-->


![Train on APEER and use model in Napari](https://github.com/sebi06/napari-czann-segment/raw/main/readme_images/Train_APEER_run_Napari_CZANN_no_highlights_small.gif)

## Installation
Expand Down Expand Up @@ -47,7 +38,6 @@ Therer two main ways hwo such a model can be created:
- Train the segmentation model fully automated on [APEER] and download the *.czann file
- Train your model in a Jupyter notebook etc. and package it using the [czmodel] python package as an *.czann


## Using this plugin

### Sample Data
Expand All @@ -61,10 +51,21 @@ In order to use this plugin the user has to do the following things:
- Open the image using "File - Open Files(s)" (requires [napari-aicsimageio] plugin).
- Click **napari-czann-segment: Segment with CZANN model** in the "Plugins" menu.
- **Select a *.czann file** to use the model for segmentation.
- metadata of the model will be shown (see example below)

| Parameter | Value | Explanation |
| :----------- | :------------------------------------------- | ------------------------------------------------------- |
| model_type | ModelType.SINGLE_CLASS_SEMANTIC_SEGMENTATION | see: [czmodel] for details
| input_shape | [1024, 1024, 1] | tile dimensions required for the input |
| output_shape | [1024, 1024, 3] | tile dimensionsof the output |
| model_id | ba32bc6d-6bc9-4774-8b47-20646c7cb838 | unique GUID for that model |
| min_overlap | [128, 128] | tile overlap used during training |
| classes | ['background', 'grains', 'inclusions'] | availbale classes |
| model_name | APEER-trained model | name of the model |

![Napari - Image loaded and czann selected](https://github.com/sebi06/napari-czann-segment/raw/main/readme_images/napari_czann1.png)

- Adjust the **minimum overlap** used to the tiling (optional).
- Adjust the **minimum overlap** for the tiling (optional, see [cztile] for details).
- Select the **layer** to be segmented.
- Press **Segment Selected Image Layer** to run the segmentation.

Expand Down Expand Up @@ -104,7 +105,6 @@ To install latest development version:

pip install git+https://github.com/sebi06/napari_czann_segment.git


## Contributing

Contributions and Feedback are very welcome.
Expand Down
Binary file modified readme_images/napari_czann1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified readme_images/napari_czann2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/napari_czann_segment/_data/REM_AL2O3_Grains_8bit.czann
Binary file not shown.
119 changes: 102 additions & 17 deletions src/napari_czann_segment/dock_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,77 @@
from czmodel.convert import DefaultConverter
from typing import Dict, List, Tuple, Union
from qtpy.QtWidgets import (QComboBox, QHBoxLayout, QLabel, QPushButton, QLineEdit, QListWidgetItem,
QVBoxLayout, QWidget, QFileDialog, QDialogButtonBox, QSlider)
QVBoxLayout, QWidget, QFileDialog, QDialogButtonBox, QSlider,
QTableWidget, QTableWidgetItem, QSizePolicy)

from qtpy.QtCore import Qt, Signal, QObject, QEvent
from qtpy.QtCore import Qt, Signal, QObject, QEvent, QSize
from qtpy.QtGui import QFont
from magicgui.widgets import FileEdit, PushButton, Slider, Container, Label, CheckBox
from magicgui.types import FileDialogMode


class TableWidget(QWidget):

def __init__(self) -> None:

super(QWidget, self).__init__()

self.layout = QVBoxLayout(self)
self.model_table = QTableWidget()

self.model_table.setShowGrid(True)
self.model_table.setHorizontalHeaderLabels(["Parameter", "Value"])
#self.model_table.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
header = self.model_table.horizontalHeader()
header.setDefaultAlignment(Qt.AlignLeft)
self.layout.addWidget(self.model_table)

def update_model_metadata(self, md_dict: Dict) -> None:

# number of rows is set to number of metadata entries
row_count = len(md_dict)
col_count = 2
self.model_table.setColumnCount(col_count)
self.model_table.setRowCount(row_count)

row = 0

# update the table with the entries from metadata dictionary
for key, value in md_dict.items():
newkey = QTableWidgetItem(key)
self.model_table.setItem(row, 0, newkey)
newvalue = QTableWidgetItem(str(value))
self.model_table.setItem(row, 1, newvalue)
row += 1

# fit columns and rows to content
self.model_table.resizeColumnsToContents()
self.model_table.resizeRowsToContents()
self.model_table.adjustSize()

def update_style(self) -> None:

# define font size and type
fnt = QFont()
fnt.setPointSize(8)
fnt.setBold(True)
fnt.setFamily("Arial")

# update both header items
fc = (25, 25, 25)
#item1 = QtWidgets.QTableWidgetItem("Parameter")
item1 = QTableWidgetItem("Parameter")
#item1.setForeground(QtGui.QColor(25, 25, 25))
item1.setFont(fnt)
self.model_table.setHorizontalHeaderItem(0, item1)

#item2 = QtWidgets.QTableWidgetItem("Value")
item2 = QTableWidgetItem("Value")
#item2.setForeground(QtGui.QColor(25, 25, 25))
item2.setFont(fnt)
self.model_table.setHorizontalHeaderItem(1, item2)


# our manifest widget command points to this class
class segment_with_czann(QWidget):
"""Widget allows selection of an Image layer, a model file and the desired border width
Expand All @@ -52,8 +116,9 @@ def __init__(self, napari_viewer):
self.min_overlap: int = 128
self.model_metadata = None
self.czann_file: str = ""
self.dnn_tile_width: int = 1024
self.dnn_tile_height: int = 1024
self.dnn_tile_width = 1024
self.dnn_tile_height = 1024
self.dnn_channel_number = 1

# TODO : Enable GPU support and make it work
self.use_gpu: bool = False
Expand All @@ -64,7 +129,7 @@ def __init__(self, napari_viewer):
# add a label
self.layout().addWidget(QLabel("Model File Selection"))

# define filter to allowed file extensions
# define filter based on file extension
model_extension = "*.czann"

# create the FileEdit widget and add to the layout and connect it
Expand All @@ -75,13 +140,25 @@ def __init__(self, napari_viewer):
self.layout().addWidget(self.filename_edit.native)
self.filename_edit.line_edit.changed.connect(self._file_changed)

# add table for model metadata
self.model_metadata_label = QLabel("Model Metadata")
self.model_metadata_label.setFont(QFont('Arial', 9, QFont.Normal))
# setting up background color and border
#self.model_metadata_label.setStyleSheet("background-color: yellow;border: 1px solid black;")
self.layout().addWidget(self.model_metadata_label)

self.model_metadata_table = TableWidget()
#self.model_metadata_table.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
#self.model_metadata_table.setFixedSize(QSize(275, 250))
self.layout().addWidget(self.model_metadata_table)

# add button for reading the model metadata
self.read_modeldata_btn = QPushButton("Reload minimum overlap from Metadata")
self.read_modeldata_btn.clicked.connect(self._read_min_overlap)
self.read_modeldata_btn = QPushButton("Reload minimum overlap")
self.read_modeldata_btn.clicked.connect(self._read_model_metadata)
self.layout().addWidget(self.read_modeldata_btn)

# add label and slider for adjusting the minimum overlap
min_overlap_label = QLabel("Adjust minimum overlap for Segmentation")
self.min_overlap_label = QLabel("Adjust minimum overlap for Segmentation")
self.min_overlap_slider = Slider(orientation="horizontal",
label="Minimum Overlap",
value=128,
Expand All @@ -92,7 +169,7 @@ def __init__(self, napari_viewer):
tooltip="Adjust the desired minimum overlap",
tracking=False)

self.layout().addWidget(min_overlap_label)
self.layout().addWidget(self.min_overlap_label)
self.layout().addWidget(self.min_overlap_slider.native)
self.min_overlap_slider.changed.connect(self._update_min_overlap)

Expand All @@ -104,7 +181,7 @@ def __init__(self, napari_viewer):
self.viewer.layers.events.inserted.connect(self._reset_layer_options)
self.viewer.layers.events.removed.connect(self._reset_layer_options)

# TODO : This checkbox is currentyl hidden
# TODO : This checkbox is currently hidden
# add the checkbox the use the GPU for the inference
self.use_gpu_checkbox = CheckBox(name="Use GPU for inference",
visible=False,
Expand Down Expand Up @@ -154,18 +231,26 @@ def add_image_combo_box(self, label_text):
# returning the combo box so we know which is which when we click run
return new_layer_combo

def _read_min_overlap(self):
"""Get model minimum overlap and store them"""
def _read_model_metadata(self):
"""Get model metadata and store them"""

# extract the model information and path
with tempfile.TemporaryDirectory() as temp_path:
self.model_metadata, self.model_path = DefaultConverter().unpack_model(model_file=self.czann_file,
target_dir=Path(temp_path))

# update the value for the minimum overlap and the respective slider
# get model metadata as dictionary
self.model_metadata_dict = self.model_metadata._asdict()
self.model_metadata_table.update_model_metadata(self.model_metadata_dict)
self.model_metadata_table.update_style()
print(self.model_metadata_table.sizeHint())

# get the values for the minimum overlap and the required input tile size
self.min_overlap = self.model_metadata.min_overlap[0]
self.min_overlap_slider.value = self.min_overlap
print("Updated value for minimum overlap and Slider from ModelMetaData: ", self.min_overlap)
self.dnn_tile_width = self.model_metadata.input_shape[0]
self.dnn_tile_height = self.model_metadata.input_shape[1]
self.dnn_channel_number = self.model_metadata.input_shape[2]

def _segment(self):

Expand Down Expand Up @@ -222,8 +307,8 @@ def _file_changed(self):
).replace("\\", "/").replace("//", "/")
print("Model Path: ", self.czann_file)

# update the minimum overlap
self._read_min_overlap()
# update the model metadata
self._read_model_metadata()

def _reset_layer_options(self, event):
"""Clear existing combo boxes and repopulate
Expand All @@ -245,7 +330,7 @@ def _reset_layer_options(self, event):
def _check_min_overlap(self):

# Minimum border width must be less than half the tile size.
min_tilesize = min(self.dnn_tile_width, self.dnn_tile_width)
min_tilesize = min(self.dnn_tile_width, self.dnn_tile_height)

# check
if self.min_overlap >= np.round(min_tilesize / 2, 0):
Expand Down
Loading

0 comments on commit 37c8bd8

Please sign in to comment.