Skip to content

organelle_segmenter_plugin

Controller

Bases: ABC

Source code in organelle_segmenter_plugin/core/controller.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Controller(ABC):
    def __init__(self, application: IApplication):
        if application is None:
            raise ValueError("application")
        self._application = application

    @abstractmethod
    def index(self):
        pass

    def cleanup(self):
        """
        Perform cleanup operations such as disconnecting events
        Override in child class if needed
        """
        pass

    @property
    def state(self) -> State:
        """
        Get the application State object
        """
        return self._application.state

    @property
    def router(self) -> IRouter:
        """
        Get the application Router
        """
        return self._application.router

    @property
    def viewer(self) -> ViewerAbstraction:
        """
        Get the Napari viewer (abstracted)
        """
        return self._application.viewer

    def load_view(self, view: View, model: Any = None):
        """
        Loads the given view
        :param: view: the View to load
        """
        return self._application.view_manager.load_view(view, model)

    def show_message_box(self, title: str, message: str):
        """
        Display a pop up message box
        :param: title: Message box title
        :param: message: message body to display
        """
        msg = QMessageBox()
        msg.setWindowTitle(title)
        msg.setText(message)
        return msg.exec()

router: IRouter property

Get the application Router

state: State property

Get the application State object

viewer: ViewerAbstraction property

Get the Napari viewer (abstracted)

cleanup()

Perform cleanup operations such as disconnecting events Override in child class if needed

Source code in organelle_segmenter_plugin/core/controller.py
20
21
22
23
24
25
def cleanup(self):
    """
    Perform cleanup operations such as disconnecting events
    Override in child class if needed
    """
    pass

load_view(view, model=None)

Loads the given view :param: view: the View to load

Source code in organelle_segmenter_plugin/core/controller.py
48
49
50
51
52
53
def load_view(self, view: View, model: Any = None):
    """
    Loads the given view
    :param: view: the View to load
    """
    return self._application.view_manager.load_view(view, model)

show_message_box(title, message)

Display a pop up message box :param: title: Message box title :param: message: message body to display

Source code in organelle_segmenter_plugin/core/controller.py
55
56
57
58
59
60
61
62
63
64
def show_message_box(self, title: str, message: str):
    """
    Display a pop up message box
    :param: title: Message box title
    :param: message: message body to display
    """
    msg = QMessageBox()
    msg.setWindowTitle(title)
    msg.setText(message)
    return msg.exec()

LayerReader

Reader / Helper class to extract information out of Napari Layers

