Skip to content

Replace use of properties with features #425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/howtos/extending/magicgui.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,8 +539,8 @@ The following are all valid {attr}`napari.types.LayerDataTuple` examples:
# an empty points layer
(None, {}, 'points')

# points with properties
(np.random.rand(20, 2), {'properties': {'values': np.random.rand(20)}}, 'points')
# points with features
(np.random.rand(20, 2), {'features': {'values': np.random.rand(20)}}, 'points')
```

An example of using a {attr}`~napari.types.LayerDataTuple` return annotation in
Expand All @@ -553,8 +553,8 @@ import napari.types
@magicgui(call_button='Make Points')
def make_points(n_points=40) -> napari.types.LayerDataTuple:
data = 500 * np.random.rand(n_points, 2)
props = {'values': np.random.rand(n_points)}
return (data, {'properties': props}, 'points')
features = {'values': np.random.rand(n_points)}
return (data, {'features': features}, 'points')

viewer = napari.Viewer()
viewer.window.add_dock_widget(make_points)
Expand Down
105 changes: 53 additions & 52 deletions docs/howtos/layers/points.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ kernelspec:

```{Admonition} DEPRECATED ATTRIBUTES
:class: warning
As of napari 0.5.0, `edge_*` attributes are being renamed to
`border_*` attributes. We have yet to update the images and/or videos in
As of napari 0.5.0, `edge_*` attributes are being renamed to
`border_*` attributes. We have yet to update the images and/or videos in
this tutorial. Please use `border` in place of `edge` for all `Points` attributes moving forward.

The code in this tutorial uses the latest API. Only images and videos may be out of date.
Expand All @@ -37,11 +37,11 @@ points independently. You can also adjust `opacity` and `symbol` representing
all the points simultaneously.

