Skip to content

Commit 5b88fb9

Browse files
Merge pull request #294 from NeuroML/feat/morphplots-mark-segments
feat: morphology plotting: allow marking/highlighting individual segments
2 parents 959497a + 00f1f64 commit 5b88fb9

File tree

2 files changed

+169
-9
lines changed

2 files changed

+169
-9
lines changed

pyneuroml/plot/PlotMorphology.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def plot_2D(
182182
plot_spec: typing.Optional[
183183
typing.Dict[str, typing.Union[str, typing.List[int], float]]
184184
] = None,
185+
highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None,
185186
):
186187
"""Plot cells in a 2D plane.
187188
@@ -192,6 +193,10 @@ def plot_2D(
192193
193194
This method uses matplotlib.
194195
196+
.. versionadded:: 1.1.12
197+
The hightlight_spec parameter
198+
199+
195200
:param nml_file: path to NeuroML cell file, or a NeuroMLDocument object
196201
:type nml_file: str or :py:class:`neuroml.NeuroMLDocument` or
197202
:py:class:`neuroml.Cell`
@@ -239,13 +244,43 @@ def plot_2D(
239244
The last three lists override the point_fraction setting. If a cell id
240245
is not included in the spec here, it will follow the plot_type provided
241246
before.
247+
:type plot_spec: dict
248+
:param highlight_spec: dictionary that allows passing some
249+
specifications to allow highlighting of particular elements. Only used
250+
when plotting multi-compartmental cells for marking segments on them
251+
("plot_type" is either "constant" or "detailed")
252+
253+
Each key in the dictionary will be of the cell id and the values will
254+
be more dictionaries, with the segment id as key and the following keys
255+
in it:
256+
257+
- marker_color: color of the marker
258+
- marker_size: width of the marker
259+
260+
E.g.:
261+
262+
.. code-block:: python
263+
264+
{
265+
"cell id1": {
266+
"seg id1": {
267+
"marker_color": "blue",
268+
"marker_size": 10
269+
}
270+
}
271+
}
272+
273+
:type highlight_spec: dict
242274
"""
243275

244276
if plot_type not in ["detailed", "constant", "schematic", "point"]:
245277
raise ValueError(
246278
"plot_type must be one of 'detailed', 'constant', 'schematic', 'point'"
247279
)
248280

281+
if highlight_spec is None:
282+
highlight_spec = {}
283+
249284
if verbose:
250285
print("Plotting %s" % nml_file)
251286

@@ -412,6 +447,11 @@ def plot_2D(
412447
or plot_type == "constant"
413448
or cell.id in constant_cells
414449
):
450+
cell_highlight_spec = {}
451+
try:
452+
cell_highlight_spec = highlight_spec[cell.id]
453+
except KeyError:
454+
pass
415455
plot_2D_cell_morphology(
416456
offset=pos,
417457
cell=cell,
@@ -427,6 +467,7 @@ def plot_2D(
427467
nogui=True,
428468
autoscale=False,
429469
square=False,
470+
highlight_spec=cell_highlight_spec,
430471
)
431472

432473
add_scalebar_to_matplotlib_plot(axis_min_max, ax)
@@ -467,6 +508,7 @@ def plot_2D_cell_morphology(
467508
datamin: typing.Optional[float] = None,
468509
datamax: typing.Optional[float] = None,
469510
colormap_name: str = "viridis",
511+
highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None,
470512
):
471513
"""Plot the detailed 2D morphology of a cell in provided plane.
472514
@@ -534,6 +576,16 @@ def plot_2D_cell_morphology(
534576
:type datamin: float
535577
:param datamax: max limits of data (useful to compare different plots)
536578
:type datamax: float
579+
:param highlight_spec: dictionary that allows passing some
580+
specifications to allow highlighting of particular elements. Mostly
581+
only helpful for marking segments on multi-compartmental cells. In the
582+
main dictionary are more dictionaries, one for each segment id which
583+
will be the key:
584+
585+
- marker_color: color of the marker
586+
- marker_size: width of the marker
587+
588+
:type highlight_spec: dict
537589
538590
:raises: ValueError if `cell` is None
539591
@@ -543,6 +595,10 @@ def plot_2D_cell_morphology(
543595
"No cell provided. If you would like to plot a network of point neurons, consider using `plot_2D_point_cells` instead"
544596
)
545597

598+
if highlight_spec is None:
599+
highlight_spec = {}
600+
logging.debug("highlight_spec is " + str(highlight_spec))
601+
546602
try:
547603
soma_segs = cell.get_all_segments_in_group("soma_group")
548604
except Exception:
@@ -599,12 +655,27 @@ def plot_2D_cell_morphology(
599655
d = seg.distal
600656
width = (p.diameter + d.diameter) / 2
601657

658+
segment_spec = {
659+
"marker_size": None,
660+
"marker_color": None,
661+
}
662+
try:
663+
segment_spec.update(highlight_spec[str(seg.id)])
664+
# if there's no spec for this segment
665+
except KeyError:
666+
logger.debug("No segment highlight spec found for segment" + str(seg.id))
667+
668+
logger.debug("segment_spec for " + str(seg.id) + " is" + str(segment_spec))
669+
602670
if width < min_width:
603671
width = min_width
604672

605673
if plot_type == "constant":
606674
width = min_width
607675

676+
if segment_spec["marker_size"] is not None:
677+
width = float(segment_spec["marker_size"])
678+
608679
if overlay_data and acolormap and norm:
609680
try:
610681
seg_color = acolormap(norm(overlay_data[seg.id]))
@@ -617,6 +688,9 @@ def plot_2D_cell_morphology(
617688
elif seg.id in axon_segs:
618689
seg_color = "r"
619690

691+
if segment_spec["marker_color"] is not None:
692+
seg_color = segment_spec["marker_color"]
693+
620694
spherical = (
621695
p.x == d.x and p.y == d.y and p.z == d.z and p.diameter == d.diameter
622696
)

pyneuroml/plot/PlotMorphologyVispy.py

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,17 @@ def plot_interactive_3D(
251251
plot_spec: typing.Optional[
252252
typing.Dict[str, typing.Union[str, typing.List[int], float]]
253253
] = None,
254+
highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None,
254255
precision: typing.Tuple[int, int] = (4, 200),
255256
):
256257
"""Plot interactive plots in 3D using Vispy
257258
258259
https://vispy.org
259260
261+
.. versionadded:: 1.1.12
262+
The hightlight_spec parameter
263+
264+
260265
:param nml_file: path to NeuroML cell file or
261266
:py:class:`neuroml.NeuroMLDocument` or :py:class:`neuroml.Cell` object
262267
:type nml_file: str or neuroml.NeuroMLDocument or neuroml.Cell
@@ -298,6 +303,34 @@ def plot_interactive_3D(
298303
The last three lists override the point_fraction setting. If a cell id
299304
is not included in the spec here, it will follow the plot_type provided
300305
before.
306+
:type plot_spec: dict
307+
:param highlight_spec: dictionary that allows passing some
308+
specifications to allow highlighting of particular elements. Only used
309+
when plotting multi-compartmental cells for marking segments on them
310+
("plot_type" is either "constant" or "detailed")
311+
312+
Each key in the dictionary will be of the cell id and the values will
313+
be more dictionaries, with the segment id as key and the following keys
314+
in it:
315+
316+
- marker_color: color of the marker
317+
- marker_size: [diameter 1, diameter 2] (in case of sphere, the first value
318+
is used)
319+
320+
E.g.:
321+
322+
.. code-block:: python
323+
324+
{
325+
"cell id1": {
326+
"seg id1": {
327+
"marker_color": "blue",
328+
"marker_size": [0.1, 0.1]
329+
}
330+
}
331+
}
332+
333+
:type highlight_spec: dict
301334
:param precision: tuple containing two values: (number of decimal places,
302335
maximum number of meshes). The first is used to group segments into
303336
meshes to create instances. More precision means fewer segments will be
@@ -315,6 +348,9 @@ def plot_interactive_3D(
315348
"plot_type must be one of 'detailed', 'constant', 'schematic', 'point'"
316349
)
317350

351+
if highlight_spec is None:
352+
highlight_spec = {}
353+
318354
if verbose:
319355
logger.info(f"Visualising {nml_file}")
320356

@@ -525,6 +561,7 @@ def plot_interactive_3D(
525561
logger.debug(f"meshdata added: {key}: {meshdata[key]}")
526562

527563
elif plot_type == "schematic" or cell.id in schematic_cells:
564+
logger.debug(f"Cell for 3d schematic is: {cell.id}")
528565
plot_3D_schematic(
529566
offset=pos,
530567
cell=cell,
@@ -544,6 +581,12 @@ def plot_interactive_3D(
544581
or cell.id in constant_cells
545582
):
546583
logger.debug(f"Cell for 3d is: {cell.id}")
584+
cell_highlight_spec = {}
585+
try:
586+
cell_highlight_spec = highlight_spec[cell.id]
587+
except KeyError:
588+
pass
589+
547590
plot_3D_cell_morphology(
548591
offset=pos,
549592
cell=cell,
@@ -556,6 +599,7 @@ def plot_interactive_3D(
556599
nogui=True,
557600
meshdata=meshdata,
558601
mesh_precision=precision[0],
602+
highlight_spec=cell_highlight_spec,
559603
)
560604

561605
# if too many meshes, reduce precision and retry, recursively
@@ -566,15 +610,16 @@ def plot_interactive_3D(
566610
f"More meshes than threshold ({len(meshdata.keys())}/{precision[1]}), reducing precision to {precision[0]} and re-calculating."
567611
)
568612
plot_interactive_3D(
569-
nml_model,
570-
min_width,
571-
verbose,
572-
plot_type,
573-
title,
574-
theme,
575-
nogui,
576-
plot_spec,
577-
precision,
613+
nml_file=nml_model,
614+
min_width=min_width,
615+
verbose=verbose,
616+
plot_type=plot_type,
617+
title=title,
618+
theme=theme,
619+
nogui=nogui,
620+
plot_spec=plot_spec,
621+
precision=precision,
622+
highlight_spec=highlight_spec,
578623
)
579624
# break the recursion, don't plot in the calling method
580625
return
@@ -603,12 +648,16 @@ def plot_3D_cell_morphology(
603648
theme: str = "light",
604649
meshdata: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None,
605650
mesh_precision: int = 2,
651+
highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None,
606652
):
607653
"""Plot the detailed 3D morphology of a cell using vispy.
608654
https://vispy.org/
609655
610656
.. versionadded:: 1.0.0
611657
658+
.. versionadded:: 1.1.12
659+
The hightlight_spec parameter
660+
612661
.. seealso::
613662
614663
:py:func:`plot_2D`
@@ -671,6 +720,17 @@ def plot_3D_cell_morphology(
671720
instances: more precision means more detail (meshes), means less
672721
performance
673722
:type mesh_precision: int
723+
:param highlight_spec: dictionary that allows passing some
724+
specifications to allow highlighting of particular elements. Mostly
725+
only helpful for marking segments on multi-compartmental cells. In the
726+
main dictionary are more dictionaries, one for each segment id which
727+
will be the key:
728+
729+
- marker_color: color of the marker
730+
- marker_size: [diameter 1, diameter 2] (in case of sphere, the first value
731+
is used)
732+
733+
:type highlight_spec: dict
674734
:raises: ValueError if `cell` is None
675735
676736
"""
@@ -679,6 +739,10 @@ def plot_3D_cell_morphology(
679739
"No cell provided. If you would like to plot a network of point neurons, consider using `plot_2D_point_cells` instead"
680740
)
681741

742+
if highlight_spec is None:
743+
highlight_spec = {}
744+
logging.debug("highlight_spec is " + str(highlight_spec))
745+
682746
try:
683747
soma_segs = cell.get_all_segments_in_group("soma_group")
684748
except Exception:
@@ -726,6 +790,25 @@ def plot_3D_cell_morphology(
726790
# round up to precision
727791
r1 = round(p.diameter / 2, mesh_precision)
728792
r2 = round(d.diameter / 2, mesh_precision)
793+
794+
segment_spec = {
795+
"marker_size": None,
796+
"marker_color": None,
797+
}
798+
try:
799+
segment_spec.update(highlight_spec[str(seg.id)])
800+
# if there's no spec for this segment
801+
except KeyError:
802+
logger.debug("No segment highlight spec found for segment" + str(seg.id))
803+
804+
logger.debug("segment_spec for " + str(seg.id) + " is" + str(segment_spec))
805+
806+
if segment_spec["marker_size"] is not None:
807+
if type(segment_spec["marker_size"]) is not list:
808+
raise RuntimeError("The marker size must be a list")
809+
r1 = round(float(segment_spec["marker_size"][0]) / 2, mesh_precision)
810+
r2 = round(float(segment_spec["marker_size"][1]) / 2, mesh_precision)
811+
729812
key = (
730813
f"{r1:.{mesh_precision}f}",
731814
f"{r2:.{mesh_precision}f}",
@@ -756,6 +839,9 @@ def plot_3D_cell_morphology(
756839
else:
757840
seg_color = color
758841

842+
if segment_spec["marker_color"] is not None:
843+
seg_color = segment_spec["marker_color"]
844+
759845
try:
760846
meshdata[key].append((p, d, seg_color, offset))
761847
except KeyError:

0 commit comments

Comments
 (0)