Source code in organelle_segmenter_plugin/core/layer_reader.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
class LayerReader:
    """
    Reader / Helper class to extract information out of Napari Layers
    """

    def get_channels(self, layer: Layer) -> List[Channel]:
        """
        Get the list of image channels from a layer

        inputs:
            layer (Layer): the Napari layer to read data from
        """
        if layer is None:
            return None

        if self._should_read_from_path(layer):
            try:
                return self._get_channels_from_path(layer.source.path)
            except Exception as ex:
                log.warning(
                    "Could not read image layer from source path even though a source path was provided."
                    "Defaulting to reading from layer data (this is less accurate). \n"
                    f"Error message: {ex}"
                )

        return self._get_channels_default(layer)

    def _get_channels_default(self, layer: Layer) -> List[Channel]:
        if len(layer.data.shape) == 6:
            # Has scenes
            image_from_layer = [layer.data[i, :, :, :, :, :] for i in range(layer.data.shape[0])]
        else:
            image_from_layer = layer.data
        img = AICSImage(image_from_layer)  # gives us a 6D image
        img.set_scene(0)

        index_c = img.dims.order.index("C")
        # index_c = img.dims.order.index("Z")

        channels = list()
        # JAH: add a -1 channel/zslice for choosing all
        channels.append(Channel(ALL_LAYERS, "All"))

        for index in range(img.shape[index_c]):
            channels.append(Channel(index))
        return channels

    def _get_channels_from_path(self, image_path: str) -> List[Channel]:
        img = AICSImage(image_path)
        img.set_scene(0)

        channels = list()
        channels.append(Channel(ALL_LAYERS, "All"))

        for index, name in enumerate(img.channel_names):
            channels.append(Channel(index, name))
        return channels

    def get_channel_data(self, channel_index: int, layer: Layer) -> np.ndarray:
        """
        Get the image data from the layer for a given channel

        inputs:
            channel_index (int): index of the channel to load
            layer (Layer): the Napari layer to read data from
        """
        if channel_index is None:
            raise ValueError("channel_index is None")
        if layer is None:
            raise ValueError("layer is None")

        if self._should_read_from_path(layer):
            try:
                return self._get_channel_data_from_path(channel_index, layer.source.path)
            except Exception as ex:
                log.warning(
                    "Could not read image layer from source path even though a source path was provided."
                    "Defaulting to reading from layer data (this is less accurate). \n"
                    f"Error message: {ex}"
                )
        return self._get_channel_data_default(channel_index, layer)

    def _get_channel_data_default(self, channel_index: int, layer: Layer):
        print(f"in _get_channell_data_default(), shape = {layer.data.shape}")
        # if len(layer.data.shape) >= 6:
        #     # Has scenes
        #     print(f">>>>>> . Has Secnes layer.data.shape>6 {layer.data.shape}")
        #     image_from_layer = [layer.data[i, :, :, :, :, :] for i in range(layer.data.shape[0])]
        # else:
        #     image_from_layer = layer.data

        # img = AICSImage(image_from_layer)  # gives us a 6D image

        # # use get_image_data() to parse out ZYX dimensions
        # # segmenter requries 3D images.
        # img.set_scene(0)
        # # return img.get_image_data("ZYX", T=0, C=channel_index)
        # out_img = img.get_image_data("CYX", T=0, Z=channel_index)
        # print(f"out_img shape {out_img.shape}")

        if channel_index < 0:
            return layer.data
        else:
            return layer.data[channel_index]

    def _get_channel_data_from_path(self, channel_index: int, image_path: str):
        print("in _get_channel_data_from_path()")
        img = AICSImage(image_path)
        img.set_scene(0)
        # return img.get_image_data("ZYX", T=0, C=channel_index)

        if channel_index < 0:
            return img.get_image_data("CZYX")
        else:
            return img.get_image_data("CZYX")[channel_index]

    def _should_read_from_path(self, layer: Layer):
        if layer.source is None:
            return False
        if layer.source.path is None:
            return False
        # Here we are making a deliberate choice to not try and load metadata from the source
        # if a reader plugin other than the default built-in plugin was used. This is because
        # plugins like napari-aicsimageio may convert channels into individual layers, which is not compatible
        # with the current plugin User Experience. This is a workaround to allow basic compatibility
        # with reader plugins and allow to do work with CZI files and other formats supported by napari-aicsimageio
        # TODO - come up with a better long term solution
        if layer.source.reader_plugin != "builtins":
            return False

        return True

    def get_all_data(self, layer: Layer) -> np.ndarray:
        """
        Get the image data from the layer for a given channel

        inputs:
            channel_index (int): index of the channel to load
            layer (Layer): the Napari layer to read data from
        """
        if layer is None:
            raise ValueError("layer is None")
        return layer.data

get_all_data(layer)

Get the image data from the layer for a given channel

inputs

channel_index (int): index of the channel to load layer (Layer): the Napari layer to read data from

Source code in organelle_segmenter_plugin/core/layer_reader.py
146
147
148
149
150
151
152
153
154
155
156
def get_all_data(self, layer: Layer) -> np.ndarray:
    """
    Get the image data from the layer for a given channel

    inputs:
        channel_index (int): index of the channel to load
        layer (Layer): the Napari layer to read data from
    """
    if layer is None:
        raise ValueError("layer is None")
    return layer.data

get_channel_data(channel_index, layer)

Get the image data from the layer for a given channel

inputs

channel_index (int): index of the channel to load layer (Layer): the Napari layer to read data from

Source code in organelle_segmenter_plugin/core/layer_reader.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def get_channel_data(self, channel_index: int, layer: Layer) -> np.ndarray:
    """
    Get the image data from the layer for a given channel

    inputs:
        channel_index (int): index of the channel to load
        layer (Layer): the Napari layer to read data from
    """
    if channel_index is None:
        raise ValueError("channel_index is None")
    if layer is None:
        raise ValueError("layer is None")

    if self._should_read_from_path(layer):
        try:
            return self._get_channel_data_from_path(channel_index, layer.source.path)
        except Exception as ex:
            log.warning(
                "Could not read image layer from source path even though a source path was provided."
                "Defaulting to reading from layer data (this is less accurate). \n"
                f"Error message: {ex}"
            )
    return self._get_channel_data_default(channel_index, layer)

get_channels(layer)

Get the list of image channels from a layer

inputs

layer (Layer): the Napari layer to read data from

Source code in organelle_segmenter_plugin/core/layer_reader.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def get_channels(self, layer: Layer) -> List[Channel]:
    """
    Get the list of image channels from a layer

    inputs:
        layer (Layer): the Napari layer to read data from
    """
    if layer is None:
        return None

    if self._should_read_from_path(layer):
        try:
            return self._get_channels_from_path(layer.source.path)
        except Exception as ex:
            log.warning(
                "Could not read image layer from source path even though a source path was provided."
                "Defaulting to reading from layer data (this is less accurate). \n"
                f"Error message: {ex}"
            )

    return self._get_channels_default(layer)

State

Application state wrapper. Use this class as a way to easily store and access stateful data that needs to be shared accross controllers

Source code in organelle_segmenter_plugin/core/state.py
 5
 6
 7
 8
 9
10
11
12
13
class State:
    """
    Application state wrapper.
    Use this class as a way to easily store and access stateful data that needs to be shared accross controllers
    """

    @lazy_property
    def segmenter_model(self) -> SegmenterModel:
        return SegmenterModel()

View

Bases: ABC, QWidget

Base class for all Views to derive from

Source code in organelle_segmenter_plugin/core/view.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class View(ABC, QWidget, metaclass=ViewMeta):
    """
    Base class for all Views to derive from
    """

    _template = None

    def __init__(self, template_class: type = None):
        QWidget.__init__(self)
        if template_class is not None:
            if not issubclass(template_class, ViewTemplate):
                raise TypeError(f"Template type must be a subclass of {ViewTemplate}")

            self._template = template_class()

    @property
    def template(self):
        """
        Returns the view template
        """
        return self._template

    def has_template(self) -> bool:
        """
        True if the view has a template view, False otherwise
        """
        return self.template is not None

    @abstractmethod
    def load(self, model: Any = None):
        """
        Called when the view is loaded.
        When implementing in child class, use to load initial model data
        and setup the view's UI components

        inputs:
            model - optional model to pass to the view at load time
        """
        pass

template property

Returns the view template

has_template()

True if the view has a template view, False otherwise

Source code in organelle_segmenter_plugin/core/view.py
32
33
34
35
36
def has_template(self) -> bool:
    """
    True if the view has a template view, False otherwise
    """
    return self.template is not None

load(model=None) abstractmethod

Called when the view is loaded. When implementing in child class, use to load initial model data and setup the view's UI components

inputs

model - optional model to pass to the view at load time

Source code in organelle_segmenter_plugin/core/view.py
38
39
40
41
42
43
44
45
46
47
48
@abstractmethod
def load(self, model: Any = None):
    """
    Called when the view is loaded.
    When implementing in child class, use to load initial model data
    and setup the view's UI components

    inputs:
        model - optional model to pass to the view at load time
    """
    pass

ViewTemplate

Bases: View

Source code in organelle_segmenter_plugin/core/view.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class ViewTemplate(View):
    @abstractmethod
    def get_container(self) -> QFrame:
        """
        Get the template's container Frame
        This should be container QFrame in which the child View or ViewTemplate be displayed
        """
        pass

    @abstractmethod
    def load(self):
        """
        Called when the view template is loaded.
        When implementing in child class, use to load initial model data
        and setup the view's UI components
        """
        pass

get_container() abstractmethod

Get the template's container Frame This should be container QFrame in which the child View or ViewTemplate be displayed

Source code in organelle_segmenter_plugin/core/view.py
52
53
54
55
56
57
58
@abstractmethod
def get_container(self) -> QFrame:
    """
    Get the template's container Frame
    This should be container QFrame in which the child View or ViewTemplate be displayed
    """
    pass

load() abstractmethod

Called when the view template is loaded. When implementing in child class, use to load initial model data and setup the view's UI components

Source code in organelle_segmenter_plugin/core/view.py
60
61
62
63
64
65
66
67
@abstractmethod
def load(self):
    """
    Called when the view template is loaded.
    When implementing in child class, use to load initial model data
    and setup the view's UI components
    """
    pass

SegmenterModel dataclass

Main Segmenter plugin model

Source code in organelle_segmenter_plugin/model/segmenter_model.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@dataclass
class SegmenterModel:
    """
    Main Segmenter plugin model
    """

    layers: List[str] = None
    selected_layer: Layer = None

    channels: List[str] = None
    selected_channel: Channel = None

    workflows: List[str] = None
    active_workflow: Workflow = None

    # not actually using these
    prebuilt_workflows: List[str] = None
    additional_workflows: List[str] = None

    def reset(self):
        """
        Reset model state
        """
        self.layers = None
        self.selected_layer = None
        self.channels = None
        self.selected_channel = None
        self.workflows = None
        self.active_workflow = None
        self.prebuilt_workflows = None
        self.additional_workflows = None

reset()

Reset model state

Source code in organelle_segmenter_plugin/model/segmenter_model.py
29
30
31
32
33
34
35
36
37
38
39
40
def reset(self):
    """
    Reset model state
    """
    self.layers = None
    self.selected_layer = None
    self.channels = None
    self.selected_channel = None
    self.workflows = None
    self.active_workflow = None
    self.prebuilt_workflows = None
    self.additional_workflows = None

Directories

Provides safe paths to common module directories

Source code in organelle_segmenter_plugin/util/directories.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Directories:
    """
    Provides safe paths to common module directories
    """

    _module_base_dir = Path(organelle_segmenter_plugin.__file__).parent

    @classmethod
    def get_assets_dir(cls) -> Path:
        """
        Path to the assets directory
        """
        return cls._module_base_dir / "assets"

    @classmethod
    def get_style_dir(cls) -> Path:
        """
        Path to the stylesheet directory
        """
        return cls._module_base_dir / "styles"

get_assets_dir() classmethod

Path to the assets directory

Source code in organelle_segmenter_plugin/util/directories.py
13
14
15
16
17
18
@classmethod
def get_assets_dir(cls) -> Path:
    """
    Path to the assets directory
    """
    return cls._module_base_dir / "assets"

get_style_dir() classmethod

Path to the stylesheet directory

Source code in organelle_segmenter_plugin/util/directories.py
20
21
22
23
24
25
@classmethod
def get_style_dir(cls) -> Path:
    """
    Path to the stylesheet directory
    """
    return cls._module_base_dir / "styles"

UiUtils

Source code in organelle_segmenter_plugin/util/ui_utils.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class UiUtils:
    @staticmethod
    def dropdown_row(label: str, placeholder: str = None, default: str = None, options=None, enabled=False) -> FormRow:
        """
        Given the contents of a dropdown and a label, return a FormRow containing
        a label and a QComboBox widget that can be used with the custom Form widget
        """
        dropdown = QComboBox()
        dropdown.setDisabled(not enabled)
        dropdown.setStyleSheet("QComboBox { combobox-popup: 0; }")
        if placeholder is not None:
            dropdown.addItem(placeholder)
        if options is not None:
            str_options = [str(option) for option in options]
            dropdown.addItems(str_options)
        if placeholder is None and default is not None and options is not None:
            default_index = options.index(default)
            dropdown.setCurrentIndex(default_index)

        return FormRow(label, dropdown)

    @staticmethod
    def multi_dropdown_row(
        label: str, placeholder: str = None, default: str = None, options=None, enabled=False
    ) -> FormRow:
        """
        Given the contents of a dropdown and a label, return a FormRow containing
        a label and a QListWidget widget that can be used with the custom Form widget
        """
        dropdown = QListWidget()
        dropdown.setDisabled(not enabled)
        dropdown.setStyleSheet("QComboBox { combobox-popup: 0; }")
        dropdown.setSelectionMode(QAbstractItemView.MultiSelection)
        if placeholder is not None:
            dropdown.addItem(placeholder)
        if options is not None:
            str_options = [str(option) for option in options]
            dropdown.addItems(str_options)
        if placeholder is None and default is not None and options is not None:
            default_index = options.index(default)
            dropdown.setCurrentIndex(default_index)

        return FormRow(label, dropdown)

dropdown_row(label, placeholder=None, default=None, options=None, enabled=False) staticmethod

Given the contents of a dropdown and a label, return a FormRow containing a label and a QComboBox widget that can be used with the custom Form widget

Source code in organelle_segmenter_plugin/util/ui_utils.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@staticmethod
def dropdown_row(label: str, placeholder: str = None, default: str = None, options=None, enabled=False) -> FormRow:
    """
    Given the contents of a dropdown and a label, return a FormRow containing
    a label and a QComboBox widget that can be used with the custom Form widget
    """
    dropdown = QComboBox()
    dropdown.setDisabled(not enabled)
    dropdown.setStyleSheet("QComboBox { combobox-popup: 0; }")
    if placeholder is not None:
        dropdown.addItem(placeholder)
    if options is not None:
        str_options = [str(option) for option in options]
        dropdown.addItems(str_options)
    if placeholder is None and default is not None and options is not None:
        default_index = options.index(default)
        dropdown.setCurrentIndex(default_index)

    return FormRow(label, dropdown)

