Skip to content

Conversation

petebankhead
Copy link
Member

@petebankhead petebankhead commented Jan 24, 2025

This PR cleans up the classification list and introduces icons so that it is easier to see that visibility can be toggled.

We still need to figure out what the right behavior is when toggling...

Support regex for filtering, code cleanup
Use nanoseconds for timestamp - this reduces the risk that changes are missed
@petebankhead petebankhead added this to the v0.6.0 milestone Jan 24, 2025
When a classification is hidden, hide all derived classifications that contain the same names - and represent this with an `X` icon.
Add 'Add...' and 'Remove' buttons, splitting out context menus and helping address qupath#1644
@petebankhead
Copy link
Member Author

I've made further changes & tried to tackle #1644

There are now buttons to add & remove classifications. This makes it easier for users to find and means that the right-click popup menus can be much shorter.

Classification toggling is now more clearly defined - as shown in the screenshots.

hiding

An icon with an open eye will indicate the classification is visible, an eye with a stroke will indicate it is not.

Taking an example of Tumor, then setting the visibility to false will mean that all objects with Tumor anywhere in their classification will be hidden. This includes Tumor , Tumor: Positive, Positive: Tumor... but not Stroma or Positive.

Similarly hiding Positive will result in all objects with Positive in their classifications to be hidden - regardless of whether they are Tumor, Stroma or something else. Previously this wasn't possible.

This is the same for multiplexed images, where classifications often have multiple parts.

It is assumed that, most of the time, all the possible combinations won't be shown in this list: usually the list will have 'single' classifications, one for each marker (and not for combinations of markers).

Taking an example of CD8, then setting the visibility to false will mean that all objects with CD8 anywhere in their classification will be hidden. This includes CD8, CD3: CD8, CD8: CD3... but not CD68.

But derived classifications can be shown, as in this screenshot:

better-eyes

Setting a derived classification to be hidden means that any object with all the parts of the hidden classification will be hidden. In the screenshot, CD8: CD68 being hidden means that CD8: CD68: PD1 is automatically hidden as well... but CD8 or CD68 on their own would not be.

This is indicated with an X icon: CD8: CD68: PD1 isn't explicitly hidden, but any objects with that classification won't be visible because of other classifications chosen to be hidden.

The tooltip text attempts to explain this, making the information also available under the context help.

This may seem complicated and won't deal with every scenario, but it's the best I've come up with so far. I think it feels intuitive to use, but need more people trying it for longer to be sure of that. Certainly, the v0.5.x behavior didn't feel intuitive to me at all for multiplexed images - because it had much more tortured logic to deal with intensity subclassifications as a special case...

The big limitation with this approach is that you can't hide CD8 but show CD3: CD8. Alternative ways to do that might be added later.

@petebankhead petebankhead marked this pull request as ready for review January 24, 2025 17:31
@alanocallaghan
Copy link
Contributor

The X looks to me like clicking it will delete that class

@petebankhead
Copy link
Member Author

Fair, any ideas for a better icon? Easiest if it's from https://fontawesome.com/v5/search?ic=free - but ControlsFX doesn't have all of them, so we're even more restricted than it first appears (unless we add another dependency... or can draw it ourselves)

