Skip to content

Commit

Permalink
added support for vtp read and write
Browse files Browse the repository at this point in the history
  • Loading branch information
jo-mueller committed Aug 15, 2023
1 parent 7d1a4c2 commit 3da1f29
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 17 deletions.
29 changes: 27 additions & 2 deletions src/napari_stl_exporter/_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@
import numpy as np
from typing import Optional, List

supported_formats = ['.stl', '.ply', '.obj']
supported_surface_formats = ['.stl', '.ply', '.obj']
suported_points_formats = ['.vtp']


def get_reader(path: str) -> Optional[callable]:
# Taken from https://napari.org/plugins/guides.html

file_ext = os.path.splitext(path)[1]
if isinstance(path, str) and file_ext in supported_formats:
if isinstance(path, str) and file_ext in supported_surface_formats:
return napari_import_surface
elif isinstance(path, str) and file_ext in suported_points_formats:
return napari_import_points

# otherwise we return None.
return None


def napari_import_surface(path: str) -> List[LayerDataTuple]:
"""
Read supported surface files using the vedo io functionality.
Expand All @@ -33,3 +38,23 @@ def napari_import_surface(path: str) -> List[LayerDataTuple]:
mesh = vedo.load(path, unpack=True, force=False)
_mesh = [mesh.points(), np.asarray(mesh.faces())]
return [tuple([_mesh, {}, 'surface'])]


def napari_import_points(path: str) -> List[LayerDataTuple]:
"""
Read supported point cloud files using the vedo io functionality.
Parameters
----------
path : str
Returns
-------
PointsData
"""
import pandas as pd
points = vedo.load(path, unpack=True, force=False)
features = pd.DataFrame(dict(points.pointdata))
return [(points.points(), {'features': features,
'face_color': features.columns[0]}, 'points')]
67 changes: 56 additions & 11 deletions src/napari_stl_exporter/_tests/test_writer_and_readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import os
import vedo

supported_formats = ['.stl', '.ply', '.obj']
supported_surface_formats = ['.stl', '.ply', '.obj']
supported_points_formats = ['.vtp']


def test_label_conversion():
import vedo
Expand All @@ -14,40 +16,70 @@ def test_label_conversion():

assert isinstance(mesh, vedo.mesh.Mesh)


def test_writer(tmpdir):
from skimage import measure
from napari_stl_exporter._writer import napari_write_labels, napari_write_surfaces
import pandas as pd
from napari_stl_exporter._writer import (
napari_write_labels,
napari_write_surfaces,
napari_write_points)

label_image = np.zeros((100, 100, 100))
label_image[25:75, 25:75, 25:75] = 1

for ext in supported_formats:
points_data_3d = np.random.rand(100, 3)
points_data_3d_features = pd.DataFrame(
np.random.rand(100, 3),
columns=['feat1', 'feat2', 'feat3'])

for ext in supported_surface_formats:
pth = os.path.join(str(tmpdir), "test_export" + ext)
stl_file = napari_write_labels(pth, label_image, None)
assert os.path.exists(pth)
assert stl_file is not None

surf = measure.marching_cubes(label_image)

for ext in supported_formats:
for ext in supported_surface_formats:
pth = os.path.join(str(tmpdir), "test_export" + ext)
stl_file = napari_write_surfaces(pth, surf, None)
assert os.path.exists(pth)
assert stl_file is not None

for ext in supported_points_formats:
pth = os.path.join(str(tmpdir), "test_export" + ext)
vtp_file3d = napari_write_points(
pth,
points_data_3d,
{'features': points_data_3d_features})
assert os.path.exists(pth)
assert vtp_file3d is not None


def test_writer_viewer(make_napari_viewer, tmpdir):
import napari
import pandas as pd
# Load and binarize image
label_image = np.zeros((100, 100, 100), dtype=int)
points_data_3d = np.random.rand(100, 3)
points_data_3d_features = pd.DataFrame(
np.random.rand(100, 3),
columns=['feat1', 'feat2', 'feat3'])
label_image[25:75, 25:75, 25:75] = 1

# Add data to viewer
viewer = make_napari_viewer()
label_layer = viewer.add_labels(label_image, name='3D object')
points_layer = viewer.add_points(points_data_3d, name='3D points',
features=points_data_3d_features)

# save the layer as 3D printable file to disc
napari.save_layers(os.path.join(tmpdir, 'test.stl'), [label_layer])
napari.save_layers(os.path.join(tmpdir, 'test.vtp'), [points_layer])
assert os.path.exists(os.path.join(tmpdir, 'test.stl'))


def test_sample_data():
from napari_stl_exporter._sample_data import make_pyramid_label, make_pyramid_surface
label_image = make_pyramid_label()
Expand All @@ -56,16 +88,23 @@ def test_sample_data():
surface_image = make_pyramid_surface()
assert surface_image is not None


def test_reader(make_napari_viewer, tmpdir):
import napari_stl_exporter
import pandas as pd
from napari_stl_exporter._sample_data import make_pyramid_surface
from napari_stl_exporter._writer import napari_write_surfaces
from napari_stl_exporter._reader import napari_import_surface
from napari_stl_exporter._writer import napari_write_surfaces, napari_write_points
from napari_stl_exporter._reader import napari_import_surface, napari_import_points

points_data_3d = np.random.rand(100, 3)
points_data_3d_features = pd.DataFrame(
np.random.rand(100, 3),
columns=['feat1', 'feat2', 'feat3'])

pyramid = napari_stl_exporter.make_pyramid_surface()[0][0]
viewer = make_napari_viewer()

for ext in supported_formats:
for ext in supported_surface_formats:
path = os.path.join(tmpdir, 'test' + ext)
napari_write_surfaces(path, pyramid, None)
_pyramid = napari_import_surface(path)[0]
Expand All @@ -76,13 +115,23 @@ def test_reader(make_napari_viewer, tmpdir):
data = reader(path)
assert data is not None

for ext in supported_points_formats:
path = os.path.join(tmpdir, 'test' + ext)
napari_write_points(path, points_data_3d, {'features': points_data_3d_features})
_points = napari_import_points(path)[0]

layer = viewer.add_points(_points[0], **_points[1])
assert hasattr(layer, 'features')


def test_image_surface_conversion():
from skimage import data
from napari_stl_exporter._image_to_surface import image_to_surface

image = data.cell()
surface = image_to_surface(image)


def test_widgets(make_napari_viewer):
from napari_stl_exporter._image_to_surface import image_to_surface_widget, extrude_widget

Expand All @@ -93,7 +142,3 @@ def test_widgets(make_napari_viewer):

viewer.window.add_dock_widget(widget_surface)
viewer.window.add_dock_widget(widget_extrude)

if __name__ == '__main__':
import napari
test_widgets(napari.Viewer)
34 changes: 30 additions & 4 deletions src/napari_stl_exporter/_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,54 @@
from skimage import measure
import vedo

from napari.types import LabelsData, SurfaceData
from napari.types import LabelsData, SurfaceData, PointsData
from typing import Optional


supported_layers = ['labels']
supported_formats = ['.stl', '.ply', '.obj']
supported_surface_formats = ['.stl', '.ply', '.obj']
supported_points_formats = ['.vtp']


def napari_write_surfaces(path: str, data: SurfaceData, meta: dict
) -> Optional[str]:
file_ext = os.path.splitext(path)[1]
if file_ext in supported_formats:
if file_ext in supported_surface_formats:
mesh = vedo.mesh.Mesh((data[0], data[1]))
vedo.write(mesh, path)

return path


def napari_write_points(
path: str, data: PointsData, meta: dict
) -> Optional[str]:
file_ext = os.path.splitext(path)[1]
if file_ext in supported_points_formats:
points = vedo.Points(data)

# if metadata doesn't exist
if meta is None:
vedo.write(points, path)
return path

# add features to pointcloud
if 'properties' in meta:
features = meta['properties']
elif 'features' in meta:
features = meta['features']
for key in features:
points.pointdata[key] = features[key]
vedo.write(points, path)

return path


def napari_write_labels(path: str, data: LabelsData, meta: dict
) -> Optional[str]:

file_ext = os.path.splitext(path)[1]
if file_ext in supported_formats:
if file_ext in supported_surface_formats:

mesh = _labels_to_mesh(data)
vedo.write(mesh, path)
Expand Down
17 changes: 17 additions & 0 deletions src/napari_stl_exporter/napari.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ contributions:
title: Write Surface
python_name: napari_stl_exporter._writer:napari_write_surfaces

- id: napari-stl-exporter.write_points
title: Write Points
python_name: napari_stl_exporter._writer:napari_write_points
- id: napari_stl_exporter.read_points
title: Read Points
python_name: napari_stl_exporter._reader:napari_import_points

- id: napari-stl-exporter.make_pyramid_label
title: Create a label image of a pyramid
python_name: napari_stl_exporter._sample_data:make_pyramid_label
Expand Down Expand Up @@ -61,10 +68,20 @@ contributions:
filename_extensions: [".stl", ".ply", ".obj"]
display_name: surface

- command: napari-stl-exporter.write_points
layer_types:
- points
filename_extensions: [".vtp"]
display_name: points

readers:
- command: napari-stl-exporter.import_surface
accepts_directories: false
filename_patterns:
- "*.stl"
- "*.ply"
- "*.obj"
- command: napari-stl-exporter.read_points
accepts_directories: false
filename_patterns:
- "*.vtp"

0 comments on commit 3da1f29

Please sign in to comment.