multi_dropdown_row(label, placeholder=None, default=None, options=None, enabled=False) staticmethod

Given the contents of a dropdown and a label, return a FormRow containing a label and a QListWidget widget that can be used with the custom Form widget

Source code in organelle_segmenter_plugin/util/ui_utils.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@staticmethod
def multi_dropdown_row(
    label: str, placeholder: str = None, default: str = None, options=None, enabled=False
) -> FormRow:
    """
    Given the contents of a dropdown and a label, return a FormRow containing
    a label and a QListWidget widget that can be used with the custom Form widget
    """
    dropdown = QListWidget()
    dropdown.setDisabled(not enabled)
    dropdown.setStyleSheet("QComboBox { combobox-popup: 0; }")
    dropdown.setSelectionMode(QAbstractItemView.MultiSelection)
    if placeholder is not None:
        dropdown.addItem(placeholder)
    if options is not None:
        str_options = [str(option) for option in options]
        dropdown.addItems(str_options)
    if placeholder is None and default is not None and options is not None:
        default_index = options.index(default)
        dropdown.setCurrentIndex(default_index)

    return FormRow(label, dropdown)

BatchProcessingView

Bases: View

Source code in organelle_segmenter_plugin/view/batch_processing_view.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
class BatchProcessingView(View):
    btn_run_batch: QPushButton
    progress_bar: QProgressBar
    # field_channel: QLineEdit
    field_segmentation_name: QLineEdit
    field_workflow_config: FileInput
    field_input_dir: DirInput
    field_output_dir: DirInput
    segmentation_name: str

    def __init__(self, controller: None):
        super().__init__(template_class=MainTemplate)

        if controller is None:
            raise ValueError("controller")
        self._controller = controller
        self.setObjectName("batchProcessingView")

    def load(self, model=None):
        self._setup_ui()

    def _setup_ui(self):
        """
        Set up the UI for the BatchProcessingView
        """
        layout = QVBoxLayout()
        self.setLayout(layout)

        # Workflow config
        self.field_workflow_config = FileInput(
            mode=FileInputMode.FILE, filter="Json file (*.json)", placeholder_text="Load a JSON workflow file..."
        )
        row1 = FormRow("1.  Load workflow:", self.field_workflow_config)
        self.field_workflow_config.file_selected.connect(self._form_field_changed)

        # output name (populate default from json when loaded)
        self.field_segmentation_name = QLabel()
        self.field_segmentation_name.setText("----segmentation-names-----")
        row2 = FormRow("2.  Seg Names", self.field_segmentation_name)

        # # Channel index  # change this to radio button
        # self.field_channel = QLineEdit("segmentation")
        # self.field_channel.setValidator(QIntValidator(bottom=-2))
        # self.field_channel.textChanged.connect(self._form_field_changed)
        # row2 = FormRow("2.  Structure channel index:", self.field_channel)

        # Input dir
        self.field_input_dir = DirInput(mode=FileInputMode.DIRECTORY, placeholder_text="Select a directory...")
        self.field_input_dir.file_selected.connect(self._form_field_changed)
        row3 = FormRow("3.  Input dir:", self.field_input_dir)

        # Output dir
        self.field_output_dir = DirInput(mode=FileInputMode.DIRECTORY, placeholder_text="Select a directory...")
        self.field_output_dir.file_selected.connect(self._form_field_changed)
        row4 = FormRow("4.  Output dir:", self.field_output_dir)

        form = QWidget()
        form.setLayout(Form([row1, row2, row3, row4]))
        layout.addWidget(form)

        # Help
        label = QLabel()
        label.setText("Supported file formats: .czi (.tiff, tif, .ome.tif, .ome.tiff)")
        layout.addWidget(label)

        # Submit
        self.btn_run_batch = QPushButton("Run Batch")
        self.btn_run_batch.clicked.connect(self._btn_run_batch_clicked)
        self.update_button(enabled=False)
        layout.addWidget(self.btn_run_batch)

        # Progress bar
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setValue(0)
        self.progress_bar.setTextVisible(True)
        self.progress_bar.setVisible(False)
        layout.addWidget(self.progress_bar)

    def update_button(self, enabled: bool):
        """
        Update state of process button
        Inputs:
            enabled: True to enable the button, false to disable it
        """
        self.btn_run_batch.setEnabled(enabled)

    def set_run_batch_in_progress(self):
        """
        Update page to reflect that a batch run is in progress
        """
        # TODO make a CancelButton widget to avoid repeating this connect / disconnect pattern
        self.btn_run_batch.setText("Cancel")
        self.btn_run_batch.clicked.disconnect()
        self.btn_run_batch.clicked.connect(self._btn_run_batch_cancel_clicked)
        self.progress_bar.setVisible(True)

    def reset_run_batch(self):
        """
        Reset page state to reflect that there is no batch run in progress
        """
        self.progress_bar.setValue(0)
        self.btn_run_batch.setText("Run Batch")
        self.btn_run_batch.clicked.disconnect()
        self.btn_run_batch.clicked.connect(self._btn_run_batch_clicked)
        self.progress_bar.setVisible(False)

    def set_progress(self, progress: int):
        """
        Update progress bar

        Inputs:
            progress (int): numerical value to set the progress bar to
        """
        self.progress_bar.setValue(progress)

    #####################################################################
    # Event handlers
    #####################################################################
    def _btn_run_batch_clicked(self):
        self._controller.run_batch()

    def _btn_run_batch_cancel_clicked(self):
        self.btn_run_batch.setText("Canceling...")
        self._controller.cancel_run_batch()

    def _form_field_changed(self, value):
        workflow_configs = self.field_workflow_config.selected_file

        # if isinstance(workflow_config, list):

        # else:

        # print(f"testing workflow_config = {workflow_config.split('/')[-1].split('.')[0]}")

        # segmentation_name = (
        #     self.field_segmentation_name.text()
        #     if self.field_segmentation_name.text()
        #     else workflow_config.split("/")[-1].split(".")[0]
        # )

        segmentation_names = [Path(wf).stem.split("-")[-1] for wf in workflow_configs]

        self.field_segmentation_name.setText(f"NAMES: {', '.join(segmentation_names)}")
        channel_index = -1.0
        # channel_index = int(self.field_channel.text()) if self.field_channel.text() else None

        input_dir = self.field_input_dir.selected_file
        output_dir = self.field_output_dir.selected_file
        self._controller.update_batch_parameters(
            workflow_configs, channel_index, input_dir, output_dir, segmentation_names
        )

reset_run_batch()

Reset page state to reflect that there is no batch run in progress

Source code in organelle_segmenter_plugin/view/batch_processing_view.py
110
111
112
113
114
115
116
117
118
def reset_run_batch(self):
    """
    Reset page state to reflect that there is no batch run in progress
    """
    self.progress_bar.setValue(0)
    self.btn_run_batch.setText("Run Batch")
    self.btn_run_batch.clicked.disconnect()
    self.btn_run_batch.clicked.connect(self._btn_run_batch_clicked)
    self.progress_bar.setVisible(False)

set_progress(progress)

Update progress bar

Inputs

progress (int): numerical value to set the progress bar to

Source code in organelle_segmenter_plugin/view/batch_processing_view.py
120
121
122
123
124
125
126
127
def set_progress(self, progress: int):
    """
    Update progress bar

    Inputs:
        progress (int): numerical value to set the progress bar to
    """
    self.progress_bar.setValue(progress)

set_run_batch_in_progress()

Update page to reflect that a batch run is in progress

Source code in organelle_segmenter_plugin/view/batch_processing_view.py
100
101
102
103
104
105
106
107
108
def set_run_batch_in_progress(self):
    """
    Update page to reflect that a batch run is in progress
    """
    # TODO make a CancelButton widget to avoid repeating this connect / disconnect pattern
    self.btn_run_batch.setText("Cancel")
    self.btn_run_batch.clicked.disconnect()
    self.btn_run_batch.clicked.connect(self._btn_run_batch_cancel_clicked)
    self.progress_bar.setVisible(True)

update_button(enabled)

Update state of process button

Inputs

enabled: True to enable the button, false to disable it

Source code in organelle_segmenter_plugin/view/batch_processing_view.py
92
93
94
95
96
97
98
def update_button(self, enabled: bool):
    """
    Update state of process button
    Inputs:
        enabled: True to enable the button, false to disable it
    """
    self.btn_run_batch.setEnabled(enabled)

