# What would be the best way to let a user select scenes and channel colours in a microscopy oriented importer?

**URL:** <https://discourse.slicer.org/t/what-would-be-the-best-way-to-let-a-user-select-scenes-and-channel-colours-in-a-microscopy-oriented-importer/31595>\
**Category:** Development\
**Tags:** microscopy, python, qt, file-import, gui\
**Created:** [September 6, 2023, 3:33pm UTC](https://discourse.slicer.org/t/what-would-be-the-best-way-to-let-a-user-select-scenes-and-channel-colours-in-a-microscopy-oriented-importer/31595 "2023-09-06T15:33:55Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![EgorZindy](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/egorzindy/32/66942_2.png) [@EgorZindy](https://discourse.slicer.org/u/EgorZindy)\
**Post date:** [September 6, 2023, 3:33pm UTC](https://discourse.slicer.org/t/what-would-be-the-best-way-to-let-a-user-select-scenes-and-channel-colours-in-a-microscopy-oriented-importer/31595/1 "2023-09-06T15:33:55Z")

</div>

Hello everyone.

So for my [CZI importer](https://discourse.slicer.org/t/working-on-a-czi-zeiss-microscopy-image-stack-reader-for-3d-slicer-any-help-or-advice-welcome/30769), there are two things I think need to be user-selectable at import time:

- A CZI file may contain multiple scenes, and a user may decide which scenes need importing.
- The channel colours in the CZI file may not be optimal, so being able to change them during the import phase may also be desirable.

My question is, would it be possible to make this selection accessible from the import panel? Maybe via “Show options”?

 ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/1/2/127045814462c6fc81825ad7bcf7d27a0d21fd7b.png)

Or, do I show a custom selection panel as an intermediate step?  
 ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/5/b/5be649e2bebf8ef49538fb9687966e12a7ba9249.png) ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/4/8/484e6361c822eb75f9d7edf41a50aa5ede68ad9c.png)

Clicking on the “…” button (or pressing Enter) brings up the color picker.