(There's one low-vision that I wanted, but not in ControlsFX)

@lauranicolass
Copy link

lauranicolass commented Jan 24, 2025

Empty circle vs circle with check?

@petebankhead
Copy link
Member Author

Actually it is possible via ControlsFX by using

GlyphFontRegistry.font("FontAwesome")
	.create('\uf2a8')
	.size(QuPathGUI.TOOLBAR_ICON_SIZE)
low-vision

@petebankhead
Copy link
Member Author

Hi @lauranicolass sorry, missed you’d posted this in between. I quite like the circle-based icons - and there are more options (checks, crosses, filled, unfilled). Would you replace the eyes entirely? Which icons specifically for each of these 3 conditions?

  1. Visible
  2. Hidden (explicitly)
  3. Hidden (because a related class is hidden)

@petebankhead
Copy link
Member Author

Continuing with this, I've introduced titled panes for both the class and annotation lists.
The annotation list now has a filter box as well.

The titled panes contain buttons, which allow us to save some space at the bottom.

I think this starts to make more functionality visible in the UI and easier to access.

annotation-titles

More awkward than it sounds... if we have multiple annotations with different classifications, we need an extra option that doesn't make changes - since otherwise we can't change the name or any other property without also changing the classification
Allows more fine-grained control of which objects are shown, when the other show/hide options are insufficient.
@petebankhead
Copy link
Member Author

Added OverlayOptions.showObjectPredicateProperty().

This aims to make it possible to control which objects are visible with more detail than any other methods. It should be used sparingly (and triggers a warning in the context help), but can be useful.

Example script:

Platform.runLater {
    var o = getQuPath().getOverlayOptions()
    o.setShowObjectPredicate {
        p -> p.measurements['Nucleus: Area'] > 100
    }
    // Call this when done!
//    o.resetShowObjectPredicate()
}

And screenshot:
Screenshot 2025-01-26 at 18 01 39

Although it adds some complexity, I think the benefits may be worth it as it can be very powerful.

Here's another example, this time intended for multiplexed images, to show all objects with a neighbor that is CD8 positive (based on a Delaunay triangulation using centroids):

Platform.runLater {
    var hierarchy = getCurrentHierarchy()
    var o = getQuPath().getOverlayOptions()
    o.setShowObjectPredicate {
        p -> hierarchy.findAllNeighbors(p).find(n -> 'CD8' in n.classifications) != null
    }
}

@petebankhead
Copy link
Member Author

Last major change here (I think): added OverlayOptions.hideExactClassesOnlyProperty().

This controls whether hiding CD8 (for example) hides cells classified as CD8 only, or all cells classified as CD8 (and potentially anything else).

The option is accessible from the More button:

match-exactly match-less-strict

The default is that we apply the more 'generous' rule: cells classified as CD8 and anything else would be hidden.

I think this is more intuitive in most easier cases, e.g. if we have Tumor: Positive, Tumor: Negative, Stroma: Positive, Stroma: Negative it's nice to be able to turn off all the positive cells or all the tumor cells by specifying Positive or Tumor, instead of having to select two classes each time to account for the Stroma or Negative parts.

I think it's also intuitive for multiplexed images, and means we don't routinely have really long lists containing all positive combinations of classifications. But in cases where we do want that level of control, this option gives it to us through the UI: we can specified exactly which classes/subclasses we want to hide.

And, when even that isn't enough, we have OverlayOptions.showObjectPredicateProperty() and scripting.

@petebankhead petebankhead changed the title Add visibility icons to classification list Add more control over showing/hiding objects based on classifications & other values Jan 26, 2025
@petebankhead petebankhead changed the title Add more control over showing/hiding objects based on classifications & other values More control to show/hide objects based on class (& other criteria) Jan 27, 2025
@petebankhead petebankhead merged commit ba2fdf5 into qupath:main Jan 27, 2025
3 checks passed
@petebankhead petebankhead deleted the classifications branch January 27, 2025 09:58
@alanocallaghan
Copy link
Contributor

This looks great! Only question I have is whether the option to be more or less liberal with showing/hiding detections should be a persistent preference

@petebankhead
Copy link
Member Author

petebankhead commented Jan 27, 2025

Thanks! Do you think it should or shouldn't be persistent?

Currently it is - although because it's via OverlayOptions it isn't defined in the normal way via PathPrefs.

(Well, it is via OverlayOptions.createSharedInstance().... but in principle you can have a different OverlayOptions that has different, non-persistent values, e.g. if you want to export in some custom way without affecting the viewers)

@alanocallaghan
Copy link
Contributor

I err on the side of persistent, yeah - certainly I can imagine grumbling if I always want it one way or the other. Persistent may confuse people who don't realise it's set, but I think that's less problematic given there's good UI indications anyway

@MichaelSNelson
Copy link

One more thought while you guys are working on this, it would be nice if there were (and may already be, I have not dug through everything) a quick way to filter by derived class length. Be it a sort by length, filter by 0, 1, ... n colons, or what have you. We can already 'Populate' base classes or all classes, but it would be nicer to be able to change and adjust the visibility rather than repopulating the list - especially in cases where not all classes will be present in every image.
If it isn't too difficult!

@petebankhead
Copy link
Member Author

I don't think I understand, but it sounds like it might be possible using the regex feature in the filter field under the list of classifications. Or using the new predicate option of OverlayOptions.

May need to be more related changes in a day or two after we've used this a bit more (and been annoyed by its limitations)

@MichaelSNelson
Copy link

Ah, if the filter accepts regex, then it is definitely already possible.
Probably not convenient for regular users multiplexing, but then I am not sure there is a convenient way to do all of the sorting you'd want to do with highly multiplex data sets without some scripting or regex. Good luck and I hope we can use this by the end of February! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants