From c06a02e6a403aaf88793d571f899b20f7c2d9dc7 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Mon, 3 Jun 2024 13:33:37 +0300 Subject: [PATCH 01/28] Get rotation angles to eliminate x,y components in PCA axis --- pyneuroml/plot/PlotMorphologyVispy.py | 61 ++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 6cb42dbc..e85386d1 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -19,7 +19,7 @@ from neuroml import Cell, NeuroMLDocument, SegmentGroup, Segment from neuroml.neuro_lex_ids import neuro_lex_ids from pyneuroml.pynml import read_neuroml2_file -from pyneuroml.utils import extract_position_info +from pyneuroml.utils import extract_position_info, rotate_cell, translate_cell_to_coords from pyneuroml.utils.plot import ( DEFAULTS, get_cell_bound_box, @@ -104,6 +104,8 @@ def create_new_vispy_canvas( axes_length: float = 100, axes_width: int = 2, theme=PYNEUROML_VISPY_THEME, + rotatedy_pca: typing.List[float] = None, + rotatedx_pca: typing.List[float] = None, ): """Create a new vispy scene canvas with a view and optional axes lines @@ -210,6 +212,19 @@ def create_new_vispy_canvas( width=axes_width, ) + pca_points = [ + axes_pos, # origin + [rotatedy_pca[0]*100 , rotatedy_pca[1]*100, rotatedy_pca[2]*100], + [rotatedx_pca[0]*100, rotatedx_pca[1]*100, rotatedx_pca[2]*100] + ] + scene.Line( + pca_points, + connect=numpy.array([[0, 1], [0, 2]]), + parent=view.scene, + color='orange', + width=axes_width+5, + ) + def vispy_rotate(self): view.camera.orbit(azim=1, elev=0) @@ -471,6 +486,50 @@ def plot_interactive_3D( else: cell = list(pop_id_vs_cell.values())[0] + #Get all segments' distal points + segment_points = [] + segments_all = cell.morphology.segments + for segment in segments_all: + segment_points.append([segment.distal.x, segment.distal.y, segment.distal.z]) + + + coords = numpy.array(segment_points) + from sklearn.decomposition import PCA + + #Get the PCA components + pca = PCA() + pca.fit(coords) + + # Get the principal component axes + principal_axes = pca.components_ + #Get the first principal component axis + first_pca = principal_axes[0] + #y angle needed to eliminate x component + y_angle = math.atan(-first_pca[0]/first_pca[2]) + rotation_y = numpy.array( + [ + [math.cos(y_angle), 0, math.sin(y_angle)], + [0, 1, 0], + [-math.sin(y_angle), 0, math.cos(y_angle)], + ] + ) + rotated_pca = numpy.dot(rotation_y,first_pca) + + #x angle needed to eliminate y component + x_angle = math.atan(rotated_pca[1]/rotated_pca[2]) + + rotation_x = numpy.array( + [ + [1, 0, 0], + [0, math.cos(x_angle), -math.sin(x_angle)], + [0, math.sin(x_angle), math.cos(x_angle)], + ] + ) + rotated_pca2 = numpy.dot(rotation_x, rotated_pca) + if rotated_pca2[2] < 0: + x_angle += numpy.pi + rotated_pca2[2] = -rotated_pca2[2] + if cell is not None: view_min, view_max = get_cell_bound_box(cell) else: From abd8e1b479a850358b6c3fe8c1b734296c380f13 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Mon, 3 Jun 2024 13:37:31 +0300 Subject: [PATCH 02/28] Rotate cell using PCA angles and translate it to origin --- pyneuroml/plot/PlotMorphologyVispy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index e85386d1..1d452147 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -721,6 +721,8 @@ def plot_3D_cell_morphology( meshdata: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, mesh_precision: int = 2, highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, + x_angle: float = None, + y_angle: float = None ): """Plot the detailed 3D morphology of a cell using vispy. https://vispy.org/ @@ -828,6 +830,9 @@ def plot_3D_cell_morphology( except Exception: axon_segs = [] + cell = rotate_cell(cell, x_angle, y_angle, 0, 'yxz', False, False) + cell = translate_cell_to_coords(cell, False, [0,0,0]) + if current_canvas is None or current_view is None: view_min, view_max = get_cell_bound_box(cell) current_canvas, current_view = create_new_vispy_canvas( From 17b2e356081c7286981fe06fcfd9e2ff0ad7f0e4 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Mon, 3 Jun 2024 13:40:45 +0300 Subject: [PATCH 03/28] Added create_new_vispy_canvas test parameters for pca --- pyneuroml/plot/PlotMorphologyVispy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 1d452147..aaab8117 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -539,7 +539,7 @@ def plot_interactive_3D( view_max = list(numpy.array(pos)) current_canvas, current_view = create_new_vispy_canvas( - view_min, view_max, title, theme=theme + view_min, view_max, title, theme=theme, rotatedy_pca=rotated_pca, rotatedx_pca=rotated_pca2 ) logger.debug(f"figure extents are: {view_min}, {view_max}") From b623a1f966274bc1e2385b1949055a070d0809c1 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Mon, 3 Jun 2024 13:58:44 +0300 Subject: [PATCH 04/28] minor fixes --- pyneuroml/plot/PlotMorphologyVispy.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index aaab8117..872e3cd9 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -217,13 +217,13 @@ def create_new_vispy_canvas( [rotatedy_pca[0]*100 , rotatedy_pca[1]*100, rotatedy_pca[2]*100], [rotatedx_pca[0]*100, rotatedx_pca[1]*100, rotatedx_pca[2]*100] ] - scene.Line( - pca_points, - connect=numpy.array([[0, 1], [0, 2]]), - parent=view.scene, - color='orange', - width=axes_width+5, - ) + scene.Line( + pca_points, + connect=numpy.array([[0, 1], [0, 2]]), + parent=view.scene, + color='orange', + width=axes_width+5, + ) def vispy_rotate(self): view.camera.orbit(azim=1, elev=0) @@ -539,7 +539,7 @@ def plot_interactive_3D( view_max = list(numpy.array(pos)) current_canvas, current_view = create_new_vispy_canvas( - view_min, view_max, title, theme=theme, rotatedy_pca=rotated_pca, rotatedx_pca=rotated_pca2 + view_min, view_max, title, axes_pos=[0,0,0], theme=theme, rotatedy_pca=rotated_pca, rotatedx_pca=rotated_pca2 ) logger.debug(f"figure extents are: {view_min}, {view_max}") @@ -672,6 +672,8 @@ def plot_interactive_3D( meshdata=meshdata, mesh_precision=precision[0], highlight_spec=cell_highlight_spec, + y_angle=y_angle, + x_angle=x_angle ) # if too many meshes, reduce precision and retry, recursively From 39c2dbf723501ec1d33ae53e22ee6495509c6090 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 4 Jun 2024 15:52:53 +0300 Subject: [PATCH 05/28] Created new function PCA_transformation --- pyneuroml/plot/PlotMorphologyVispy.py | 112 ++++++++++++++++---------- 1 file changed, 68 insertions(+), 44 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 872e3cd9..f82f6546 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -486,50 +486,8 @@ def plot_interactive_3D( else: cell = list(pop_id_vs_cell.values())[0] - #Get all segments' distal points - segment_points = [] - segments_all = cell.morphology.segments - for segment in segments_all: - segment_points.append([segment.distal.x, segment.distal.y, segment.distal.z]) - coords = numpy.array(segment_points) - from sklearn.decomposition import PCA - - #Get the PCA components - pca = PCA() - pca.fit(coords) - - # Get the principal component axes - principal_axes = pca.components_ - #Get the first principal component axis - first_pca = principal_axes[0] - #y angle needed to eliminate x component - y_angle = math.atan(-first_pca[0]/first_pca[2]) - rotation_y = numpy.array( - [ - [math.cos(y_angle), 0, math.sin(y_angle)], - [0, 1, 0], - [-math.sin(y_angle), 0, math.cos(y_angle)], - ] - ) - rotated_pca = numpy.dot(rotation_y,first_pca) - - #x angle needed to eliminate y component - x_angle = math.atan(rotated_pca[1]/rotated_pca[2]) - - rotation_x = numpy.array( - [ - [1, 0, 0], - [0, math.cos(x_angle), -math.sin(x_angle)], - [0, math.sin(x_angle), math.cos(x_angle)], - ] - ) - rotated_pca2 = numpy.dot(rotation_x, rotated_pca) - if rotated_pca2[2] < 0: - x_angle += numpy.pi - rotated_pca2[2] = -rotated_pca2[2] - if cell is not None: view_min, view_max = get_cell_bound_box(cell) else: @@ -707,6 +665,73 @@ def plot_interactive_3D( app.run() +def PCA_transformation( + cell: Optional[Cell] = None, + inplace: bool = False, + +)-> Cell: + """ Use cell's PCA to make it upright + + .. versionadded:: 1.2.13 + + :param cell: cell object to translate + :type cell: neuroml.Cell + :param inplace: toggle whether the cell object should be modified inplace + or a copy created (creates and returns a copy by default) + :type inplace: bool + :returns: new neuroml.Cell object + :rtype: neuroml.Cell + """ + + #Get all segments' distal points + segment_points = [] + segments_all = cell.morphology.segments + for segment in segments_all: + segment_points.append([segment.distal.x, segment.distal.y, segment.distal.z]) + + + coords = numpy.array(segment_points) + from sklearn.decomposition import PCA + + #Get the PCA components + pca = PCA() + pca.fit(coords) + + # Get the principal component axes + principal_axes = pca.components_ + #Get the first principal component axis + first_pca = principal_axes[0] + #y angle needed to eliminate x component + y_angle = math.atan(-first_pca[0]/first_pca[2]) + rotation_y = numpy.array( + [ + [math.cos(y_angle), 0, math.sin(y_angle)], + [0, 1, 0], + [-math.sin(y_angle), 0, math.cos(y_angle)], + ] + ) + rotated_pca = numpy.dot(rotation_y,first_pca) + + #x angle needed to eliminate y component + x_angle = math.atan(rotated_pca[1]/rotated_pca[2]) + + rotation_x = numpy.array( + [ + [1, 0, 0], + [0, math.cos(x_angle), -math.sin(x_angle)], + [0, math.sin(x_angle), math.cos(x_angle)], + ] + ) + rotated_pca2 = numpy.dot(rotation_x, rotated_pca) + if rotated_pca2[2] < 0: + x_angle += numpy.pi + rotated_pca2[2] = -rotated_pca2[2] + + cell = translate_cell_to_coords(cell, inplace=inplace, dest=[0,0,0]) + cell = rotate_cell(cell, x_angle, y_angle, 0, 'yxz', relative_to_soma=False, inplace=inplace) + return cell + + def plot_3D_cell_morphology( offset: typing.List[float] = [0, 0, 0], cell: Optional[Cell] = None, @@ -832,8 +857,7 @@ def plot_3D_cell_morphology( except Exception: axon_segs = [] - cell = rotate_cell(cell, x_angle, y_angle, 0, 'yxz', False, False) - cell = translate_cell_to_coords(cell, False, [0,0,0]) + #Placeholder for pca_ if current_canvas is None or current_view is None: view_min, view_max = get_cell_bound_box(cell) From efcfa10120a53b01ce93ec96eb2293f816047e72 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 4 Jun 2024 15:58:38 +0300 Subject: [PATCH 06/28] Removed pca axes visualization and unecessary arguments related to that --- pyneuroml/plot/PlotMorphologyVispy.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index f82f6546..4edd2f9c 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -104,8 +104,6 @@ def create_new_vispy_canvas( axes_length: float = 100, axes_width: int = 2, theme=PYNEUROML_VISPY_THEME, - rotatedy_pca: typing.List[float] = None, - rotatedx_pca: typing.List[float] = None, ): """Create a new vispy scene canvas with a view and optional axes lines @@ -212,19 +210,6 @@ def create_new_vispy_canvas( width=axes_width, ) - pca_points = [ - axes_pos, # origin - [rotatedy_pca[0]*100 , rotatedy_pca[1]*100, rotatedy_pca[2]*100], - [rotatedx_pca[0]*100, rotatedx_pca[1]*100, rotatedx_pca[2]*100] - ] - scene.Line( - pca_points, - connect=numpy.array([[0, 1], [0, 2]]), - parent=view.scene, - color='orange', - width=axes_width+5, - ) - def vispy_rotate(self): view.camera.orbit(azim=1, elev=0) @@ -497,7 +482,7 @@ def plot_interactive_3D( view_max = list(numpy.array(pos)) current_canvas, current_view = create_new_vispy_canvas( - view_min, view_max, title, axes_pos=[0,0,0], theme=theme, rotatedy_pca=rotated_pca, rotatedx_pca=rotated_pca2 + view_min, view_max, title, axes_pos=[0,0,0], theme=theme ) logger.debug(f"figure extents are: {view_min}, {view_max}") @@ -630,8 +615,6 @@ def plot_interactive_3D( meshdata=meshdata, mesh_precision=precision[0], highlight_spec=cell_highlight_spec, - y_angle=y_angle, - x_angle=x_angle ) # if too many meshes, reduce precision and retry, recursively From 3383f6af30cfc9c96cf96927a688d92ee9c8fd3b Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 4 Jun 2024 16:48:03 +0300 Subject: [PATCH 07/28] Add view_center argument and logic in create_new_vispy_canvas --- pyneuroml/plot/PlotMorphologyVispy.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 4edd2f9c..c2417195 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -99,6 +99,7 @@ def add_text_to_vispy_3D_plot( def create_new_vispy_canvas( view_min: typing.Optional[typing.List[float]] = None, view_max: typing.Optional[typing.List[float]] = None, + view_center: typing.Optional[typing.List[float]] = None, title: str = "", axes_pos: typing.Optional[typing.List] = None, axes_length: float = 100, @@ -113,6 +114,8 @@ def create_new_vispy_canvas( :type view_min: [float, float, float] :param view_max: max view co-ordinates :type view_max: [float, float, float] + :param view_center: center view co-ordinates + :type view_center: [float, float, float] :param title: title of plot :type title: str :param axes_pos: position to draw axes at @@ -160,7 +163,9 @@ def create_new_vispy_canvas( view.camera = cams[cam_index] if view_min is not None and view_max is not None: - view_center = (numpy.array(view_max) + numpy.array(view_min)) / 2 + #Calculate view center if it is None + if view_center is None: + view_center = (numpy.array(view_max) + numpy.array(view_min)) / 2 logger.debug(f"Center is {view_center}") cam1.center = [view_center[0], view_center[1]] cam2.center = view_center From cb77a183cccb3faa311205478cefcc93ce6d7cd8 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 4 Jun 2024 18:57:12 +0300 Subject: [PATCH 08/28] fix:Cameras' center was being set and then recalculated --- pyneuroml/plot/PlotMorphologyVispy.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index c2417195..01906670 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -99,12 +99,13 @@ def add_text_to_vispy_3D_plot( def create_new_vispy_canvas( view_min: typing.Optional[typing.List[float]] = None, view_max: typing.Optional[typing.List[float]] = None, - view_center: typing.Optional[typing.List[float]] = None, title: str = "", axes_pos: typing.Optional[typing.List] = None, axes_length: float = 100, axes_width: int = 2, theme=PYNEUROML_VISPY_THEME, + view_center: typing.Optional[typing.List[float]] = None, + ): """Create a new vispy scene canvas with a view and optional axes lines @@ -163,15 +164,6 @@ def create_new_vispy_canvas( view.camera = cams[cam_index] if view_min is not None and view_max is not None: - #Calculate view center if it is None - if view_center is None: - view_center = (numpy.array(view_max) + numpy.array(view_min)) / 2 - logger.debug(f"Center is {view_center}") - cam1.center = [view_center[0], view_center[1]] - cam2.center = view_center - cam3.center = view_center - cam4.center = view_center - for acam in cams: x_width = abs(view_min[0] - view_max[0]) y_width = abs(view_min[1] - view_max[1]) @@ -195,6 +187,15 @@ def create_new_vispy_canvas( logger.debug(f"{xrange}, {yrange}, {zrange}") acam.set_range(x=xrange, y=yrange, z=zrange) + + #Calculate view center if it is None + if view_center is None: + view_center = (numpy.array(view_max) + numpy.array(view_min)) / 2 + logger.debug(f"Center is {view_center}") + cam1.center = [view_center[0], view_center[1]] + cam2.center = view_center + cam3.center = view_center + cam4.center = view_center for acam in cams: acam.set_default_state() From bb94ef86fe794cc1cd8825e4b04efc6e7570f1f7 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Wed, 5 Jun 2024 14:19:56 +0300 Subject: [PATCH 09/28] Add call to PCA_transformation() and set view_center to origin for singleCell --- pyneuroml/plot/PlotMorphologyVispy.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 01906670..957d0a96 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -149,7 +149,7 @@ def create_new_vispy_canvas( # https://vispy.org/gallery/scene/flipped_axis.html cam1 = scene.cameras.PanZoomCamera(parent=view.scene, name="PanZoom") - cam2 = scene.cameras.TurntableCamera(parent=view.scene, name="Turntable") + cam2 = scene.cameras.TurntableCamera(parent=view.scene, azimuth=0.0, elevation=0.0, name="Turntable") cam3 = scene.cameras.ArcballCamera(parent=view.scene, name="Arcball") @@ -445,6 +445,8 @@ def plot_interactive_3D( # not used later, clear up del cell_id_vs_cell + singleCell = False + if len(positions) > 1: only_pos = [] for posdict in positions.values(): @@ -476,6 +478,7 @@ def plot_interactive_3D( logger.debug(f"center, view_min, max are {center}, {view_min}, {view_max}") else: + singleCell = True cell = list(pop_id_vs_cell.values())[0] @@ -487,8 +490,12 @@ def plot_interactive_3D( view_min = list(numpy.array(pos)) view_max = list(numpy.array(pos)) + if singleCell: + view_center=[0,0,0] + else: + view_center=None current_canvas, current_view = create_new_vispy_canvas( - view_min, view_max, title, axes_pos=[0,0,0], theme=theme + view_min, view_max, title, axes_pos=[0,0,0], theme=theme, view_center=view_center ) logger.debug(f"figure extents are: {view_min}, {view_max}") @@ -847,6 +854,8 @@ def plot_3D_cell_morphology( axon_segs = [] #Placeholder for pca_ + cell = PCA_transformation(cell) + current_canvas = current_view = None if current_canvas is None or current_view is None: view_min, view_max = get_cell_bound_box(cell) From 43bf525bbf2502db85a733bcdd443f4620cf0e0c Mon Sep 17 00:00:00 2001 From: lej0hn Date: Fri, 7 Jun 2024 17:29:34 +0300 Subject: [PATCH 10/28] Change turntable camera elevation to look at y axis --- pyneuroml/plot/PlotMorphologyVispy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 957d0a96..80f90a6a 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -149,7 +149,7 @@ def create_new_vispy_canvas( # https://vispy.org/gallery/scene/flipped_axis.html cam1 = scene.cameras.PanZoomCamera(parent=view.scene, name="PanZoom") - cam2 = scene.cameras.TurntableCamera(parent=view.scene, azimuth=0.0, elevation=0.0, name="Turntable") + cam2 = scene.cameras.TurntableCamera(parent=view.scene, azimuth=0.0, elevation=90.0, name="Turntable") cam3 = scene.cameras.ArcballCamera(parent=view.scene, name="Arcball") From b13036e4b0011fc7899e744d4e40c85e45b8639a Mon Sep 17 00:00:00 2001 From: lej0hn Date: Mon, 10 Jun 2024 14:28:54 +0300 Subject: [PATCH 11/28] Changed axis visualization to XYZ --changed pca alignment to y axis -- make camera look at y axis --- pyneuroml/plot/PlotMorphologyVispy.py | 67 ++++++++++++++++----------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 80f90a6a..7ccbcc20 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -202,19 +202,32 @@ def create_new_vispy_canvas( if axes_pos is not None: # can't get XYZAxis to work, so create manually - points = [ - axes_pos, # origin - [axes_pos[0] + axes_length, axes_pos[1], axes_pos[2]], - [axes_pos[0], axes_pos[1] + axes_length, axes_pos[2]], - [axes_pos[0], axes_pos[1], axes_pos[2] + axes_length], - ] - scene.Line( - points, - connect=numpy.array([[0, 1], [0, 2], [0, 3]]), - parent=view.scene, - color=VISPY_THEME[theme]["fg"], - width=axes_width, - ) + # points = [ + # axes_pos, # origin + # [axes_pos[0] + axes_length, axes_pos[1], axes_pos[2]], + # [axes_pos[0], axes_pos[1] + axes_length, axes_pos[2]], + # [axes_pos[0], axes_pos[1], axes_pos[2] + axes_length], + # ] + # scene.Line( + # points, + # connect=numpy.array([[0, 1], [0, 2], [0, 3]]), + # parent=view.scene, + # color=VISPY_THEME[theme]["fg"], + # width=axes_width, + # ) + pos = numpy.array([[0, 0, 0], + [axes_length, 0, 0], + [0, 0, 0], + [0, axes_length, 0], + [0, 0, 0], + [0, 0, axes_length]]) + color = numpy.array([[1, 0, 0, 1], + [1, 0, 0, 1], + [0, 1, 0, 1], + [0, 1, 0, 1], + [0, 0, 1, 1], + [0, 0, 1, 1]]) + axis = scene.visuals.XYZAxis(parent=view.scene, pos=pos) def vispy_rotate(self): view.camera.orbit(azim=1, elev=0) @@ -697,8 +710,8 @@ def PCA_transformation( principal_axes = pca.components_ #Get the first principal component axis first_pca = principal_axes[0] - #y angle needed to eliminate x component - y_angle = math.atan(-first_pca[0]/first_pca[2]) + #y angle needed to eliminate z component + y_angle = math.atan(-first_pca[2]/first_pca[0]) rotation_y = numpy.array( [ [math.cos(y_angle), 0, math.sin(y_angle)], @@ -708,23 +721,23 @@ def PCA_transformation( ) rotated_pca = numpy.dot(rotation_y,first_pca) - #x angle needed to eliminate y component - x_angle = math.atan(rotated_pca[1]/rotated_pca[2]) + #z angle needed to eliminate x component + z_angle = math.atan(rotated_pca[0]/rotated_pca[1]) - rotation_x = numpy.array( - [ - [1, 0, 0], - [0, math.cos(x_angle), -math.sin(x_angle)], - [0, math.sin(x_angle), math.cos(x_angle)], - ] - ) - rotated_pca2 = numpy.dot(rotation_x, rotated_pca) + rotation_z = numpy.array( + [ + [math.cos(z_angle), -math.sin(z_angle), 0], + [math.sin(z_angle), math.cos(z_angle), 0], + [0, 0, 1], + ] + ) + rotated_pca2 = numpy.dot(rotation_z, rotated_pca) if rotated_pca2[2] < 0: - x_angle += numpy.pi + z_angle += numpy.pi rotated_pca2[2] = -rotated_pca2[2] cell = translate_cell_to_coords(cell, inplace=inplace, dest=[0,0,0]) - cell = rotate_cell(cell, x_angle, y_angle, 0, 'yxz', relative_to_soma=False, inplace=inplace) + cell = rotate_cell(cell, z_angle, y_angle, 0, 'yxz', relative_to_soma=False, inplace=inplace) return cell From 62884f9075677155ed0ed5e29d3d41b306d62056 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Mon, 10 Jun 2024 15:00:25 +0300 Subject: [PATCH 12/28] Minor fix in rotation angles --- pyneuroml/plot/PlotMorphologyVispy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 7ccbcc20..774bc088 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -711,7 +711,7 @@ def PCA_transformation( #Get the first principal component axis first_pca = principal_axes[0] #y angle needed to eliminate z component - y_angle = math.atan(-first_pca[2]/first_pca[0]) + y_angle = math.atan(first_pca[2]/first_pca[0]) rotation_y = numpy.array( [ [math.cos(y_angle), 0, math.sin(y_angle)], @@ -737,7 +737,7 @@ def PCA_transformation( rotated_pca2[2] = -rotated_pca2[2] cell = translate_cell_to_coords(cell, inplace=inplace, dest=[0,0,0]) - cell = rotate_cell(cell, z_angle, y_angle, 0, 'yxz', relative_to_soma=False, inplace=inplace) + cell = rotate_cell(cell, 0, y_angle, z_angle, 'yzx', relative_to_soma=False, inplace=inplace) return cell From a3c26744ec6bd1884f346b2129d4c03013d42986 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Mon, 10 Jun 2024 16:16:58 +0300 Subject: [PATCH 13/28] Added function orbit to orbit camera around y_axis --- pyneuroml/plot/PlotMorphologyVispy.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 61bf7925..fc0ca7f4 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -96,6 +96,22 @@ def add_text_to_vispy_3D_plot( parent=current_canvas, ) +def orbit(camera, azim, elev, roll): + """Orbits the camera around the center position. + + Parameters + ---------- + azim : float + Angle in degrees to rotate horizontally around the center point. + elev : float + Angle in degrees to rotate vertically around the center point. + roll : float + Angle in degrees to rotate around the y axis of the center point. + """ + camera.azimuth += azim + camera.elevation = numpy.clip(camera.elevation + elev, -90, 90) + camera.roll += roll + camera.view_changed() def create_new_vispy_canvas( view_min: typing.Optional[typing.List[float]] = None, @@ -231,7 +247,7 @@ def create_new_vispy_canvas( axis = scene.visuals.XYZAxis(parent=view.scene, pos=pos) def vispy_rotate(self): - view.camera.orbit(azim=1, elev=0) + orbit(view.camera, azim=0, elev=0, roll=1) rotation_timer = app.Timer(connect=vispy_rotate) From cecbf9dc075a0fe339bdbbd9bc68853b41ef48b3 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Mon, 10 Jun 2024 16:39:37 +0300 Subject: [PATCH 14/28] Fix: Set y axis as up in cameras --- pyneuroml/plot/PlotMorphologyVispy.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index fc0ca7f4..c1efc6ce 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -96,23 +96,6 @@ def add_text_to_vispy_3D_plot( parent=current_canvas, ) -def orbit(camera, azim, elev, roll): - """Orbits the camera around the center position. - - Parameters - ---------- - azim : float - Angle in degrees to rotate horizontally around the center point. - elev : float - Angle in degrees to rotate vertically around the center point. - roll : float - Angle in degrees to rotate around the y axis of the center point. - """ - camera.azimuth += azim - camera.elevation = numpy.clip(camera.elevation + elev, -90, 90) - camera.roll += roll - camera.view_changed() - def create_new_vispy_canvas( view_min: typing.Optional[typing.List[float]] = None, view_max: typing.Optional[typing.List[float]] = None, @@ -166,7 +149,7 @@ def create_new_vispy_canvas( # https://vispy.org/gallery/scene/flipped_axis.html cam1 = scene.cameras.PanZoomCamera(parent=view.scene, name="PanZoom") - cam2 = scene.cameras.TurntableCamera(parent=view.scene, azimuth=0.0, elevation=90.0, name="Turntable") + cam2 = scene.cameras.TurntableCamera(parent=view.scene, name="Turntable") cam3 = scene.cameras.ArcballCamera(parent=view.scene, name="Arcball") @@ -180,6 +163,9 @@ def create_new_vispy_canvas( cam_index = 1 view.camera = cams[cam_index] + for acam in cams: + acam.up = 'y' + if view_min is not None and view_max is not None: for acam in cams: x_width = abs(view_min[0] - view_max[0]) @@ -244,10 +230,10 @@ def create_new_vispy_canvas( [0, 1, 0, 1], [0, 0, 1, 1], [0, 0, 1, 1]]) - axis = scene.visuals.XYZAxis(parent=view.scene, pos=pos) + scene.visuals.XYZAxis(parent=view.scene, pos=pos) def vispy_rotate(self): - orbit(view.camera, azim=0, elev=0, roll=1) + view.camera.orbit(azim=1, elev=0) rotation_timer = app.Timer(connect=vispy_rotate) From 7c7307e8af3ffac8fd995a24d494a138ee767f5e Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 01:54:46 +0300 Subject: [PATCH 15/28] Add color to axis --- pyneuroml/plot/PlotMorphologyVispy.py | 62 +++++++++++++-------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index c1efc6ce..aa8cffac 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -147,13 +147,13 @@ def create_new_vispy_canvas( # create cameras # https://vispy.org/gallery/scene/flipped_axis.html - cam1 = scene.cameras.PanZoomCamera(parent=view.scene, name="PanZoom") + cam1 = scene.cameras.PanZoomCamera(parent=view.scene, name="PanZoom", up='y') - cam2 = scene.cameras.TurntableCamera(parent=view.scene, name="Turntable") + cam2 = scene.cameras.TurntableCamera(parent=view.scene, name="Turntable", up='y') - cam3 = scene.cameras.ArcballCamera(parent=view.scene, name="Arcball") + cam3 = scene.cameras.ArcballCamera(parent=view.scene, name="Arcball", up='y') - cam4 = scene.cameras.FlyCamera(parent=view.scene, name="Fly") + cam4 = scene.cameras.FlyCamera(parent=view.scene, name="Fly", up='y') # do not keep z up cam4.autoroll = False @@ -163,9 +163,6 @@ def create_new_vispy_canvas( cam_index = 1 view.camera = cams[cam_index] - for acam in cams: - acam.up = 'y' - if view_min is not None and view_max is not None: for acam in cams: x_width = abs(view_min[0] - view_max[0]) @@ -205,32 +202,31 @@ def create_new_vispy_canvas( if axes_pos is not None: # can't get XYZAxis to work, so create manually - # points = [ - # axes_pos, # origin - # [axes_pos[0] + axes_length, axes_pos[1], axes_pos[2]], - # [axes_pos[0], axes_pos[1] + axes_length, axes_pos[2]], - # [axes_pos[0], axes_pos[1], axes_pos[2] + axes_length], - # ] - # scene.Line( - # points, - # connect=numpy.array([[0, 1], [0, 2], [0, 3]]), - # parent=view.scene, - # color=VISPY_THEME[theme]["fg"], - # width=axes_width, - # ) - pos = numpy.array([[0, 0, 0], - [axes_length, 0, 0], - [0, 0, 0], - [0, axes_length, 0], - [0, 0, 0], - [0, 0, axes_length]]) - color = numpy.array([[1, 0, 0, 1], - [1, 0, 0, 1], - [0, 1, 0, 1], - [0, 1, 0, 1], - [0, 0, 1, 1], - [0, 0, 1, 1]]) - scene.visuals.XYZAxis(parent=view.scene, pos=pos) + points = [ + axes_pos, # origin + [axes_pos[0] + axes_length, axes_pos[1], axes_pos[2]], + [axes_pos[0], axes_pos[1] + axes_length, axes_pos[2]], + [axes_pos[0], axes_pos[1], axes_pos[2] + axes_length], + ] + scene.Line( + [points[0], points[1]], + parent=view.scene, + color='red', + width=axes_width, + ) + scene.Line( + [points[0], points[2]], + parent=view.scene, + color='green', + width=axes_width, + ) + scene.Line( + [points[0], points[3]], + parent=view.scene, + color='blue', + width=axes_width, + ) + def vispy_rotate(self): view.camera.orbit(azim=1, elev=0) From d39e3718d6352e28a217562ab0efd4f62235aac7 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 15:58:58 +0300 Subject: [PATCH 16/28] Added test for PCA_transformation function --- tests/plot/test_morphology_plot.py | 70 ++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/plot/test_morphology_plot.py b/tests/plot/test_morphology_plot.py index 38dc1b37..f314bea3 100644 --- a/tests/plot/test_morphology_plot.py +++ b/tests/plot/test_morphology_plot.py @@ -28,6 +28,7 @@ plot_3D_cell_morphology, plot_3D_schematic, plot_interactive_3D, + PCA_transformation ) from pyneuroml.pynml import read_neuroml2_file @@ -440,3 +441,72 @@ def test_cylindrical_mesh_generator(self): mesh2 = create_cylindrical_mesh(5, 10, 1.0, 1, closed=True) self.assertEqual(mesh.n_vertices + 2, mesh2.n_vertices) + + def test_PCA_transformation(self): + """Test principle component axis rotation after PCA cell transformation""" + + acell = neuroml.utils.component_factory("Cell", id="test_cell", validate=False) # type: neuroml.Cell + acell.set_spike_thresh("10mV") + soma = acell.add_segment( + prox=[0, 0, 0, 15], + dist=[10, 0, 0, 15], + seg_id=0, + use_convention=False, + reorder_segment_groups=False, + optimise_segment_groups=False, + ) + + acell.add_segment( + prox=[10, 0, 0, 12], + dist=[110, 0, 0, 12], + seg_id=1, + use_convention=False, + reorder_segment_groups=False, + optimise_segment_groups=False, + parent=soma, + ) + + acell.add_segment( + prox=[110, 0, 0, 7], + dist=[250, 0, 0, 7], + seg_id=2, + use_convention=False, + reorder_segment_groups=False, + optimise_segment_groups=False, + parent=soma, + ) + + acell.add_segment( + prox=[250, 0, 0, 4], + dist=[320, 0, 0, 4], + seg_id=3, + use_convention=False, + reorder_segment_groups=False, + optimise_segment_groups=False, + parent=soma, + ) + + print(f"cell before: {acell}") + + transformed_cell = PCA_transformation(acell) + transformed_cell.id = "test_transformed_cell" + print(f"cell after transformation: {transformed_cell}") + + #Get all segments' distal points + segment_points = [] + segments_all = transformed_cell.morphology.segments + for segment in segments_all: + segment_points.append([segment.distal.x, segment.distal.y, segment.distal.z]) + + coords = numpy.array(segment_points) + from sklearn.decomposition import PCA + + #Get the PCA components + pca = PCA() + pca.fit(coords) + # Get the principal component axes + first_pca = pca.components_ + pca_goal = numpy.array([0,first_pca[0][1],0]) + #Test if PCA of transformed cell is on y axis + print(f'type first pca {first_pca} and type pca_goal {pca_goal}') + numpy.testing.assert_almost_equal(pca_goal, first_pca[0], 3) From 4bc0e6f59703502b53eff3171eefaa2055813a1e Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 16:09:50 +0300 Subject: [PATCH 17/28] Added isPCA argument to enable PCA transformation from plot_interactive_3D --- pyneuroml/plot/PlotMorphologyVispy.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index aa8cffac..a2023f46 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -277,6 +277,7 @@ def plot_interactive_3D( ] = None, highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, precision: typing.Tuple[int, int] = (4, 200), + isPCA: bool=False, ): """Plot interactive plots in 3D using Vispy @@ -373,6 +374,8 @@ def plot_interactive_3D( If you have a good GPU, you can increase both these values to get more detailed visualizations :type precision: (int, int) + :param isPCA: bool that if True, transforms single Cell according to its PCA to make it look 'upwards' + :type isPCA: bool """ if plot_type not in ["detailed", "constant", "schematic", "point"]: raise ValueError( @@ -502,7 +505,7 @@ def plot_interactive_3D( view_min = list(numpy.array(pos)) view_max = list(numpy.array(pos)) - if singleCell: + if singleCell and isPCA: view_center=[0,0,0] else: view_center=None @@ -613,6 +616,7 @@ def plot_interactive_3D( nogui=True, meshdata=meshdata, mesh_precision=precision[0], + isPCA=isPCA ) elif ( plot_type == "detailed" @@ -640,6 +644,7 @@ def plot_interactive_3D( meshdata=meshdata, mesh_precision=precision[0], highlight_spec=cell_highlight_spec, + isPCA = isPCA ) # if too many meshes, reduce precision and retry, recursively @@ -660,6 +665,7 @@ def plot_interactive_3D( plot_spec=plot_spec, precision=precision, highlight_spec=highlight_spec, + isPCA=isPCA ) # break the recursion, don't plot in the calling method return @@ -756,8 +762,7 @@ def plot_3D_cell_morphology( meshdata: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, mesh_precision: int = 2, highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, - x_angle: float = None, - y_angle: float = None + isPCA: bool=False ): """Plot the detailed 3D morphology of a cell using vispy. https://vispy.org/ @@ -840,6 +845,8 @@ def plot_3D_cell_morphology( is used) :type highlight_spec: dict + :param isPCA: bool that if True, transforms single Cell according to its PCA to make it look 'upwards' + :type isPCA: bool :raises: ValueError if `cell` is None """ @@ -864,9 +871,9 @@ def plot_3D_cell_morphology( axon_segs = cell.get_all_segments_in_group("axon_group") except Exception: axon_segs = [] - - #Placeholder for pca_ - cell = PCA_transformation(cell) + + if isPCA: + cell = PCA_transformation(cell) current_canvas = current_view = None if current_canvas is None or current_view is None: @@ -1114,6 +1121,7 @@ def plot_3D_schematic( color: typing.Optional[str] = "Cell", meshdata: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, mesh_precision: int = 2, + isPCA: bool = False ) -> None: """Plot a 3D schematic of the provided segment groups using vispy. layer.. @@ -1168,10 +1176,14 @@ def plot_3D_schematic( dendrites, and soma segments :type color: str + :param isPCA: bool that if True, transforms single Cell according to its PCA to make it look 'upwards' + :type isPCA: bool """ if title == "": title = f"3D schematic of segment groups from {cell.id}" + if isPCA: + cell = PCA_transformation(cell) try: soma_segs = cell.get_all_segments_in_group("soma_group") except Exception: From 4f153b1c964eda7b12d1fafa70c791db79930f52 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 20:03:36 +0300 Subject: [PATCH 18/28] Fixed z_angle rotation in PCA_transformation --- pyneuroml/plot/PlotMorphologyVispy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index a2023f46..238f4cf8 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -727,7 +727,7 @@ def PCA_transformation( rotated_pca = numpy.dot(rotation_y,first_pca) #z angle needed to eliminate x component - z_angle = math.atan(rotated_pca[0]/rotated_pca[1]) + z_angle = -math.atan(rotated_pca[0]/rotated_pca[1]) rotation_z = numpy.array( [ From c7808eeb44fc22a96ce4cbb0fbb9116993ed68fe Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 20:19:41 +0300 Subject: [PATCH 19/28] Documentation fixes --- pyneuroml/plot/PlotMorphologyVispy.py | 43 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 238f4cf8..d64f517b 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -115,11 +115,11 @@ def create_new_vispy_canvas( :type view_min: [float, float, float] :param view_max: max view co-ordinates :type view_max: [float, float, float] - :param view_center: center view co-ordinates + :param view_center: center view co-ordinates, calculated from view max/min if omitted :type view_center: [float, float, float] :param title: title of plot :type title: str - :param axes_pos: position to draw axes at + :param axes_pos: None by default: axes omitted, otherwise axes centered at given position with colours red, green, blue for x,y,z axis respecitvely :type axes_pos: [float, float, float] :param axes_length: length of axes :type axes_length: float @@ -277,7 +277,7 @@ def plot_interactive_3D( ] = None, highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, precision: typing.Tuple[int, int] = (4, 200), - isPCA: bool=False, + upright: bool=False, ): """Plot interactive plots in 3D using Vispy @@ -374,8 +374,11 @@ def plot_interactive_3D( If you have a good GPU, you can increase both these values to get more detailed visualizations :type precision: (int, int) - :param isPCA: bool that if True, transforms single Cell according to its PCA to make it look 'upwards' - :type isPCA: bool + :param upright: bool only applicable for single cells: Makes cells "upright" + (along Y axis) by calculating it PCA and transforming cell co-ordinates to + align along the first principal component. Note that the original cell object + is unchanged, this is for visualization purposes only + :type upright: bool """ if plot_type not in ["detailed", "constant", "schematic", "point"]: raise ValueError( @@ -505,7 +508,7 @@ def plot_interactive_3D( view_min = list(numpy.array(pos)) view_max = list(numpy.array(pos)) - if singleCell and isPCA: + if singleCell and upright: view_center=[0,0,0] else: view_center=None @@ -616,7 +619,7 @@ def plot_interactive_3D( nogui=True, meshdata=meshdata, mesh_precision=precision[0], - isPCA=isPCA + upright=upright ) elif ( plot_type == "detailed" @@ -644,7 +647,7 @@ def plot_interactive_3D( meshdata=meshdata, mesh_precision=precision[0], highlight_spec=cell_highlight_spec, - isPCA = isPCA + upright = upright ) # if too many meshes, reduce precision and retry, recursively @@ -665,7 +668,7 @@ def plot_interactive_3D( plot_spec=plot_spec, precision=precision, highlight_spec=highlight_spec, - isPCA=isPCA + upright=upright ) # break the recursion, don't plot in the calling method return @@ -762,7 +765,7 @@ def plot_3D_cell_morphology( meshdata: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, mesh_precision: int = 2, highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, - isPCA: bool=False + upright: bool=False ): """Plot the detailed 3D morphology of a cell using vispy. https://vispy.org/ @@ -845,8 +848,11 @@ def plot_3D_cell_morphology( is used) :type highlight_spec: dict - :param isPCA: bool that if True, transforms single Cell according to its PCA to make it look 'upwards' - :type isPCA: bool + :param upright: bool only applicable for single cells: Makes cells "upright" + (along Y axis) by calculating it PCA and transforming cell co-ordinates to + align along the first principal component. Note that the original cell object + is unchanged, this is for visualization purposes only + :type upright: bool :raises: ValueError if `cell` is None """ @@ -872,7 +878,7 @@ def plot_3D_cell_morphology( except Exception: axon_segs = [] - if isPCA: + if upright: cell = PCA_transformation(cell) current_canvas = current_view = None @@ -1121,7 +1127,7 @@ def plot_3D_schematic( color: typing.Optional[str] = "Cell", meshdata: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, mesh_precision: int = 2, - isPCA: bool = False + upright: bool = False ) -> None: """Plot a 3D schematic of the provided segment groups using vispy. layer.. @@ -1176,13 +1182,16 @@ def plot_3D_schematic( dendrites, and soma segments :type color: str - :param isPCA: bool that if True, transforms single Cell according to its PCA to make it look 'upwards' - :type isPCA: bool + :param upright: bool only applicable for single cells: Makes cells "upright" + (along Y axis) by calculating it PCA and transforming cell co-ordinates to + align along the first principal component. Note that the original cell object + is unchanged, this is for visualization purposes only + :type upright: bool """ if title == "": title = f"3D schematic of segment groups from {cell.id}" - if isPCA: + if upright: cell = PCA_transformation(cell) try: soma_segs = cell.get_all_segments_in_group("soma_group") From f791fc4c463b5cf9b8e618f219c108e493ba3455 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 20:59:06 +0300 Subject: [PATCH 20/28] test pre commit --- pyneuroml/plot/PlotMorphologyVispy.py | 141 ++++++++++++++------------ 1 file changed, 75 insertions(+), 66 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index d64f517b..7e385394 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -96,6 +96,7 @@ def add_text_to_vispy_3D_plot( parent=current_canvas, ) + def create_new_vispy_canvas( view_min: typing.Optional[typing.List[float]] = None, view_max: typing.Optional[typing.List[float]] = None, @@ -105,7 +106,6 @@ def create_new_vispy_canvas( axes_width: int = 2, theme=PYNEUROML_VISPY_THEME, view_center: typing.Optional[typing.List[float]] = None, - ): """Create a new vispy scene canvas with a view and optional axes lines @@ -119,7 +119,7 @@ def create_new_vispy_canvas( :type view_center: [float, float, float] :param title: title of plot :type title: str - :param axes_pos: None by default: axes omitted, otherwise axes centered at given position with colours red, green, blue for x,y,z axis respecitvely + :param axes_pos: None by default: axes omitted, otherwise axes centered at given position with colours red, green, blue for x,y,z axis respecitvely :type axes_pos: [float, float, float] :param axes_length: length of axes :type axes_length: float @@ -147,13 +147,13 @@ def create_new_vispy_canvas( # create cameras # https://vispy.org/gallery/scene/flipped_axis.html - cam1 = scene.cameras.PanZoomCamera(parent=view.scene, name="PanZoom", up='y') + cam1 = scene.cameras.PanZoomCamera(parent=view.scene, name="PanZoom", up="y") - cam2 = scene.cameras.TurntableCamera(parent=view.scene, name="Turntable", up='y') + cam2 = scene.cameras.TurntableCamera(parent=view.scene, name="Turntable", up="y") - cam3 = scene.cameras.ArcballCamera(parent=view.scene, name="Arcball", up='y') + cam3 = scene.cameras.ArcballCamera(parent=view.scene, name="Arcball", up="y") - cam4 = scene.cameras.FlyCamera(parent=view.scene, name="Fly", up='y') + cam4 = scene.cameras.FlyCamera(parent=view.scene, name="Fly", up="y") # do not keep z up cam4.autoroll = False @@ -187,8 +187,8 @@ def create_new_vispy_canvas( logger.debug(f"{xrange}, {yrange}, {zrange}") acam.set_range(x=xrange, y=yrange, z=zrange) - - #Calculate view center if it is None + + # Calculate view center if it is None if view_center is None: view_center = (numpy.array(view_max) + numpy.array(view_min)) / 2 logger.debug(f"Center is {view_center}") @@ -211,22 +211,21 @@ def create_new_vispy_canvas( scene.Line( [points[0], points[1]], parent=view.scene, - color='red', + color="red", width=axes_width, ) scene.Line( [points[0], points[2]], parent=view.scene, - color='green', + color="green", width=axes_width, ) scene.Line( - [points[0], points[3]], - parent=view.scene, - color='blue', - width=axes_width, - ) - + [points[0], points[3]], + parent=view.scene, + color="blue", + width=axes_width, + ) def vispy_rotate(self): view.camera.orbit(azim=1, elev=0) @@ -277,7 +276,7 @@ def plot_interactive_3D( ] = None, highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, precision: typing.Tuple[int, int] = (4, 200), - upright: bool=False, + upright: bool = False, ): """Plot interactive plots in 3D using Vispy @@ -374,8 +373,8 @@ def plot_interactive_3D( If you have a good GPU, you can increase both these values to get more detailed visualizations :type precision: (int, int) - :param upright: bool only applicable for single cells: Makes cells "upright" - (along Y axis) by calculating it PCA and transforming cell co-ordinates to + :param upright: bool only applicable for single cells: Makes cells "upright" + (along Y axis) by calculating it PCA and transforming cell co-ordinates to align along the first principal component. Note that the original cell object is unchanged, this is for visualization purposes only :type upright: bool @@ -498,7 +497,6 @@ def plot_interactive_3D( else: singleCell = True cell = list(pop_id_vs_cell.values())[0] - if cell is not None: view_min, view_max = get_cell_bound_box(cell) @@ -509,11 +507,16 @@ def plot_interactive_3D( view_max = list(numpy.array(pos)) if singleCell and upright: - view_center=[0,0,0] + view_center = [0, 0, 0] else: - view_center=None + view_center = None current_canvas, current_view = create_new_vispy_canvas( - view_min, view_max, title, axes_pos=[0,0,0], theme=theme, view_center=view_center + view_min, + view_max, + title, + axes_pos=[0, 0, 0], + theme=theme, + view_center=view_center, ) logger.debug(f"figure extents are: {view_min}, {view_max}") @@ -619,7 +622,7 @@ def plot_interactive_3D( nogui=True, meshdata=meshdata, mesh_precision=precision[0], - upright=upright + upright=upright, ) elif ( plot_type == "detailed" @@ -647,7 +650,7 @@ def plot_interactive_3D( meshdata=meshdata, mesh_precision=precision[0], highlight_spec=cell_highlight_spec, - upright = upright + upright=upright, ) # if too many meshes, reduce precision and retry, recursively @@ -668,7 +671,7 @@ def plot_interactive_3D( plot_spec=plot_spec, precision=precision, highlight_spec=highlight_spec, - upright=upright + upright=upright, ) # break the recursion, don't plot in the calling method return @@ -683,11 +686,10 @@ def plot_interactive_3D( def PCA_transformation( - cell: Optional[Cell] = None, + cell: Cell = None, inplace: bool = False, - -)-> Cell: - """ Use cell's PCA to make it upright +) -> Cell: + """Use cell's PCA to make it upright .. versionadded:: 1.2.13 @@ -699,55 +701,56 @@ def PCA_transformation( :returns: new neuroml.Cell object :rtype: neuroml.Cell """ - - #Get all segments' distal points + + # Get all segments' distal points segment_points = [] segments_all = cell.morphology.segments for segment in segments_all: segment_points.append([segment.distal.x, segment.distal.y, segment.distal.z]) - coords = numpy.array(segment_points) from sklearn.decomposition import PCA - #Get the PCA components + # Get the PCA components pca = PCA() pca.fit(coords) # Get the principal component axes principal_axes = pca.components_ - #Get the first principal component axis + # Get the first principal component axis first_pca = principal_axes[0] - #y angle needed to eliminate z component - y_angle = math.atan(first_pca[2]/first_pca[0]) + # y angle needed to eliminate z component + y_angle = math.atan(first_pca[2] / first_pca[0]) rotation_y = numpy.array( - [ - [math.cos(y_angle), 0, math.sin(y_angle)], - [0, 1, 0], - [-math.sin(y_angle), 0, math.cos(y_angle)], - ] - ) - rotated_pca = numpy.dot(rotation_y,first_pca) + [ + [math.cos(y_angle), 0, math.sin(y_angle)], + [0, 1, 0], + [-math.sin(y_angle), 0, math.cos(y_angle)], + ] + ) + rotated_pca = numpy.dot(rotation_y, first_pca) - #z angle needed to eliminate x component - z_angle = -math.atan(rotated_pca[0]/rotated_pca[1]) + # z angle needed to eliminate x component + z_angle = -math.atan(rotated_pca[0] / rotated_pca[1]) rotation_z = numpy.array( - [ - [math.cos(z_angle), -math.sin(z_angle), 0], - [math.sin(z_angle), math.cos(z_angle), 0], - [0, 0, 1], - ] - ) + [ + [math.cos(z_angle), -math.sin(z_angle), 0], + [math.sin(z_angle), math.cos(z_angle), 0], + [0, 0, 1], + ] + ) rotated_pca2 = numpy.dot(rotation_z, rotated_pca) if rotated_pca2[2] < 0: z_angle += numpy.pi rotated_pca2[2] = -rotated_pca2[2] - cell = translate_cell_to_coords(cell, inplace=inplace, dest=[0,0,0]) - cell = rotate_cell(cell, 0, y_angle, z_angle, 'yzx', relative_to_soma=False, inplace=inplace) + cell = translate_cell_to_coords(cell, inplace=inplace, dest=[0, 0, 0]) + cell = rotate_cell( + cell, 0, y_angle, z_angle, "yzx", relative_to_soma=False, inplace=inplace + ) return cell - + def plot_3D_cell_morphology( offset: typing.List[float] = [0, 0, 0], @@ -765,7 +768,7 @@ def plot_3D_cell_morphology( meshdata: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, mesh_precision: int = 2, highlight_spec: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, - upright: bool=False + upright: bool = False, ): """Plot the detailed 3D morphology of a cell using vispy. https://vispy.org/ @@ -848,8 +851,8 @@ def plot_3D_cell_morphology( is used) :type highlight_spec: dict - :param upright: bool only applicable for single cells: Makes cells "upright" - (along Y axis) by calculating it PCA and transforming cell co-ordinates to + :param upright: bool only applicable for single cells: Makes cells "upright" + (along Y axis) by calculating it PCA and transforming cell co-ordinates to align along the first principal component. Note that the original cell object is unchanged, this is for visualization purposes only :type upright: bool @@ -877,15 +880,16 @@ def plot_3D_cell_morphology( axon_segs = cell.get_all_segments_in_group("axon_group") except Exception: axon_segs = [] - + view_center = None if upright: cell = PCA_transformation(cell) - current_canvas = current_view = None + current_canvas = current_view = None + view_center = [0, 0, 0] if current_canvas is None or current_view is None: view_min, view_max = get_cell_bound_box(cell) current_canvas, current_view = create_new_vispy_canvas( - view_min, view_max, title, theme=theme + view_min, view_max, title, theme=theme, view_center=view_center ) if color == "Groups": @@ -1127,7 +1131,7 @@ def plot_3D_schematic( color: typing.Optional[str] = "Cell", meshdata: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, mesh_precision: int = 2, - upright: bool = False + upright: bool = False, ) -> None: """Plot a 3D schematic of the provided segment groups using vispy. layer.. @@ -1182,8 +1186,8 @@ def plot_3D_schematic( dendrites, and soma segments :type color: str - :param upright: bool only applicable for single cells: Makes cells "upright" - (along Y axis) by calculating it PCA and transforming cell co-ordinates to + :param upright: bool only applicable for single cells: Makes cells "upright" + (along Y axis) by calculating it PCA and transforming cell co-ordinates to align along the first principal component. Note that the original cell object is unchanged, this is for visualization purposes only :type upright: bool @@ -1217,11 +1221,16 @@ def plot_3D_schematic( segment_groups, check_parentage=False ) - # if no canvas is defined, define a new one + view_center = None + if upright: + cell = PCA_transformation(cell) + current_canvas = current_view = None + view_center = [0, 0, 0] + if current_canvas is None or current_view is None: view_min, view_max = get_cell_bound_box(cell) current_canvas, current_view = create_new_vispy_canvas( - view_min, view_max, title, theme=theme + view_min, view_max, title, theme=theme, view_center=view_center ) # colors for cell From ae681bebc3b56c19564148cde9238fbd76256398 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 21:04:40 +0300 Subject: [PATCH 21/28] Change PCA_transformation to make_cell_upright --- pyneuroml/plot/PlotMorphologyVispy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 7e385394..d55b134f 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -685,7 +685,7 @@ def plot_interactive_3D( app.run() -def PCA_transformation( +def make_cell_upright( cell: Cell = None, inplace: bool = False, ) -> Cell: @@ -882,7 +882,7 @@ def plot_3D_cell_morphology( axon_segs = [] view_center = None if upright: - cell = PCA_transformation(cell) + cell = make_cell_upright(cell) current_canvas = current_view = None view_center = [0, 0, 0] @@ -1196,7 +1196,7 @@ def plot_3D_schematic( title = f"3D schematic of segment groups from {cell.id}" if upright: - cell = PCA_transformation(cell) + cell = make_cell_upright(cell) try: soma_segs = cell.get_all_segments_in_group("soma_group") except Exception: @@ -1223,7 +1223,7 @@ def plot_3D_schematic( view_center = None if upright: - cell = PCA_transformation(cell) + cell = make_cell_upright(cell) current_canvas = current_view = None view_center = [0, 0, 0] From 5280e05f8bc8b756f769b938a2be4d5c10d9fd2e Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 21:07:46 +0300 Subject: [PATCH 22/28] Deleted unecessary variable --- pyneuroml/plot/PlotMorphologyVispy.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index d55b134f..440ca32a 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -733,17 +733,8 @@ def make_cell_upright( # z angle needed to eliminate x component z_angle = -math.atan(rotated_pca[0] / rotated_pca[1]) - rotation_z = numpy.array( - [ - [math.cos(z_angle), -math.sin(z_angle), 0], - [math.sin(z_angle), math.cos(z_angle), 0], - [0, 0, 1], - ] - ) - rotated_pca2 = numpy.dot(rotation_z, rotated_pca) - if rotated_pca2[2] < 0: + if z_angle < 0: z_angle += numpy.pi - rotated_pca2[2] = -rotated_pca2[2] cell = translate_cell_to_coords(cell, inplace=inplace, dest=[0, 0, 0]) cell = rotate_cell( From 0ced4d1c8ce7c0c75f097dead6e8a7432f9498f1 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 21:17:54 +0300 Subject: [PATCH 23/28] Added scikit-learn library dep --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index fed162a7..31a9910c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -97,6 +97,7 @@ vispy-common = scipy pyopengl PyOpenGL-accelerate + scikit-learn vispy-qt5 = pyNeuroML[vispy-common] From 24c34cbdb194d4b297cab7d6bf4f657c3236d607 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 21:23:32 +0300 Subject: [PATCH 24/28] Minor fix --- tests/plot/test_morphology_plot.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/plot/test_morphology_plot.py b/tests/plot/test_morphology_plot.py index f314bea3..4d709010 100644 --- a/tests/plot/test_morphology_plot.py +++ b/tests/plot/test_morphology_plot.py @@ -25,10 +25,10 @@ ) from pyneuroml.plot.PlotMorphologyVispy import ( create_cylindrical_mesh, + make_cell_upright, plot_3D_cell_morphology, plot_3D_schematic, plot_interactive_3D, - PCA_transformation ) from pyneuroml.pynml import read_neuroml2_file @@ -444,7 +444,7 @@ def test_cylindrical_mesh_generator(self): def test_PCA_transformation(self): """Test principle component axis rotation after PCA cell transformation""" - + acell = neuroml.utils.component_factory("Cell", id="test_cell", validate=False) # type: neuroml.Cell acell.set_spike_thresh("10mV") soma = acell.add_segment( @@ -488,25 +488,27 @@ def test_PCA_transformation(self): print(f"cell before: {acell}") - transformed_cell = PCA_transformation(acell) + transformed_cell = make_cell_upright(acell) transformed_cell.id = "test_transformed_cell" print(f"cell after transformation: {transformed_cell}") - #Get all segments' distal points + # Get all segments' distal points segment_points = [] segments_all = transformed_cell.morphology.segments for segment in segments_all: - segment_points.append([segment.distal.x, segment.distal.y, segment.distal.z]) - + segment_points.append( + [segment.distal.x, segment.distal.y, segment.distal.z] + ) + coords = numpy.array(segment_points) from sklearn.decomposition import PCA - #Get the PCA components + # Get the PCA components pca = PCA() pca.fit(coords) # Get the principal component axes first_pca = pca.components_ - pca_goal = numpy.array([0,first_pca[0][1],0]) - #Test if PCA of transformed cell is on y axis - print(f'type first pca {first_pca} and type pca_goal {pca_goal}') + pca_goal = numpy.array([0, first_pca[0][1], 0]) + # Test if PCA of transformed cell is on y axis + print(f"type first pca {first_pca} and type pca_goal {pca_goal}") numpy.testing.assert_almost_equal(pca_goal, first_pca[0], 3) From daf152aa4dec3f6bc6f259048350eac8604d59ab Mon Sep 17 00:00:00 2001 From: lej0hn Date: Tue, 11 Jun 2024 22:02:53 +0300 Subject: [PATCH 25/28] Doc fix: upright argument doc addition --- pyneuroml/plot/PlotMorphologyVispy.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 440ca32a..d2fda7fe 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -374,9 +374,12 @@ def plot_interactive_3D( detailed visualizations :type precision: (int, int) :param upright: bool only applicable for single cells: Makes cells "upright" - (along Y axis) by calculating it PCA and transforming cell co-ordinates to - align along the first principal component. Note that the original cell object - is unchanged, this is for visualization purposes only + (along Y axis) by calculating its PCA, rotating it so it is along the Y axis, + and transforming cell co-ordinates to align along the rotated first principal + component. If the rotation around the z axis needed is < 0, then the cell is + rotated an additional 180 degrees. This is empirically found to rotate the cell + "upwards" instead of "downwards" in most cases. Note that the original cell object + is unchanged, this is for visualization purposes only. :type upright: bool """ if plot_type not in ["detailed", "constant", "schematic", "point"]: @@ -843,9 +846,12 @@ def plot_3D_cell_morphology( :type highlight_spec: dict :param upright: bool only applicable for single cells: Makes cells "upright" - (along Y axis) by calculating it PCA and transforming cell co-ordinates to - align along the first principal component. Note that the original cell object - is unchanged, this is for visualization purposes only + (along Y axis) by calculating its PCA, rotating it so it is along the Y axis, + and transforming cell co-ordinates to align along the rotated first principal + component. If the rotation around the z axis needed is < 0, then the cell is + rotated an additional 180 degrees. This is empirically found to rotate the cell + "upwards" instead of "downwards" in most cases. Note that the original cell object + is unchanged, this is for visualization purposes only. :type upright: bool :raises: ValueError if `cell` is None @@ -1178,9 +1184,12 @@ def plot_3D_schematic( :type color: str :param upright: bool only applicable for single cells: Makes cells "upright" - (along Y axis) by calculating it PCA and transforming cell co-ordinates to - align along the first principal component. Note that the original cell object - is unchanged, this is for visualization purposes only + (along Y axis) by calculating its PCA, rotating it so it is along the Y axis, + and transforming cell co-ordinates to align along the rotated first principal + component. If the rotation around the z axis needed is < 0, then the cell is + rotated an additional 180 degrees. This is empirically found to rotate the cell + "upwards" instead of "downwards" in most cases. Note that the original cell object + is unchanged, this is for visualization purposes only. :type upright: bool """ if title == "": From 166eb9eac205fc1481dedda49dc033a36360b902 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Wed, 12 Jun 2024 14:34:41 +0300 Subject: [PATCH 26/28] Added axes_pos argument in plot_interactive_3d, plot_3d_cell_morphology and plot_3d_schematic functions --- pyneuroml/plot/PlotMorphologyVispy.py | 34 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index d2fda7fe..e08881b1 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -268,6 +268,7 @@ def plot_interactive_3D( min_width: float = DEFAULTS["minWidth"], verbose: bool = False, plot_type: str = "constant", + axes_pos: typing.Optional[typing.List] = None, title: typing.Optional[str] = None, theme: str = "light", nogui: bool = False, @@ -314,6 +315,8 @@ def plot_interactive_3D( morphology) :type plot_type: str + :param axes_pos: None by default: axes omitted, otherwise axes centered at given position with colours red, green, blue for x,y,z axis respecitvely + :type axes_pos: [float, float, float] :param title: title of plot :type title: str :param theme: theme to use (light/dark) @@ -465,8 +468,6 @@ def plot_interactive_3D( # not used later, clear up del cell_id_vs_cell - singleCell = False - if len(positions) > 1: only_pos = [] for posdict in positions.values(): @@ -498,7 +499,6 @@ def plot_interactive_3D( logger.debug(f"center, view_min, max are {center}, {view_min}, {view_max}") else: - singleCell = True cell = list(pop_id_vs_cell.values())[0] if cell is not None: @@ -509,15 +509,16 @@ def plot_interactive_3D( view_min = list(numpy.array(pos)) view_max = list(numpy.array(pos)) - if singleCell and upright: + if upright: view_center = [0, 0, 0] else: view_center = None + current_canvas, current_view = create_new_vispy_canvas( view_min, view_max, title, - axes_pos=[0, 0, 0], + axes_pos=axes_pos, theme=theme, view_center=view_center, ) @@ -622,6 +623,7 @@ def plot_interactive_3D( verbose=verbose, current_canvas=current_canvas, current_view=current_view, + axes_pos=axes_pos, nogui=True, meshdata=meshdata, mesh_precision=precision[0], @@ -648,6 +650,7 @@ def plot_interactive_3D( verbose=verbose, current_canvas=current_canvas, current_view=current_view, + axes_pos=axes_pos, min_width=min_width, nogui=True, meshdata=meshdata, @@ -668,6 +671,7 @@ def plot_interactive_3D( min_width=min_width, verbose=verbose, plot_type=plot_type, + axes_pos=axes_pos, title=title, theme=theme, nogui=nogui, @@ -756,6 +760,7 @@ def plot_3D_cell_morphology( current_view: Optional[scene.ViewBox] = None, min_width: float = DEFAULTS["minWidth"], axis_min_max: typing.List = [float("inf"), -1 * float("inf")], + axes_pos: typing.Optional[typing.List] = None, nogui: bool = True, plot_type: str = "constant", theme: str = "light", @@ -802,6 +807,8 @@ def plot_3D_cell_morphology( :type min_width: float :param axis_min_max: min, max value of axes :type axis_min_max: [float, float] + :param axes_pos: None by default: axes omitted, otherwise axes centered at given position with colours red, green, blue for x,y,z axis respecitvely + :type axes_pos: [float, float, float] :param title: title of plot :type title: str :param verbose: show extra information (default: False) @@ -886,7 +893,12 @@ def plot_3D_cell_morphology( if current_canvas is None or current_view is None: view_min, view_max = get_cell_bound_box(cell) current_canvas, current_view = create_new_vispy_canvas( - view_min, view_max, title, theme=theme, view_center=view_center + view_min, + view_max, + title, + theme=theme, + axes_pos=axes_pos, + view_center=view_center, ) if color == "Groups": @@ -1124,6 +1136,7 @@ def plot_3D_schematic( title: str = "", current_canvas: Optional[scene.SceneCanvas] = None, current_view: Optional[scene.ViewBox] = None, + axes_pos: typing.Optional[typing.List] = None, theme: str = "light", color: typing.Optional[str] = "Cell", meshdata: typing.Optional[typing.Dict[typing.Any, typing.Any]] = None, @@ -1173,6 +1186,8 @@ def plot_3D_schematic( :type current_canvas: scene.SceneCanvas :param current_view: vispy viewbox to use :type current_view: ViewBox + :param axes_pos: None by default: axes omitted, otherwise axes centered at given position with colours red, green, blue for x,y,z axis respecitvely + :type axes_pos: [float, float, float] :param theme: theme to use (light/dark) :type theme: str :param color: color to use for segment groups with some special values: @@ -1230,7 +1245,12 @@ def plot_3D_schematic( if current_canvas is None or current_view is None: view_min, view_max = get_cell_bound_box(cell) current_canvas, current_view = create_new_vispy_canvas( - view_min, view_max, title, theme=theme, view_center=view_center + view_min, + view_max, + title, + theme=theme, + axes_pos=axes_pos, + view_center=view_center, ) # colors for cell From 431c49753c8ff27d43bb07d1593854159baa323d Mon Sep 17 00:00:00 2001 From: lej0hn Date: Thu, 20 Jun 2024 23:58:33 +0300 Subject: [PATCH 27/28] Raise error when attempting to run functions with upright but not single cell --- pyneuroml/plot/PlotMorphologyVispy.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index e08881b1..645e0d4f 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -469,6 +469,8 @@ def plot_interactive_3D( del cell_id_vs_cell if len(positions) > 1: + if upright == True: + raise AttributeError("Argument upright can be True only for single cells") only_pos = [] for posdict in positions.values(): for poss in posdict.values(): @@ -872,6 +874,11 @@ def plot_3D_cell_morphology( highlight_spec = {} logging.debug("highlight_spec is " + str(highlight_spec)) + view_center = None + if upright: + cell = make_cell_upright(cell) + current_canvas = current_view = None + view_center = [0, 0, 0] try: soma_segs = cell.get_all_segments_in_group("soma_group") except Exception: @@ -884,11 +891,6 @@ def plot_3D_cell_morphology( axon_segs = cell.get_all_segments_in_group("axon_group") except Exception: axon_segs = [] - view_center = None - if upright: - cell = make_cell_upright(cell) - current_canvas = current_view = None - view_center = [0, 0, 0] if current_canvas is None or current_view is None: view_min, view_max = get_cell_bound_box(cell) @@ -1210,8 +1212,11 @@ def plot_3D_schematic( if title == "": title = f"3D schematic of segment groups from {cell.id}" + view_center = None if upright: cell = make_cell_upright(cell) + current_canvas = current_view = None + view_center = [0, 0, 0] try: soma_segs = cell.get_all_segments_in_group("soma_group") except Exception: @@ -1236,12 +1241,6 @@ def plot_3D_schematic( segment_groups, check_parentage=False ) - view_center = None - if upright: - cell = make_cell_upright(cell) - current_canvas = current_view = None - view_center = [0, 0, 0] - if current_canvas is None or current_view is None: view_min, view_max = get_cell_bound_box(cell) current_canvas, current_view = create_new_vispy_canvas( From a2f6ba5c27f07ca0c953cc910078a7682737c098 Mon Sep 17 00:00:00 2001 From: lej0hn Date: Fri, 21 Jun 2024 00:01:01 +0300 Subject: [PATCH 28/28] Ruff fix --- pyneuroml/plot/PlotMorphologyVispy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyneuroml/plot/PlotMorphologyVispy.py b/pyneuroml/plot/PlotMorphologyVispy.py index 645e0d4f..c2c0f489 100644 --- a/pyneuroml/plot/PlotMorphologyVispy.py +++ b/pyneuroml/plot/PlotMorphologyVispy.py @@ -469,7 +469,7 @@ def plot_interactive_3D( del cell_id_vs_cell if len(positions) > 1: - if upright == True: + if upright: raise AttributeError("Argument upright can be True only for single cells") only_pos = [] for posdict in positions.values():