Skip to content
Snippets Groups Projects
Commit e5c6fc89 authored by David Grundfest's avatar David Grundfest
Browse files

Finished working on Layered surface segmentation

Gui is finished, refined and consulted
I removed the input images as the are redundant
I added documentation for the gui and for segment_layers and get_lines
parent d662cfd6
No related branches found
No related tags found
2 merge requests!117New Layered Surface Segmentation,!42Layered Surface Segmentation Feature
This commit is part of merge request !42. Comments created here will be created in the context of that merge request.
docs/assets/screenshots/GUI-layers.png

223 KiB

docs/assets/screenshots/layers.png

77 KiB

docs/assets/screenshots/segmented_layers.png

83.1 KiB

......@@ -30,6 +30,8 @@ This offers quick interactions, making it ideal for tasks that require efficienc
| `--data-explorer` | Starts the Data Explorer |
| `--iso3d` | Starts the 3D Isosurfaces visualization |
| `--local-thickness` | Starts the Local thickness tool |
| `--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 |
......
......@@ -36,3 +36,7 @@ For details see [here](cli.md#qim3d-gui).
::: qim3d.gui.annotation_tool
options:
members: False
::: qim3d.gui.layers2d
options:
members: False
\ No newline at end of file
......@@ -14,6 +14,8 @@ Here, we provide functionalities designed specifically for 3D image analysis and
- maximum
- minimum
- tophat
- get_lines
- segment_layers
::: qim3d.processing.Pipeline
options:
......
......@@ -48,6 +48,9 @@ class BaseInterface(ABC):
def set_invisible(self):
return gr.update(visible=False)
def change_visibility(self, is_visible):
return gr.update(visible = is_visible)
def launch(self, img=None, force_light_mode: bool = True, **kwargs):
"""
img: If None, user can upload image after the interface is launched.
......
This diff is collapsed.
......@@ -4,3 +4,4 @@ from .detection import blob_detection
from .filters import *
from .operations import *
from .cc import get_3d_cc
from .layers2d import segment_layers, get_lines
"""Class for layered surface segmentation in 2D images."""
import numpy as np
from scipy import signal as sig
import os
from slgbuilder import GraphObject
from slgbuilder import MaxflowBuilder
class Layers2d:
def segment_layers(data:np.ndarray, inverted:bool = False, n_layers:int = 1, delta:float = 1, min_margin:int = 10, max_margin:int = None, wrap:bool = False):
"""
Create an object to store graphs for layered surface segmentations.
Works on 2D and 3D data.
Light one function wrapper around slgbuilder https://github.com/Skielex/slgbuilder to do layer segmentation
Now uses only MaxflowBuilder for solving.
Args:
data (numpy.ndarray, optional): 2D image data.
n_layers (int, optional): Number of layers. Defaults to 1.
delta (int, optional): Smoothness parameter. Defaults to 1.
min_margin (int, optional): Minimum margin between layers. Defaults to 10.
inverted (bool, optional): Choose inverted data for segmentation. Defaults to False.
data: 2D or 3D array on which it will be computed
inverted: if True, it will invert the brightness of the image
n_layers: How many layers are we looking for (result in a layer and background)
delta: Smoothness parameter
min_margin: If we want more layers, we have to have a margin otherwise they are all going to be exactly the same
max_margin: Maximum margin between layers
wrap: If True, starting and ending point of the border between layers are at the same level
Returns:
segmentations: list of numpy arrays, even if n_layers == 1, each array is only 0s and 1s, 1s segmenting this specific layer
Raises:
TypeError: If `data` is not numpy.ndarray.
TypeError: If Data is not np.array, if n_layers is not integer.
ValueError: If n_layers is less than 1, if delta is negative or zero
Example:
layers2d = Layers2d(data = np_arr, n_layers = 3, delta = 5, min_margin = 20)
"""
def __init__(self,
data = None,
is_inverted = False,
n_layers = 1,
delta = 1,
min_margin = 10
):
'''
Create an object to store graphs for layered surface segmentations.\n
- 'Data' must be a numpy.ndarray.\n
- 'is_inverted' is a boolean which decides if the data is inverted or not.\n
- 'n_layers' is the number of layers.\n
- 'delta' is the smoothness parameter.\n
- 'min_margin' is the minimum margin between layers.\n
- 'data_not_inverted' is the original data.\n
- 'data_inverted' is the inverted data.\n
- 'layers' is a list of GraphObject objects.\n
- 'helper' is a MaxflowBuilder object.\n
- 'flow' is the result of the maxflow algorithm on the helper.\n
- 'segmentations' is a list of segmentations.\n
- 'segmentation_lines' is a list of segmentation lines.\n
'''
if data is not None:
if not isinstance(data, np.ndarray):
raise TypeError("Data must be a numpy.ndarray.")
self.data = data.astype(np.int32)
self.is_inverted = is_inverted
self.n_layers = n_layers
self.delta = delta
self.min_margin = min_margin
self.data_not_inverted = None
self.data_inverted = None
self.layers = []
self.helper = MaxflowBuilder()
self.flow = None
self.segmentations = []
self.segmentation_lines = []
def get_data(self):
return self.data
def set_data(self, data):
'''
Sets data.\n
- Data must be a numpy.ndarray.
'''
if not isinstance(data, np.ndarray):
raise TypeError("Data must be a numpy.ndarray.")
self.data = data.astype(np.int32)
def get_is_inverted(self):
return self.is_inverted
def set_is_inverted(self, is_inverted):
self.is_inverted = is_inverted
def get_delta(self):
return self.delta
def set_delta(self, delta):
self.delta = delta
def get_min_margin(self):
return self.min_margin
def set_min_margin(self, min_margin):
self.min_margin = min_margin
def get_data_not_inverted(self):
return self.data_not_inverted
def set_data_not_inverted(self, data_not_inverted):
self.data_not_inverted = data_not_inverted
Example is only shown on 2D image, but segment_layers can also take 3D structures.
```python
import qim3d
def get_data_inverted(self):
return self.data_inverted
layers_image = qim3d.io.load('layers3d.tif')[:,:,0]
layers = qim3d.processing.segment_layers(layers_image, n_layers = 2)
layer_lines = qim3d.processing.get_lines(layers)
def set_data_inverted(self, data_inverted):
self.data_inverted = data_inverted
from matplotlib import pyplot as plt
def update_data_not_inverted(self):
self.set_data_not_inverted(self.get_data())
plt.imshow(layers_image, cmap='gray')
plt.axis('off')
for layer_line in layer_lines:
plt.plot(layer_line, linewidth = 3)
```
![layer_segmentation](assets/screenshots/layers.png)
![layer_segmentation](assets/screenshots/segmented_layers.png)
def update_data_inverted(self):
if self.get_data() is not None:
self.set_data_inverted(~self.get_data())
else:
self.set_data_inverted(None)
def update_data(self):
'''
Updates data:\n
- If 'is_inverted' is True, data is set to 'data_inverted'.\n
- If 'is_inverted' is False, data is set to 'data_not_inverted'.
'''
if self.get_is_inverted():
self.set_data(self.get_data_inverted())
"""
if isinstance(data, np.ndarray):
data = data.astype(np.int32)
if inverted:
data = ~data
else:
self.set_data(self.get_data_not_inverted())
def get_n_layers(self):
return self.n_layers
def set_n_layers(self, n_layers):
self.n_layers = n_layers
def get_layers(self):
return self.layers
def set_layers(self, layers):
self.layers = layers
def add_layer_to_layers(self):
'''
Append a layer to layers.\n
- Data must be set and not Nonetype before adding a layer.\n
'''
if self.get_data() is None:
raise ValueError("Data must be set before adding a layer.")
self.get_layers().append(GraphObject(self.get_data()))
def add_n_layers_to_layers(self):
'''
Append n_layers to layers.
'''
for i in range(self.get_n_layers()):
self.add_layer_to_layers()
def update_layers(self):
'''
Updates layers:\n
- Resets layers to empty list.\n
- Appends n_layers to layers.
'''
self.set_layers([])
self.add_n_layers_to_layers()
def get_helper(self):
return self.helper
def set_helper(self, helper):
self.helper = helper
def create_new_helper(self):
'''
Creates a new helper MaxflowBuilder object.
'''
self.set_helper(MaxflowBuilder())
def add_objects_to_helper(self):
'''
Adds layers as objects to the helper.
'''
self.get_helper().add_objects(self.get_layers())
def add_layered_boundary_cost_to_helper(self):
'''
Adds layered boundary cost to the helper.
'''
self.get_helper().add_layered_boundary_cost()
raise TypeError(F"Data has to be type np.ndarray. Your data is of type {type(data)}")
def add_layered_smoothness_to_helper(self):
'''
Adds layered smoothness to the helper.
'''
self.get_helper().add_layered_smoothness(delta = self.get_delta())
helper = MaxflowBuilder()
if not isinstance(n_layers, int):
raise TypeError(F"Number of layers has to be positive integer. You passed {type(n_layers)}")
def add_a_layered_containment_to_helper(self, outer_object, inner_object):
'''
Adds a layered containment to the helper.
'''
self.get_helper().add_layered_containment(
outer_object = outer_object,
inner_object = inner_object,
min_margin = self.get_min_margin()
)
if n_layers == 1:
layer = GraphObject(data)
helper.add_object(layer)
elif n_layers > 1:
layers = [GraphObject(data) for _ in range(n_layers)]
helper.add_objects(layers)
for i in range(len(layers)-1):
helper.add_layered_containment(layers[i], layers[i+1], min_margin=min_margin, max_margin=max_margin)
def add_all_layered_containments_to_helper(self):
'''
Adds all layered containments to the helper.\n
n_layers most be at least 1.
'''
if len(self.get_layers()) < 1:
raise ValueError("There must be at least 1 layer to add containment.")
for i in range(self.get_n_layers()-1):
self.add_a_layered_containment_to_helper(
outer_object = self.get_layers()[i],
inner_object = self.get_layers()[i + 1]
)
def get_flow(self):
return self.flow
def set_flow(self, flow):
self.flow = flow
def solve_helper(self):
'''
Solves maxflow of the helper and stores the result in self.flow.
'''
self.set_flow(self.get_helper().solve())
def update_helper(self):
'''
Updates helper MaxflowBuilder object:\n
- Adds to helper:
- objects\n
- layered boundary cost\n
- layered smoothness\n
- all layered containments\n
- Finally solves maxflow of the helper.
'''
self.add_objects_to_helper()
self.add_layered_boundary_cost_to_helper()
self.add_layered_smoothness_to_helper()
self.add_all_layered_containments_to_helper()
self.solve_helper()
def get_segmentations(self):
return self.segmentations
def set_segmentations(self, segmentations):
self.segmentations = segmentations
def add_segmentation_to_segmentations(self, layer, type = np.int32):
'''
Adds a segmentation of a layer to segmentations.\n
'''
self.get_segmentations().append(self.get_helper().what_segments(layer).astype(type))
def add_all_segmentations_to_segmentations(self, type = np.int32):
'''
Adds all segmentations to segmentations.\n
- Resets segmentations to empty list.\n
- Appends segmentations of all layers to segmentations.
'''
self.set_segmentations([])
for l in self.get_layers():
self.add_segmentation_to_segmentations(l, type = type)
def get_segmentation_lines(self):
return self.segmentation_lines
def set_segmentation_lines(self, segmentation_lines):
self.segmentation_lines = segmentation_lines
def add_segmentation_line_to_segmentation_lines(self, segmentation):
'''
Adds a segmentation line to segmentation_lines.\n
- A segmentation line is the minimum values along a given axis of a segmentation.\n
- Each segmentation line is shifted by 0.5 to be in the middle of the pixel.
'''
self.get_segmentation_lines().append(sig.medfilt(
np.argmin(segmentation, axis = 0), kernel_size = 3))
def add_all_segmentation_lines_to_segmentation_lines(self):
'''
Adds all segmentation lines to segmentation_lines.\n
- Resets segmentation_lines to an empty list.\n
- Appends segmentation lines of all segmentations to segmentation_lines.
'''
self.set_segmentation_lines([])
for s in self.get_segmentations():
self.add_segmentation_line_to_segmentation_lines(s)
def update_semgmentations_and_semgmentation_lines(self, type = np.int32):
'''
Updates segmentations and segmentation_lines:\n
- Adds all segmentations to segmentations.\n
- Adds all segmentation lines to segmentation_lines.
'''
self.add_all_segmentations_to_segmentations(type = type)
self.add_all_segmentation_lines_to_segmentation_lines()
def prepare_update(self,
data = None,
is_inverted = None,
n_layers = None,
delta = None,
min_margin = None
):
'''
Prepare update of all fields of the object.\n
- If a field is None, it is not updated.\n
- If a field is not None, it is updated.
'''
if data is not None:
self.set_data(data)
if is_inverted is not None:
self.set_is_inverted(is_inverted)
if n_layers is not None:
self.set_n_layers(n_layers)
if delta is not None:
self.set_delta(delta)
if min_margin is not None:
self.set_min_margin(min_margin)
def update(self, type = np.int32):
'''
Update all fields of the object.
'''
self.update_data_not_inverted()
self.update_data_inverted()
self.update_data()
self.update_layers()
self.create_new_helper()
self.update_helper()
self.update_semgmentations_and_semgmentation_lines(type = type)
def __repr__(self):
'''
Returns string representation of all fields of the object.
'''
return "data: %s\n, \nis_inverted: %s, \nn_layers: %s, \ndelta: %s, \nmin_margin: %s, \ndata_not_inverted: %s, \ndata_inverted: %s, \nlayers: %s, \nhelper: %s, \nflow: %s, \nsegmentations: %s, \nsegmentations_lines: %s" % (
self.get_data(),
self.get_is_inverted(),
self.get_n_layers(),
self.get_delta(),
self.get_min_margin(),
self.get_data_not_inverted(),
self.get_data_inverted(),
self.get_layers(),
self.get_helper(),
self.get_flow(),
self.get_segmentations(),
self.get_segmentation_lines()
)
import matplotlib.pyplot as plt
from skimage.io import imread
from qim3d.io import load
if __name__ == "__main__":
# Draw results.
def visulise(l2d = None):
plt.figure(figsize = (10, 10))
ax = plt.subplot(1, 3, 1)
ax.imshow(l2d.get_data(), cmap = "gray")
ax = plt.subplot(1, 3, 2)
ax.imshow(np.sum(l2d.get_segmentations(), axis = 0))
ax = plt.subplot(1, 3, 3)
ax.imshow(data, cmap = "gray")
for line in l2d.get_segmentation_lines():
ax.plot(line)
plt.show()
# Data input
d_switch = False
if d_switch:
path = os.path.join(os.getcwd(), "qim3d", "img_examples", "slice_218x193.png")
data = imread(path).astype(np.int32)
else:
path = os.path.join(os.getcwd(), "qim3d", "img_examples", "bone_128x128x128.tif")
data3D = load(
path,
virtual_stack=True,
dataset_name="",
)
x = data3D.shape[0]
y = data3D.shape[1]
z = data3D.shape[2]
raise ValueError(F"Number of layers has to be positive integer. You passed {n_layers}")
helper.add_layered_boundary_cost()
if delta > 1:
delta = int(delta)
elif delta <= 0:
raise ValueError(F'Delta has to be positive number. You passed {delta}')
helper.add_layered_smoothness(delta=delta, wrap = bool(wrap))
helper.solve()
if n_layers == 1:
segmentations =[helper.what_segments(layer)]
else:
segmentations = [helper.what_segments(l).astype(np.int32) for l in layers]
data = data3D[x//2, :, :]
data = data3D[:, y//2, :]
data = data3D[:, :, z//2]
return segmentations
layers2d = Layers2d(data = data, n_layers = 3, delta = 1, min_margin = 10)
layers2d.update()
visulise(layers2d)
def get_lines(segmentations:list|np.ndarray) -> list:
"""
Expects list of arrays where each array is 2D segmentation with only 2 classes. This function gets the border between those two
so it could be plotted. Used with qim3d.processing.segment_layers
layers2d.prepare_update(n_layers = 1)
layers2d.update()
visulise(layers2d)
Args:
segmentations: list of arrays where each array is 2D segmentation with only 2 classes
layers2d.prepare_update(is_inverted = True)
layers2d.update()
visulise(layers2d)
\ No newline at end of file
Returns:
segmentation_lines: list of 1D numpy arrays
"""
segmentation_lines = [np.argmin(s, axis=0) - 0.5 for s in segmentations]
return segmentation_lines
\ No newline at end of file
""" Provides a collection of visualisation functions for the Layers2d class."""
import io
import matplotlib.pyplot as plt
import numpy as np
<<<<<<< HEAD
from qim3d.processing import layers2d as l2d
=======
from qim3d.process import layers2d as l2d
>>>>>>> 1622d378193877cb85f53b1a05207088b3f3cf0a
def create_subplot_of_2d_arrays(data, m_rows = 1, n_cols = 1, figsize = None):
'''
Creates a `m x n` grid subplot from a collection of 2D arrays.
Args:
`data` (list of 2D numpy.ndarray): A list of 2d numpy.ndarray.
`m_rows` (int): The number of rows in the subplot grid.
`n_cols` (int): The number of columns in the subplot grid.
from qim3d.processing import layers2d as l2d
Raises:
ValueError: If the product of m_rows and n_cols is not equal to the number of 2d arrays in data.
from PIL import Image
def image_with_overlay(image:np.ndarray, overlay:np.ndarray, alpha:int|float|np.ndarray = 125) -> Image:
#TODO : also accepts Image type
# We want to accept as many different values as possible to make convenient for the user.
"""
Takes image and puts a transparent segmentation mask on it.
Notes:
- Subplots are organized in a m rows x n columns Grid.
- The total number of subplots is equal to the product of m_rows and n_cols.
Parameters:
-----------
Image: Can be grayscale or colorful, accepts all kinds of shapes, color has to be the last axis
Overlay: If has its own alpha channel, alpha argument is ignored.
Alpha: Can be ansolute value as int, relative vlaue as float or an array so different parts can differ with the transparency.
Returns:
A tuple of (`fig`, `ax_list`), where fig is a matplotlib.pyplot.figure and ax_list is a list of matplotlib.pyplot.axes.
'''
total = m_rows * n_cols
---------
Image: PIL.Image in the original size as the image array
if total != len(data):
raise ValueError("The product of m_rows and n_cols must be equal to the number of 2D arrays in data.\nCurrently, m_rows * n_cols = {}, while arrays in data = {}".format(m_rows * n_cols, len(data)))
pos_idx = range(1, total + 1)
Raises:
--------
ValueError: If there is a missmatch of shapes or alpha has an invalid value.
"""
def check_dtype(image:np.ndarray):
if image.dtype != np.uint8:
minimal = np.min(image)
if minimal < 0:
image = image + minimal
maximum = np.max(image)
if maximum > 255:
image = (image/maximum)*255
elif maximum <= 1:
image = image*255
image = np.uint8(image)
return image
image = check_dtype(image)
overlay = check_dtype(overlay)
if image.shape[0] != overlay.shape[0] or image.shape[1] != overlay.shape[1]:
raise ValueError(F"The first two dimensions of overlay image must match those of background image.\nYour background image: {image.shape}\nYour overlay image: {overlay.shape}")
if image.ndim == 3:
if image.shape[2] < 3:
image = np.repeat(image[:,:,:1], 3, -1)
elif image.shape[2] > 4:
image = image[:,:,:4]
elif image.ndim == 2:
image = np.repeat(image[..., None], 3, -1)
if figsize is None:
figsize = (m_rows * 10, n_cols * 10)
fig = plt.figure(figsize = figsize)
else:
raise ValueError(F"Background image must have 2 or 3 dimensions. Yours have {image.ndim}")
ax_list = []
for k in range(total):
ax_list.append(fig.add_subplot(m_rows, n_cols, pos_idx[k]))
ax_list[k].imshow(data[k], cmap = "gray")
plt.tight_layout()
return fig, ax_list
if isinstance(alpha, (float, int)):
if alpha<0:
raise ValueError(F"Alpha can not be negative. You passed {alpha}")
elif alpha<=1:
alpha = int(255*alpha)
elif alpha> 255:
alpha = 255
else:
alpha = int(alpha)
def create_plot_of_2d_array(data, figsize = (10, 10)):
'''
Creates a plot of a 2D array.
elif isinstance(alpha, np.ndarray):
if alpha.ndim == 3:
alpha = alpha[..., :1] # Making sure it is only one layer
elif alpha.ndim == 2:
alpha = alpha[..., None] # Making sure it has 3 dimensions
else:
raise ValueError(F"If alpha is numpy array, it must have 2 or 3 dimensions. Your have {alpha.ndim}")
# We have not checked ndims of overlay
try:
if alpha.shape[0] != overlay.shape[0] or alpha.shape[1] != overlay.shape[1]:
raise ValueError(F"The first two dimensions of alpha must match those of overlay image.\nYour alpha: {alpha.shape}\nYour overlay: {overlay.shape}")
except IndexError:
raise ValueError(F"Overlay image must have 2 or 3 dimensions. Yours have {overlay.ndim}")
if overlay.ndim == 3:
if overlay.shape[2] < 3:
overlay = np.repeat(overlay[..., :1], 4, -1)
if alpha is None:
raise ValueError("Alpha can not be None if overlay image doesn't have alpha channel")
overlay[..., 3] = alpha
elif overlay.shape[2] == 3:
if isinstance(alpha, int):
overlay = np.concatenate((overlay, np.full((overlay.shape[0], overlay.shape[1], 1,), alpha, dtype = np.uint8)), axis = -1)
elif isinstance(alpha, np.ndarray):
overlay = np.concatenate((overlay, alpha), axis = -1)
elif overlay.shape[2]>4:
raise ValueError(F"Overlay image can not have more than 4 channels. Yours have {overlay.shape[2]}")
elif overlay.ndim == 2:
overlay = np.repeat(overlay[..., None], 4, axis = -1)
overlay[..., 3] = alpha
else:
raise ValueError(F"Overlay image must have 2 or 3 dimensions. Yours have {overlay.ndim}")
Args:
`data` (list of 2D numpy.ndarray): A list of 2d numpy.ndarray.
`figsize` (tuple of int): The figure size.
Notes:
- If data is not a list, it is converted to a list.
Returns:
A tuple of (`fig`, `ax`), where fig is a matplotlib.pyplot.figure and ax is a matplotlib.pyplot.axes.
'''
if not isinstance(data, list):
data = [data]
background = Image.fromarray(image)
overlay = Image.fromarray(overlay)
background.paste(overlay, mask = overlay)
return background
fig, ax_list = create_subplot_of_2d_arrays(data, figsize = figsize)
return fig, ax_list[0]
def merge_multiple_segmentations_2d(segmentations):
'''
Merges multiple segmentations of a 2D image into a single image.
def image_with_lines(image:np.ndarray, lines: list, line_thickness:float|int) -> Image:
"""
Plots the image and plots the lines on top of it. Then extracts it as PIL.Image and in the same size as the input image was.
Paramters:
-----------
image: Image on which we put the lines
lines: list of 1D arrays to be plotted on top of the image
line_thickness: how thick is the line supposed to be
Args:
`segmenations` (list of numpy.ndarray): A list of 2D numpy.ndarray.
Returns:
A 2D numpy.ndarray representing the merged segmentations.
'''
if len(segmentations) == 0:
raise ValueError("Segmentations must contain at least one segmentation.")
if len(segmentations) == 1:
return segmentations[0]
else:
return np.sum(segmentations, axis = 0)
def add_line_to_plot(axes, line, line_color = None):
'''
Adds a line to plot.
Args:
`axes` (matplotlib.pyplot.axes): A matplotlib.pyplot.axes.
`line` (numpy.ndarray): A 1D numpy.ndarray.
----------
image_with_lines:
"""
fig, ax = plt.subplots()
ax.imshow(image, cmap = 'gray')
ax.axis('off')
Notes:
- The line is added on top of to the plot.
'''
if line_color is None:
axes.plot(line)
else:
axes.plot(line, color = line_color)
def add_lines_to_plot(axes, lines, line_colors = None):
'''
Adds multiple lines to plot.
for line in lines:
ax.plot(line, linewidth = line_thickness)
Args:
`axes` (matplotlib.pyplot.axes): A matplotlib.pyplot.axes.
`lines` (list of numpy.ndarray): A list of 1D numpy.ndarray.
buf = io.BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0)
plt.close()
Notes:
- The lines are added on top of to the plot.
'''
if line_colors is None:
for line in lines:
axes.plot(line)
else:
for i in range(len(lines)):
axes.plot(lines[i], color = line_colors[i])
import os
from skimage.io import imread
if __name__ == "__main__":
path = os.path.join(os.getcwd(), "qim3d", "img_examples", "slice_218x193.png")
data = imread(path).astype(np.int32)
l2d_obj = l2d.Layers2d()
l2d_obj.prepare_update(
data = data,
is_inverted=False,
delta=1,
min_margin=10,
n_layers=4,
)
l2d_obj.update()
# Show how create_plot_from_2d_arrays works:
fig1, ax1 = create_plot_of_2d_array(l2d_obj.get_data())
data_lines = []
for i in range(len(l2d_obj.get_segmentation_lines())):
data_lines.append(l2d_obj.get_segmentation_lines()[i])
# Show how add_line_to_plot works:
add_line_to_plot(ax1, data_lines[3])
# Show how merge_multiple_segmentations_2d works:
data_seg = []
for i in range(len(l2d_obj.get_segmentations())):
data_seg.append(merge_multiple_segmentations_2d(l2d_obj.get_segmentations()[:i+1]))
# Show how create_subplot_of_2d_arrays works:
fig2, ax_list = create_subplot_of_2d_arrays(
data_seg,
m_rows = 1,
n_cols = len(l2d_obj.get_segmentations())
# m_rows = len(l2d_obj.get_segmentations()),
# n_cols = 1
)
# Show how add_lines_to_plot works:
add_lines_to_plot(ax_list[1], data_lines[0:3])
plt.show()
buf.seek(0)
return Image.open(buf).resize(size = image.squeeze().shape[::-1])
......@@ -10,7 +10,7 @@ Pillow>=10.0.1
plotly>=5.14.1
scipy>=1.11.2
seaborn>=0.12.2
pydicom>=2.4.4
pydicom==2.4.4
setuptools>=68.0.0
tifffile>=2023.4.12
torch>=2.0.1
......
......@@ -49,7 +49,7 @@ setup(
"h5py>=3.9.0",
"localthickness>=0.1.2",
"matplotlib>=3.8.0",
"pydicom>=2.4.4",
"pydicom==2.4.4",
"numpy>=1.26.0",
"outputformat>=0.1.3",
"Pillow>=10.0.1",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment