Skip to content

Commit

Permalink
Merge pull request #84 from haesleinhuepf/sliceplot
Browse files Browse the repository at this point in the history
Added interactive sliceplot
  • Loading branch information
haesleinhuepf authored Nov 3, 2024
2 parents afc662a + 78e7522 commit 81b2f4b
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,5 @@ cython_debug/
.DS_STORE
deploy.bat
*Untitled*

docs/png_umap*
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,16 @@ This functionality is inspired from the [napari-clusters-plotter](https://github

![clusterplot.gif](https://raw.githubusercontent.com/haesleinhuepf/stackview/main/docs/images/clusterplot.gif)

### Sliceplot

Another similar interactive plot is the `sliceplot`: In case you have a pandas DataFrame with a columns representing measurements of slices in an image stack, you can use this function to visualize the slices in a plot.

```python
stackview.sliceplot(df, images, column_x="UMAP0", column_y="UMAP1")
```

![](https://raw.githubusercontent.com/haesleinhuepf/stackview/main/docs/images/sliceplot.gif)

### Interact

Exploration of the parameter space of image processing functions is available using `interact`:
Expand Down
Binary file added docs/images/sliceplot.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
303 changes: 303 additions & 0 deletions docs/sliceplot.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9974e82c-795b-4f44-9ed0-1d539b1a71de",
"metadata": {},
"source": [
"# Exploring slices of stacks by plotting corresponding data\n",
"In this notebook we will explore a stack of images using an interactive scatterplot of measurements that were done on the individual slices of the stack. For demonstration purposes, we explore a stack of teaching slides an embedding generated using large language models."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "24a3041b-56b1-4f44-ad18-9b06c5eb62b6",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import stackview\n",
"import os\n",
"import numpy as np\n",
"from skimage.io import imread\n",
"import yaml\n",
"import requests\n",
"import zipfile\n"
]
},
{
"cell_type": "markdown",
"id": "e0e5c014-8e9d-47cf-a23e-668d80c9a7d7",
"metadata": {},
"source": [
"First, we download [the dataset](https://zenodo.org/records/14030307), which is licensed [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) by Robert Haase."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "3ae7e89b-79cb-43a0-948d-49a7b5bfb32a",
"metadata": {},
"outputs": [],
"source": [
"# URL of the zip file\n",
"url = \"https://zenodo.org/records/14030307/files/png_umap.zip?download=1\"\n",
"# Save the zip file locally\n",
"zip_path = \"png_umap.zip\"\n",
"\n",
"if not os.path.exists(zip_path):\n",
" # Download the zip file\n",
" response = requests.get(url)\n",
" with open(zip_path, \"wb\") as f:\n",
" f.write(response.content)\n",
" \n",
" # Extract the contents\n",
" with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n",
" zip_ref.extractall()\n",
"\n",
"# Optionally, remove the zip file after extraction\n",
"#os.remove(zip_path)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "dd5f83a7-f774-4d3e-871a-429775be7c55",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>UMAP0</th>\n",
" <th>UMAP1</th>\n",
" <th>filename</th>\n",
" <th>page_index</th>\n",
" <th>png_filename</th>\n",
" <th>text</th>\n",
" <th>url</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2.785299</td>\n",
" <td>5.125338</td>\n",
" <td>12623730_14_Summary.pdf</td>\n",
" <td>0</td>\n",
" <td>12623730_14_Summary_0.png</td>\n",
" <td>Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/...</td>\n",
" <td>https://zenodo.org/api/records/12623730/files/...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>1.759109</td>\n",
" <td>5.196022</td>\n",
" <td>12623730_14_Summary.pdf</td>\n",
" <td>1</td>\n",
" <td>12623730_14_Summary_1.png</td>\n",
" <td>Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/...</td>\n",
" <td>https://zenodo.org/api/records/12623730/files/...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>1.605859</td>\n",
" <td>6.084491</td>\n",
" <td>12623730_14_Summary.pdf</td>\n",
" <td>2</td>\n",
" <td>12623730_14_Summary_2.png</td>\n",
" <td>Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/...</td>\n",
" <td>https://zenodo.org/api/records/12623730/files/...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>1.581907</td>\n",
" <td>6.084695</td>\n",
" <td>12623730_14_Summary.pdf</td>\n",
" <td>3</td>\n",
" <td>12623730_14_Summary_3.png</td>\n",
" <td>Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/...</td>\n",
" <td>https://zenodo.org/api/records/12623730/files/...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2.163119</td>\n",
" <td>7.161102</td>\n",
" <td>12623730_14_Summary.pdf</td>\n",
" <td>4</td>\n",
" <td>12623730_14_Summary_4.png</td>\n",
" <td>Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/...</td>\n",
" <td>https://zenodo.org/api/records/12623730/files/...</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" UMAP0 UMAP1 filename page_index \\\n",
"0 2.785299 5.125338 12623730_14_Summary.pdf 0 \n",
"1 1.759109 5.196022 12623730_14_Summary.pdf 1 \n",
"2 1.605859 6.084491 12623730_14_Summary.pdf 2 \n",
"3 1.581907 6.084695 12623730_14_Summary.pdf 3 \n",
"4 2.163119 7.161102 12623730_14_Summary.pdf 4 \n",
"\n",
" png_filename \\\n",
"0 12623730_14_Summary_0.png \n",
"1 12623730_14_Summary_1.png \n",
"2 12623730_14_Summary_2.png \n",
"3 12623730_14_Summary_3.png \n",
"4 12623730_14_Summary_4.png \n",
"\n",
" text \\\n",
"0 Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/... \n",
"1 Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/... \n",
"2 Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/... \n",
"3 Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/... \n",
"4 Robert Haase\\n@haesleinhuepf\\nBIDS Lecture 14/... \n",
"\n",
" url \n",
"0 https://zenodo.org/api/records/12623730/files/... \n",
"1 https://zenodo.org/api/records/12623730/files/... \n",
"2 https://zenodo.org/api/records/12623730/files/... \n",
"3 https://zenodo.org/api/records/12623730/files/... \n",
"4 https://zenodo.org/api/records/12623730/files/... "
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Read YAML file\n",
"with open('png_umap/png_umap.yml', 'r') as file:\n",
" loaded_dict = yaml.safe_load(file)\n",
"\n",
"\n",
"df = pd.DataFrame(loaded_dict)\n",
"\n",
"# Show first few rows of the loaded DataFrame\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"id": "0822cc8e-46c9-47ce-9d4b-181913dd1eb3",
"metadata": {},
"source": [
"We also define a helper function that loads all images mentioned in a dataframe into one big numpy array."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "5cf520c5-4a1b-4708-b39e-de9922cb5680",
"metadata": {},
"outputs": [],
"source": [
"def get_images(selected_rows):\n",
" # Load images for selected pages\n",
" images = []\n",
" for _, row in selected_rows.iterrows():\n",
" img_path = os.path.join('png_umap', row['png_filename'])\n",
" img = imread(img_path)\n",
" images.append(img)\n",
"\n",
" if len(images) == 0:\n",
" return np.zeros((2,2,2))\n",
" else:\n",
" return np.asarray(images)"
]
},
{
"cell_type": "markdown",
"id": "4829158d-d705-4070-ba65-a9dd64f3ebbc",
"metadata": {},
"source": [
"We can use the `sliceplot` function of stackview to visualize the embedding next to selected slides."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "feac021f-d0a5-4f92-b8dd-4b23c90be0bb",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "bc68c6c84230415bb4ea64701670d2f1",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(HBox(children=(VBox(children=(VBox(children=(HBox(children=(VBox(children=(ImageWidget(height=4…"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"stackview.sliceplot(df, get_images(df), column_x=\"UMAP0\", column_y=\"UMAP1\", zoom_factor=1.5, zoom_spline_order=2)"
]
},
{
"cell_type": "markdown",
"id": "b871cc66-6088-42ab-9708-42858100ab52",
"metadata": {},
"source": [
"## Exercise\n",
"Explore the plot by dragging lines around islands of datapoints with the mouse. What content are these islands about?"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1cc76f41-1668-45a4-82aa-61d4cb3dd783",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="stackview",
version="0.11.2",
version="0.12.0",
author="Robert Haase",
author_email="[email protected]",
description="Interactive image stack viewing in jupyter notebooks",
Expand Down
4 changes: 2 additions & 2 deletions stackview/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.11.2"
__version__ = "0.12.0"

from ._static_view import jupyter_displayable_output, insight
from ._utilities import merge_rgb
Expand All @@ -21,6 +21,6 @@
from ._scatterplot import scatterplot
from ._grid import grid
from ._clusterplot import clusterplot

from ._sliceplot import sliceplot


8 changes: 7 additions & 1 deletion stackview/_slice_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ def __init__(self,

self.sliders = []
offset = 2
if 3 >= image.shape[-1] >= 4: # RGB or RGBA images
if image.shape[-1] == 3 or image.shape[-1] == 4: # RGB or RGBA images
offset = 3

for d in range(len(image.shape) - offset):
slider = intSlider(
value=slice_number[d],
Expand All @@ -60,6 +61,11 @@ def __init__(self,
self.slice_slider = ipywidgets.VBox(self.sliders[::-1])
self.update()

def set_image(self, image):
for i, s in enumerate(self.sliders):
s._set_value_min_max(int(image.shape[i] / 2), 0, image.shape[i] - 1)
self.image = image

def observe(self, x):
self.update_active = False
for s in self.sliders:
Expand Down
Loading

0 comments on commit 81b2f4b

Please sign in to comment.