> **Hidden spaghetti code below**
>
> For now, this is just a small experiment conducted in the Jupyter extension, hence the splits corresponding to my various cells. If you find any of this code useful, feel free to re-use it any way you like, ChatGPT certainly did 🤔.
> 
> ```python
> import slicer
> import qt
> from qt import Qt
> 
> ```
> 
> Here’s the code to select colors in a drop down menu:
> 
> ```python
> class CustomComboBox(qt.QComboBox):
> def __init__ (self, parent=None):
> super(). __init__ (parent)
> 
> def focusInEvent(self, event):
> # Pass the focus to the parent ComboChanCol
> if self.parent():
> self.parent().setFocus()
>                         
> class ComboChanCol(qt.QWidget):
> """
> Custom Qt Widget to show a dropdown menu with the channels
> and a button to change the color.
> 
> If no colors are provided, then they are automatically set to a HSV sequence
> that hopefully makes sense.
> """
> 
> def __init__ (self, channels, colors=None, *args, **kwargs):
> super(ComboChanCol, self). __init__ (*args, **kwargs)
>         
> # Create the horizontal layout and combobox
> layout = qt.QHBoxLayout()
> #layout.setSpacing(0)
> layout.setContentsMargins(0, 0, 0, 0)
> 
> self._comboBox = CustomComboBox() #qt.QComboBox()
> layout.addWidget(self._comboBox)
> 
> # Create the button with three dots
> self._button = qt.QPushButton("...")
> self._button.setFixedHeight(self._comboBox.sizeHint.height())
>         
> self._button.setMaximumWidth(30) # Adjust the width as needed
> layout.addWidget(self._button)
>         
> self.setLayout(layout)
> 
> print(channels)
> print(colors)
> if colors is None:
> n = len(channels)
> self._colors = self.generate_colors(n)
> else:
> self._colors = [c if type(c) == qt.QColor else qt.QColor(c) for c in colors]
>         
> for c,n in zip(self._colors,channels):
> icon = self.create_solid_color_icon(c)
> self._comboBox.addItem(icon,n)
>             
> #Connect the button
> self._button.connect('clicked()', self.on_pick_color)
>     
> def keyPressEvent(self, event):
> # Check if the Enter key is pressed and if this widget has focus
> if event.key() == Qt.Key_Return and self.hasFocus():
> self.on_pick_color()
> elif event.key() == qt.Qt.Key_Up or event.key() == qt.Qt.Key_Down:
> # Pass the up and down arrow key events to the QComboBox
> self._comboBox.event(event)
>             
> def on_pick_color(self):
> channel_index = self._comboBox.currentIndex
> channel_name = self._comboBox.currentText
> initial_color = self._colors[channel_index]
> 
> color_dialog = qt.QColorDialog(self)
> color_dialog.setCurrentColor(initial_color)
> color_dialog.setWindowTitle('Select Color for '+channel_name)
> 
> # Show the dialog and check the result
> result = color_dialog.exec_()
> 
> if result == qt.QColorDialog.Accepted:
> icon = self.create_solid_color_icon(color_dialog.currentColor)
> self._comboBox.setItemIcon(channel_index, icon)
> self._colors[channel_index] = color_dialog.currentColor
> 
> def generate_colors(self, n):
> colors = []
> 
> for i in range(n):
> hue = i * (360 / n) # Divide the hue range evenly
> saturation = 255 # Maximum saturation
> value = 255 # Maximum value (brightness)
>         
> # Create a QColor with the specified HSV values
> color = qt.QColor.fromHsv(hue, saturation, value)        
> colors.append(color)
>     
> return colors
>     
> def create_solid_color_icon(self, color, size=8):
> pixmap = qt.QPixmap(size,size)
> pixmap.fill(color)
> return qt.QIcon(pixmap)    
> 
> def get_colors(self,as_hex=False):
> return [c.name() if as_hex else c for c in self._colors]
> 
> ```
> 
> Here’s the code for selecting scenes via checkboxes in a drop down menu:
> 
> ```python
> class CheckboxComboBox(qt.QComboBox):
> def __init__ (self, items, states=None):
> super(). __init__ ()
> self.setView(qt.QListView()) # Set the view for the drop-down list
> self.activated.connect(self.activated_custom) # Connect the activated signal to your custom slot
> 
> #check if states is the same size as items
> if states is not None and len(items) != len(states):
> raise ValueError("states and items must have the same length")
> 
> self.item_dict = {}
> self.setModel(qt.QStandardItemModel())
> 
> for i, item in enumerate(items):
> checkbox_item = qt.QStandardItem(item)
> checkbox_item.setCheckable(True)
> if states is not None:
> checkbox_item.setCheckState(
> qt.Qt.Checked if states[i]
> else qt.Qt.Unchecked
> )
>                     
> self.item_dict[item] = checkbox_item
> self.model().appendRow(checkbox_item)
> 
> self.setView(qt.QListView()) # Set the view for the drop-down list
> 
> def activated_custom(self, index):
> selected_item = self.model().item(index) #.row())
> if selected_item:
> selected_text = selected_item.text() # Get the selected item text
> if selected_text in self.item_dict:
> checkbox_item = self.item_dict[selected_text]
> print(selected_text, checkbox_item.checkState())
> checkbox_item.setCheckState(
> qt.Qt.Checked if checkbox_item.checkState() == qt.Qt.Unchecked
> else qt.Qt.Unchecked
> )
> 
> def get_checked_items(self):
> checked_items = {}
> for item, checkbox_item in self.item_dict.items():
> checked_items[item] = checkbox_item.checkState() == qt.Qt.Checked
> return checked_items
> 
> ```
> 
> And here’s a dialog box that shows both with a text window for padding:
> 
> ```python
> class TestWindow(qt.QWidget):
> def __init__ (self, parent=None, scenes=[], channels=[], colors=None):
> super(TestWindow, self). __init__ (parent)
> 
> parent.resize(400,400)
> parent.setWindowTitle("CZI Scene importer")
> 
> # Create a QDialogButtonBox with OK and Cancel buttons
> buttonBox = qt.QDialogButtonBox()
> okButton = buttonBox.addButton(qt.QDialogButtonBox.Ok)
> cancelButton = buttonBox.addButton(qt.QDialogButtonBox.Cancel)
>         
> layout = qt.QFormLayout()
> 
> states = [True] * len(scenes)
> self.combo_bc = CheckboxComboBox(scenes, states)
> self._scenes_dict = self.combo_bc.get_checked_items()
>         
> layout.addRow("Scenes :", self.combo_bc)
>         
> self.ccc = ComboChanCol(channels, colors)
> self._colors = self.ccc.get_colors()        
> layout.addRow("Colors :", self.ccc)
> 
> big_editor = qt.QTextEdit()
>         
> layout.addWidget(big_editor)
> layout.addWidget(buttonBox)        
> parent.setLayout(layout)
> 
> buttonBox.accepted.connect(self.ok_callback)
> buttonBox.rejected.connect(self.cancel_callback)
> 
> def ok_callback(self):
> self._colors = self.ccc.get_colors()
> self._scenes_dict = self.combo_bc.get_checked_items()
> self.parentWidget().accept()
> 
> def cancel_callback(self):
> self.parentWidget().reject()
> 
> def get_colors(self, as_hex=False):
> return [c.name() if as_hex else c for c in self._colors]
> 
> def get_scenes(self):
> return self._scenes_dict
> 
> scenes = ["Scene %d"%i for i in range(10)]
> channels = ["Channel %d"%i for i in range(5)]
> colors = None
> 
> messagePopup = qt.QDialog()
> tw = TestWindow(messagePopup,scenes,channels,colors)    
> print("Before:", tw.get_colors(as_hex=True))
> 
> result = messagePopup.exec_()
> print("After:", result, tw.get_colors(as_hex=True))
> 
> print(tw.get_scenes())
> 
> ```

The user experience (UX) with these widgets is pretty poor in my opinion, so that’s why I’m asking for potential ideas to streamline the process. Of course, if the user is happy with importing all the scenes and the colours are fine, then it’s just a matter of clicking on OK (or Cancel).

