diff --git a/src/blik/napari.yaml b/src/blik/napari.yaml index b8c2912..275d7ef 100644 --- a/src/blik/napari.yaml +++ b/src/blik/napari.yaml @@ -40,12 +40,15 @@ contributions: - id: blik.main_widget python_name: blik.widgets.main_widget:MainBlikWidget title: "Open blik main widget" - - id: blik.filament_picking - python_name: blik.widgets.picking:FilamentWidget - title: "Open blik filament picking widget" - id: blik.surface_picking python_name: blik.widgets.picking:SurfaceWidget title: "Open blik surface picking widget" + - id: blik.sphere_picking + python_name: blik.widgets.picking:SphereWidget + title: "Open sphere picking widget" + - id: blik.filament_picking + python_name: blik.widgets.picking:FilamentWidget + title: "Open blik filament picking widget" - id: blik.file_reader_widget python_name: blik.widgets.file_reader:file_reader title: "Open blik file reader widget" @@ -66,8 +69,8 @@ contributions: - command: blik.power_spectrum napari/layers/annotate: - command: blik.main_widget - - command: blik.filament_picking - command: blik.surface_picking + - command: blik.sphere_picking - command: blik.filament_picking napari/layers/filter: - command: blik.bandpass_filter @@ -127,10 +130,12 @@ contributions: widgets: - command: blik.main_widget display_name: "Blik main widget" - - command: blik.filament_picking - display_name: "Filament picking" - command: blik.surface_picking display_name: "Surface picking" + - command: blik.sphere_picking + display_name: "Sphere picking" + - command: blik.filament_picking + display_name: "Filament picking" - command: blik.file_reader_widget display_name: "File reader" - command: blik.bandpass_filter diff --git a/src/blik/widgets/main_widget.py b/src/blik/widgets/main_widget.py index d78b32d..a1081c8 100644 --- a/src/blik/widgets/main_widget.py +++ b/src/blik/widgets/main_widget.py @@ -155,7 +155,13 @@ def add_to_exp(layer: napari.layers.Layer): labels=False, call_button="Create", l_type={ - "choices": ["segmentation", "particles", "surface_picking", "filament_picking"] + "choices": [ + "segmentation", + "particles", + "surface_picking", + "sphere_picking", + "filament_picking", + ] }, ) def new( @@ -221,6 +227,20 @@ def new( units="angstrom", ) + return [pts] + elif l_type == "sphere_picking": + for lay in layers: + if isinstance(lay, Image) and lay.metadata["experiment_id"] == exp_id: + pts = Points( + name=f"{exp_id} - sphere picks", + size=20 / lay.scale[0], + scale=lay.scale, + metadata={"experiment_id": exp_id}, + ndim=3, + # axis_labels=('z', 'y', 'x'), + units="angstrom", + ) + return [pts] show_info(f"cannot create a new {l_type}") diff --git a/src/blik/widgets/picking.py b/src/blik/widgets/picking.py index 469a3fc..788b249 100644 --- a/src/blik/widgets/picking.py +++ b/src/blik/widgets/picking.py @@ -5,12 +5,15 @@ from magicgui import magicgui from magicgui.widgets import Container from morphosamplers.helical_filament import HelicalFilament +from morphosamplers.models import Sphere +from morphosamplers.preprocess import get_label_paths_3d from morphosamplers.sampler import ( sample_volume_along_spline, sample_volume_around_surface, ) +from morphosamplers.samplers.sphere_samplers import PointSampler, PoseSampler from morphosamplers.surface_spline import GriddedSplineSurface -from morphosamplers.preprocess import get_label_paths_3d +from scipy.spatial import ConvexHull from scipy.spatial.transform import Rotation from ..reader import construct_particle_layer_tuples @@ -85,7 +88,9 @@ def _generate_surface_grids_from_labels_layer( ) # doing this "custom" because we need to flip xyz - surfaces_lines = get_label_paths_3d(compute(surface_label.data)[0], axis=0, slicing_step=10, sampling_step=10) + surfaces_lines = get_label_paths_3d( + compute(surface_label.data)[0], axis=0, slicing_step=10, sampling_step=10 + ) for lines in surfaces_lines: lines = [invert_xyz(line.astype(float)) for line in lines] @@ -393,6 +398,99 @@ def resample_filament( ) +@magicgui( + labels=True, + call_button="Generate", +) +def sphere( + point_picks: napari.layers.Points, +) -> napari.types.LayerDataTuple: + points = invert_xyz(point_picks.data) + vert_all = [] + faces_all = [] + spheres_all = [] + + exp_id = "" + faces_offset = 0 + for i, (c, n) in enumerate(zip(points[::2], points[1::2])): + r = np.linalg.norm(n - c) + + s = Sphere(center=c, radius=r) + ps = PointSampler(spacing=r / 10) + positions = ps.sample(s) + + h = ConvexHull(positions) + tri = h.points[h.simplices] + + # fix faces ordering + edges = tri - np.roll(tri, 1, axis=1) + cross = np.cross(edges[:, 0], edges[:, 1]) + direction = np.einsum("...j,...j", cross, tri[:, 0] - (0, 0, 0)) + faces = h.simplices.copy() + faces[direction < 0] = h.simplices[direction < 0][:, ::-1] + faces += i * faces_offset + faces_offset += len(positions) + + exp_id = point_picks.metadata["experiment_id"] + vert_all.append(positions) + faces_all.append(faces) + spheres_all.append(s) + + surface_layer_tuple = ( + (invert_xyz(np.concatenate(vert_all)), np.concatenate(faces_all)), + { + "name": f"{exp_id} - surface", + "metadata": { + "experiment_id": exp_id, + "spheres": spheres_all, + }, + "scale": point_picks.scale, + # smooth shading is bugged cause of some ordering issue + "shading": "flat", + }, + "surface", + ) + return [surface_layer_tuple] + + +@magicgui( + labels=True, + call_button="Generate", + spacing_A={"widget_type": "FloatSlider", "min": 0.01, "max": 10000}, +) +def sphere_particles( + sphere_surf: napari.layers.Surface, + spacing_A=50, +) -> napari.types.LayerDataTuple: + spheres = sphere_surf.metadata.get("spheres", None) + if spheres is None: + raise ValueError("This surface layer contains no sphere objects.") + + exp_id = sphere_surf.metadata["experiment_id"] + + spacing_A /= sphere_surf.scale[0] + + pos = [] + ori = [] + for s in spheres: + ps = PoseSampler(spacing=spacing_A) + poses = ps.sample(s) + + features = pd.DataFrame( + {"orientation": np.asarray(Rotation.from_matrix(poses.orientations))} + ) + pos.append(poses.positions) + ori.append(features) + + return construct_particle_layer_tuples( + coords=np.concatenate(pos), + features=pd.concat(ori, axis=0), + scale=sphere_surf.scale[0], + exp_id=exp_id, + name_suffix="spheres picked", + ) + + class FilamentWidget(Container): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -409,3 +507,11 @@ def __init__(self, *args, **kwargs): self.append(surface) self.append(surface_particles) self.append(resample_surface) + + +class SphereWidget(Container): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.append(sphere) + self.append(sphere_particles)