Skip to content

Commit

Permalink
Merge pull request #10 from LaboratoryOpticsBiosciences/update
Browse files Browse the repository at this point in the history
Multiple improvements
  • Loading branch information
ClementCaporal authored Dec 20, 2024
2 parents 2190411 + 2e16985 commit 345eb17
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 48 deletions.
6 changes: 4 additions & 2 deletions src/napari_swc_editor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from ._version import version as __version__
except ImportError:
__version__ = "unknown"
from ._reader import napari_get_reader
from ._sample_data import make_sample_data
from ._reader import napari_get_reader, read_swc
from ._sample_data import make_empty_sample, make_sample_data
from ._widget import (
ExampleQWidget,
ImageThreshold,
Expand All @@ -14,9 +14,11 @@

__all__ = (
"napari_get_reader",
"read_swc",
"write_single_image",
"write_multiple",
"make_sample_data",
"make_empty_sample",
"ExampleQWidget",
"ImageThreshold",
"threshold_autogenerate_widget",
Expand Down
32 changes: 5 additions & 27 deletions src/napari_swc_editor/_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

import napari

from .bindings import bind_layers_with_events
from .swc_io import parse_data_from_swc_file, structure_id_to_symbol
from .bindings import add_napari_layers_from_swc_content


def napari_get_reader(path):
Expand Down Expand Up @@ -66,29 +65,8 @@ def reader_function(path):
with open(_path) as f:
file_content = f.read()

points, radius, lines, structure = parse_data_from_swc_file(
file_content
)
point_layer, shape_layer = add_napari_layers_from_swc_content(file_content, viewer)
return (point_layer, shape_layer)

structure_symbol = structure_id_to_symbol(structure)

shape_layer = viewer.add_shapes(
lines, shape_type="line", edge_width=radius
)

add_kwargs_points = {
"n_dimensional": True,
"size": radius,
"blending": "additive",
"symbol": structure_symbol,
"metadata": {
"raw_swc": file_content,
"shape_layer": shape_layer,
},
}

point_layer = viewer.add_points(points, **add_kwargs_points)

bind_layers_with_events(point_layer, shape_layer)

return [point_layer, shape_layer]
def read_swc(path):
return reader_function(path)
12 changes: 12 additions & 0 deletions src/napari_swc_editor/_sample_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from __future__ import annotations

import tempfile
from urllib.request import urlretrieve

from ._reader import reader_function
Expand All @@ -20,3 +21,14 @@ def make_sample_data():
urlretrieve(url, "204-2-6nj.CNG.swc")
result = reader_function("204-2-6nj.CNG.swc")
return result


def make_empty_sample():
content = "# SWC created using napari-swc-editor\n"
# create temp file
with tempfile.NamedTemporaryFile(
mode="w", suffix=".swc", delete=False
) as f:
f.write(content)
result = reader_function(f.name)
return result
85 changes: 82 additions & 3 deletions src/napari_swc_editor/bindings.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,74 @@
import napari

from .swc_io import (
add_edge,
add_points,
create_line_data_from_swc_data,
get_treenode_id_from_index,
move_points,
parse_data_from_swc_file,
parse_swc_content,
remove_edge,
remove_points,
sort_edge_indices,
structure_id_to_symbol,
symbol_to_structure_id,
update_point_properties,
)


def add_napari_layers_from_swc_content(
file_content: str, viewer: napari.Viewer
):
"""Create layers from a swc file
Parameters
----------
file_content : swc_content
Content of the swc file
Must have the following columns:
- treenode_id
- structure_id
- x
- y
- z
- r
- parent_treenode_id
viewer : napari.Viewer
Returns
-------
layers : list of tuples
List of layers to be added to the napari viewer
"""

points, radius, lines, structure = parse_data_from_swc_file(file_content)

structure_symbol = structure_id_to_symbol(structure)

shape_layer = viewer.add_shapes(
lines, shape_type="line", edge_width=radius
)

add_kwargs_points = {
"n_dimensional": True,
"size": radius,
"blending": "additive",
"symbol": structure_symbol,
"metadata": {
"raw_swc": file_content,
"shape_layer": shape_layer,
"Ctrl_activated": False,
},
}

point_layer = viewer.add_points(points, **add_kwargs_points)

bind_layers_with_events(point_layer, shape_layer)

return [point_layer, shape_layer]


def bind_layers_with_events(point_layer, shape_layer):
"""Bind the events of the point layer with the swc content
Expand Down Expand Up @@ -52,12 +109,33 @@ def event_add_points(event):
new_pos = event.source.data[list(event.data_indices)]
new_radius = event.source.size[list(event.data_indices)]
new_structure = event.source.symbol[list(event.data_indices)]
new_parents = -1

new_swc = add_points(raw_swc, new_pos, new_radius, new_structure, df)
# if shift is activated, the add the new edges from previous selected point
if (
event.source.metadata["Ctrl_activated"]
and len(event.source.selected_data) > 0
):

previous_selected = list(event.source.selected_data)[-1]
new_parents = get_treenode_id_from_index([previous_selected], df)[
0
]

new_swc, df = add_points(
raw_swc, new_pos, new_radius, new_structure, new_parents, df
)

event.source.metadata["raw_swc"] = new_swc
event.source.metadata["swc_data"] = df

if new_parents != -1:
new_lines, new_r = create_line_data_from_swc_data(df)
event.source.metadata["shape_layer"].data = []
event.source.metadata["shape_layer"].add_lines(
new_lines, edge_width=new_r
)


def event_move_points(event):

Expand Down Expand Up @@ -147,7 +225,7 @@ def event_add_edge_wo_sort(layer):
event_add_edge(layer, sort=False)


def event_add_edge(layer, sort=True):
def event_add_edge(layer, indices=None, sort=True):
"""Add an edge between two selected points
Parameters
Expand All @@ -162,7 +240,8 @@ def event_add_edge(layer, sort=True):
raw_swc = layer.metadata["raw_swc"]
df = parse_swc_content(raw_swc)

indices = get_treenode_id_from_index(list(layer.selected_data), df)
if indices is None:
indices = get_treenode_id_from_index(list(layer.selected_data), df)

if sort:
indices = sort_edge_indices(raw_swc, indices, df)
Expand Down
8 changes: 7 additions & 1 deletion src/napari_swc_editor/napari.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ contributions:
- id: napari-swc-editor.make_sample_data
python_name: napari_swc_editor._sample_data:make_sample_data
title: Load sample data from napari-swc-editor
- id: napari-swc-editor.make_empty_sample
python_name: napari_swc_editor._sample_data:make_empty_sample
title: Create empty swc from napari-swc-editor
- id: napari-swc-editor.make_container_widget
python_name: napari_swc_editor:ImageThreshold
title: Make threshold Container widget
Expand Down Expand Up @@ -43,8 +46,11 @@ contributions:
filename_extensions: ['.swc']
sample_data:
- command: napari-swc-editor.make_sample_data
display_name: napari-swc-editor
display_name: sample-napari-swc-editor
key: unique_id.1
- command: napari-swc-editor.make_empty_sample
display_name: empty-napari-swc-editor
key: unique_id.2
widgets:
- command: napari-swc-editor.make_container_widget
display_name: Container Threshold
Expand Down
43 changes: 28 additions & 15 deletions src/napari_swc_editor/swc_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def create_point_data_from_swc_data(df):
Returns
-------
points : np.ndarray
All positions of the points
All positions of the points. The coordinates are in the napari order (z, y, x)
radius : np.ndarray
Radius of the points
structure : np.ndarray
Expand All @@ -159,7 +159,7 @@ def create_point_data_from_swc_data(df):
structure = df["structure_id"].values

# for each node create a point
points = df[["x", "y", "z"]].values
points = df[["z", "y", "x"]].values

return points, radius, structure

Expand All @@ -186,7 +186,7 @@ def create_line_data_from_swc_data(df):
"""

# for each nodes create a point
points = df[["x", "y", "z"]].values
points = df[["z", "y", "x"]].values

# for each edge create a line
edges = df["parent_treenode_id"].values
Expand All @@ -196,7 +196,7 @@ def create_line_data_from_swc_data(df):
edges = edges[edges != -1]

# for each id in edges, get the corresponding node according to its index
prev_point = df.loc[edges, ["x", "y", "z"]].values
prev_point = df.loc[edges, ["z", "y", "x"]].values

lines = np.array([points, prev_point])
lines = np.moveaxis(lines, 0, 1)
Expand Down Expand Up @@ -245,7 +245,12 @@ def write_swc_content(df, swc_content=None):


def add_points(
swc_content, new_positions, new_radius, new_structure=0, swc_df=None
swc_content,
new_positions,
new_radius,
structure_id=0,
parent_treenode_id=-1,
swc_df=None,
):
"""Add a point to the swc content
Expand All @@ -254,12 +259,15 @@ def add_points(
swc_content : swc_content
Content of the swc file
new_positions : np.ndarray
New positions to be added
New positions to be added in napari order (z, y, x)
new_radius : np.ndarray
Radius of the new positions
new_structure : np.ndarray
structure_id : np.ndarray
Structure of the new positions, see SWC_SYMBOL
Default is 0 (undefined)
parent_treenode_id : np.ndarray
Parent of the new positions
Default is -1 (no parent)
swc_df : pd.DataFrame
Dataframe extracted from the swc file. Should have the following columns:
- x: x coordinate of the node
Expand All @@ -280,10 +288,16 @@ def add_points(
if swc_df is None:
swc_df = parse_swc_content(swc_content)

new_points = pd.DataFrame(new_positions, columns=["x", "y", "z"])
# change napari position order to swc order
new_points = pd.DataFrame(new_positions, columns=["z", "y", "x"])
new_points["r"] = new_radius
new_points["structure_id"] = new_structure
new_points["parent_treenode_id"] = -1
new_points["structure_id"] = structure_id
new_points["parent_treenode_id"] = parent_treenode_id

# order columns to respect swc format
new_points = new_points[
["structure_id", "x", "y", "z", "r", "parent_treenode_id"]
]

if swc_df.size > 0:
previous_max = swc_df.index.max()
Expand All @@ -294,11 +308,11 @@ def add_points(

new_df = pd.concat([swc_df, new_points])
else:
new_df = new_points
new_df = new_points.copy()

new_df.index.name = "treenode_id"
new_swc_content = write_swc_content(new_df, swc_content)
return new_swc_content
return new_swc_content, new_df


def move_points(swc_content, index, new_positions, swc_df=None):
Expand All @@ -311,7 +325,7 @@ def move_points(swc_content, index, new_positions, swc_df=None):
index : int
Index of the point to be moved
new_positions : np.ndarray
New positions of the point
New positions of the point in napari order (z, y, x)
swc_df : pd.DataFrame
Dataframe extracted from the swc file. Should have the following columns:
- x: x coordinate of the node
Expand All @@ -333,7 +347,7 @@ def move_points(swc_content, index, new_positions, swc_df=None):
if swc_df is None:
swc_df = parse_swc_content(swc_content)

swc_df.loc[index, ["x", "y", "z"]] = new_positions
swc_df.loc[index, ["z", "y", "x"]] = new_positions

new_swc_content = write_swc_content(swc_df, swc_content)
moved_lines, _ = create_line_data_from_swc_data(swc_df)
Expand Down Expand Up @@ -593,7 +607,6 @@ def update_point_properties(swc_content, indices, new_properties, swc_df=None):
swc_df = parse_swc_content(swc_content)

for key, value in new_properties.items():
print(key, value)
swc_df.loc[indices, key] = value

new_lines, new_r = create_line_data_from_swc_data(swc_df)
Expand Down

0 comments on commit 345eb17

Please sign in to comment.