voxel51 / fiftyone

Refine high-quality datasets and visual AI models
https://fiftyone.ai
Apache License 2.0
8.85k stars 558 forks source link

[FR] Add support for adjusting opacity for unselected points in interactive plots #1341

Open tszumowski opened 3 years ago

tszumowski commented 3 years ago

Consider the embeddings tutorial. When you lasso or change the view in the session, the selected points remain colored, and filtered points get a lower opacity.

However, if you have lots of points, it can still be hard to see the difference between selected and filtered.

Is there a way to adjust this?

brimoor commented 3 years ago

Hmm so those interactive scatterplots are generated by this method.

They are pure Plotly plots (go.Scatter to be precise), so if plotly supports customizing the look and feel of selected vs non-selected points, then we could expose such customizability as optional kwargs to scatterplot(), which are passed through when you call visualize().

Any chance you can take a look into this to see if you can find a look-and-feel that works for you?

tszumowski commented 3 years ago

They are pure Plotly plots (go.Scatter to be precise),

@brimoor thank you for the quick reply. I noticed that the visualize() function returns a Layout object (or something that inherits it).

So I thought perhaps I can update the layout, or pass template into the `visualize() function to serve as a kwarg passthrough. I didn't have any luck.

For example, using the image embeddings code, I added the following to define the layout template object that defines parameters for a scatterplot:

import plotly.graph_objects as go

diamond_template = go.layout.Template(
    {
        "data": {
            "scatter": [
                {
                    "marker": {
                        "size": 20,
                        "symbol": "diamond"
                    },
                    "type": "scatter",
                    "unselected": {
                        "marker": {
                            "opacity": 0
                        }
                    }
                }
            ]
        }
    }    
)

Then I tried the following two methods:

1) Direct parameter:

plot = results.visualize(labels="ground_truth.label", template=diamond_template)

2) Update after:

plot = results.visualize(labels="ground_truth.label")
plot.update_layout(template=diamond_template)

Neither seemed to take. They should have created large diamonds.

I attached the notebook (zipped): image_embeddings.ipynb.zip

Also, I tried this out on example plotly code from the docs, and this seemed to work

import plotly.graph_objects as go
import numpy as np

N = 1000
t = np.linspace(0, 10, 100)
y = np.sin(t)

import plotly.graph_objects as go

diamond_template = go.layout.Template(
    {
        "data": {
            "scatter": [
                {
                    "marker": {
                        "size": 20,
                        "symbol": "diamond"
                    },
                    "type": "scatter",
                    "unselected": {
                        "marker": {
                            "opacity": 0
                        }
                    }
                }
            ]
        }
    }    
)

fig = go.Figure(
    data=go.Scatter(x=t, y=y, mode='markers'), 
    layout={"template": diamond_template}
)

fig.show()

image

brimoor commented 3 years ago

@tszumowski apologies for the delay. I think either of your approaches should work:

plot = results.visualize(..., **kwargs)
plot.show()

plot.update_layout(**kwargs)
plot.show()

since both of these simply pass **kwargs through to the underlying plotly FigureWidget: https://github.com/voxel51/fiftyone/blob/56020a239b7db6cfc40958b406487c3514693a78/fiftyone/core/plots/plotly.py#L1126-L1128

However, you'll have to tweak your template arg a bit since the scatterplot may have multiple traces depending on how visualize() is called. For example:

# Compute and store visualization so we can reuse it
import cv2
import numpy as np

import fiftyone as fo
import fiftyone.brain as fob
import fiftyone.zoo as foz

dataset = foz.load_zoo_dataset("mnist", split="test")
dataset.persistent = True

images = np.array([
    cv2.imread(f, cv2.IMREAD_UNCHANGED).ravel()
    for f in dataset.values("filepath")
])

results = fob.compute_visualization(dataset, embeddings=images, seed=51, brain_key="images")
# Now visualize
import fiftyone as fo

dataset = fo.load_dataset("mnist-test")
results = dataset.load_brain_results("images")

plot = results.visualize(labels="ground_truth.label")
plot.show(height=720)

# This is a tuple of traces
print(plot._widget.data)