Each data point can have annotations associated with it using the
`Points.properties` dictionary. These properties can be used to set the face and
`Points.features` table. These features can be used to set the face and
border colors of the points. For example, when displaying points of different
classes/types, one could automatically set color the individual points by their
respective class/type. For more details on point properties, see
[](#setting-point-border-and-face-color-with-properties) or
respective class/type. For more details on point features, see
[](#setting-point-border-and-face-color-with-features) or
[point annotation tutorial](../../tutorials/annotation/annotate_points).

## Creating and editing the `points` layer using the GUI
Expand Down Expand Up @@ -116,7 +116,7 @@ layer:
to move around the `points` layer as you create your selection.

* **Pan/zoom**
![image: Pan/zoom tool](../../images/pan-zoom-tool.png)
![image: Pan/zoom tool](../../images/pan-zoom-tool.png)

The default mode of the points layer supports panning and zooming, as in the
image layer. This mode is represented by the magnifying glass in the layers
Expand Down Expand Up @@ -194,7 +194,7 @@ layer:
Note that when entering 3D rendering mode the GUI `Add point`,
`Delete selected points`, and `Select points` tools are all disabled. Those
options are supported only when viewing a layer using 2D rendering.

* `ctrl-c` and `ctrl-v` (copying and pasting points)

Copy and paste any selected points using `ctrl-c` and `ctrl-v`, respectively.
Expand All @@ -214,12 +214,12 @@ the same. In these examples we'll mainly use `add_points` to overlay points onto
on an existing image.

Each data point can have annotations associated with it using the
`Points.properties` dictionary. These properties can be used to set the face and
`Points.features` table. These features can be used to set the face and
border colors of the points. For example, when displaying points of different
classes/types, one could automatically set the color of the individual points by
their respective class/type. For more details on point properties, see
[](#setting-point-border-and-face-color-with-properties) below or the
[Point annotation tutorial](../../tutorials/annotation/annotate_points).
their respective class/type. For more details on point features, see
[](#setting-point-border-and-face-color-with-features) below or the
[Point annotation tutorial](annotating-points).

In this example, we will overlay some points on the image of an astronaut:

Expand Down Expand Up @@ -267,14 +267,15 @@ the same as the ordering of the dimensions for image layers. This array is
always accessible through the `layer.data` property and will grow or shrink as
new points are either added or deleted.

### Using the points properties dictionary
### Using the points features table

The `points` layer can contain properties that annotate each point.
`Points.properties` stores the properties in a dictionary where each key is the
name of the property and the values are NumPy arrays with a value for each point
(i.e., length N for N points in `Points.data`). As we will see below, we can use
the values in a property to set the display properties of the points (e.g., face
color or border color). To see the points properties in action, please see the
The `Points` layer can contain features that annotate each point.
`Points.features` stores the features in a table where each column represents
a feature and each row represents each the feature values for a point.
Therefore, the table has N rows for N points in `Points.data`.
As we will see below, we can use feature values to determine the display properties
of the points (e.g., face color or border color).
To see the points features in action, please see the
[Point annotation tutorial](annotating-points).


Expand Down Expand Up @@ -338,17 +339,17 @@ properties are different from the `layer.current_border_color` and
`layer.current_face_color` properties that will determine the color of the next
point to be added or any currently selected points.

### Setting point border and face color with properties
### Setting point border and face color with features

Point border and face colors can be set as a function of a property in
`Points.properties`. There are two ways the values in `Points.properties` can be
Point border and face colors can be set as a function of a feature in
`Points.features`. There are two ways that these feature values can be
mapped to colors: (1) color cycles and (2) colormaps.

Color cycles are sets of colors that are mapped to categorical properties. The
colors are repeated if the number of unique property values is greater than the
number of colors in the color cycle.
Color cycles are sets of colors that are mapped to categorical features.
The colors are repeated if the number of unique feature values is greater
than the number of colors in the color cycle.

Colormaps are a continuum of colors that are mapped to a continuous property
Colormaps are a continuum of colors that are mapped to a continuous feature
value. The available colormaps are listed below (colormaps are from
[vispy](https://vispy.org/api/vispy.color.colormap.html#vispy.color.colormap.Colormap)).
For guidance on choosing colormaps, see the
Expand All @@ -360,21 +361,21 @@ list(napari.utils.colormaps.AVAILABLE_COLORMAPS)

### Setting border or face color with a color cycle

Here we will set the border color of the markers with a color cycle on a property.
Here we will set the border color of the markers with a color cycle on a feature.
To do the same for a face color, substitute `face_color` for `border_color` in the
example snippet below.

```{code-cell} python
viewer = napari.view_image(data.astronaut(), rgb=True)
points = np.array([[100, 100], [200, 200], [300, 100]])
point_properties = {
'good_point': np.array([True, True, False]),
'confidence': np.array([0.99, 0.8, 0.2]),
point_features = {
'good_point': [True, True, False],
'confidence': [0.99, 0.8, 0.2],
}

points_layer = viewer.add_points(
points,
properties=point_properties,
features=point_features,
border_color='good_point',
border_color_cycle=['magenta', 'green'],
border_width=0.5,
Expand All @@ -392,33 +393,33 @@ nbscreenshot(viewer, alt_text="3 points overlaid on an astronaut image, where th
viewer.close()
```

In the example above, the `point_properties` were provided as a dictionary with
two properties: `good_point` and `confidence`. The values of each property are
stored in a NumPy ndarray with length 3 since there were 3 coordinates provided
in `points`. We set the border color as a function of the `good_point` property by
providing the keyword argument `border_color='good_point'` to the
`viewer.add_points()` method. The color cycle is set via the `border_color_cycle`
keyword argument, `border_color_cycle=['magenta', 'green']`. The color cycle can
be provided as a list of colors (as a list of strings or a (M x 4) array of M
RGBA colors).
In the example above, the `point_features` were provided as a
dictionary with two keys or columns: `good_point` and `confidence`.
The values of each feature are stored in a list of length 3 since there were three
coordinates provided in `points`. We set the border color as a function of the
`good_point` feature by providing the keyword argument
`border_color='good_point'` to the `viewer.add_points()` method.
The color cycle is set via the `border_color_cycle` keyword argument,
`border_color_cycle=['magenta', 'green']`. The color cycle can be provided as a
list of colors (a list of strings or a (M x 4) array of M RGBA colors).

### Setting border or face color with a colormap

In the example snippet below, we set the face color of the markers with a
colormap on a property. To do the same for a border color, substitute `face` for
colormap on a feature. To do the same for a border color, substitute `face` for
`border`.

```{code-cell} python
viewer = napari.view_image(data.astronaut(), rgb=True)
points = np.array([[100, 100], [200, 200], [300, 100]])
point_properties = {
'good_point': np.array([True, True, False]),
'confidence': np.array([0.99, 0.8, 0.2]),
point_features = {
'good_point': [True, True, False],
'confidence': [0.99, 0.8, 0.2],
}

points_layer = viewer.add_points(
points,
properties=point_properties,
features=point_features,
face_color='confidence',
face_colormap='viridis',
)
Expand All @@ -435,13 +436,13 @@ nbscreenshot(viewer, alt_text="3 points overlaid on an astronaut image, where th
viewer.close()
```

In the example above, the `point_properties` were provided as a dictionary with
two properties: `good_point` and `confidence`. The values of each property are
stored in a NumPy ndarray with length 3 since there were 3 coordinates provided
in `points`. We set the face color as a function of the `confidence` property by
providing the keyword argument `face_color='confidence'` to the
`viewer.add_points()` method. We set the colormap to viridis using the
`face_colormap` keyword argument as `face_colormap='viridis'`.
In the example above, the `point_features` were provided as a
dictionary with two feature columns: `good_point` and `confidence`.
The table has three rows since there were three coordinates provided in `points`.
We set the face color as a function of the `confidence` feature by providing the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a bit confusing for folks unfamiliar with pandas to talk about columns/rows in a dictionary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, indeed, maybe worth saying that this is converted internally to a data frame with two columns and three rows? (I wouldn't specify pandas necessarily, but maybe.)

Copy link
Member Author

@andy-sweet andy-sweet Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this paragraph is mixing things a bit too much. Let me try to fix that.

But I also want to make a distinction between the meaning of features (i.e. it's a table) and the type used to provide or implement it (i.e. a dict or DataFrame). Ideally, we could do that in one place for all layers (see #23).

For now, there's a little effort above to describe what the table is in the "### Using the points features table" section. I'll clean that up a little and will mention that the table can be provided as a dictionary where the values are array-likes of the same length.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also (tried to) put a link back to the section that describes the dictionary and table/dataframe relationship in both of these similar paragraphs.

keyword argument `face_color='confidence'` to the `viewer.add_points()` method.
We set the colormap to viridis using the `face_colormap` keyword argument
as `face_colormap='viridis'`.

### Changing the points symbol

Expand All @@ -452,7 +453,7 @@ layer using the `symbol` keyword argument.
## Putting it all together

Here you can see an example of adding, selecting, deleting points, and changing
their properties:
their features:

```{raw} html
<figure>
Expand Down
95 changes: 18 additions & 77 deletions docs/howtos/layers/tracks.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ code there to add a tracks layer first, then explore the GUI controls.
The `tracks` layer allows you to display trajectories in `nD+t` while
visualizing the recent history of the track via a fading tail.

Each track can have annotations associated with it using the `Tracks.properties`
dictionary. These properties can be used to set the colors of the tracks.
Each track can have annotations associated with it using the `Tracks.features`
table. These features can be used to set the colors of the tracks.

For example, when displaying tracks of different classes/types, one could
automatically set the color of the individual tracks by their respective
Expand All @@ -41,8 +41,9 @@ class/type.
## A simple example

You can create a new viewer and add a set of tracks in one go using the
`napari.view_tracks` method, or if you already have an existing viewer, you can
add tracks to it using `viewer.add_tracks`. The API of both methods is the same.
:func:`napari.view_tracks` method, or if you already have an existing viewer, you can
add tracks to it using :func:`viewer.add_tracks<napari.components.ViewerModel.add_tracks>`.
The API of both methods is the same.

In this example, we will overlay some tracks on an image from the Hubble space
telescope:
Expand Down Expand Up @@ -121,66 +122,6 @@ napari.run()
* Graph - check this box to display a previously created graph as explained in
[](#arguments-of-view_tracks-and-add_tracks).

## Arguments of `view_tracks` and `add_tracks`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These docs are incomplete and stale for multiple reasons (including properties/features), so I removed this in favor of link to the two functions in the text above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lmao I came here while trying to resolve merge conflicts with #420 going, "why on earth did this get removed. And no discussion either, wth!"

... Then I found my thumbs-up above 🤣


Both `view_tracks` and `add_tracks` have the following docstrings:

```python
"""
Parameters
----------
data : array (N, D+1)
Coordinates for N points in D+1 dimensions. ID,T,(Z),Y,X. The first
axis is the integer ID of the track. D is either 3 or 4 for planar
or volumetric timeseries respectively.
properties : dict {str: array (N,)}, DataFrame
Properties for each point. Each property should be an array of length N,
where N is the number of points.
graph : dict {int: list}
Graph representing associations between tracks. Dictionary defines the
mapping between a track ID and the parents of the track. This can be
one (the track has one parent, and the parent has >=1 child) in the
case of track splitting, or more than one (the track has multiple
parents, but only one child) in the case of track merging.
See examples/tracks_3d_with_graph.py
color_by: str
Track property (from property keys) by which to color vertices.
tail_width : float
Width of the track tails in pixels.
tail_length : float
Length of the track tails in units of time.
colormap : str
Default colormap to use to set vertex colors. Specialized colormaps,
relating to specified properties can be passed to the layer via
colormaps_dict.
colormaps_dict : dict {str: napari.utils.Colormap}
Optional dictionary mapping each property to a colormap for that
property. This allows each property to be assigned a specific colormap,
rather than having a global colormap for everything.
name : str
Name of the layer.
metadata : dict
Layer metadata.
scale : tuple of float
Scale factors for the layer.
translate : tuple of float
Translation values for the layer.
opacity : float
Opacity of the layer visual, between 0.0 and 1.0.
blending : str
One of a list of preset blending modes that determines how RGB and
alpha values of the layer visual get mixed. Allowed values are
{'opaque', 'translucent', and 'additive'}.
visible : bool
Whether the layer visual is currently being displayed.

Returns
-------
layer : napari.layers.Tracks
The newly-created tracks layer.
"""
```

## Tracks data

The input data to the tracks layer must be an `NxD+1` NumPy array or list
Expand Down Expand Up @@ -250,13 +191,13 @@ graph = {
For a full example of 3d+t tracks data with a parent graph, please see
[](../../gallery/tracks_3d_with_graph).

## Using the `tracks` properties dictionary
## Using the `tracks` features table

The `tracks` layer can contain properties that annotate the vertices of each
track. `Tracks.properties` stores the properties in a dictionary where each key
is the name of the property and the values are NumPy arrays with a value for
The `Tracks` layer can contain features that annotate the vertices of each
track. `Tracks.features` stores the features in a table where each column
is the name of the feature and the values are rows with a value for
each vertex in the track (i.e., length `N` for `N` vertices in `Tracks.data`).
As we will see below, we can use the values in a property to set the display
As we will see below, we can use the feature values to set the display
properties of the tracks (e.g., the track color).

## 3D rendering
Expand Down Expand Up @@ -335,14 +276,14 @@ length" slider in the `tracks` layer controls.
</figure>
```

## Setting the track color with properties
## Setting the track color with features

We can color the tracks by mapping colors to the track properties defined in
`Tracks.properties`. If we define properties and pass them via the `properties`
We can color the tracks by mapping colors to the track features defined in
`Tracks.features`. If we define features and pass them via the `features`
keyword argument in the `viewer.add_tracks()` and `napari.view_tracks()`
methods, we can then select the property we would like to color the tracks by in
methods, we can then select the feature we would like to color the tracks by in
the "color by" dropdown menu in the `tracks` layer controls. We can additionally
specify the colormap used to map the property value to color via the "colormap"
specify the colormap used to map the feature value to color via the "colormap"
dropdown menu.

```python
Expand Down Expand Up @@ -370,13 +311,13 @@ tracks_data = np.asarray([
[3, 4, 636, 1000]
])
track_confidence = np.array(5*[0.9] + 5*[0.3] + 5 * [0.1])
properties = {
features = {
'time': tracks_data[:, 1],
'confidence': track_confidence
}

viewer = napari.view_image(hubble_image)
viewer.add_tracks(tracks_data, properties=properties)
viewer.add_tracks(tracks_data, features=features)
napari.run()
```

Expand All @@ -387,7 +328,7 @@ napari.run()
<source src="../../_static/images/tracks_color_by.mp4" type="video/mp4" />
<img src="../../_static/images/tracks_color_by.png"
title="Your browser does not support the video tag"
alt="Selecting tracks by the time property and changing the color."
alt="Selecting tracks by the time feature and changing the color."
>
</video>
</figure>
Expand Down
Loading