Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • QIM/tools/qim3d
1 result
Show changes
Commits on Source (22)
Showing
with 222 additions and 62 deletions
# Qim3D (Quantitative Imaging in 3D)
# qim3D (Quantitative Imaging in 3D)
The `qim3d` (kɪm θriː diː) 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.
......@@ -8,6 +8,7 @@ Whether you are working with medical imaging data, materials science data, or an
Documentation available at https://platform.qim.dk/qim3d/
For more information on the QIM center visit https://qim.dk/
# License
This project is licensed under the MIT License.
\ No newline at end of file
This project is licensed under the MIT License.
docs/assets/screenshots/synthetic_blob_cylinder_slice.png

168 KiB

docs/assets/screenshots/synthetic_blob_tube_slice.png

194 KiB

docs/assets/screenshots/synthetic_collection_cylinder_slices.png

179 KiB

docs/assets/screenshots/synthetic_collection_tube_slices.png

100 KiB

docs/assets/screenshots/viz-histogram-slice.png

16.1 KiB

docs/assets/screenshots/viz-histogram-vol.png

31.9 KiB

docs/assets/screenshots/viz-histogram.png

34.8 KiB

......@@ -33,7 +33,7 @@ This offers quick interactions, making it ideal for tasks that require efficienc
| `--anotation-tool` | Starts the annotation tool |
| `--layers` | Starts the tool for segmenting layers |
| `--host` | Desired host for the server. By default runs on `0.0.0.0` |
| `--platform` | Uses the Qim platform API for a unique path and port depending on the username |
| `--platform` | Uses the QIM platform API for a unique path and port depending on the username |
!!! Example
......
......@@ -137,6 +137,16 @@ The latest stable version can be simply installed using `pip`. Open your termina
!!! note
The base installation of `qim3d` does not include deep-learning dependencies, keeping the library lighter for scenarios where they are unnecessary. If you need to use deep-learning features, you can install the additional dependencies by running: **`pip install qim3d['deep-learning']`**
After completing the installation, you can verify its success by running one or both of the following commands:
qim3d
or:
pip show qim3d
If either command displays information about the qim3d library, the installation was successful.
### Troubleshooting
Here are some solutions for commonly found issues during installation and usage of `qim3d`.
......@@ -203,15 +213,17 @@ This project is licensed under the [MIT License](https://lab.compute.dtu.dk/QIM/
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 | 195 | 2023-05-12 |
| Stefan Engelmann Jensen | 29 | 2023-06-29 |
| Oskar Kristoffersen | 15 | 2023-07-05 |
| Christian Kento Rasmussen | 22 | 2024-02-01 |
| Alessia Saccardo | 7 | 2024-02-19 |
| David Grundfest | 8 | 2024-04-12 |
| Anna Bøgevang Ekner | 5 | 2024-04-18 |
| Author | Commits | First commit |
|:----------------------------|----------:|-------------:|
| Felipe Delestro | 231 | 2023-05-12 |
| Stefan Engelmann Jensen | 29 | 2023-06-29 |
| Oskar Kristoffersen | 15 | 2023-07-05 |
| Christian Kento Rasmussen | 22 | 2024-02-01 |
| Alessia Saccardo | 13 | 2024-02-19 |
| David Grundfest | 16 | 2024-04-12 |
| Anna Bøgevang Ekner | 6 | 2024-04-18 |
| David Diamond Wang Johansen | 1 | 2024-10-31 |
## Support
......
......@@ -9,6 +9,14 @@ 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/#get-the-latest-version) so that you have the latest features!
### v0.4.5 (21/11/2024)
- Updated Layer surface segmentation GUI
- Sphericity as feature from volumes
- Colorbar for visualization functions
- Chunk visualization tool
- Histogram visualization
### v0.4.4 (11/10/2024)
- Introduction of `itk-vtk-viewer` for OME-Zarr data visualization 🎉 ![itk-vtk-viewer](assets/screenshots/itk-vtk-viewer.gif)
......
......@@ -4,6 +4,7 @@ The `qim3d` library aims to provide easy ways to explore and get insights from v
::: qim3d.viz
options:
members:
- histogram
- slices
- slicer
- orthogonal
......
site_name: qim3d documentation
site_url: https://platform.qim.dk/qim3d/
site_author: Qim3d contributors
site_description: Documentation for the Qim3d python library
site_author: qim3d contributors
site_description: Documentation for the qim3d python library
repo_url: https://lab.compute.dtu.dk/QIM/tools/qim3d
repo_name: Gitlab
......
......@@ -8,7 +8,7 @@ Documentation available at https://platform.qim.dk/qim3d/
"""
__version__ = "0.4.4"
__version__ = "0.4.5"
import importlib as _importlib
......
......@@ -18,7 +18,7 @@ def parse_tuple(arg):
def main():
parser = argparse.ArgumentParser(description="Qim3d command-line interface.")
parser = argparse.ArgumentParser(description="qim3d command-line interface.")
subparsers = parser.add_subparsers(title="Subcommands", dest="subcommand")
# GUIs
......@@ -167,7 +167,7 @@ def main():
except qim3d.viz.NotInstalledError as err:
print(err)
message = "Itk-vtk-viewer is not installed or qim3d can not find it.\nYou can either:\n\to Use 'qim3d viz SOURCE -m k3d' to display data using different method\n\to Install itk-vtk-viewer yourself following https://kitware.github.io/itk-vtk-viewer/docs/cli.html#Installation\n\to Let QIM3D install itk-vtk-viewer now (it will also install node.js in qim3d library)\nDo you want QIM3D to install itk-vtk-viewer now?"
message = "Itk-vtk-viewer is not installed or qim3d can not find it.\nYou can either:\n\to Use 'qim3d viz SOURCE -m k3d' to display data using different method\n\to Install itk-vtk-viewer yourself following https://kitware.github.io/itk-vtk-viewer/docs/cli.html#Installation\n\to Let qim3D install itk-vtk-viewer now (it will also install node.js in qim3d library)\nDo you want qim3D to install itk-vtk-viewer now?"
print(message)
answer = input("[Y/n]:")
if answer in "Yy":
......
......@@ -2,6 +2,8 @@ import numpy as np
import scipy.ndimage
from noise import pnoise3
import qim3d.processing
def blob(
base_shape: tuple = (128, 128, 128),
final_shape: tuple = (128, 128, 128),
......@@ -11,6 +13,7 @@ def blob(
max_value: int = 255,
threshold: float = 0.5,
smooth_borders: bool = False,
object_shape: str = None,
dtype: str = "uint8",
) -> np.ndarray:
"""
......@@ -25,6 +28,7 @@ def blob(
max_value (int, optional): Maximum value for the volume intensity. Defaults to 255.
threshold (float, optional): Threshold value for clipping low intensity values. Defaults to 0.5.
smooth_borders (bool, optional): Flag for automatic computation of the threshold value to ensure a blob with no straight edges. If True, the `threshold` parameter is ignored. Defaults to False.
object_shape (str, optional): Shape of the object to generate, either "cylinder", or "tube". Defaults to None.
dtype (str, optional): Desired data type of the output volume. Defaults to "uint8".
Returns:
......@@ -41,17 +45,64 @@ def blob(
# Generate synthetic blob
synthetic_blob = qim3d.generate.blob(noise_scale = 0.015)
# Visualize 3D volume
qim3d.viz.vol(synthetic_blob)
```
<iframe src="https://platform.qim.dk/k3d/synthetic_blob.html" width="100%" height="500" frameborder="0"></iframe>
```python
# Visualize slices
qim3d.viz.slices(synthetic_blob, vmin = 0, vmax = 255, n_slices = 15)
```
![synthetic_blob](assets/screenshots/synthetic_blob_slices.png)
Example:
```python
# Visualize 3D volume
qim3d.viz.vol(synthetic_blob)
import qim3d
# Generate tubular synthetic blob
vol = qim3d.generate.blob(base_shape = (10, 300, 300),
final_shape = (100, 100, 100),
noise_scale = 0.3,
gamma = 2,
threshold = 0.0,
object_shape = "cylinder"
)
# Visualize synthetic blob
qim3d.viz.vol(vol)
```
<iframe src="https://platform.qim.dk/k3d/synthetic_blob.html" width="100%" height="500" frameborder="0"></iframe>
<iframe src="https://platform.qim.dk/k3d/synthetic_blob_cylinder.html" width="100%" height="500" frameborder="0"></iframe>
```python
# Visualize slices
qim3d.viz.slices(vol, n_slices=15, axis=1)
```
![synthetic_blob_cylinder_slice](assets/screenshots/synthetic_blob_cylinder_slice.png)
Example:
```python
import qim3d
# Generate tubular synthetic blob
vol = qim3d.generate.blob(base_shape = (200, 100, 100),
final_shape = (400, 100, 100),
noise_scale = 0.03,
gamma = 0.12,
threshold = 0.85,
object_shape = "tube"
)
# Visualize synthetic blob
qim3d.viz.vol(vol)
```
<iframe src="https://platform.qim.dk/k3d/synthetic_blob_tube.html" width="100%" height="500" frameborder="0"></iframe>
```python
# Visualize
qim3d.viz.slices(vol, n_slices=15)
```
![synthetic_blob_tube_slice](assets/screenshots/synthetic_blob_tube_slice.png)
"""
if not isinstance(final_shape, tuple) or len(final_shape) != 3:
......@@ -93,6 +144,10 @@ def blob(
# Scale the volume to the maximum value
volume = volume * max_value
# If object shape is specified, smooth borders are disabled
if object_shape:
smooth_borders = False
if smooth_borders:
# Maximum value among the six sides of the 3D volume
max_border_value = np.max([
......@@ -115,4 +170,46 @@ def blob(
volume, np.array(final_shape) / np.array(base_shape), order=order
)
return volume.astype(dtype)
# Fade into a shape if specified
if object_shape == "cylinder":
# Arguments for the fade_mask function
geometry = "cylindrical" # Fade in cylindrical geometry
axis = np.argmax(volume.shape) # Fade along the dimension where the object is the largest
target_max_normalized_distance = 1.4 # This value ensures that the object will become cylindrical
volume = qim3d.processing.operations.fade_mask(volume,
geometry = geometry,
axis = axis,
target_max_normalized_distance = target_max_normalized_distance
)
elif object_shape == "tube":
# Arguments for the fade_mask function
geometry = "cylindrical" # Fade in cylindrical geometry
axis = np.argmax(volume.shape) # Fade along the dimension where the object is the largest
decay_rate = 5 # Decay rate for the fade operation
target_max_normalized_distance = 1.4 # This value ensures that the object will become cylindrical
# Fade once for making the object cylindrical
volume = qim3d.processing.operations.fade_mask(volume,
geometry = geometry,
axis = axis,
decay_rate = decay_rate,
target_max_normalized_distance = target_max_normalized_distance,
invert = False
)
# Fade again with invert = True for making the object a tube (i.e. with a hole in the middle)
volume = qim3d.processing.operations.fade_mask(volume,
geometry = geometry,
axis = axis,
decay_rate = decay_rate,
invert = True
)
# Convert to desired data type
volume = volume.astype(dtype)
return volume
\ No newline at end of file
......@@ -139,6 +139,7 @@ def collection(
min_threshold: float = 0.5,
max_threshold: float = 0.6,
smooth_borders: bool = False,
object_shape: str = None,
seed: int = 0,
verbose: bool = False,
) -> tuple[np.ndarray, object]:
......@@ -163,30 +164,30 @@ def collection(
max_high_value (int, optional): Maximum maximum value for the volume intensity. Defaults to 255.
min_threshold (float, optional): Minimum threshold value for clipping low intensity values. Defaults to 0.5.
max_threshold (float, optional): Maximum threshold value for clipping low intensity values. Defaults to 0.6.
smooth_borders (bool, optional): Flag for smoothing blob borders to avoid straight edges in the objects. If True, the `min_threshold` and `max_threshold` parameters are ignored. Defaults to False.
smooth_borders (bool, optional): Flag for smoothing object borders to avoid straight edges in the objects. If True, the `min_threshold` and `max_threshold` parameters are ignored. Defaults to False.
object_shape (str, optional): Shape of the object to generate, either "cylinder", or "tube". Defaults to None.
seed (int, optional): Seed for reproducibility. Defaults to 0.
verbose (bool, optional): Flag to enable verbose logging. Defaults to False.
Returns:
synthetic_collection (numpy.ndarray): 3D volume of the generated collection of synthetic objects with specified parameters.
labels (numpy.ndarray): Array with labels for each voxel, same shape as synthetic_collection.
Raises:
TypeError: If `collection_shape` is not 3D.
ValueError: If blob parameters are invalid.
ValueError: If object parameters are invalid.
Note:
- The function places objects without overlap.
- The function can either place objects at random positions in the collection (if `positions = None`) or at specific positions provided in the `positions` argument. If specific positions are provided, the number of blobs must match the number of positions (e.g. `num_objects = 2` with `positions = [(12, 8, 10), (24, 20, 18)]`).
- If not all `num_objects` can be placed, the function returns the `synthetic_collection` volume with as many blobs as possible in it, and logs an error.
- Labels for all objects are returned, even if they are not a sigle connected component.
- If not all `num_objects` can be placed, the function returns the `synthetic_collection` volume with as many objects as possible in it, and logs an error.
- Labels for all objects are returned, even if they are not a single connected component.
Example:
```python
import qim3d
# Generate synthetic collection of blobs
# Generate synthetic collection of objects
num_objects = 15
synthetic_collection, labels = qim3d.generate.collection(num_objects = num_objects)
......@@ -207,12 +208,11 @@ def collection(
```
![synthetic_collection](assets/screenshots/synthetic_collection_default_labels.gif)
Example:
```python
import qim3d
# Generate synthetic collection of dense blobs
# Generate synthetic collection of dense objects
synthetic_collection, labels = qim3d.generate.collection(
min_high_value = 255,
max_high_value = 255,
......@@ -228,34 +228,66 @@ def collection(
```
<iframe src="https://platform.qim.dk/k3d/synthetic_collection_dense.html" width="100%" height="500" frameborder="0"></iframe>
Example:
```python
import qim3d
# Generate synthetic collection of tubular structures
synthetic_collection, labels = qim3d.generate.collection(
num_objects=10,
collection_shape=(200,100,100),
min_shape = (190, 50, 50),
max_shape = (200, 60, 60),
object_shape_zoom = (1, 0.2, 0.2),
min_object_noise = 0.01,
max_object_noise = 0.02,
max_rotation_degrees=10,
min_threshold = 0.95,
max_threshold = 0.98,
min_gamma = 0.02,
max_gamma = 0.03
)
# Generate synthetic collection of cylindrical structures
vol, labels = qim3d.generate.collection(num_objects = 40,
collection_shape = (300, 150, 150),
min_shape = (280, 10, 10),
max_shape = (290, 15, 15),
min_object_noise = 0.08,
max_object_noise = 0.09,
max_rotation_degrees = 5,
min_threshold = 0.7,
max_threshold = 0.9,
min_gamma = 0.10,
max_gamma = 0.11,
object_shape = "cylinder"
)
# Visualize synthetic collection
qim3d.viz.vol(synthetic_collection)
qim3d.viz.vol(vol)
```
<iframe src="https://platform.qim.dk/k3d/synthetic_collection_cylinder.html" width="100%" height="500" frameborder="0"></iframe>
```python
# Visualize slices
qim3d.viz.slices(vol, n_slices=15)
```
<iframe src="https://platform.qim.dk/k3d/synthetic_collection_tubular.html" width="100%" height="500" frameborder="0"></iframe>
![synthetic_collection_cylinder](assets/screenshots/synthetic_collection_cylinder_slices.png)
Example:
```python
import qim3d
# Generate synthetic collection of tubular (hollow) structures
vol, labels = qim3d.generate.collection(num_objects = 10,
collection_shape = (200, 200, 200),
min_shape = (180, 25, 25),
max_shape = (190, 35, 35),
min_object_noise = 0.02,
max_object_noise = 0.03,
max_rotation_degrees = 5,
min_threshold = 0.7,
max_threshold = 0.9,
min_gamma = 0.10,
max_gamma = 0.11,
object_shape = "tube"
)
# Visualize synthetic collection
qim3d.viz.vol(vol)
```
<iframe src="https://platform.qim.dk/k3d/synthetic_collection_tube.html" width="100%" height="500" frameborder="0"></iframe>
```python
# Visualize slices
qim3d.viz.slices(vol, n_slices=15, axis=1)
```
![synthetic_collection_tube](assets/screenshots/synthetic_collection_tube_slices.png)
"""
if verbose:
original_log_level = log.getEffectiveLevel()
......@@ -270,10 +302,6 @@ def collection(
if len(min_shape) != len(max_shape):
raise ValueError("Object shapes must be tuples of the same length")
# if not isinstance(blob_shapes, list) or \
# len(blob_shapes) != 2 or len(blob_shapes[0]) != 3 or len(blob_shapes[1]) != 3:
# raise TypeError("Blob shapes must be a list of two tuples with three dimensions (z, y, x)")
if (positions is not None) and (len(positions) != num_objects):
raise ValueError(
"Number of objects must match number of positions, otherwise set positions = None"
......@@ -301,6 +329,10 @@ def collection(
)
log.debug(f"- Blob shape: {blob_shape}")
# Scale object shape
final_shape = tuple(l * r for l, r in zip(blob_shape, object_shape_zoom))
final_shape = tuple(int(x) for x in final_shape) # NOTE: Added this
# Sample noise scale
noise_scale = rng.uniform(low=min_object_noise, high=max_object_noise)
log.debug(f"- Object noise scale: {noise_scale:.4f}")
......@@ -317,15 +349,16 @@ def collection(
threshold = rng.uniform(low=min_threshold, high=max_threshold)
log.debug(f"- Threshold: {threshold:.3f}")
# Generate synthetic blob
# Generate synthetic object
blob = qim3d.generate.blob(
base_shape=blob_shape,
final_shape=tuple(l * r for l, r in zip(blob_shape, object_shape_zoom)),
final_shape=final_shape,
noise_scale=noise_scale,
gamma=gamma,
max_value=max_value,
threshold=threshold,
smooth_borders=smooth_borders,
object_shape=object_shape,
)
# Rotate object
......@@ -336,21 +369,21 @@ def collection(
axes = rng.choice(rotation_axes) # Sample the two axes to rotate around
log.debug(f"- Rotation angle: {angle:.2f} at axes: {axes}")
blob = scipy.ndimage.rotate(blob, angle, axes, order=0)
blob = scipy.ndimage.rotate(blob, angle, axes, order=1)
# Place synthetic object into the collection
# If positions are specified, place blob at one of the specified positions
# If positions are specified, place object at one of the specified positions
collection_before = collection_array.copy()
if positions:
collection_array, placed, positions = specific_placement(
collection_array, blob, positions
)
# Otherwise, place blob at a random available position
# Otherwise, place object at a random available position
else:
collection_array, placed = random_placement(collection_array, blob, rng)
# Break if blob could not be placed
# Break if object could not be placed
if not placed:
break
......
......@@ -57,7 +57,7 @@ class BaseInterface(ABC):
If defined, the interface will be launched with the image already there
This argument is used especially in jupyter notebooks, where you can launch
interface in loop with different picture every step
force_light_mode: The qim platform doesn't have night mode. The qim_theme thus
force_light_mode: The QIM platform doesn't have night mode. The QimTheme thus
has option to display only light mode so it corresponds with the website. Preferably
will be removed as we add night mode to the website.
"""
......
......@@ -2,7 +2,7 @@ import gradio as gr
class QimTheme(gr.themes.Default):
"""
QIM3D Theme for gradio interface
Theme for qim3d gradio interfaces.
The theming options are quite broad. However if there is something you can not achieve with this theme
there is a possibility to add some more css if you override _get_css_theme function as shown at the bottom
in comments.
......@@ -12,7 +12,7 @@ class QimTheme(gr.themes.Default):
Parameters:
-----------
- force_light_mode (bool, optional): Gradio themes have dark mode by default.
Qim platform is not ready for dark mode yet, thus the tools should also be in light mode.
QIM platform is not ready for dark mode yet, thus the tools should also be in light mode.
This sets the darkmode values to be the same as light mode values.
"""
super().__init__()
......
......@@ -413,7 +413,8 @@ def save(
"""Save data to a specified file path.
Args:
path (str): The path to save file to
path (str): The path to save file to. File format is chosen based on the extension.
Supported extensions are: <em>'.tif', '.tiff', '.nii', '.nii.gz', '.h5', '.vol', '.vgi', '.dcm', '.DCM', '.zarr', '.jpeg', '.jpg', '.png'</em>
data (numpy.ndarray): The data to be saved
replace (bool, optional): Specifies if an existing file with identical path should be replaced.
Default is False.
......@@ -425,6 +426,13 @@ def save(
as several files (only relevant for TIFF stacks). Default is 0, i.e., the first dimension.
**kwargs: Additional keyword arguments to be passed to the DataSaver constructor
Raises:
ValueError: If the provided path is an existing directory and self.basename is not provided <strong>OR</strong>
If the file format is not supported <strong>OR</strong>
If the provided path does not exist and self.basename is not provided <strong>OR</strong>
If a file extension is not provided <strong>OR</strong>
if a file with the specified path already exists and replace=False.
Example:
```python
import qim3d
......