Skip to content
Snippets Groups Projects
Commit 1d797e38 authored by Christian Kento Rasmussen's avatar Christian Kento Rasmussen
Browse files

Merge branch 'main' into conv_zarr_nifti

parents 33d1dc89 a05e2fe7
Branches
No related tags found
2 merge requests!102Conv zarr tiff folders,!100Conv zarr nifti
Showing
with 278 additions and 184 deletions
docs/assets/screenshots/generate_volume.png

91.7 KiB

docs/assets/screenshots/gui-annotation_tool.gif

2.28 MiB

docs/assets/screenshots/operations-edge_fade_after.png

457 KiB

docs/assets/screenshots/operations-edge_fade_before.png

508 KiB

docs/assets/screenshots/viz-fade_mask.gif

1.08 MiB

...@@ -131,8 +131,21 @@ You can find us at Gitlab: ...@@ -131,8 +131,21 @@ You can find us at Gitlab:
[https://lab.compute.dtu.dk/QIM/tools/qim3d](https://lab.compute.dtu.dk/QIM/tools/qim3d [https://lab.compute.dtu.dk/QIM/tools/qim3d](https://lab.compute.dtu.dk/QIM/tools/qim3d
) )
This project is licensed under the MIT License. This project is licensed under the [MIT License](https://lab.compute.dtu.dk/QIM/tools/qim3d/-/blob/main/LICENSE).
### Contributors
Below is a list of contributors to the project, arranged in chronological order of their first commit to the repository:
| Author | Commits | First commit |
|:--------------------------|----------:|-------------:|
| Felipe Delestro | 170 | 2023-05-12 |
| Stefan Engelmann Jensen | 29 | 2023-06-29 |
| Oskar Kristoffersen | 15 | 2023-07-05 |
| Christian Kento Rasmussen | 19 | 2024-02-01 |
| Alessia Saccardo | 7 | 2024-02-19 |
| David Grundfest | 4 | 2024-04-12 |
| Anna Bøgevang Ekner | 3 | 2024-04-18 |
## Support ## Support
......
%% Cell type:code id:0b73f2d8 tags:
``` python
import qim3d
```
%% Cell type:code id:73db6886 tags:
``` python
vol = qim3d.examples.bone_128x128x128
```
%% Cell type:code id:22d86d4d tags:
``` python
qim3d.viz.orthogonal(vol)
```
%% Output
HBox(children=(interactive(children=(IntSlider(value=64, description='Z', max=127), Output()), layout=Layout(a…
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
import qim3d import qim3d
``` ```
%% Output
WARNING:root:Could not load CuPy: No module named 'cupy'
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Blob detection notebook ### Blob detection notebook
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This notebook shows how to do **blob detection** in a 3D volume using the `qim3d` library. This notebook shows how to do **blob detection** in a 3D volume using the `qim3d` library.
Blob detection is done by initializing a `qim3d.processing.Blob` object, and then calling the `qim3d.processing.Blob.detect` method. The `qim3d.processing.Blob.detect` method detects blobs by using the Difference of Gaussian (DoG) blob detection method, and returns an array `blobs` with the blobs found in the volume stored as `(p, r, c, radius)`. Subsequently, a binary mask of the volume can be retrieved with the `qim3d.processing.get_mask` method, in which the found blobs are marked as `True`. Blob detection is done by using the `qim3d.processing.blob_detection` method, which detects blobs by using the Difference of Gaussian (DoG) blob detection method, and returns two arrays:
- `blobs`: The blobs found in the volume stored as `(p, r, c, radius)`
- `binary_volume`: A binary mask of the volume with the blobs marked as `True`
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **Example 1**: Blob detection in cement volume ### **Example 1**: Blob detection in cement volume
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Applying Gaussian filter to volume** **Applying Gaussian filter to volume**
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Import 3D volume of cement # Import 3D volume of cement
cement = qim3d.examples.cement_128x128x128 cement = qim3d.examples.cement_128x128x128
# Visualize slices of the original cement volume # Visualize slices of the original cement volume
qim3d.viz.slices(cement, n_slices = 5, show = True) qim3d.viz.slices(cement, n_slices = 5, show = True)
# Apply Gaussian filter to the cement volume # Apply Gaussian filter to the cement volume
cement_filtered = qim3d.processing.gaussian(cement, sigma = 2) cement_filtered = qim3d.processing.gaussian(cement, sigma = 2)
# Visualize slices of the filtered cement volume # Visualize slices of the filtered cement volume
qim3d.viz.slices(cement_filtered) qim3d.viz.slices(cement_filtered)
``` ```
%% Output %% Output
<Figure size 1000x200 with 5 Axes> <Figure size 1000x200 with 5 Axes>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Detecting blobs in volume** **Detecting blobs in volume**
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Initialize blob detector # Detect blobs, and get binary mask
blob_detector = qim3d.processing.Blob( blobs, mask = qim3d.processing.blob_detection(
background = "bright", cement_filtered,
min_sigma = 1, min_sigma=1,
max_sigma = 8, max_sigma=8,
threshold = 0.001, threshold=0.001,
overlap = 0.1 overlap=0.1,
background="bright"
) )
# Detect blobs in filtered volume
blobs = blob_detector.detect(vol = cement_filtered)
# Number of blobs found # Number of blobs found
print(f'Number of blobs found in the volume: {len(blobs)} blobs') print(f'Number of blobs found in the volume: {len(blobs)} blobs')
``` ```
%% Output %% Output
Bright background selected, volume will be inverted.
Number of blobs found in the volume: 1813 blobs Number of blobs found in the volume: 1813 blobs
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Visualize blobs on slices of cement volume # Visualize blobs on slices of cement volume
qim3d.viz.detection.circles(blobs, cement, show = True) qim3d.viz.detection.circles(blobs, cement, alpha = 0.8, show = True, color = 'red')
``` ```
%% Output %% Output
interactive(children=(IntSlider(value=64, description='Slice', max=127), Output()), layout=Layout(align_items=… interactive(children=(IntSlider(value=64, description='Slice', max=127), Output()), layout=Layout(align_items=…
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Get binary mask of detected blobs** **Binary mask of detected blobs**
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Get binary mask of detected blobs # Visualize binary mask
mask = blob_detector.get_mask()
# Visualize mask
qim3d.viz.slicer(mask) qim3d.viz.slicer(mask)
``` ```
%% Output %% Output
interactive(children=(IntSlider(value=64, description='Slice', max=127), Output()), layout=Layout(align_items=… interactive(children=(IntSlider(value=64, description='Slice', max=127), Output()), layout=Layout(align_items=…
......
...@@ -5,14 +5,26 @@ Here, we provide functionalities designed specifically for 3D image analysis and ...@@ -5,14 +5,26 @@ Here, we provide functionalities designed specifically for 3D image analysis and
::: qim3d.processing ::: qim3d.processing
options: options:
members: members:
- test_blob_detection
- blob_detection
- structure_tensor - structure_tensor
- local_thickness - local_thickness
- get_3d_cc - get_3d_cc
- Pipeline - gaussian
- Blob - median
- maximum
- minimum
- tophat
::: qim3d.processing.Pipeline
options:
members:
- append
::: qim3d.processing.operations ::: qim3d.processing.operations
options: options:
members: members:
- remove_background - remove_background
- watershed - watershed
- edge_fade
- fade_mask
...@@ -9,9 +9,17 @@ As the library is still in its early development stages, **there may be breaking ...@@ -9,9 +9,17 @@ As the library is still in its early development stages, **there may be breaking
And remember to keep your pip installation [up to date](/qim3d/#upgrade) so that you have the latest features! And remember to keep your pip installation [up to date](/qim3d/#upgrade) so that you have the latest features!
### v0.3.6 (30/05/2024)
- Refactoring for performance improvement
- Welcome message for the CLI
- Introduction of `qim3d.processing.fade_mask` 🎉
### v0.3.5 (27/05/2024) ### v0.3.5 (27/05/2024)
- Added runtime and memory usage in the documentation - Added runtime and memory usage in the documentation
- Introduction of `qim3d.utils.generate_volume` 🎉 - Introduction of `qim3d.utils.generate_volume` 🎉
- Introduction of `preview` CLI 🎉
### v0.3.4 (22/05/2024) ### v0.3.4 (22/05/2024)
- Documentation for `qim3d.viz.plot_cc` - Documentation for `qim3d.viz.plot_cc`
......
...@@ -5,6 +5,7 @@ A set of tools to ease managment of the system, with the common needs for large ...@@ -5,6 +5,7 @@ A set of tools to ease managment of the system, with the common needs for large
::: qim3d.utils.img ::: qim3d.utils.img
options: options:
members: members:
- generate_volume
- overlay_rgb_images - overlay_rgb_images
::: qim3d.utils.system ::: qim3d.utils.system
......
...@@ -12,6 +12,7 @@ The `qim3d` library aims to provide easy ways to explore and get insights from v ...@@ -12,6 +12,7 @@ The `qim3d` library aims to provide easy ways to explore and get insights from v
- vectors - vectors
- plot_cc - plot_cc
- colormaps - colormaps
- interactive_fade_mask
::: qim3d.viz.colormaps ::: qim3d.viz.colormaps
options: options:
......
...@@ -62,6 +62,7 @@ markdown_extensions: ...@@ -62,6 +62,7 @@ markdown_extensions:
- admonition - admonition
- attr_list - attr_list
- md_in_html - md_in_html
- tables
- pymdownx.inlinehilite - pymdownx.inlinehilite
- pymdownx.snippets - pymdownx.snippets
- pymdownx.details - pymdownx.details
...@@ -84,5 +85,5 @@ plugins: ...@@ -84,5 +85,5 @@ plugins:
show_root_full_path: true show_root_full_path: true
show_object_full_path: true show_object_full_path: true
show_symbol_type_heading: true show_symbol_type_heading: true
show_symbol_type_toc: true show_symbol_type_toc: false
separate_signature: true separate_signature: true
\ No newline at end of file
"""qim3d: A Python package for 3D image processing and visualization.
The qim3d library is designed to make it easier to work with 3D imaging data in Python.
It offers a range of features, including data loading and manipulation,
image processing and filtering, visualization of 3D data, and analysis of imaging results.
Documentation available at https://platform.qim.dk/qim3d/
"""
__version__ = "0.3.6"
import logging import logging
logging.basicConfig(level=logging.ERROR) logging.basicConfig(level=logging.ERROR)
from qim3d import io from . import io
from qim3d import gui from . import gui
from qim3d import viz from . import viz
from qim3d import utils from . import utils
from qim3d import models from . import processing
from qim3d import processing
# Commenting out models because it takes too long to import
# from . import models
__version__ = "0.3.2"
examples = io.ImgExamples() examples = io.ImgExamples()
io.logger.set_level_info() io.logger.set_level_info()
...@@ -10,9 +10,14 @@ Or launched from a python script ...@@ -10,9 +10,14 @@ Or launched from a python script
```python ```python
import qim3d import qim3d
app = qim3d.gui.annotation_tool.Interface() vol = qim3d.examples.NT_128x128x128
app.launch() annotation_tool = qim3d.gui.annotation_tool.Interface()
# We can directly pass the image we loaded to the interface
app = annotation_tool.launch(vol[0])
``` ```
![gui-annotation_tool](assets/screenshots/gui-annotation_tool.gif)
""" """
import getpass import getpass
......
...@@ -31,7 +31,7 @@ from qim3d.io.logger import log ...@@ -31,7 +31,7 @@ from qim3d.io.logger import log
from qim3d.utils.internal_tools import get_file_size, sizeof, stringify_path from qim3d.utils.internal_tools import get_file_size, sizeof, stringify_path
from qim3d.utils.system import Memory from qim3d.utils.system import Memory
dask.config.set(scheduler="processes") # Dask parallel goes brrrrr dask.config.set(scheduler="processes")
class DataLoader: class DataLoader:
...@@ -772,6 +772,16 @@ def load( ...@@ -772,6 +772,16 @@ def load(
""" """
Load data from the specified file or directory. Load data from the specified file or directory.
Supported formats:
- `Tiff` (including file stacks)
- `HDF5`
- `TXRM`/`TXM`/`XRM`
- `NIfTI`
- `PIL` (including file stacks)
- `VOL`/`VGI`
- `DICOM`
Args: Args:
path (str or os.PathLike): The path to the file or directory. path (str or os.PathLike): The path to the file or directory.
virtual_stack (bool, optional): Specifies whether to use virtual virtual_stack (bool, optional): Specifies whether to use virtual
...@@ -856,14 +866,17 @@ class ImgExamples: ...@@ -856,14 +866,17 @@ class ImgExamples:
shell_225x128x128 (numpy.ndarray): A 3D volume of a shell. shell_225x128x128 (numpy.ndarray): A 3D volume of a shell.
Tip: Tip:
Call `qim3d.examples.<name>` to access the image examples easily as this class is instantiated when importing `qim3d` Simply call `qim3d.examples.<name>` to access the image examples.
Example: Example:
```python ```python
import qim3d import qim3d
data = qim3d.examples.blobs_256x256 vol = qim3d.examples.shell_225x128x128
qim3d.viz.slices(vol, n_slices=15)
``` ```
![Grid of slices](assets/screenshots/viz-slices.png)
""" """
......
"Testing docstring"
from .local_thickness_ import local_thickness from .local_thickness_ import local_thickness
from .structure_tensor_ import structure_tensor from .structure_tensor_ import structure_tensor
from .detection import blob_detection
from .filters import * from .filters import *
from .detection import *
from .operations import * from .operations import *
from .cc import get_3d_cc from .cc import get_3d_cc
""" Blob detection using Difference of Gaussian (DoG) method """
import numpy as np import numpy as np
from qim3d.io.logger import log from qim3d.io.logger import log
from skimage.feature import blob_dog from skimage.feature import blob_dog
__all__ = ["Blob"]
class Blob: def blob_detection(
""" vol: np.ndarray,
Extract blobs from a volume using Difference of Gaussian (DoG) method background: str = "dark",
min_sigma: float = 1,
max_sigma: float = 50,
sigma_ratio: float = 1.6,
threshold: float = 0.5,
overlap: float = 0.5,
threshold_rel: float = None,
exclude_border: bool = False,
) -> np.ndarray:
""" """
def __init__( Extract blobs from a volume using Difference of Gaussian (DoG) method, and retrieve a binary volume with the blobs marked as True
self,
background="dark",
min_sigma=1,
max_sigma=50,
sigma_ratio=1.6,
threshold=0.5,
overlap=0.5,
threshold_rel=None,
exclude_border=False,
):
"""
Initialize the blob detection object
Args: Args:
vol: The volume to detect blobs in
background: 'dark' if background is darker than the blobs, 'bright' if background is lighter than the blobs background: 'dark' if background is darker than the blobs, 'bright' if background is lighter than the blobs
min_sigma: The minimum standard deviation for Gaussian kernel min_sigma: The minimum standard deviation for Gaussian kernel
max_sigma: The maximum standard deviation for Gaussian kernel max_sigma: The maximum standard deviation for Gaussian kernel
sigma_ratio: The ratio between the standard deviation of Gaussian Kernels sigma_ratio: The ratio between the standard deviation of Gaussian Kernels
threshold: The absolute lower bound for scale space maxima. Reduce this to detect blobs with lower intensities. threshold: The absolute lower bound for scale space maxima. Reduce this to detect blobs with lower intensities
overlap: The fraction of area of two blobs that overlap overlap: The fraction of area of two blobs that overlap
threshold_rel: The relative lower bound for scale space maxima threshold_rel: The relative lower bound for scale space maxima
exclude_border: If True, exclude blobs that are too close to the border of the image exclude_border: If True, exclude blobs that are too close to the border of the image
"""
self.background = background
self.min_sigma = min_sigma
self.max_sigma = max_sigma
self.sigma_ratio = sigma_ratio
self.threshold = threshold
self.overlap = overlap
self.threshold_rel = threshold_rel
self.exclude_border = exclude_border
self.vol_shape = None
self.blobs = None
def detect(self, vol):
"""
Detect blobs in the volume
Args:
vol: The volume to detect blobs in
Returns: Returns:
blobs: The blobs found in the volume as (p, r, c, radius) blobs: The blobs found in the volume as (p, r, c, radius)
binary_volume: A binary volume with the blobs marked as True
Example: Example:
```python ```python
...@@ -62,8 +42,9 @@ class Blob: ...@@ -62,8 +42,9 @@ class Blob:
vol = qim3d.examples.cement_128x128x128 vol = qim3d.examples.cement_128x128x128
vol_blurred = qim3d.processing.gaussian(vol, sigma=2) vol_blurred = qim3d.processing.gaussian(vol, sigma=2)
# Initialize Blob detector # Detect blobs, and get binary mask
blob_detector = qim3d.processing.Blob( blobs, mask = qim3d.processing.blob_detection(
vol_blurred,
min_sigma=1, min_sigma=1,
max_sigma=8, max_sigma=8,
threshold=0.001, threshold=0.001,
...@@ -71,88 +52,63 @@ class Blob: ...@@ -71,88 +52,63 @@ class Blob:
background="bright" background="bright"
) )
# Detect blobs # Visualize detected blobs
blobs = blob_detector.detect(vol_blurred)
# Visualize results
qim3d.viz.circles(blobs, vol, alpha=0.8, color='blue') qim3d.viz.circles(blobs, vol, alpha=0.8, color='blue')
``` ```
![blob detection](assets/screenshots/blob_detection.gif) ![blob detection](assets/screenshots/blob_detection.gif)
```python
# Visualize binary mask
qim3d.viz.slicer(mask)
```
![blob detection](assets/screenshots/blob_get_mask.gif)
""" """
self.vol_shape = vol.shape
if self.background == "bright": if background == "bright":
log.info("Bright background selected, volume will be inverted.") log.info("Bright background selected, volume will be inverted.")
vol = np.invert(vol) vol = np.invert(vol)
blobs = blob_dog( blobs = blob_dog(
vol, vol,
min_sigma=self.min_sigma, min_sigma=min_sigma,
max_sigma=self.max_sigma, max_sigma=max_sigma,
sigma_ratio=self.sigma_ratio, sigma_ratio=sigma_ratio,
threshold=self.threshold, threshold=threshold,
overlap=self.overlap, overlap=overlap,
threshold_rel=self.threshold_rel, threshold_rel=threshold_rel,
exclude_border=self.exclude_border, exclude_border=exclude_border,
)
blobs[:, 3] = blobs[:, 3] * np.sqrt(3) # Change sigma to radius
self.blobs = blobs
return self.blobs
def get_mask(self):
'''
Retrieve a binary volume with the blobs marked as True
Returns:
binary_volume: A binary volume with the blobs marked as True
Example:
```python
import qim3d
# Get data
vol = qim3d.examples.cement_128x128x128
vol_blurred = qim3d.processing.gaussian(vol, sigma=2)
# Initialize Blob detector
blob_detector = qim3d.processing.Blob(
min_sigma=1,
max_sigma=8,
threshold=0.001,
overlap=0.1,
background="bright"
) )
# Change sigma to radius
blobs[:, 3] = blobs[:, 3] * np.sqrt(3)
# Detect blobs # Create binary mask of detected blobs
blobs = blob_detector.detect(vol_blurred) vol_shape = vol.shape
binary_volume = np.zeros(vol_shape, dtype=bool)
# Get mask and visualize for z, y, x, radius in blobs:
mask = blob_detector.get_mask()
qim3d.viz.slicer(mask)
```
![blob detection](assets/screenshots/blob_get_mask.gif)
'''
binary_volume = np.zeros(self.vol_shape, dtype=bool)
for z, y, x, radius in self.blobs:
# Calculate the bounding box around the blob # Calculate the bounding box around the blob
z_start = max(0, int(z - radius)) z_start = max(0, int(z - radius))
z_end = min(self.vol_shape[0], int(z + radius) + 1) z_end = min(vol_shape[0], int(z + radius) + 1)
y_start = max(0, int(y - radius)) y_start = max(0, int(y - radius))
y_end = min(self.vol_shape[1], int(y + radius) + 1) y_end = min(vol_shape[1], int(y + radius) + 1)
x_start = max(0, int(x - radius)) x_start = max(0, int(x - radius))
x_end = min(self.vol_shape[2], int(x + radius) + 1) x_end = min(vol_shape[2], int(x + radius) + 1)
z_indices, y_indices, x_indices = np.indices((z_end - z_start, y_end - y_start, x_end - x_start)) z_indices, y_indices, x_indices = np.indices(
(z_end - z_start, y_end - y_start, x_end - x_start)
)
z_indices += z_start z_indices += z_start
y_indices += y_start y_indices += y_start
x_indices += x_start x_indices += x_start
# Calculate distances from the center of the blob to voxels within the bounding box # Calculate distances from the center of the blob to voxels within the bounding box
dist = np.sqrt((x_indices - x)**2 + (y_indices - y)**2 + (z_indices - z)**2) dist = np.sqrt(
(x_indices - x) ** 2 + (y_indices - y) ** 2 + (z_indices - z) ** 2
binary_volume[z_start:z_end, y_start:y_end, x_start:x_end][dist <= radius] = True )
return binary_volume
binary_volume[z_start:z_end, y_start:y_end, x_start:x_end][
dist <= radius
] = True
return blobs, binary_volume
...@@ -265,11 +265,13 @@ def minimum(vol, **kwargs): ...@@ -265,11 +265,13 @@ def minimum(vol, **kwargs):
def tophat(vol, **kwargs): def tophat(vol, **kwargs):
""" """
Remove background from the volume Remove background from the volume.
Args: Args:
vol: The volume to remove background from vol: The volume to remove background from
radius: The radius of the structuring element (default: 3) radius: The radius of the structuring element (default: 3)
background: color of the background, 'dark' or 'bright' (default: 'dark'). If 'bright', volume will be inverted. background: color of the background, 'dark' or 'bright' (default: 'dark'). If 'bright', volume will be inverted.
Returns: Returns:
vol: The volume with background removed vol: The volume with background removed
""" """
......
...@@ -5,8 +5,8 @@ import numpy as np ...@@ -5,8 +5,8 @@ import numpy as np
from typing import Optional from typing import Optional
from skimage.filters import threshold_otsu from skimage.filters import threshold_otsu
from qim3d.io.logger import log from qim3d.io.logger import log
from qim3d.viz import local_thickness as viz_local_thickness #from qim3d.viz import local_thickness as viz_local_thickness
import qim3d
def local_thickness( def local_thickness(
image: np.ndarray, image: np.ndarray,
...@@ -96,6 +96,6 @@ def local_thickness( ...@@ -96,6 +96,6 @@ def local_thickness(
# Visualize the local thickness if requested # Visualize the local thickness if requested
if visualize: if visualize:
display(viz_local_thickness(image, local_thickness, **viz_kwargs)) display(qim3d.viz.local_thickness(image, local_thickness, **viz_kwargs))
return local_thickness return local_thickness
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment