Skip to content
Merged
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
6 changes: 6 additions & 0 deletions src/cedalion/geometry/landmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import xarray as xr
import pyvista as pv

import cedalion
import cedalion.vis.blocks as vbx
from cedalion import cite

Expand Down Expand Up @@ -198,6 +199,11 @@ def __init__(self, scalp_surface: Surface, landmarks: LabeledPoints):
landmarks (LabeledPoints): positions of "Nz", "Iz", "LPA", "RPA"
"""
cite("Oostenveld2001")
if scalp_surface.units != cedalion.units.mm:
raise NotImplementedError(
"LandmarksBuilder1010 currently requires a scalp surface in "
f"mm; got units={scalp_surface.units}."
)
if isinstance(scalp_surface, TrimeshSurface):
scalp_surface = VTKSurface.from_trimeshsurface(scalp_surface)

Expand Down
6 changes: 5 additions & 1 deletion src/cedalion/geometry/photogrammetry/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,11 @@ def process(
surface normal vectors
aux. detail object if detail == True
"""
assert surface.units == units.mm # FIXME Einstar yields mm. Allow other units.
if surface.units != units.mm:
raise NotImplementedError(
"ColoredStickerProcessor currently supports mm-scale "
f"surfaces only; got units={surface.units}."
)

vertex_colors, h, s, v = self._extract_vertex_color_data(surface)

Expand Down
18 changes: 12 additions & 6 deletions src/cedalion/io/photogrammetry.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
"""Module for reading photogrammetry output file formats."""

import cedalion
import cedalion.dataclasses as cdc
import numpy as np
from collections import OrderedDict


def read_photogrammetry_einstar(fn):
def read_photogrammetry_einstar(fn, units=cedalion.units.mm):
"""Read optodes and fiducials from photogrammetry pipeline.

This method reads the output file as returned by the
photogrammetry pipeline using an einstar device.

Args:
fn (str): The filename of the einstar photogrammetry output file.
units: Units of the coordinates in the file. Defaults to millimetres
(Einstar convention). Pass e.g. ``cedalion.units.m`` for exports
in metres.

Returns:
tuple: A tuple containing:
Expand All @@ -24,7 +28,7 @@ def read_photogrammetry_einstar(fn):
fiducials, optodes = read_einstar(fn)
fiducials = OrderedDict((k, v) for k, v in fiducials.items() if v != [])
optodes = OrderedDict((k, v) for k, v in optodes.items() if v != [])
fiducials, optodes = opt_fid_to_xr(fiducials, optodes)
fiducials, optodes = opt_fid_to_xr(fiducials, optodes, units=units)
return fiducials, optodes


Expand Down Expand Up @@ -53,12 +57,13 @@ def read_einstar(fn):
return fiducials, optodes


def opt_fid_to_xr(fiducials, optodes):
def opt_fid_to_xr(fiducials, optodes, units=cedalion.units.mm):
"""Convert OrderedDicts fiducials and optodes to cedalion LabeledPoints objects.

Args:
fiducials (OrderedDict): The fiducials as an OrderedDict.
optodes (OrderedDict): The optodes as an OrderedDict.
units: Units to attach to the returned LabeledPoints. Defaults to mm.

Returns:
tuple: A tuple containing:
Expand All @@ -81,8 +86,8 @@ def opt_fid_to_xr(fiducials, optodes):
opt_coords = np.array(list(optodes.values()))

fiducials = cdc.build_labeled_points(
fidu_coords, labels=list(fiducials.keys()), crs=CRS
) # , units="mm")
fidu_coords, labels=list(fiducials.keys()), crs=CRS, units=units,
)

types = []
for lab in list(optodes.keys()):
Expand All @@ -93,6 +98,7 @@ def opt_fid_to_xr(fiducials, optodes):
else:
types.append(cdc.PointType(0))
optodes = cdc.build_labeled_points(
opt_coords, labels=list(optodes.keys()), crs=CRS, types=types
opt_coords, labels=list(optodes.keys()), crs=CRS, types=types,
units=units,
)
return fiducials, optodes
16 changes: 12 additions & 4 deletions src/cedalion/io/probe_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,25 @@ def read_digpts(fname: str, units: str="mm") -> xr.DataArray:
return result


def read_einstar_obj(fname: str) -> TrimeshSurface:
"""Read a textured triangle mesh generated by Einstar 3D scanners.
def read_einstar_obj(
fname: str,
crs: str = "digitized",
units=cedalion.units.mm,
) -> TrimeshSurface:
"""Read a textured triangle mesh (Einstar, Scaniverse, ...).

Args:
fname: Path to the ``.obj`` mesh file.
crs: Coordinate reference system tag. Defaults to ``"digitized"``.
units: Units of the vertex coordinates in the file. Defaults to
millimetres (Einstar convention). Pass e.g. ``cedalion.units.m``
for Scaniverse exports.

Returns:
TrimeshSurface in the ``"digitized"`` CRS with millimetre units.
TrimeshSurface in the given CRS and units.
"""
mesh = trimesh.load(fname)
return TrimeshSurface(mesh, crs="digitized", units=cedalion.units.mm)
return TrimeshSurface(mesh, crs=crs, units=units)


def read_fieldtrip_elc(fname : Path | str) -> cdt.LabeledPoints:
Expand Down
17 changes: 15 additions & 2 deletions src/cedalion/vis/anatomy/optode_selector.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pyvista as pv
import cedalion
from cedalion.dataclasses import PointType
import cedalion.dataclasses as cdc
import numpy as np
Expand Down Expand Up @@ -35,6 +36,20 @@ class OptodeSelector:
- Masha Iudina | mashayudi@gmail.com | 2024
"""
def __init__(self, surface, points, normals=None, plotter=None, labels = None):
if surface.units != cedalion.units.mm:
raise NotImplementedError(
"OptodeSelector currently requires a surface in mm; got "
f"units={surface.units}. Convert the surface to mm before "
"picking (e.g. `surface.mesh.apply_scale(factor); "
"surface.units = cedalion.units.mm`)."
)
if points.pint.units != cedalion.units.mm:
raise NotImplementedError(
"OptodeSelector currently requires points in mm; got "
f"units={points.pint.units}. Convert with "
"`points = points.pint.to('mm')` first."
)

self.points = points
self.normals = normals
self.surface = surface
Expand Down Expand Up @@ -65,8 +80,6 @@ def plot(self):
PointType.ELECTRODE: 3,
}

# points = points.pint.to("mm").pint.dequantify() # FIXME unit handling
# points = points.pint.dequantify() # FIXME unit handling
for type, x in points.groupby("type"):
for i_point in range(len(x)):

Expand Down
9 changes: 9 additions & 0 deletions src/cedalion/vis/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from vtk.util.numpy_support import numpy_to_vtk
from numpy.typing import ArrayLike

import cedalion
import cedalion.dataclasses as cdc
import cedalion.typing as cdt
from cedalion.dataclasses import PointType
Expand Down Expand Up @@ -162,6 +163,14 @@ def plot_surface(
- Masha Iudina | mashayudi@gmail.com | 2024
"""

if pick_landmarks and surface.units != cedalion.units.mm:
raise NotImplementedError(
"plot_surface(pick_landmarks=...) currently requires a surface "
f"in mm; got units={surface.units}. Convert the surface to mm "
"before picking (e.g. `surface.mesh.apply_scale(factor); "
"surface.units = cedalion.units.mm`)."
)

if isinstance(surface, cdc.VTKSurface):
mesh = surface.mesh
elif isinstance(surface, cdc.TrimeshSurface):
Expand Down
3 changes: 2 additions & 1 deletion tests/test_landmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import trimesh
import xarray as xr

import cedalion
import cedalion.dataclasses as cdc
from cedalion.geometry.landmarks import (
LandmarksBuilder1010,
Expand Down Expand Up @@ -262,7 +263,7 @@ def _icosphere_scalp():
sphere = trimesh.creation.icosphere(subdivisions=4, radius=90.0)
# squash into a vaguely head-shaped ellipsoid (a, b, c)
sphere.vertices = sphere.vertices * np.array([85.0, 100.0, 90.0]) / 90.0
surf = cdc.TrimeshSurface(sphere, crs="ras", units="mm")
surf = cdc.TrimeshSurface(sphere, crs="ras", units=cedalion.units.mm)

landmarks = cdc.build_labeled_points(
[
Expand Down
Loading