Skip to content
Snippets Groups Projects
Commit f5516fa1 authored by fima's avatar fima :beers:
Browse files

Annotation tool 4 0

parent defd3d14
No related branches found
No related tags found
1 merge request!59Annotation tool 4 0
%% Cell type:markdown id: tags:
# Image annotation tool
This notebook shows how the annotation interface can be used to create masks for images
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
import qim3d import qim3d
from scipy import ndimage
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib as mpl qim3d.io.logger.level("info")
import numpy as np
%matplotlib inline
```
%% Cell type:code id: tags:
``` python # Load example image
# Load 2D example image img = qim3d.examples.bone_128x128x128
img = qim3d.examples.blobs_256x256
# Display image
plt.imshow(img)
plt.show()
```
%% Cell type:code id: tags:
``` python
# Start annotation tool # Start annotation tool
interface = qim3d.gui.annotation_tool.Interface() interface = qim3d.gui.annotation_tool.Interface()
interface.max_masks = 4
# We can directly pass the image we loaded to the interface # We can directly pass the image we loaded to the interface
interface.launch(img=img) #interface.launch()
``` interface.launch(ndimage.zoom(img[0], 3, order=0))
%% Cell type:code id: tags:
``` python
# When 'prepare mask for download' is pressed once, the mask can be retrieved with the get_result() method
mask = interface.get_result()
```
%% Cell type:markdown id: tags:
## Check the obtained mask
%% Cell type:code id: tags:
``` python
print (f"Original image shape..: {img.shape}")
print (f"Mask image shape......: {mask.shape}")
print (f"\nNumber of masks: {np.max(mask)}")
```
%% Cell type:markdown id: tags:
## Show the masked regions
%% Cell type:code id: tags:
``` python
%matplotlib inline
nmasks = np.max(mask)
fig, axs = plt.subplots(nrows=1, ncols=nmasks+2, figsize=(12,3))
# Show original image
axs[0].imshow(img)
axs[0].set_title("Original")
axs[0].axis('off')
# Show masks
cmap = mpl.colormaps["rainbow"].copy()
cmap.set_under(color='black') # Sets the background to black
axs[1].imshow(mask, interpolation='none', cmap=cmap, vmin=1, vmax=nmasks+1)
axs[1].set_title("Masks")
axs[1].axis('off')
# Show masked regions
for idx in np.arange(2, nmasks+2):
mask_id = idx-1
submask = mask.copy()
submask[submask != mask_id] = 0
masked_img = img.copy()
masked_img[submask==0] = 0
axs[idx].imshow(masked_img)
axs[idx].set_title(f"Mask {mask_id}")
axs[idx].axis('off')
plt.show()
``` ```
......
...@@ -6,6 +6,11 @@ icon: fontawesome/solid/screwdriver-wrench ...@@ -6,6 +6,11 @@ icon: fontawesome/solid/screwdriver-wrench
A set of tools to ease managment of the system, with the common needs for large data in mind. A set of tools to ease managment of the system, with the common needs for large data in mind.
::: qim3d.utils.img
options:
members:
- overlay_rgb_images
::: qim3d.utils.system ::: qim3d.utils.system
options: options:
members: members:
......
/* Override for dark mode */ /* Override for dark mode */
.dark{ .dark {
--name: default; --name: default;
--primary-50: #fff7ed; --primary-50: #fff7ed;
--primary-100: #ffedd5; --primary-100: #ffedd5;
...@@ -73,9 +73,9 @@ ...@@ -73,9 +73,9 @@
--link-text-color-hover: var(--secondary-700); --link-text-color-hover: var(--secondary-700);
--link-text-color-visited: var(--secondary-500); --link-text-color-visited: var(--secondary-500);
--body-text-color-subdued: var(--neutral-400); --body-text-color-subdued: var(--neutral-400);
--shadow-drop: rgba(0,0,0,0.05) 0px 1px 2px 0px; --shadow-drop: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
--shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset; --shadow-inset: rgba(0, 0, 0, 0.05) 0px 2px 4px 0px inset;
--shadow-spread: 3px; --shadow-spread: 3px;
--block-background-fill: var(--background-fill-primary); --block-background-fill: var(--background-fill-primary);
--block-border-color: var(--border-color-primary); --block-border-color: var(--border-color-primary);
...@@ -203,6 +203,7 @@ ...@@ -203,6 +203,7 @@
--button-small-text-weight: 400; --button-small-text-weight: 400;
--button-transition: none; --button-transition: none;
} }
/* Up here is the override for dark mode */ /* Up here is the override for dark mode */
/* Now comes our custom CSS */ /* Now comes our custom CSS */
...@@ -228,9 +229,10 @@ h2 { ...@@ -228,9 +229,10 @@ h2 {
white-space: pre !important; white-space: pre !important;
} }
.no-border{ .no-border {
border-width: 0px !important; border-width: 0px !important;
} }
/* Hides Gradio footer */ /* Hides Gradio footer */
footer { footer {
visibility: hidden; visibility: hidden;
...@@ -341,9 +343,9 @@ input[type="range"]::-webkit-slider-thumb { ...@@ -341,9 +343,9 @@ input[type="range"]::-webkit-slider-thumb {
} }
/* Buttons */ /* Buttons */
.btn-html{ .btn-html {
position: sticky; position: sticky;
display:flex; display: flex;
justify-content: center; justify-content: center;
background: white !important; background: white !important;
border-color: #6c757d !important; border-color: #6c757d !important;
...@@ -372,7 +374,7 @@ input[type="range"]::-webkit-slider-thumb { ...@@ -372,7 +374,7 @@ input[type="range"]::-webkit-slider-thumb {
text-align: left !important; text-align: left !important;
} }
.btn-spinner::after{ .btn-spinner::after {
content: ""; content: "";
position: absolute; position: absolute;
width: 16px; width: 16px;
...@@ -547,7 +549,7 @@ div.svelte-1frtwj3 { ...@@ -547,7 +549,7 @@ div.svelte-1frtwj3 {
.w-64 { .w-64 {
width: 64px !important; width: 64px !important;
min-width: 64px !important; min-width: 64px !important;
} }
...@@ -558,7 +560,7 @@ div.svelte-1frtwj3 { ...@@ -558,7 +560,7 @@ div.svelte-1frtwj3 {
.w-36 { .w-36 {
width: 36px !important; width: 36px !important;
min-width: 36px !important; min-width: 36px !important;
} }
...@@ -569,7 +571,7 @@ div.svelte-1frtwj3 { ...@@ -569,7 +571,7 @@ div.svelte-1frtwj3 {
.w-32 { .w-32 {
width: 32px !important; width: 32px !important;
min-width: 32px !important; min-width: 32px !important;
} }
...@@ -579,7 +581,7 @@ div.svelte-1frtwj3 { ...@@ -579,7 +581,7 @@ div.svelte-1frtwj3 {
.w-16 { .w-16 {
width: 16px !important; width: 16px !important;
min-width: 16px !important; min-width: 16px !important;
} }
.h-0 { .h-0 {
...@@ -590,33 +592,124 @@ div.svelte-1frtwj3 { ...@@ -590,33 +592,124 @@ div.svelte-1frtwj3 {
width: 0px !important; width: 0px !important;
} }
.options{ .options {
border: 1px solid #bababa !important; border: 1px solid #bababa !important;
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.15) !important; box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.15) !important;
} }
.mt-32{ .mt-32 {
margin-top: 32px; margin-top: 32px;
} }
.mt-48{ .mt-48 {
margin-top: 48px; margin-top: 48px;
} }
.mt-64{ .mt-64 {
margin-top: 64px; margin-top: 64px;
} }
.mt-128{ .mt-128 {
margin-top: 128px; margin-top: 128px;
} }
.hide-overflow{ .hide-overflow {
overflow: hidden !important; overflow: hidden !important;
} }
.wrap.default{ .wrap.default {
visibility: hidden !important; visibility: hidden !important;
} }
\ No newline at end of file
/* Annotation tool CSS */
.annotation-tool .layer-wrap {
display: none !important;
}
.annotation-tool button[title="Image button"] {
display: none !important;
}
.annotation-tool:not(.no-img) button[title="Upload button"] {
display: none !important;
}
.annotation-tool button[title="Clear canvas"] {
display: none !important;
}
.annotation-tool button[title="Color button"] {
color: var(--block-label-text-color) !important;
margin-inline-end: 32px !important;
}
.annotation-tool button[title="Color button"]::after {
content: "Select mask";
}
.annotation-tool button[title="Size button"] {
color: var(--block-label-text-color) !important;
}
.annotation-tool button[title="Size button"]::after {
content: "Brush size";
}
.annotation-tool .row-wrap {
justify-content: flex-start !important;
}
.annotation-tool .controls-wrap .small {
width: 20px !important;
height: 20px !important;
}
.annotation-tool .controls-wrap {
top: 0px !important;
left: 0px !important;
}
.annotation-tool .controls-wrap .padded {
padding: 8px !important;
border: 0px !important;
background-color: rgba(255, 255, 255, 0.8);
}
.annotation-tool .stage-wrap {
margin-top: 0px !important;
margin-bottom: 16px !important;
}
.annotation-tool .svelte-b3dw9m {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
}
.annotation-tool .bottom {
bottom: 8px !important;
left: 0px !important;
position: absolute !important;
}
.annotation-tool .download {
min-width: 4rem !important;
width: 10%;
white-space: nowrap;
text-align: right;
}
.annotation-tool .stage-wrap canvas {
border: 0px !important;
max-width: 512px !important;
max-height: 512px !important;
height: 512px !important;
width: fit-content !important;
}
This diff is collapsed.
...@@ -4,4 +4,5 @@ from .augmentations import Augmentation ...@@ -4,4 +4,5 @@ from .augmentations import Augmentation
from .data import Dataset, prepare_datasets, prepare_dataloaders from .data import Dataset, prepare_datasets, prepare_dataloaders
#from .doi import get_bibtex, get_reference #from .doi import get_bibtex, get_reference
from . import doi from . import doi
from .system import Memory from .system import Memory
\ No newline at end of file from .img import overlay_rgb_images
\ No newline at end of file
import numpy as np
def overlay_rgb_images(background, foreground, alpha=0.5):
"""Overlay multiple RGB foreground onto an RGB background image using alpha blending.
Args:
background (numpy.ndarray): The background RGB image.
foreground (numpy.ndarray): The foreground RGB image (usually masks).
alpha (float, optional): The alpha value for blending. Defaults to 0.5.
Returns:
numpy.ndarray: The composite RGB image with overlaid foreground.
Raises:
ValueError: If input images have different shapes.
Note:
- The function performs alpha blending to overlay the foreground onto the background.
- It ensures that the background and foreground have the same shape before blending.
- It calculates the maximum projection of the foreground and blends them onto the background.
- Brightness outside the foreground is adjusted to maintain consistency with the background.
"""
# Igonore alpha in case its there
background = background[..., :3]
foreground = foreground[..., :3]
# Ensure both images have the same shape
if background.shape != foreground.shape:
raise ValueError("Input images must have the same shape")
# Perform alpha blending
foreground_max_projection = np.amax(foreground, axis=2)
foreground_max_projection = np.stack((foreground_max_projection,) * 3, axis=-1)
# Normalize if we have something
if np.max(foreground_max_projection) > 0:
foreground_max_projection = foreground_max_projection / np.max(foreground_max_projection)
composite = background * (1 - alpha) + foreground * alpha
composite = np.clip(composite, 0, 255).astype("uint8")
# Adjust brightness outside foreground
composite = composite + (background * (1 - alpha)) * (1 - foreground_max_projection)
return composite.astype("uint8")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment