Interactive napari cell annotation on spatial proteomics#
Harpy works with SpatialData and AnnData objects. This allows for interoperability with other libraries in the scverse ecosystem that also work with these objects.
Anndata objects can be converted to pandas DataFrames, which can be used to store features in a napari Labels layer. These features can be used in a wide range of napari plugins, which can be used to perform interactive cell type labeling.
In this notebook, we load an artificial example dataset and perform annotation using napari-clusters-plotter. The clustering result can be visualized within napari and saved back to the SpatialData object within the notebook.
# Install napari-clusters-plotter as shown here https://github.com/BiAPoL/napari-clusters-plotter/tree/main?tab=readme-ov-file#installation
# e.g. conda install -c conda-forge napari-clusters-plotter
# load some example SpatialData
from harpy.datasets import multisample_blobs
sdata = multisample_blobs(n_samples=1)
sdata
INFO no axes information specified in the object, setting `dims` to: ('c', 'y', 'x')
INFO no axes information specified in the object, setting `dims` to: ('y', 'x')
SpatialData object
├── Images
│ └── 'sample_0_image': DataArray[cyx] (11, 512, 512)
├── Labels
│ └── 'sample_0_labels': DataArray[yx] (512, 512)
├── Points
│ └── 'sample_0_points': DataFrame with shape: (<Delayed>, 2) (2D points)
└── Tables
├── 'sample_0_table': AnnData (20, 11)
└── 'table': AnnData (20, 11)
with coordinate systems:
▸ 'sample_0', with elements:
sample_0_image (Images), sample_0_labels (Labels), sample_0_points (Points)
table = sdata["sample_0_table"]
table
AnnData object with n_obs × n_vars = 20 × 11
obs: 'instance_id', 'region', 'fov_labels', 'cell_ID', 'phenotype', 'area', 'eccentricity', 'major_axis_length', 'minor_axis_length', 'perimeter', 'centroid-0', 'centroid-1', 'convex_area', 'equivalent_diameter', '_major_minor_axis_ratio', '_perim_square_over_area', '_major_axis_equiv_diam_ratio', '_convex_hull_resid', '_centroid_dif'
var: 'cycle'
uns: 'spatialdata_attrs'
Here we create the DataFrame of features to be used in napari. The DataFrame should have a label and index column refering back to the cell instances in the label mask. This is the index of the AnnData table, so we set this index as the column ‘label’ and ‘index’ in the DataFrame. The DataFrame should also contain all the features you want to visualize in napari and cluster on.
df = table.to_df()
df["label"] = df.index.astype(int)
df["index"] = df.index.astype(int)
df
| nucleus | lineage_0 | lineage_1 | lineage_2 | lineage_3 | lineage_4 | lineage_5 | lineage_6 | lineage_7 | lineage_8 | lineage_9 | label | index | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cells | |||||||||||||
| 1 | 130579.074583 | 73143.384718 | 90176.329291 | 78887.342227 | 83547.398858 | 0.0 | 85104.954042 | 89203.618952 | 71052.452962 | 166453.784109 | 79783.723973 | 1 | 1 |
| 2 | 97747.257070 | 44127.352051 | 54403.315415 | 47592.677537 | 50404.086393 | 0.0 | 51343.758329 | 103611.315408 | 42865.894408 | 48557.293269 | 48133.464008 | 2 | 2 |
| 3 | 129783.693347 | 72871.920340 | 89159.713324 | 77997.994298 | 82605.515103 | 0.0 | 132335.765591 | 88197.968976 | 70251.432797 | 79581.243727 | 78884.270554 | 3 | 3 |
| 4 | 126967.536598 | 69764.494608 | 86010.594981 | 159287.142678 | 79687.890840 | 0.0 | 81180.370216 | 85082.819414 | 67770.154343 | 76768.146443 | 76776.118424 | 4 | 4 |
| 5 | 107214.353348 | 52460.254002 | 64676.705321 | 56579.963133 | 124573.992918 | 0.0 | 61039.388909 | 63979.053282 | 50960.585762 | 57726.734557 | 57222.870407 | 5 | 5 |
| 6 | 118023.115808 | 137276.241101 | 76316.189314 | 66762.324339 | 70706.128294 | 0.0 | 72387.349397 | 75492.984966 | 60131.660871 | 71197.154810 | 67520.931832 | 6 | 6 |
| 7 | 122155.172414 | 140294.370188 | 79746.467902 | 69763.173487 | 73884.244499 | 0.0 | 75261.651691 | 78886.261965 | 62834.473349 | 71177.144253 | 75451.055432 | 7 | 7 |
| 8 | 132338.563601 | 152777.165022 | 92088.042864 | 80559.732355 | 85318.580915 | 0.0 | 86909.155844 | 91094.776083 | 72558.745575 | 82192.529441 | 81475.117149 | 8 | 8 |
| 9 | 127852.325618 | 70794.839797 | 183547.310962 | 76354.365832 | 80864.793725 | 0.0 | 82372.337711 | 86339.399487 | 68771.045311 | 77901.927901 | 77221.965853 | 9 | 9 |
| 10 | 121525.452585 | 67926.259433 | 80034.961874 | 70015.551499 | 74151.531060 | 0.0 | 75534.839518 | 79171.644022 | 63061.785821 | 154850.113126 | 70811.124788 | 10 | 10 |
| 11 | 131650.132124 | 74095.756472 | 91350.353696 | 79914.392959 | 84635.119838 | 0.0 | 86212.953157 | 139548.028686 | 71977.499640 | 81534.110207 | 80822.444886 | 11 | 11 |
| 12 | 127371.369690 | 70297.364119 | 86667.554141 | 76338.564150 | 80296.556433 | 0.0 | 81793.614099 | 85732.692114 | 68287.790846 | 77354.510681 | 157361.012640 | 12 | 12 |
| 13 | 114577.728962 | 59503.671129 | 73360.327287 | 64176.500530 | 67967.553929 | 0.0 | 115488.462960 | 74399.400815 | 57802.654475 | 65477.239746 | 64905.725802 | 13 | 13 |
| 14 | 102792.173341 | 47854.334318 | 58998.202313 | 51612.340103 | 54661.199666 | 0.0 | 58107.597624 | 106489.632074 | 46486.334359 | 52658.426977 | 52198.801229 | 14 | 14 |
| 15 | 98593.953283 | 48764.333471 | 56132.317387 | 49105.229349 | 52005.988117 | 0.0 | 52975.523944 | 55526.831603 | 44228.223439 | 50100.501715 | 126998.845549 | 15 | 15 |
| 16 | 128578.399102 | 71259.826508 | 87854.145733 | 76855.867998 | 81395.920777 | 0.0 | 82913.366444 | 86906.484229 | 145386.128521 | 78413.594590 | 77729.166492 | 16 | 16 |
| 17 | 133312.011550 | 75540.130776 | 93131.207064 | 81472.304999 | 86285.061323 | 0.0 | 87893.654127 | 141309.672418 | 73380.683836 | 83123.598249 | 82398.059181 | 17 | 17 |
| 18 | 98854.304635 | 45097.277102 | 55599.107503 | 48655.494694 | 51511.974897 | 0.0 | 101403.272287 | 54999.373325 | 43808.092452 | 49624.589015 | 49191.749709 | 18 | 18 |
| 19 | 103946.301444 | 48853.336484 | 60229.842722 | 52689.794012 | 55802.301254 | 0.0 | 93048.637568 | 59580.157919 | 47456.778298 | 53757.718888 | 75974.922923 | 19 | 19 |
| 20 | 101242.245589 | 47964.507147 | 59134.031156 | 51731.164816 | 54787.043628 | 0.0 | 68469.878151 | 58496.166608 | 46593.357705 | 52779.660047 | 111525.597766 | 20 | 20 |
We add image and label mask as a napari layer and add the features to the Labels layer. Then we run napari, which will open in a new window. The workflow can be found here and is as follows:
In the napari window, open the Plotter widget via
Plugins > napari-clusters-plotter > Plotte Widget.In the new widget window on the right, select as labels the labels element with the features dataframe. You can select a feature for both the x-axis and y-axis and click
Plotto visualize the cells in a scatter plot.By drawing around groups of cells in the scatter plot, you can assign cell types to the cells.
By holding SHIFT, you can add create new clusters.
import napari
viewer = napari.view_image(sdata["sample_0_image"], name="image")
labels_view = viewer.add_labels(sdata["sample_0_labels"], name="labels", features=df)
napari.run()
# Do annotation in napari window
After annotation, the labels will be saved in the DataFrame and can be saved back to the SpatialData object, as shown in the code below.
# Here we add a dummy labeling to the feature table to simulate annotation
# COMMENT OUT THIS LINE IF YOU ARE DOING REAL ANNOTATION
labels_view.features["MANUAL_CLUSTER_ID"] = labels_view.features["label"] + 1
labels_view.features
| nucleus | lineage_0 | lineage_1 | lineage_2 | lineage_3 | lineage_4 | lineage_5 | lineage_6 | lineage_7 | lineage_8 | lineage_9 | label | index | MANUAL_CLUSTER_ID | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 130579.074583 | 73143.384718 | 90176.329291 | 78887.342227 | 83547.398858 | 0.0 | 85104.954042 | 89203.618952 | 71052.452962 | 166453.784109 | 79783.723973 | 1 | 1 | 2 |
| 1 | 97747.257070 | 44127.352051 | 54403.315415 | 47592.677537 | 50404.086393 | 0.0 | 51343.758329 | 103611.315408 | 42865.894408 | 48557.293269 | 48133.464008 | 2 | 2 | 3 |
| 2 | 129783.693347 | 72871.920340 | 89159.713324 | 77997.994298 | 82605.515103 | 0.0 | 132335.765591 | 88197.968976 | 70251.432797 | 79581.243727 | 78884.270554 | 3 | 3 | 4 |
| 3 | 126967.536598 | 69764.494608 | 86010.594981 | 159287.142678 | 79687.890840 | 0.0 | 81180.370216 | 85082.819414 | 67770.154343 | 76768.146443 | 76776.118424 | 4 | 4 | 5 |
| 4 | 107214.353348 | 52460.254002 | 64676.705321 | 56579.963133 | 124573.992918 | 0.0 | 61039.388909 | 63979.053282 | 50960.585762 | 57726.734557 | 57222.870407 | 5 | 5 | 6 |
| 5 | 118023.115808 | 137276.241101 | 76316.189314 | 66762.324339 | 70706.128294 | 0.0 | 72387.349397 | 75492.984966 | 60131.660871 | 71197.154810 | 67520.931832 | 6 | 6 | 7 |
| 6 | 122155.172414 | 140294.370188 | 79746.467902 | 69763.173487 | 73884.244499 | 0.0 | 75261.651691 | 78886.261965 | 62834.473349 | 71177.144253 | 75451.055432 | 7 | 7 | 8 |
| 7 | 132338.563601 | 152777.165022 | 92088.042864 | 80559.732355 | 85318.580915 | 0.0 | 86909.155844 | 91094.776083 | 72558.745575 | 82192.529441 | 81475.117149 | 8 | 8 | 9 |
| 8 | 127852.325618 | 70794.839797 | 183547.310962 | 76354.365832 | 80864.793725 | 0.0 | 82372.337711 | 86339.399487 | 68771.045311 | 77901.927901 | 77221.965853 | 9 | 9 | 10 |
| 9 | 121525.452585 | 67926.259433 | 80034.961874 | 70015.551499 | 74151.531060 | 0.0 | 75534.839518 | 79171.644022 | 63061.785821 | 154850.113126 | 70811.124788 | 10 | 10 | 11 |
| 10 | 131650.132124 | 74095.756472 | 91350.353696 | 79914.392959 | 84635.119838 | 0.0 | 86212.953157 | 139548.028686 | 71977.499640 | 81534.110207 | 80822.444886 | 11 | 11 | 12 |
| 11 | 127371.369690 | 70297.364119 | 86667.554141 | 76338.564150 | 80296.556433 | 0.0 | 81793.614099 | 85732.692114 | 68287.790846 | 77354.510681 | 157361.012640 | 12 | 12 | 13 |
| 12 | 114577.728962 | 59503.671129 | 73360.327287 | 64176.500530 | 67967.553929 | 0.0 | 115488.462960 | 74399.400815 | 57802.654475 | 65477.239746 | 64905.725802 | 13 | 13 | 14 |
| 13 | 102792.173341 | 47854.334318 | 58998.202313 | 51612.340103 | 54661.199666 | 0.0 | 58107.597624 | 106489.632074 | 46486.334359 | 52658.426977 | 52198.801229 | 14 | 14 | 15 |
| 14 | 98593.953283 | 48764.333471 | 56132.317387 | 49105.229349 | 52005.988117 | 0.0 | 52975.523944 | 55526.831603 | 44228.223439 | 50100.501715 | 126998.845549 | 15 | 15 | 16 |
| 15 | 128578.399102 | 71259.826508 | 87854.145733 | 76855.867998 | 81395.920777 | 0.0 | 82913.366444 | 86906.484229 | 145386.128521 | 78413.594590 | 77729.166492 | 16 | 16 | 17 |
| 16 | 133312.011550 | 75540.130776 | 93131.207064 | 81472.304999 | 86285.061323 | 0.0 | 87893.654127 | 141309.672418 | 73380.683836 | 83123.598249 | 82398.059181 | 17 | 17 | 18 |
| 17 | 98854.304635 | 45097.277102 | 55599.107503 | 48655.494694 | 51511.974897 | 0.0 | 101403.272287 | 54999.373325 | 43808.092452 | 49624.589015 | 49191.749709 | 18 | 18 | 19 |
| 18 | 103946.301444 | 48853.336484 | 60229.842722 | 52689.794012 | 55802.301254 | 0.0 | 93048.637568 | 59580.157919 | 47456.778298 | 53757.718888 | 75974.922923 | 19 | 19 | 20 |
| 19 | 101242.245589 | 47964.507147 | 59134.031156 | 51731.164816 | 54787.043628 | 0.0 | 68469.878151 | 58496.166608 | 46593.357705 | 52779.660047 | 111525.597766 | 20 | 20 | 21 |
table.obs["manual_clustering"] = labels_view.features["MANUAL_CLUSTER_ID"]
table
AnnData object with n_obs × n_vars = 20 × 11
obs: 'instance_id', 'region', 'fov_labels', 'cell_ID', 'phenotype', 'area', 'eccentricity', 'major_axis_length', 'minor_axis_length', 'perimeter', 'centroid-0', 'centroid-1', 'convex_area', 'equivalent_diameter', '_major_minor_axis_ratio', '_perim_square_over_area', '_major_axis_equiv_diam_ratio', '_convex_hull_resid', '_centroid_dif', 'manual_clustering'
var: 'cycle'
uns: 'spatialdata_attrs'