Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for vtp read and write #41

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions .github/workflows/test_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,6 @@ jobs:
# these libraries enable testing on Qt on linux
- uses: tlambert03/setup-qt-libs@v1

- name: Set up conda ${{ matrix.python-version }}
uses: conda-incubator/setup-miniconda@v2.0.0
with:
auto-update-conda: true
activate-environment: test
python-version: ${{ matrix.python-version }}
channels: conda-forge

- name: Conda info
shell: bash -l {0}
run: conda info

# note: if you need dependencies from conda, considering using
# setup-miniconda: https://github.com/conda-incubator/setup-miniconda
# and
Expand Down
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"