WorkflowSelectView

Bases: View

Source code in organelle_segmenter_plugin/view/workflow_select_view.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
class WorkflowSelectView(View):
    _combo_layers: QComboBox
    # _combo_channels: QComboBox
    _load_image_warning: WarningMessage
    _workflow_buttons: WorkflowButtons
    _channels_note: QLabel
    # _combo_workflows: QComboBox
    # _workflows: List[WorkflowDefinition]
    # _workflow_names: List[str]
    _field_add_workflow: FileInput

    def __init__(self, controller: IWorkflowSelectController):
        super().__init__(template_class=MainTemplate)

        if controller is None:
            raise ValueError("controller")
        self._controller = controller
        self.setObjectName("workflowSelectView")

    def load(self, model: SegmenterModel):
        self._setup_ui()

        self.update_layers(model.layers, model.selected_layer)
        # self.update_channels(model.channels, model.selected_channel)
        self._load_workflows(model.workflows)

    #    # JAH: combo_box_workflows
    #     # self._workflows = self._controller._workflow_engine._load_workflow_definitions()
    #     # self.update_workflows(self._workflows)
    #     self.update_workflows(model.workflows)

    def _setup_ui(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        # Title
        workflow_selection_title = QLabel("Workflow selection steps:")
        workflow_selection_title.setObjectName("workflowSelectionTitle")

        # Warning
        self._load_image_warning = WarningMessage("Open a 3D image in Napari first!")
        self._load_image_warning.setVisible(False)

        # Dropdowns
        layers_dropdown = UiUtils.dropdown_row("1.", "Select multichannel 3D Napari image layer", enabled=False)
        self._combo_layers = layers_dropdown.widget
        self._combo_layers.setStyleSheet("QComboBox { combobox-popup: 0; }")
        self._combo_layers.setMaxVisibleItems(20)
        self._combo_layers.activated.connect(self._combo_layers_activated)


        # channels_dropdown = UiUtils.dropdown_row("2.", "Select Channels)", enabled=False)
        # self._combo_channels = channels_dropdown.widget
        # self._combo_channels.setStyleSheet("QComboBox { combobox-popup: 0; }")
        # self._combo_channels.setMaxVisibleItems(20)
        # self._combo_channels.activated.connect(self._combo_channels_activated)

        self._channels_note = QLabel() # in case we want to update selection?
        self._channels_note.setText("----ALL CHANNELS-----")
        channels_note_label = FormRow("2.  Channels", self._channels_note)

        # Workflow config add
        self._field_add_workflow = FileInput(
            mode=FileInputMode.FILE, filter="Json file (*.json)", placeholder_text="None loaded..."
        )
        add_workflow = FormRow("3.  Add  workflow:", self._field_add_workflow)
        self._field_add_workflow.file_selected.connect(self._form_field_changed)

        layer_channel_selections = QWidget()
        layer_channel_selections.setLayout(Form([layers_dropdown, channels_note_label, add_workflow]))

        # Add all widgets
        widgets = [
            workflow_selection_title,
            self._load_image_warning,
            layer_channel_selections,
        ]
        for widget in widgets:
            layout.addWidget(widget)

        self._workflow_buttons = WorkflowButtons()
        self._workflow_buttons.workflowSelected.connect(self._workflow_selected)
        self.layout().addWidget(self._workflow_buttons)

        # TODO:  add alternative "load workflow widget here"
        # e.g. from batch_processing_view
        ## Workflow config
        ## self.field_workflow_config = FileInput(
        ##     mode=FileInputMode.FILE, filter="Json file (*.json)", placeholder_text="Load a JSON workflow file..."
        ## )
        ## self.field_workflow_config.file_selected.connect(self._form_field_changed)
        ## row1 = FormRow("1.  Load workflow:", self.field_workflow_config)

    def update_layers(self, layers: List[str], selected_layer: Layer = None):
        """
        Update / repopulate the list of selectable layers
        Inputs:
            layers: List of layer names
            selected_layer_name: (optional) name of the layer to pre-select
        """
        self._reset_combo_box(self._combo_layers)

        if layers is None or len(layers) == 0:
            self._load_image_warning.setVisible(True)
            self._combo_layers.setEnabled(False)
        else:
            # reverse layer list when adding to combobox
            # to mimic layer list on napari ui
            self._combo_layers.addItems(layers[::-1])
            if selected_layer is not None:
                self._combo_layers.setCurrentText(selected_layer.name)
            self._combo_layers.setEnabled(True)
            self._load_image_warning.setVisible(False)


    # def update_channels(self, channels: List[Channel], selected_channel: Channel = None):
    #     """
    #     Update / repopulate the list of selectable channels
    #     Inputs:
    #         channels: List of channel names
    #     """
    #     self._reset_combo_box(self._combo_channels)
    #     # JAH:  make a default "negative" channel to NOT choose one...
    #     if channels is None or len(channels) == 0:
    #         self._combo_channels.setEnabled(False)
    #     else:
    #         model = QStandardItemModel()
    #         model.appendRow(QStandardItem(self._combo_channels.itemText(0)))

    #         for channel in channels:
    #             item = QStandardItem(channel.display_name)
    #             item.setData(channel, QtCore.Qt.UserRole)
    #             model.appendRow(item)

    #         self._combo_channels.setModel(model)

    #         if selected_channel is not None:
    #             # TODO relying on display name isn't the best as it will probably
    #             #      cause issues if channel names aren't unique
    #             # TODO refactor by making Channel derive from QStandardItem and do something like this:
    #             #      selected_index = model.indexFromItem(selected_channel)
    #             #      self.combo_channels.setCurrentIndex(selected_index)
    #             self._combo_channels.setCurrentText(selected_channel.display_name)

    #         self._combo_channels.setEnabled(True)

    def update_workflows(self, enabled: bool):
        """
        Update state of workflow list
        Inputs:
            enabled: True to enable the list, False to disable it
        """
        self._workflow_buttons.setEnabled(enabled)

    def _load_workflows(self, workflows: List[WorkflowDefinition]):
        """
        Load workflows into workflow grid
        """
        # self._workflow = workflows
        # self._workflow_names = [wf.name for wf in workflows]
        self._workflow_buttons.load_workflows(workflows)

    def _reset_combo_box(self, combo: QComboBox):
        """
        Reset a combo box to its original state, keeping the header but removing all other items
        """
        if combo.count() > 0:
            header = combo.itemText(0)
            combo.clear()
            combo.addItem(header)

    #####################################################################
    # Event handlers
    #####################################################################

    def _combo_layers_activated(self, index: int):
        if index == 0:  # index 0 is the dropdown header
            self._controller.unselect_layer()
        else:
            self._controller.select_layer(self._combo_layers.itemText(index))

    # def _combo_channels_activated(self, index: int):
    #     if index == 0:
    #         self._controller.unselect_channel()
    #     else:
    #         self._controller.select_channel(self._combo_channels.itemData(index, role=QtCore.Qt.UserRole))

    def _workflow_selected(self, workflow_name: str):
        self._controller.select_workflow(workflow_name)


    def _form_field_changed(self, value):
        workflow_configs = self._field_add_workflow.selected_file
        for wf in workflow_configs:
            self._controller.add_workflow(wf)
            self._workflow_buttons._add_new_button(Path(wf).stem)
        self.update_workflows(enabled=True)

update_layers(layers, selected_layer=None)

Update / repopulate the list of selectable layers

Inputs

layers: List of layer names selected_layer_name: (optional) name of the layer to pre-select

Source code in organelle_segmenter_plugin/view/workflow_select_view.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def update_layers(self, layers: List[str], selected_layer: Layer = None):
    """
    Update / repopulate the list of selectable layers
    Inputs:
        layers: List of layer names
        selected_layer_name: (optional) name of the layer to pre-select
    """
    self._reset_combo_box(self._combo_layers)

    if layers is None or len(layers) == 0:
        self._load_image_warning.setVisible(True)
        self._combo_layers.setEnabled(False)
    else:
        # reverse layer list when adding to combobox
        # to mimic layer list on napari ui
        self._combo_layers.addItems(layers[::-1])
        if selected_layer is not None:
            self._combo_layers.setCurrentText(selected_layer.name)
        self._combo_layers.setEnabled(True)
        self._load_image_warning.setVisible(False)

update_workflows(enabled)

Update state of workflow list

Inputs

enabled: True to enable the list, False to disable it

Source code in organelle_segmenter_plugin/view/workflow_select_view.py
177
178
179
180
181
182
183
def update_workflows(self, enabled: bool):
    """
    Update state of workflow list
    Inputs:
        enabled: True to enable the list, False to disable it
    """
    self._workflow_buttons.setEnabled(enabled)