Cheers,  
Egor

---

<div class="post-metadata">

**Author:** ![pieper](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/pieper/32/8_2.png) [@pieper](https://discourse.slicer.org/u/pieper)\
**Post date:** [September 6, 2023, 7:13pm UTC](https://discourse.slicer.org/t/what-would-be-the-best-way-to-let-a-user-select-scenes-and-channel-colours-in-a-microscopy-oriented-importer/31595/2 "2023-09-06T19:13:18Z")

</div>

The options in the Add Data dialog might be able to handle these but it doesn’t seem ideal to me.

It may make more sense to make a dedicated importer module that can pull out the pieces from the CZI file and map them to the corresponding Slicer set of nodes. I’m kind of assuming here that you don’t want to read the full file into memory if you only want to display a subset of it.

---

<div class="post-metadata">

**Author:** ![EgorZindy](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/egorzindy/32/66942_2.png) [@EgorZindy](https://discourse.slicer.org/u/EgorZindy)\
**Post date:** [September 6, 2023, 8:08pm UTC](https://discourse.slicer.org/t/what-would-be-the-best-way-to-let-a-user-select-scenes-and-channel-colours-in-a-microscopy-oriented-importer/31595/3 "2023-09-06T20:08:27Z")

</div>

Hi Steve,

> The options in the Add Data dialog might be able to handle these but it doesn’t seem ideal to me.

I was wondering about this. If I could hook my two drop down menus (scenes, channels) next to each other into the “Show options”, “Options” column, that would be a very good start. Right now, that space is empty:

 ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/7/7/77cd73f21effa07251abb0f2095d2cf45c2c81ad.png)

This could go in the empty space (one for each file row):  
 ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/9/9/991ac47d38d7efcb50f4b92efcb766e1ccf494b9.png)

> It may make more sense to make a dedicated importer module that can pull out the pieces from the CZI file and map them to the corresponding Slicer set of nodes. I’m kind of assuming here that you don’t want to read the full file into memory if you only want to display a subset of it.

Yes, that’s definitely what I want to do, and at least for the CZI format, I have all the pieces I need to pull individual scenes into Slicer.

However, I’ve just now started to look into building a user interface for my importer, and was wondering exactly how to tackle this.

The only other importer I’ve found with a complex user interface is [ImportOsirixROI](https://github.com/PerkLab/SlicerSandbox/tree/master/ImportOsirixROI) but in my case, popping a window open whether the user wants to change the import parameters or not doesn’t seem ideal.

I’m really new to this, are there any medical formats with multiple sub images and/or multiple channels or is this more a “microscopy thing”?

So that was the general idea behind my question. Any ideas welcome 🙂

---

<div class="post-metadata">

**Author:** ![muratmaga](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/muratmaga/32/3622_2.png) [@muratmaga](https://discourse.slicer.org/u/muratmaga)\
**Post date:** [September 6, 2023, 8:22pm UTC](https://discourse.slicer.org/t/what-would-be-the-best-way-to-let-a-user-select-scenes-and-channel-colours-in-a-microscopy-oriented-importer/31595/4 "2023-09-06T20:22:22Z")

</div>

Data dialog box is difficult to navigate. I would encourage you to take a look at the image stacks interface of Slicermorph, in which we walk user through a steps of decision making about importing imagestack.

You seem to have a lot of decisions to make and a dedicated interface.will give you more control over them.

---

<div class="post-metadata">

**Author:** ![muratmaga](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/muratmaga/32/3622_2.png) [@muratmaga](https://discourse.slicer.org/u/muratmaga)\
**Post date:** [September 6, 2023, 8:46pm UTC](https://discourse.slicer.org/t/what-would-be-the-best-way-to-let-a-user-select-scenes-and-channel-colours-in-a-microscopy-oriented-importer/31595/5 "2023-09-06T20:46:15Z")

</div>

Here is a screenshot of the interface  
[![](https://raw.githubusercontent.com/SlicerMorph/Tutorials/main/ImageStacks/ImageStacks1.png) ](https://raw.githubusercontent.com/SlicerMorph/Tutorials/main/ImageStacks/ImageStacks1.png)

---

<div class="post-metadata">

**Author:** ![EgorZindy](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/egorzindy/32/66942_2.png) [@EgorZindy](https://discourse.slicer.org/u/EgorZindy)\
**Post date:** [September 6, 2023, 9:01pm UTC](https://discourse.slicer.org/t/what-would-be-the-best-way-to-let-a-user-select-scenes-and-channel-colours-in-a-microscopy-oriented-importer/31595/6 "2023-09-06T21:01:47Z")

</div>

Thanks @muratmaga

I also found the [Slicermorph YouTube channel](https://www.youtube.com/@slicermorphslicermorph8728/videos), great resource!

The “quality” setting in the screenshot above is a really good idea. I’ll definitely keep it in mind. Not sure in the case of CZI images if it’ll decrease the loading time but I won’t know unless I try.

Cheers,  
Egor
