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

Multiple improvements #10

Merged
merged 6 commits into from
Dec 20, 2024
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: 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
Loading