diff --git a/data/spines-meshes/lq/spine_1.mtl b/data/spines-meshes/lq/spine_1.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_1.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_1.obj b/data/spines-meshes/lq/spine_1.obj index aa885ca78..eb867d305 100644 --- a/data/spines-meshes/lq/spine_1.obj +++ b/data/spines-meshes/lq/spine_1.obj @@ -380,7 +380,6 @@ vn 0.9821 -0.1666 0.0874 vn 0.3216 -0.9396 0.1172 vn 0.6748 0.2947 -0.6766 vn 0.9608 -0.2542 0.1104 -usemtl color.basal_dendrites_skeleton_color_0_color s 1 f 136//1 2//2 1//3 f 4//4 5//5 6//6 diff --git a/data/spines-meshes/lq/spine_10.mtl b/data/spines-meshes/lq/spine_10.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_10.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_11.mtl b/data/spines-meshes/lq/spine_11.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_11.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_12.mtl b/data/spines-meshes/lq/spine_12.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_12.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_13.mtl b/data/spines-meshes/lq/spine_13.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_13.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_14.mtl b/data/spines-meshes/lq/spine_14.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_14.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_15.mtl b/data/spines-meshes/lq/spine_15.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_15.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_16.mtl b/data/spines-meshes/lq/spine_16.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_16.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_17.mtl b/data/spines-meshes/lq/spine_17.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_17.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_18.mtl b/data/spines-meshes/lq/spine_18.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_18.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_19.mtl b/data/spines-meshes/lq/spine_19.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_19.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_2.mtl b/data/spines-meshes/lq/spine_2.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_2.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_2.obj b/data/spines-meshes/lq/spine_2.obj index 0a79af89e..c5ba1303a 100644 --- a/data/spines-meshes/lq/spine_2.obj +++ b/data/spines-meshes/lq/spine_2.obj @@ -1,6 +1,3 @@ -# Blender v2.82 (sub 7) OBJ File: 'eyewire-neuron-em-stack.blend' -# www.blender.org -mtllib spine_2.mtl o -simple_2.135 v 0.067481 -0.108317 2.775392 v 0.186313 -0.082456 2.723809 @@ -500,7 +497,6 @@ vn -0.1233 0.8594 0.4962 vn -0.9043 -0.3969 0.1570 vn 0.4998 -0.8020 0.3269 vn -0.4928 -0.3126 -0.8121 -usemtl color.basal_dendrites_skeleton_color_0_color s 1 f 2//1 49//2 1//3 f 15//4 7//5 12//6 diff --git a/data/spines-meshes/lq/spine_20.mtl b/data/spines-meshes/lq/spine_20.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_20.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_21.mtl b/data/spines-meshes/lq/spine_21.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_21.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_22.mtl b/data/spines-meshes/lq/spine_22.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_22.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_23.mtl b/data/spines-meshes/lq/spine_23.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_23.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_24.mtl b/data/spines-meshes/lq/spine_24.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_24.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_25.mtl b/data/spines-meshes/lq/spine_25.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_25.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_27.mtl b/data/spines-meshes/lq/spine_27.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_27.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_28.mtl b/data/spines-meshes/lq/spine_28.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_28.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_29.mtl b/data/spines-meshes/lq/spine_29.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_29.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_3.mtl b/data/spines-meshes/lq/spine_3.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_3.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_30.mtl b/data/spines-meshes/lq/spine_30.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_30.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_31.mtl b/data/spines-meshes/lq/spine_31.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_31.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_32.mtl b/data/spines-meshes/lq/spine_32.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_32.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_33.mtl b/data/spines-meshes/lq/spine_33.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_33.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_34.mtl b/data/spines-meshes/lq/spine_34.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_34.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_35.mtl b/data/spines-meshes/lq/spine_35.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_35.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_36.mtl b/data/spines-meshes/lq/spine_36.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_36.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_37.mtl b/data/spines-meshes/lq/spine_37.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_37.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_38.mtl b/data/spines-meshes/lq/spine_38.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_38.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_39.mtl b/data/spines-meshes/lq/spine_39.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_39.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_4.mtl b/data/spines-meshes/lq/spine_4.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_4.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_40.mtl b/data/spines-meshes/lq/spine_40.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_40.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_41.mtl b/data/spines-meshes/lq/spine_41.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_41.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_42.mtl b/data/spines-meshes/lq/spine_42.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_42.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_43.mtl b/data/spines-meshes/lq/spine_43.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_43.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_44.mtl b/data/spines-meshes/lq/spine_44.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_44.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_45.mtl b/data/spines-meshes/lq/spine_45.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_45.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_5.mtl b/data/spines-meshes/lq/spine_5.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_5.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_6.mtl b/data/spines-meshes/lq/spine_6.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_6.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_7.mtl b/data/spines-meshes/lq/spine_7.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_7.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_8.mtl b/data/spines-meshes/lq/spine_8.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_8.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/spine_9.mtl b/data/spines-meshes/lq/spine_9.mtl deleted file mode 100644 index 7e634a721..000000000 --- a/data/spines-meshes/lq/spine_9.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'eyewire-neuron-em-stack.blend' -# Material Count: 1 - -newmtl color.basal_dendrites_skeleton_color_0_color -Ns 900.000000 -Ka 1.000000 1.000000 1.000000 -Kd 0.900000 0.100000 0.075000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 -illum 2 diff --git a/data/spines-meshes/lq/tip.obj b/data/spines-meshes/lq/tip.obj deleted file mode 100644 index 5f8d2ef82..000000000 --- a/data/spines-meshes/lq/tip.obj +++ /dev/null @@ -1,654 +0,0 @@ -# Blender v2.79 (sub 0) OBJ File: 'tip.blend' -# www.blender.org -mtllib tip.mtl -o Landscape.001 -v 0.241361 -0.380828 -0.269263 -v 0.336428 -0.388645 -0.269289 -v 0.449973 -0.256101 -0.258019 -v 0.379483 -0.281321 -0.324348 -v 0.379503 -0.210532 -0.357552 -v 0.441288 -0.177489 -0.272779 -v 0.426719 -0.085862 -0.306771 -v 0.402748 -0.145441 -0.314652 -v 0.399276 -0.033377 -0.336815 -v 0.352182 0.131427 -0.357618 -v 0.428446 0.125752 -0.277866 -v 0.262989 0.375454 -0.269529 -v 0.360037 0.264313 -0.279550 -v -0.171475 -0.405795 -0.255499 -v 0.124568 -0.397412 -0.317847 -v 0.044990 -0.376498 -0.362903 -v -0.087069 -0.403496 -0.307708 -v -0.209879 -0.353084 -0.308553 -v -0.178772 -0.302765 -0.369676 -v -0.086513 -0.358731 -0.357180 -v 0.004385 -0.358639 -0.407206 -v -0.091450 -0.333078 -0.425658 -v -0.333880 -0.245240 -0.313052 -v -0.461016 -0.154802 -0.289994 -v -0.338171 -0.107476 -0.376221 -v -0.396871 -0.115453 -0.347998 -v -0.452129 -0.044897 -0.326528 -v -0.329061 -0.011489 -0.406757 -v -0.249926 -0.207995 -0.400326 -v -0.158214 -0.301878 -0.430578 -v -0.157595 -0.252999 -0.468424 -v -0.084944 -0.285150 -0.487219 -v -0.106147 -0.261033 -0.523143 -v -0.170220 -0.132468 -0.553706 -v -0.296974 -0.125670 -0.441736 -v -0.303404 0.027994 -0.416635 -v -0.236690 0.071081 -0.504149 -v -0.223555 -0.128384 -0.488755 -v -0.208683 -0.120439 -0.529602 -v 0.266990 -0.322681 -0.357848 -v 0.134038 -0.361458 -0.399161 -v 0.250398 -0.270930 -0.418146 -v 0.309808 -0.170914 -0.414596 -v 0.263787 -0.183717 -0.424562 -v 0.348753 -0.085154 -0.422828 -v 0.336016 0.020607 -0.387043 -v 0.211733 -0.174069 -0.475705 -v 0.190324 -0.270288 -0.451481 -v 0.003017 -0.270752 -0.486935 -v 0.176472 -0.255583 -0.491574 -v 0.150566 -0.198670 -0.543621 -v 0.059848 -0.250497 -0.538468 -v 0.026274 -0.222857 -0.589599 -v 0.226306 -0.108398 -0.493387 -v 0.013324 -0.181908 -0.618424 -v 0.298981 0.020827 -0.462842 -v 0.287724 -0.072912 -0.473155 -v 0.251232 0.015387 -0.586447 -v 0.231638 -0.053408 -0.574782 -v 0.113624 -0.179290 -0.632065 -v 0.115563 -0.118122 -0.668811 -v 0.168507 -0.029026 -0.685410 -v 0.168606 -0.080520 -0.639776 -v 0.185575 0.009273 -0.662228 -v -0.020230 -0.216295 -0.550804 -v -0.134549 -0.086175 -0.623770 -v -0.111148 -0.009851 -0.655190 -v 0.079105 -0.173602 -0.672135 -v 0.010964 -0.152977 -0.659588 -v -0.073395 -0.089401 -0.709783 -v -0.067650 -0.018584 -0.686874 -v -0.005923 -0.106463 -0.717752 -v 0.002530 -0.041791 -0.730285 -v 0.079421 -0.105779 -0.688458 -v 0.110213 -0.005085 -0.718016 -v -0.009787 0.046045 -0.751271 -v -0.003909 0.378895 -0.348373 -v -0.009317 0.308482 -0.400851 -v -0.066019 0.502608 -0.256101 -v -0.129535 0.495083 -0.262776 -v -0.109927 0.383106 -0.345755 -v -0.062437 0.434279 -0.328066 -v -0.381902 0.025935 -0.348388 -v -0.404342 0.106614 -0.312793 -v -0.379913 0.172084 -0.305894 -v -0.288450 0.136495 -0.395110 -v -0.311494 0.296686 -0.282854 -v -0.476219 0.126666 -0.286635 -v -0.222268 0.180441 -0.500124 -v -0.211409 0.032595 -0.516839 -v -0.186030 0.118219 -0.583567 -v -0.138596 0.218940 -0.531254 -v -0.147507 0.308748 -0.417086 -v -0.172645 0.356870 -0.330120 -v -0.207372 0.264061 -0.382574 -v -0.098110 0.271453 -0.481414 -v -0.075227 0.294547 -0.455772 -v 0.294546 0.162885 -0.432648 -v 0.172591 0.218948 -0.467858 -v 0.273625 0.203256 -0.380603 -v 0.105316 0.291469 -0.447616 -v 0.125236 0.302176 -0.389740 -v 0.001211 0.237337 -0.498525 -v 0.112383 0.259979 -0.475702 -v 0.089687 0.232105 -0.540909 -v 0.276734 0.115267 -0.481455 -v 0.159618 0.124641 -0.652299 -v 0.242606 0.127477 -0.568107 -v 0.112639 0.204773 -0.589374 -v 0.146895 0.154970 -0.614339 -v -0.159001 0.055989 -0.613701 -v -0.123888 0.181527 -0.600628 -v -0.003727 0.187067 -0.579949 -v 0.055246 0.177906 -0.611443 -v -0.030257 0.126600 -0.678487 -v -0.088842 0.093379 -0.668490 -v -0.037639 0.103606 -0.719644 -v -0.096643 0.149839 -0.648606 -v -0.077917 0.219696 -0.543955 -v 0.068885 0.101096 -0.681124 -v 0.015898 0.090811 -0.739216 -v 0.059677 0.150416 -0.657542 -v -0.131314 0.501516 -0.253098 -v -0.136206 0.496746 -0.253098 -v -0.192127 0.429727 -0.253098 -v -0.144593 0.488616 -0.253098 -v -0.398693 0.224318 -0.253098 -v -0.453824 0.164497 -0.253098 -v -0.262018 0.371952 -0.253098 -v -0.340737 0.301330 -0.253098 -v -0.532104 0.030030 -0.253098 -v -0.532737 0.025751 -0.253098 -v -0.488862 0.142947 -0.253098 -v -0.501175 0.120851 -0.253098 -v -0.507817 -0.108336 -0.253098 -v -0.514154 -0.081956 -0.253098 -v 0.463454 -0.054008 -0.253098 -v 0.462246 -0.072088 -0.253098 -v 0.456138 -0.149368 -0.253098 -v 0.453751 -0.177180 -0.253098 -v -0.418582 -0.198613 -0.253098 -v -0.408419 -0.201872 -0.253098 -v -0.441284 -0.193490 -0.253098 -v -0.486601 -0.171846 -0.253098 -v -0.395048 -0.231487 -0.253098 -v 0.455240 -0.182064 -0.253098 -v 0.453533 -0.254247 -0.253098 -v 0.449734 -0.261731 -0.253098 -v -0.333503 -0.295082 -0.253098 -v -0.337559 -0.291676 -0.253098 -v -0.264881 -0.342473 -0.253098 -v -0.174349 -0.405037 -0.253098 -v -0.177476 -0.402290 -0.253098 -v -0.172237 -0.407615 -0.253098 -v -0.089802 -0.444711 -0.253098 -v -0.054724 -0.448687 -0.253098 -v 0.013528 -0.458280 -0.253098 -v -0.167058 -0.409665 -0.253098 -v 0.128099 -0.444668 -0.253098 -v 0.206063 -0.409121 -0.253098 -v 0.054361 -0.452699 -0.253098 -v 0.265200 -0.397830 -0.253098 -v 0.330156 -0.403187 -0.253098 -v 0.408981 -0.317634 -0.253098 -v 0.353352 -0.384593 -0.253098 -v 0.343066 -0.393026 -0.253098 -v -0.068857 0.504495 -0.253098 -v -0.065294 0.504423 -0.253098 -v 0.046005 0.464527 -0.253098 -v 0.055311 0.459530 -0.253098 -v -0.039035 0.495559 -0.253098 -v -0.062336 0.502517 -0.253098 -v 0.142852 0.442324 -0.253098 -v 0.168905 0.435681 -0.253098 -v 0.225028 0.410724 -0.253098 -v 0.275657 0.408889 -0.253098 -v 0.337497 0.343867 -0.253098 -v 0.396041 0.290288 -0.253098 -v 0.391482 0.253660 -0.253098 -v 0.441250 0.150324 -0.253098 -v 0.465671 0.096466 -0.253098 -v 0.469297 0.073112 -0.253098 -v 0.461850 0.104234 -0.253098 -v 0.453847 0.141597 -0.253098 -v 0.464410 0.135670 -0.253098 -v 0.459879 -0.017465 -0.253098 -vn 0.0000 -0.0000 1.0000 -vn -0.5529 0.4133 -0.7235 -vn -0.9282 0.1628 0.3344 -vn -0.8155 0.3426 0.4665 -vn 0.7834 -0.2747 -0.5574 -vn 0.7326 -0.4854 0.4771 -vn 0.6939 -0.5431 0.4727 -vn 0.5672 -0.4386 -0.6970 -vn 0.3475 -0.7064 -0.6166 -vn 0.3688 -0.6814 -0.6322 -vn 0.6607 -0.1115 -0.7423 -vn 0.6451 -0.6002 0.4727 -vn 0.5299 -0.6585 0.5343 -vn 0.8086 -0.0482 -0.5863 -vn 0.8760 -0.0630 0.4781 -vn 0.8744 -0.0704 0.4801 -vn 0.8056 -0.0232 -0.5920 -vn 0.7522 -0.0869 -0.6532 -vn 0.7319 0.2665 -0.6271 -vn 0.6531 0.3228 -0.6850 -vn 0.9490 0.3102 0.0561 -vn 0.8986 0.2457 0.3634 -vn 0.5455 0.3482 -0.7624 -vn 0.9697 0.2436 0.0158 -vn 0.7932 0.6049 0.0703 -vn -0.4250 -0.7256 -0.5411 -vn -0.4316 -0.7005 -0.5683 -vn -0.2115 -0.7552 -0.6204 -vn -0.2776 -0.8169 -0.5055 -vn -0.0116 -0.8349 -0.5503 -vn -0.6234 -0.6516 0.4322 -vn -0.4632 -0.6582 0.5934 -vn -0.6599 0.0607 -0.7489 -vn -0.5967 -0.3276 -0.7325 -vn -0.4011 -0.4424 -0.8021 -vn -0.6038 -0.0345 -0.7964 -vn -0.4905 -0.4963 -0.7163 -vn -0.6073 -0.5259 -0.5954 -vn -0.0974 -0.8019 -0.5894 -vn -0.5329 -0.7186 -0.4467 -vn -0.6014 -0.5139 -0.6117 -vn -0.6922 -0.3381 -0.6376 -vn -0.7585 -0.1399 -0.6364 -vn -0.6386 -0.3632 -0.6784 -vn -0.7354 -0.0182 -0.6773 -vn -0.7703 -0.4278 -0.4729 -vn -0.6893 -0.3455 -0.6367 -vn -0.5226 -0.5579 -0.6446 -vn 0.5553 -0.2990 -0.7760 -vn 0.7354 -0.1270 -0.6656 -vn 0.6369 -0.4939 -0.5919 -vn 0.6675 -0.3114 -0.6763 -vn 0.4968 -0.4791 -0.7236 -vn 0.0338 -0.7772 -0.6283 -vn 0.0526 -0.8818 -0.4686 -vn 0.4218 -0.5830 -0.6943 -vn 0.5299 -0.6359 -0.5611 -vn 0.8725 0.0894 -0.4803 -vn 0.6692 -0.2613 -0.6956 -vn 0.8506 -0.0707 -0.5211 -vn 0.7700 -0.4384 -0.4636 -vn 0.5519 -0.2217 -0.8038 -vn 0.6629 -0.4665 -0.5856 -vn 0.4999 -0.6913 -0.5217 -vn 0.5534 -0.2772 -0.7854 -vn 0.6875 0.1216 -0.7159 -vn 0.4870 -0.4569 -0.7444 -vn -0.6863 0.0231 -0.7269 -vn -0.5349 -0.3037 -0.7884 -vn -0.2670 -0.7641 -0.5872 -vn -0.2508 -0.7268 -0.6393 -vn -0.7117 -0.3485 -0.6099 -vn 0.2933 -0.3390 -0.8939 -vn 0.1306 -0.6800 -0.7214 -vn -0.2038 -0.7369 -0.6445 -vn -0.2882 -0.6620 -0.6919 -vn -0.2365 -0.7847 -0.5729 -vn -0.5016 -0.7049 -0.5014 -vn -0.2346 -0.8422 -0.4853 -vn 0.4322 -0.0060 -0.9017 -vn 0.0218 0.7962 -0.6047 -vn -0.2190 0.6108 -0.7608 -vn 0.1045 0.8236 -0.5574 -vn -0.3278 0.6818 -0.6540 -vn -0.6073 0.6250 0.4905 -vn -0.3527 0.8460 0.3998 -vn 0.1426 0.7920 -0.5936 -vn 0.2706 0.8647 0.4230 -vn 0.3052 0.8191 0.4857 -vn -0.4982 0.5865 -0.6386 -vn -0.6893 0.6486 0.3228 -vn -0.6856 0.5846 0.4336 -vn 0.0301 0.5752 -0.8174 -vn 0.1285 0.6011 -0.7888 -vn 0.3095 0.9292 0.2020 -vn 0.2160 0.9013 0.3755 -vn -0.7201 0.3424 -0.6034 -vn -0.4073 0.2730 -0.8715 -vn -0.5327 0.4178 -0.7359 -vn -0.5386 0.5126 -0.6687 -vn -0.7261 0.5904 0.3524 -vn -0.6891 0.6031 0.4016 -vn -0.7415 0.2227 -0.6329 -vn -0.3993 0.7475 -0.5308 -vn -0.7200 0.4774 -0.5036 -vn -0.4464 0.6860 -0.5745 -vn -0.2002 0.7528 -0.6271 -vn -0.6431 0.5528 -0.5300 -vn 0.3245 0.7417 -0.5869 -vn 0.3013 0.4789 -0.8245 -vn 0.5426 0.6189 -0.5678 -vn 0.2816 0.8274 -0.4859 -vn 0.2943 0.7695 -0.5667 -vn 0.0190 0.8408 -0.5409 -vn 0.5246 0.7482 -0.4060 -vn 0.9086 0.0902 -0.4077 -vn 0.7967 0.2713 -0.5399 -vn 0.2037 0.7583 -0.6192 -vn 0.7381 0.4469 -0.5054 -vn 0.7119 0.5541 -0.4313 -vn 0.3531 0.9333 0.0656 -vn 0.3952 0.9014 0.1770 -vn -0.3738 0.7153 -0.5904 -vn -0.3186 0.5773 -0.7517 -vn -0.1186 0.7996 -0.5886 -vn -0.0501 0.8000 -0.5978 -vn -0.0447 0.8181 -0.5733 -vn -0.5756 0.1126 -0.8099 -vn -0.4467 0.5611 -0.6968 -vn -0.2583 0.0084 -0.9660 -vn -0.5970 0.1877 -0.7799 -vn -0.0104 0.8065 -0.5912 -vn -0.0162 0.8893 -0.4570 -vn 0.2085 0.5702 -0.7946 -vn 0.3256 0.5033 -0.8004 -vn 0.0840 0.7065 -0.7026 -vn 0.3510 0.7210 -0.5974 -vn -0.2667 -0.8780 0.3973 -vn -0.7104 -0.6060 0.3578 -vn -0.8885 -0.2323 0.3956 -vn -0.8999 -0.1899 0.3924 -vn -0.5749 0.2100 -0.7908 -vn 0.7839 0.1160 -0.6099 -vn 0.9358 0.1042 0.3368 -vn 0.8950 0.1001 0.4346 -vn 0.9300 -0.0125 0.3672 -vn 0.8964 0.0339 0.4419 -vn 0.8841 -0.1714 0.4346 -vn 0.9251 0.0768 0.3717 -vn 0.4965 0.8136 0.3025 -vn 0.8789 0.4187 0.2284 -vn 0.6688 0.7181 0.1922 -vn 0.8979 0.3828 0.2175 -vn 0.1143 -0.8189 -0.5623 -vn -0.6013 -0.4910 -0.6303 -vn 0.1387 -0.7653 -0.6285 -vn 0.2010 -0.8419 -0.5008 -vn 0.1715 -0.7883 -0.5908 -vn 0.6308 -0.5375 -0.5596 -vn 0.0014 -0.5284 -0.8490 -vn -0.0778 -0.1818 -0.9803 -vn -0.0246 0.8835 0.4678 -vn 0.4054 0.8360 0.3698 -vn -0.7527 0.1459 -0.6419 -vn 0.3053 0.8816 0.3600 -vn 0.4342 0.4798 -0.7623 -vn -0.5931 -0.6495 0.4758 -vn -0.7827 -0.4680 0.4102 -vn -0.5542 -0.6788 0.4817 -vn -0.5853 0.7001 0.4090 -vn 0.1866 -0.9116 0.3663 -vn -0.0173 -0.9781 0.2074 -vn 0.3638 -0.8723 0.3265 -vn -0.4714 -0.6826 0.5584 -vn -0.5441 0.7641 0.3464 -vn -0.3663 -0.8238 0.4326 -vn -0.5288 -0.7566 0.3845 -vn -0.9449 0.0389 0.3249 -vn -0.6808 -0.7307 -0.0491 -vn -0.2459 -0.9012 0.3568 -vn -0.6422 0.6289 0.4382 -vn 0.2308 -0.8762 0.4230 -vn -0.0183 -0.9080 0.4184 -vn -0.1092 -0.8956 0.4312 -vn -0.2321 -0.8787 0.4172 -vn 0.1149 -0.8867 0.4478 -vn 0.3098 0.8563 0.4132 -usemtl None -s off -f 131//1 132//1 136//1 135//1 144//1 143//1 141//1 142//1 145//1 150//1 149//1 151//1 153//1 152//1 154//1 158//1 155//1 156//1 157//1 161//1 159//1 160//1 162//1 163//1 166//1 165//1 164//1 148//1 147//1 146//1 140//1 139//1 138//1 137//1 186//1 182//1 181//1 183//1 185//1 184//1 180//1 179//1 178//1 177//1 176//1 175//1 174//1 173//1 170//1 169//1 171//1 172//1 168//1 167//1 123//1 124//1 126//1 125//1 129//1 130//1 127//1 128//1 133//1 134//1 -s 1 -f 88//2 131//3 134//4 -f 3//5 148//6 164//7 4//8 -f 4//8 2//9 40//10 -f 5//11 3//5 4//8 -f 2//9 165//12 166//13 -f 7//14 138//15 139//16 6//17 -f 5//11 8//18 6//17 -f 5//11 6//17 3//5 -f 10//19 11//20 183//21 181//22 -f 11//20 13//23 179//24 180//25 -f 14//26 18//27 17//28 -f 17//28 20//29 16//30 -f 14//26 152//31 153//32 -f 28//33 25//34 26//35 -f 27//36 26//35 24//37 -f 31//38 32//39 30//40 -f 31//38 30//40 29//41 -f 35//42 29//41 25//34 -f 90//43 35//42 37//44 -f 90//43 111//45 39//46 -f 35//42 38//47 29//41 -f 34//48 31//38 38//47 -f 5//11 4//8 40//10 -f 43//49 45//50 8//18 -f 54//51 43//49 47//52 -f 47//52 43//49 44//53 -f 49//54 52//55 48//56 -f 48//56 52//55 50//57 -f 56//58 45//50 57//59 -f 58//60 56//58 59//61 -f 61//62 63//63 60//64 -f 62//65 64//66 59//61 -f 56//58 57//59 59//61 -f 48//56 47//52 42//67 -f 67//68 39//46 111//45 -f 70//69 55//70 65//71 -f 34//48 66//72 65//71 -f 74//73 68//74 69//75 -f 34//48 65//71 33//76 -f 65//71 55//70 53//77 -f 19//78 30//40 22//79 -f 53//77 55//70 60//64 -f 55//70 68//74 60//64 -f 63//63 75//80 62//65 -f 97//81 81//82 78//83 -f 80//84 124//85 123//86 -f 79//87 168//88 172//89 -f 80//84 94//90 125//91 126//92 -f 82//93 81//82 80//84 -f 77//94 170//95 173//96 -f 86//97 84//98 85//99 -f 87//100 85//99 127//101 130//102 -f 91//103 111//45 37//44 -f 37//44 111//45 90//43 -f 92//104 91//103 89//105 -f 93//106 81//82 97//81 -f 96//107 93//106 97//81 -f 89//105 93//106 96//107 -f 94//90 93//106 95//108 -f 87//100 86//97 85//99 -f 102//109 12//110 100//111 -f 101//112 104//113 103//114 -f 101//112 78//83 102//109 -f 99//115 101//112 102//109 -f 77//94 102//109 78//83 -f 106//116 46//117 56//58 -f 109//118 104//113 99//115 -f 108//119 99//115 98//120 -f 12//110 175//121 176//122 -f 112//123 118//124 91//103 -f 112//123 91//103 92//104 -f 115//125 113//126 114//127 -f 71//128 117//129 76//130 -f 118//124 112//123 115//125 -f 118//124 116//131 91//103 -f 119//132 97//81 103//114 -f 119//132 103//114 105//133 -f 121//134 76//130 117//129 -f 121//134 115//125 120//135 -f 119//132 105//133 113//126 -f 122//136 114//127 110//137 -f 24//37 143//138 144//139 -f 27//36 24//37 135//140 136//141 -f 43//49 8//18 5//11 -f 83//142 27//36 88//2 -f 56//58 46//117 9//143 -f 2//9 4//8 164//7 165//12 -f 6//17 140//144 146//145 -f 7//14 9//143 186//146 137//147 -f 8//18 7//14 6//17 -f 6//17 146//145 147//148 3//5 -f 9//143 182//149 186//146 -f 11//20 184//150 185//151 -f 11//20 180//25 184//150 -f 10//19 181//22 182//149 9//143 -f 10//19 13//23 11//20 -f 12//110 176//122 177//152 -f 12//110 177//152 178//153 13//23 -f 21//154 16//30 20//29 -f 17//28 18//27 19//78 -f 17//28 19//78 20//29 -f 20//29 22//79 21//154 -f 26//35 27//36 28//33 -f 19//78 29//41 30//40 -f 31//38 34//48 33//76 -f 31//38 33//76 32//39 -f 35//42 90//43 38//47 -f 39//46 66//72 34//48 -f 38//47 39//46 34//48 -f 90//43 39//46 38//47 -f 25//34 29//41 23//155 -f 29//41 38//47 31//38 -f 1//156 40//10 2//9 -f 15//157 41//158 40//10 -f 42//67 5//11 40//10 -f 40//10 41//158 42//67 -f 15//157 40//10 1//156 -f 41//158 48//56 42//67 -f 41//158 16//30 49//54 -f 41//158 49//54 48//56 -f 51//159 47//52 50//57 -f 53//77 60//64 52//55 -f 51//159 52//55 60//64 -f 52//55 51//159 50//57 -f 21//154 22//79 49//54 -f 51//159 54//51 47//52 -f 56//58 9//143 45//50 -f 63//63 54//51 51//159 -f 64//66 58//60 59//61 -f 63//63 59//61 54//51 -f 57//59 43//49 54//51 -f 43//49 57//59 45//50 -f 60//64 63//63 51//159 -f 47//52 48//56 50//57 -f 54//51 59//61 57//59 -f 47//52 44//53 42//67 -f 49//54 33//76 65//71 -f 55//70 70//69 69//75 -f 69//75 70//69 72//160 -f 66//72 67//68 70//69 -f 71//128 70//69 67//68 -f 66//72 70//69 65//71 -f 68//74 74//73 61//62 -f 74//73 75//80 63//63 -f 75//80 73//161 76//130 -f 72//160 73//161 74//73 -f 74//73 69//75 72//160 -f 76//130 73//161 71//128 -f 70//69 73//161 72//160 -f 15//157 16//30 41//158 -f 16//30 21//154 49//54 -f 49//54 32//39 33//76 -f 22//79 30//40 32//39 -f 49//54 65//71 52//55 -f 65//71 53//77 52//55 -f 68//74 61//62 60//64 -f 61//62 74//73 63//63 -f 82//93 77//94 81//82 -f 77//94 78//83 81//82 -f 79//87 167//162 168//88 -f 79//87 80//84 123//86 167//162 -f 80//84 81//82 94//90 -f 82//93 80//84 79//87 -f 77//94 82//93 169//163 170//95 -f 88//2 84//98 83//142 -f 88//2 85//99 84//98 -f 83//142 84//98 28//33 -f 84//98 86//97 28//33 -f 36//164 91//103 37//44 -f 89//105 91//103 36//164 -f 36//164 86//97 89//105 -f 81//82 93//106 94//90 -f 87//100 95//108 86//97 -f 93//106 89//105 95//108 -f 92//104 89//105 96//107 -f 94//90 95//108 87//100 -f 86//97 36//164 28//33 -f 86//97 95//108 89//105 -f 10//19 98//120 100//111 -f 98//120 99//115 100//111 -f 102//109 77//94 173//96 174//165 -f 103//114 97//81 101//112 -f 101//112 97//81 78//83 -f 98//120 106//116 108//119 -f 104//113 101//112 99//115 -f 105//133 103//114 104//113 -f 58//60 106//116 56//58 -f 107//166 122//136 110//137 -f 106//116 10//19 46//117 -f 58//60 108//119 106//116 -f 107//166 108//119 58//60 -f 108//119 109//118 99//115 -f 107//166 58//60 64//66 -f 120//135 107//166 64//66 -f 109//118 110//137 114//127 -f 110//137 109//118 108//119 -f 107//166 110//137 108//119 -f 100//111 13//23 10//19 -f 105//133 104//113 109//118 -f 111//45 91//103 116//131 -f 119//132 112//123 92//104 -f 109//118 114//127 113//126 -f 116//131 115//125 117//129 -f 116//131 117//129 71//128 -f 116//131 67//68 111//45 -f 112//123 119//132 113//126 -f 115//125 112//123 113//126 -f 96//107 119//132 92//104 -f 120//135 64//66 75//80 -f 96//107 97//81 119//132 -f 121//134 120//135 75//80 -f 121//134 117//129 115//125 -f 115//125 122//136 120//135 -f 122//136 115//125 114//127 -f 109//118 113//126 105//133 -f 120//135 122//136 107//166 -f 23//155 150//167 145//168 -f 45//50 7//14 8//18 -f 45//50 9//143 7//14 -f 46//117 10//19 9//143 -f 83//142 28//33 27//36 -f 67//68 116//131 71//128 -f 23//155 29//41 19//78 18//27 -f 20//29 19//78 22//79 -f 23//155 18//27 149//169 150//167 -f 36//164 37//44 35//42 -f 28//33 36//164 35//42 25//34 -f 44//53 43//49 5//11 42//67 -f 63//63 62//65 59//61 -f 62//65 75//80 64//66 -f 66//72 39//46 67//68 -f 69//75 68//74 55//70 -f 75//80 74//73 73//161 -f 73//161 70//69 71//128 -f 49//54 22//79 32//39 -f 87//100 129//170 125//91 94//90 -f 13//23 100//111 12//110 -f 102//109 100//111 99//115 -f 98//120 10//19 106//116 -f 116//131 118//124 115//125 -f 121//134 75//80 76//130 -f 2//9 163//171 162//172 1//156 -f 1//156 162//172 160//173 -f 144//139 135//140 24//37 -f 185//151 183//21 11//20 -f 153//32 151//174 14//26 -f 166//13 163//171 2//9 -f 128//175 127//101 85//99 -f 158//176 154//177 14//26 -f 147//148 148//6 3//5 -f 132//178 131//3 88//2 27//36 -f 142//179 141//180 26//35 25//34 -f 133//181 128//175 85//99 88//2 -f 136//141 132//178 27//36 -f 130//102 129//170 87//100 -f 174//165 175//121 12//110 102//109 -f 141//180 143//138 24//37 26//35 -f 160//173 159//182 15//157 1//156 -f 145//168 142//179 25//34 23//155 -f 178//153 179//24 13//23 -f 157//183 156//184 16//30 -f 151//174 149//169 18//27 14//26 -f 155//185 158//176 14//26 17//28 -f 161//186 157//183 16//30 15//157 -f 134//4 133//181 88//2 -f 139//16 140//144 6//17 -f 159//182 161//186 15//157 -f 137//147 138//15 7//14 -f 126//92 124//85 80//84 -f 172//89 171//187 79//87 -f 171//187 169//163 82//93 79//87 -f 156//184 155//185 17//28 16//30 -f 154//177 152//31 14//26 diff --git a/nmv/bbp/__init__.py b/nmv/bbp/__init__.py new file mode 100644 index 000000000..547814fcd --- /dev/null +++ b/nmv/bbp/__init__.py @@ -0,0 +1,28 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +from .bbp_circuit import * +from .circuits import * +from .neurons import * +from .spines import * +from .synapses import * +from .synapse_group import * +from .synapses_color_coding import * +from .synapses_generation import * +from .synapses_visualization import * +from .transformations import * + diff --git a/nmv/bbp/bbp_circuit.py b/nmv/bbp/bbp_circuit.py new file mode 100644 index 000000000..f21161b8f --- /dev/null +++ b/nmv/bbp/bbp_circuit.py @@ -0,0 +1,569 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import numpy + +# Blender imports +from mathutils import Vector, Matrix + +# Internal imports +from .circuit import Circuit + + +#################################################################################################### +# @BBPCircuit +#################################################################################################### +class BBPCircuit(Circuit): + """A wrapper on top of the circuit loading API of BluePy to facilitate loading circuit-based + data from old circuits that are not stored in libSonata format. + Documentation: https://bbpteam.epfl.ch/documentation/projects/bluepy/latest/index.html + """ + + ################################################################################################ + # @__init__ + ################################################################################################ + def __init__(self, + circuit_config): + """Constructor""" + + # Propagate to the base + Circuit.__init__(self, circuit_config=circuit_config) + + # Load the circuit + self.circuit = self.load_circuit() + + ################################################################################################ + # @get_circuit_config + ################################################################################################ + def get_circuit_config(self): + """Returns the circuit configuration file.""" + + return self.circuit_config + + ################################################################################################ + # @get_neuron_morphology_path + ################################################################################################ + def get_neuron_morphology_path(self, + gid): + """Returns the path of the morphology of a neuron. + + :param gid: + The GID of the neuron. + :return: + The file path of the neuron morphology. + """ + + return self.circuit.morph.get_filepath(int(gid)) + + ################################################################################################ + # @load_circuit + ################################################################################################ + def load_circuit(self): + """Loads a returns a reference to a BBP circuit from its circuit config. file""" + + import bluepy + return bluepy.Circuit(self.circuit_config) + + ################################################################################################ + # @get_mtype_strings_set + ################################################################################################ + def get_mtype_strings_set(self): + """Returns a sorted set of all the mtypes in the circuit""" + + return sorted(self.circuit.cells.mtypes) + + ################################################################################################ + # @get_circuit_mtype_strings_list + ################################################################################################ + def get_mtype_strings_list(self): + """Returns a sorted list of all the mtypes in the circuit""" + + return list(self.get_mtype_strings_set()) + + ################################################################################################ + # @get_etype_strings_set + ################################################################################################ + def get_etype_strings_set(self): + """Returns a sorted set of all the etypes in the circuit""" + + return sorted(self.circuit.cells.etypes) + + ################################################################################################ + # @get_etype_strings_list + ################################################################################################ + def get_etype_strings_list(self): + """Returns a sorted list of all the etypes in the circuit""" + + return list(self.get_etype_strings_set()) + + ################################################################################################ + # @get_neuron_translation_vector + ################################################################################################ + def get_neuron_translation_vector(self, + gid): + """Returns the translation vector of the neuron in the circuit. + + :param gid: + The GID of the neuron. + :return: + The translation vector, which is a mathutils::Vector() + """ + + neuron = self.circuit.cells.get(int(gid)) + return Vector((neuron['x'], neuron['y'], neuron['z'])) + + ################################################################################################ + # @get_neuron_orientation_matrix + ################################################################################################ + def get_neuron_orientation_matrix(self, + gid): + """Returns the orientation matrix of the neuron in the circuit. + + :param gid: + The GID of the neuron. + :return: + The orientation matrix of the neuron in the circuit, which is a mathutils::Matrix(). + """ + + # Get the orientation + neuron = self.circuit.cells.get(int(gid)) + o = neuron['orientation'] + o0 = Vector((o[0][0], o[0][1], o[0][2])) + o1 = Vector((o[1][0], o[1][1], o[1][2])) + o2 = Vector((o[2][0], o[2][1], o[2][2])) + + # Initialize the orientation matrix to I + orientation_matrix = Matrix() + + orientation_matrix[0][0] = o0[0] + orientation_matrix[0][1] = o0[1] + orientation_matrix[0][2] = o0[2] + orientation_matrix[0][3] = 1.0 + + orientation_matrix[1][0] = o1[0] + orientation_matrix[1][1] = o1[1] + orientation_matrix[1][2] = o1[2] + orientation_matrix[1][3] = 1.0 + + orientation_matrix[2][0] = o2[0] + orientation_matrix[2][1] = o2[1] + orientation_matrix[2][2] = o2[2] + orientation_matrix[2][3] = 1.0 + + orientation_matrix[3][0] = 0.0 + orientation_matrix[3][1] = 0.0 + orientation_matrix[3][2] = 0.0 + orientation_matrix[3][3] = 1.0 + + return orientation_matrix + + ################################################################################################ + # @get_neuron_transformation_matrix + ################################################################################################ + def get_neuron_transformation_matrix(self, + gid): + """Returns the transformation matrix of the neuron in the circuit. + + :param gid: + Neuron GID. + :return: + The transformation matrix of the neuron in the circuit. + """ + + # Get the orientation and update the translation elements in the orientation matrix + matrix = self.get_neuron_orientation_matrix(gid=gid) + translation_vector = self.get_neuron_translation_vector(gid=gid) + matrix[0][3] = translation_vector[0] + matrix[1][3] = translation_vector[1] + matrix[2][3] = translation_vector[2] + + return matrix + + ################################################################################################ + # @get_neuron_inverse_transformation_matrix + ################################################################################################ + def get_neuron_inverse_transformation_matrix(self, + gid): + """Gets the inverse transformation matrix of the neuron in the circuit.""" + + return self.get_neuron_transformation_matrix(gid=gid).inverted() + + ################################################################################################ + # @get_afferent_synapses_ids + ################################################################################################ + def get_afferent_synapses_ids(self, + gid): + """Gets the IDs of all the afferent synapses in the circuit for a specific neuron specified + by its GID. + + :param gid: + The GID of the neuron. + :return: + A list of the IDs of all the afferent synapses on a specific neuron in the circuit. + """ + + return self.circuit.connectome.afferent_synapses(int(gid)).tolist() + + ################################################################################################ + # @get_efferent_synapses_ids + ################################################################################################ + def get_efferent_synapses_ids(self, + gid): + """Gets the IDs of all the efferent synapses in the circuit for a specific neuron specified + by its GID. + + :param gid: + The GID of the neuron. + :return: + A list of the IDs of all the efferent synapses on a specific neuron in the circuit. + """ + return self.circuit.connectome.efferent_synapses(int(gid)).tolist() + + ################################################################################################ + # @get_all_synapses_ids + ################################################################################################ + def get_all_synapses_ids(self, + gid): + """Gets the IDs of all the synapses in the circuit for a specific neuron specified + by its GID. + + :param gid: + The GID of the neuron. + :return: + A list of the IDs of all the afferent synapses on a specific neuron in the circuit. + """ + + synapses_ids_list = self.get_afferent_synapses_ids(gid=gid) + synapses_ids_list.extend(self.get_efferent_synapses_ids(gid=gid)) + return synapses_ids_list + + ################################################################################################ + # @get_afferent_synapses_ids_for_projection + ################################################################################################ + def get_afferent_synapses_ids_for_projection(self, + gid, + projection_name): + """TODO + + :param gid: + :type gid: + :param projection_name: + :type projection_name: + :return: + :rtype: + """ + + projection = self.circuit.projection(projection_name) + return projection.afferent_synapses(gid=int(gid)).tolist() + + ################################################################################################ + # @get_synapse_types_ids + ################################################################################################ + def get_synapse_types_ids(self, + synapse_ids_list): + """Gets the types of the synapses from their IDs. + + :param synapse_ids_list: + A list containing the IDs of the synapses. + :return: + A list containing the types of the synapses in the same order. + """ + + import bluepy + return self.circuit.connectome.synapse_properties( + numpy.array(synapse_ids_list), + [bluepy.enums.Synapse.TYPE])[bluepy.enums.Synapse.TYPE].values.tolist() + + ################################################################################################ + # @get_pre_synaptic_synapse_positions + ################################################################################################ + def get_pre_synaptic_synapse_positions(self, + synapse_ids_list, + synapse_location='center'): + """Gets the pre-synaptic positions of the given synapses list. + + :param synapse_ids_list: + A list containing the IDs of the synapses. + :param synapse_location: + The location of the synapse. Option: 'center' or 'contour'. + :return: + A list of the positions of the synapses. + """ + + return self.circuit.connectome.synapse_positions( + numpy.array(synapse_ids_list), 'pre', synapse_location).values.tolist() + + ################################################################################################ + # @get_post_synaptic_synapse_positions + ################################################################################################ + def get_post_synaptic_synapse_positions(self, + synapse_ids_list, + synapse_location='center'): + """Gets the post-synaptic positions of the given synapses list. + + :param synapse_ids_list: + A list containing the IDs of the synapses. + :param synapse_location: + The location of the synapse. Option: 'center' or 'contour'. + :return: + A list of the positions of the synapses. + """ + + return self.circuit.connectome.synapse_positions( + numpy.array(synapse_ids_list), 'post', synapse_location).values.tolist() + + ################################################################################################ + # @get_pre_synaptic_mtypes + ################################################################################################ + def get_pre_synaptic_mtypes(self, + afferent_synapses_ids_list): + """Gets the mtypes (represented by strings) of the given afferent synapses. + + :param afferent_synapses_ids_list: + A list containing the IDs of a given set of afferent synapses. + :return: + A list containing the mtypes of the synapses. + """ + + import bluepy + pre_synaptic_gids = self.circuit.connectome.synapse_properties( + numpy.array(afferent_synapses_ids_list), + [bluepy.enums.Synapse.PRE_GID])[bluepy.enums.Synapse.PRE_GID].values.tolist() + return self.circuit.cells.get(pre_synaptic_gids)['mtype'].values.tolist() + + ################################################################################################ + # @get_post_synaptic_mtypes + ################################################################################################ + def get_post_synaptic_mtypes(self, + efferent_synapses_ids_list): + """Gets the mtypes (represented by strings) of the given efferent synapses. + + :param efferent_synapses_ids_list: + A list containing the IDs of a given set of efferent synapses. + :return: + A list containing the mtypes of the synapses. + """ + + import bluepy + post_synaptic_gids = self.circuit.connectome.synapse_properties( + numpy.array(efferent_synapses_ids_list), + [bluepy.enums.Synapse.POST_GID])[bluepy.enums.Synapse.POST_GID].values.tolist() + return self.circuit.cells.get(post_synaptic_gids)['mtype'].values.tolist() + + ################################################################################################ + # @get_pre_synaptic_etypes + ################################################################################################ + def get_pre_synaptic_etypes(self, + afferent_synapses_ids_list): + """Gets the etypes (represented by strings) of the given afferent synapses. + + :param afferent_synapses_ids_list: + A list containing the IDs of a given set of afferent synapses. + :return: + A list containing the etypes of the synapses. + """ + + import bluepy + pre_synaptic_gids = self.circuit.connectome.synapse_properties( + numpy.array(afferent_synapses_ids_list), + [bluepy.enums.Synapse.PRE_GID])[bluepy.enums.Synapse.PRE_GID].values.tolist() + return self.circuit.cells.get(pre_synaptic_gids)['etype'].values.tolist() + + ################################################################################################ + # @get_post_synaptic_etypes + ################################################################################################ + def get_post_synaptic_etypes(self, + efferent_synapses_ids_list): + """Gets the etypes (represented by strings) of the given efferent synapses. + + :param efferent_synapses_ids_list: + A list containing the IDs of a given set of efferent synapses. + :return: + A list containing the etypes of the synapses. + """ + + import bluepy + post_synaptic_gids = self.circuit.connectome.synapse_properties( + numpy.array(efferent_synapses_ids_list), + [bluepy.enums.Synapse.POST_GID])[bluepy.enums.Synapse.POST_GID].values.tolist() + return self.circuit.cells.get(post_synaptic_gids)['etype'].values.tolist() + + ################################################################################################ + # @is_synapse_excitatory + ################################################################################################ + def is_synapse_excitatory(self, + synapse_type_id): + """Checks if the synapses given - based on its ID - is excitatory or inhibitory. + + :param synapse_type_id: + The ID of the synapse. + :return: + True if the synapse is excitatory, False otherwise. + """ + + return True if synapse_type_id > 100 else False + + ################################################################################################ + # @is_synapse_inhibitory + ################################################################################################ + def is_synapse_inhibitory(self, + synapse_type_id): + """Checks if the synapses given - based on its ID - is excitatory or inhibitory. + + :param synapse_type_id: + The ID of the synapse. + :return: + True if the synapse is inhibitory, False otherwise. + """ + + return True if synapse_type_id < 100 else False + + ################################################################################################ + # @is_axo_somatic_synapse + ################################################################################################ + def is_axo_somatic_synapse(self, + synapse_id): + """TODO + + :param synapse_id: + :return: + """ + + import bluepy + value = self.circuit.connectome.synapse_properties( + numpy.array([synapse_id]), [bluepy.enums.Synapse.POST_BRANCH_TYPE]).values.tolist()[0] + if value == 1: + return True + else: + return False + + ################################################################################################ + # @get_excitatory_synapses_ids + ################################################################################################ + def get_excitatory_synapses_ids(self, + gid): + """Returns a list of all excitatory synapses of a specific neuron. + + :param gid: + The GID of the neuron. + :return: + A list of the IDs of the excitatory synapses. + """ + + # Get the IDs of all the synapses on the neuron + synapse_ids_list = self.get_all_synapses_ids(gid=gid) + + # Get the IDs of the types of the synapses + synapse_types_ids = self.get_synapse_types_ids(synapse_ids_list=synapse_ids_list) + + # A list to collect all the synapse IDs + excitatory_synapses = list() + for i, synapse_type_id in enumerate(synapse_types_ids): + if self.is_synapse_excitatory(synapse_type_id=synapse_type_id): + excitatory_synapses.append(synapse_ids_list[i]) + return excitatory_synapses + + ################################################################################################ + # @get_inhibitory_synapses_ids + ################################################################################################ + def get_inhibitory_synapses_ids(self, + gid): + """Returns a list of all inhibitory synapses of a specific neuron. + + :param gid: + The GID of the neuron. + :return: + A list of the IDs of the inhibitory synapses. + """ + + # Get the IDs of all the synapses on the neuron + synapse_ids_list = self.get_all_synapses_ids(gid=gid) + + # Get the IDs of the types of the synapses + synapse_types_ids = self.get_synapse_types_ids( + synapse_ids_list=synapse_ids_list) + + # A list to collect all the synapse IDs + inhibitory_synapses = list() + for i, synapse_type_id in enumerate(synapse_types_ids): + if not self.is_synapse_excitatory(synapse_type_id=synapse_type_id): + inhibitory_synapses.append(synapse_ids_list[i]) + return inhibitory_synapses + + ################################################################################################ + # @get_excitatory_and_inhibitory_synapses_ids + ################################################################################################ + def get_excitatory_and_inhibitory_synapses_ids(self, + gid): + """Gets two lists of the excitatory and inhibitory synapses IDs. + + :param gid: + The GID of the neuron. + :return: + Two lists: excitatory and inhibitory synapse IDs. + """ + + # Get the IDs of all the synapses on the neuron + synapse_ids_list = self.get_all_synapses_ids(gid=gid) + + # Get the IDs of the types of the synapses + synapse_types_ids = self.get_synapse_types_ids(synapse_ids_list=synapse_ids_list) + + # A list to collect all the synapse IDs + excitatory_synapses = list() + inhibitory_synapses = list() + for synapse_type_id in synapse_types_ids: + if self.is_synapse_excitatory(synapse_type_id=synapse_type_id): + excitatory_synapses.append(synapse_type_id) + else: + inhibitory_synapses.append(synapse_type_id) + return excitatory_synapses, inhibitory_synapses + + ################################################################################################ + # @get_shared_synapses_ids_between_two_neurons + ################################################################################################ + def get_shared_synapses_ids_between_two_neurons(self, + pre_gid, + post_gid): + """Gets a list of the IDs of the synapses shared between two neurons. + + :param pre_gid: + The GID of the pre-synaptic neuron. + :param post_gid: + The GID of the post-synaptic neuron. + :return: + A list of the IDs of the shared synapses. + """ + + import bluepy + + # Get the afferent synapses of the post-synaptic neuron + post_afferent_synapses_ids = self.get_afferent_synapses_ids(gid=post_gid) + + # Get a corresponding list of GIDs that represent the pre-synaptic neurons + pre_gids = self.circuit.connectome.synapse_properties( + post_afferent_synapses_ids, [bluepy.enums.Synapse.PRE_GID]).values + + # Determine the synapses that only connect with the pre_gid neuron + shared_synapses_ids = list() + for i in range(len(pre_gids)): + if int(pre_gid) == int(pre_gids[i]): + shared_synapses_ids.append(post_afferent_synapses_ids[i]) + + # Return the resulting list + return shared_synapses_ids diff --git a/scripts/vasculature/vasculature_sample.py b/nmv/bbp/circuit.py similarity index 59% rename from scripts/vasculature/vasculature_sample.py rename to nmv/bbp/circuit.py index 577a0313a..de9d2f268 100644 --- a/scripts/vasculature/vasculature_sample.py +++ b/nmv/bbp/circuit.py @@ -1,5 +1,5 @@ #################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project +# Copyright (c) 2023, EPFL / Blue Brain Project # Marwan Abdellah # # This file is part of NeuroMorphoVis @@ -15,40 +15,36 @@ # If not, see . #################################################################################################### +# System imports +import random + +# Blender imports +from mathutils import Vector + +# Internal imports +import nmv.utilities + #################################################################################################### -# VasculatureSample +# @Circuit #################################################################################################### -class VasculatureSample: - """Vasculature morphological sample. - - The section is composed of a set of segments or edges , and each segment is composed of two - samples. Each sample has a point in the cartesian coordinates and a radius that reflect the - cross-sectional area of the morphology at a certain point. - """ +class Circuit: + """Base class for the Circuit.""" ################################################################################################ # @__init__ ################################################################################################ def __init__(self, - index, - point, - radius): + circuit_config): """Constructor - :param point: - Sample position in the cartesian space, Vector((x, y, z)). - :param radius: - Sample radius in microns. - :param index: - Sample index along the section from 0 to N-1 if the section has N samples. + :param circuit_config: + Circuit configuration file. """ - # Sample cartesian point - self.point = point + # Configuration file + self.circuit_config = circuit_config - # Sample radius - self.radius = radius + # This circuit must be loaded before being used later to access its contents + self.nmv_circuit = None - # Sample index along the section (from 0 to N, updated after section construction) - self.index = index diff --git a/scripts/vasculature/README b/nmv/bbp/circuits.py similarity index 65% rename from scripts/vasculature/README rename to nmv/bbp/circuits.py index aca2f1c0e..7a440dd39 100644 --- a/scripts/vasculature/README +++ b/nmv/bbp/circuits.py @@ -1,5 +1,5 @@ #################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project +# Copyright (c) 2023, EPFL / Blue Brain Project # Marwan Abdellah # # This file is part of NeuroMorphoVis @@ -15,19 +15,17 @@ # If not, see . #################################################################################################### -__author__ = "Marwan Abdellah" -__copyright__ = "Copyright (c) 2016 - 2020, Blue Brain Project / EPFL" -__credits__ = ["Ahmet Bilgili", "Juan Hernando", "Stefan Eilemann"] -__version__ = "1.0.0" -__maintainer__ = "Marwan Abdellah" -__email__ = "marwan.abdellah@epfl.ch" -__status__ = "Production" #################################################################################################### -To run this workflow: +# @get_circuit_mtype_strings_set +#################################################################################################### +def get_circuit_mtype_strings_set(circuit): + """Returns a set of all the morphological types of the neurons in a given circuit. + + :param circuit: + A BBP circuit. + :return: + A set of all the morphological types of the neurons in a given circuit. + """ - 1) Open Blender interface - 2) Switch to the text editor - 3) Pers Alt + O and open the main.py file - 4) Press Run Script - 5) Enjoy + return sorted(circuit.cells.mtypes) diff --git a/nmv/bbp/libsonata_circuit.py b/nmv/bbp/libsonata_circuit.py new file mode 100644 index 000000000..8dd4872a3 --- /dev/null +++ b/nmv/bbp/libsonata_circuit.py @@ -0,0 +1,45 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import numpy + +# Blender imports +from mathutils import Vector, Matrix + +# Internal imports +import nmv.utilities +from .circuit import Circuit + + +#################################################################################################### +# @libSonataCircuit +#################################################################################################### +class libSonataCircuit(Circuit): + """A wrapper on top of the circuit loading API of BluePy to facilitate loading circuit-based + data from old circuits that are not stored in libSonata format.""" + + ################################################################################################ + # @__init__ + ################################################################################################ + def __init__(self, + circuit_config): + + # Propagate to the base + Circuit.__init__(self, circuit_config=circuit_config) + + # TODO: Implement this class \ No newline at end of file diff --git a/nmv/bbp/neurons.py b/nmv/bbp/neurons.py new file mode 100644 index 000000000..545af9618 --- /dev/null +++ b/nmv/bbp/neurons.py @@ -0,0 +1,285 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal import +import nmv.builders +import nmv.consts +import nmv.enums +import nmv.options +import nmv.mesh +import nmv.shading + + +#################################################################################################### +# @create_neuron_mesh_in_circuit +#################################################################################################### +def create_neuron_mesh_in_circuit( + circuit, gid, + branch_radius_type=nmv.enums.Skeleton.Radii.ORIGINAL, + branch_radius=1.0, branch_radius_scale_factor=1.0, + basal_branching_order=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, + apical_branching_order=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, + axon_branching_order=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, + soma_color=nmv.enums.Color.SOMA, + basal_dendrites_color=nmv.enums.Color.BASAL_DENDRITES, + apical_dendrites_color=nmv.enums.Color.APICAL_DENDRITES, + axons_color=nmv.enums.Color.AXONS, + material_type=nmv.enums.Shader.LAMBERT_WARD): + + # Get the path of the morphology from the circuit + morphology_path = circuit.get_neuron_morphology_path(gid=int(gid)) + + # Read the morphology and get its NMV object, and ensure that it is centered at the origin + morphology = nmv.file.read_morphology_with_morphio( + morphology_file_path=morphology_path, + morphology_format=nmv.file.get_morphology_file_format(morphology_file_path=morphology_path), + center_at_origin=True) + + # Adjust the label to be set according to the GID not the morphology label + morphology.label = str(gid) + + # Create default NMV options with fixed radius value for the visualization + nmv_options = nmv.options.NeuroMorphoVisOptions() + + # Radii + nmv_options.morphology.arbors_radii = branch_radius_type + if branch_radius_type == nmv.enums.Skeleton.Radii.ORIGINAL: + pass + elif branch_radius_type == nmv.enums.Skeleton.Radii.UNIFIED: + nmv_options.morphology.samples_unified_radii_value = branch_radius + elif branch_radius_type == nmv.enums.Skeleton.Radii.SCALED: + nmv_options.morphology.sections_radii_scale = branch_radius_scale_factor + else: + pass + + # TODO: Remove me please, this is just a hack + nmv_options.morphology.arbor_style = nmv.enums.Skeleton.Style.TAPERED_ZIGZAG + + # Branching orders + nmv_options.morphology.basal_dendrites_branch_order = basal_branching_order + nmv_options.morphology.apical_dendrite_branch_order = apical_branching_order + nmv_options.morphology.axon_branch_order = axon_branching_order + + # Morphology colors and materials + nmv_options.shading.morphology_soma_color = soma_color + nmv_options.shading.morphology_basal_dendrites_color = basal_dendrites_color + nmv_options.shading.morphology_apical_dendrites_color = apical_dendrites_color + nmv_options.shading.morphology_axons_color = axons_color + nmv_options.shading.morphology_material = material_type + + # Mesh colors and materials + nmv_options.shading.mesh_soma_color = soma_color + nmv_options.shading.mesh_basal_dendrites_color = basal_dendrites_color + nmv_options.shading.mesh_apical_dendrites_color = apical_dendrites_color + nmv_options.shading.mesh_axons_color = axons_color + nmv_options.shading.mesh_material = material_type + + # Soma + nmv_options.mesh.soma_type = nmv.enums.Soma.Representation.META_BALLS + + mesh_builder = nmv.builders.PiecewiseBuilder(morphology=morphology, + options=nmv_options) + neuron_meshes = mesh_builder.reconstruct_mesh() + + # Join all the mesh objects into a single mesh object to represent the neuron + neuron_mesh = nmv.mesh.join_mesh_objects(mesh_list=neuron_meshes, name='Neuron') + + # Return a reference to the neuron mesh + return neuron_mesh + + +#################################################################################################### +# @visualize_circuit_neuron_for_synaptics +#################################################################################################### +def visualize_circuit_neuron_for_synaptics(circuit, + gid, + options, + which_neuron=nmv.enums.Synaptics.WhichNeuron.INDIVIDUAL): + + # Adjust the branching order of the dendrites and axons + dendrites_branching_order = 0 + axons_branching_order = 0 + + # Adjust the coloring parameters based on the neuron order (pre- or post-synaptic neuron) + if which_neuron == nmv.enums.Synaptics.WhichNeuron.PRE_SYNAPTIC: + soma_color = options.synaptics.pre_synaptic_dendrites_color + dendrites_color = options.synaptics.pre_synaptic_dendrites_color + axons_color = options.synaptics.pre_synaptic_axons_color + + if options.synaptics.display_pre_synaptic_dendrites: + dendrites_branching_order = nmv.consts.Skeleton.MAX_BRANCHING_ORDER + if options.synaptics.display_pre_synaptic_axons: + axons_branching_order = nmv.consts.Skeleton.MAX_BRANCHING_ORDER + + elif which_neuron == nmv.enums.Synaptics.WhichNeuron.POST_SYNAPTIC: + soma_color = options.synaptics.post_synaptic_dendrites_color + dendrites_color = options.synaptics.post_synaptic_dendrites_color + axons_color = options.synaptics.post_synaptic_axons_color + + if options.synaptics.display_post_synaptic_dendrites: + dendrites_branching_order = nmv.consts.Skeleton.MAX_BRANCHING_ORDER + if options.synaptics.display_post_synaptic_axons: + axons_branching_order = nmv.consts.Skeleton.MAX_BRANCHING_ORDER + else: + soma_color = options.synaptics.dendrites_color + dendrites_color = options.synaptics.dendrites_color + axons_color = options.synaptics.axons_color + + if options.synaptics.display_dendrites: + dendrites_branching_order = nmv.consts.Skeleton.MAX_BRANCHING_ORDER + if options.synaptics.display_axons: + axons_branching_order = nmv.consts.Skeleton.MAX_BRANCHING_ORDER + + # If we ignore both, then return None + if dendrites_branching_order == 0 and axons_branching_order == 0: + return None + + if options.synaptics.unify_branch_radii: + branches_radii_type = nmv.enums.Skeleton.Radii.UNIFIED + else: + branches_radii_type = nmv.enums.Skeleton.Radii.ORIGINAL + + # Create the mesh and return a reference to it + return create_neuron_mesh_in_circuit(circuit=circuit, gid=gid, + branch_radius_type=branches_radii_type, + branch_radius=options.synaptics.unified_radius, + basal_branching_order=dendrites_branching_order, + apical_branching_order=dendrites_branching_order, + axon_branching_order=axons_branching_order, + soma_color=soma_color, + basal_dendrites_color=dendrites_color, + apical_dendrites_color=dendrites_color, + axons_color=axons_color, + material_type=options.synaptics.shader) + + +#################################################################################################### +# @create_symbolic_neuron_mesh_in_circuit +#################################################################################################### +def create_symbolic_neuron_mesh_in_circuit( + circuit, gid, + unified_radius=True, + branch_radius=1.0, + basal_branching_order=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, + apical_branching_order=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, + axon_branching_order=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, + soma_color=nmv.enums.Color.SOMA, + basal_dendrites_color=nmv.enums.Color.BASAL_DENDRITES, + apical_dendrites_color=nmv.enums.Color.APICAL_DENDRITES, + axons_color=nmv.enums.Color.AXONS, + material_type=nmv.enums.Shader.LAMBERT_WARD): + """Creates a symbolic mesh of a neuron, specified by a GID in a given circuit. + + :param circuit: + BBP circuit. + :param gid: + Neuron GID. + :param color: + Neuron RGB color in a Vector. + :param material_type: + The type of the material to be applied to the mesh. + :param unified_radius: + A flag to indicate if we will set the radii of all the branches in the neuron or not. + :param branch_radius: + The unified radius of all the branches in the neuron. + :param basal_branching_order: + The maximum branching order of the basal dendrites. + :param apical_branching_order: + The maximum branching order of the apical dendrites. + :param axon_branching_order: + The maximum branching order of the axons. + :return: + A reference to the created mesh object. + """ + + return create_neuron_mesh_in_circuit( + circuit=circuit, gid=gid, + unified_radius=unified_radius, branch_radius=branch_radius, + basal_branching_order=basal_branching_order, + apical_branching_order=apical_branching_order, + axon_branching_order=axon_branching_order, + soma_color=soma_color, + basal_dendrites_color=basal_dendrites_color, + apical_dendrites_color=apical_dendrites_color, + axons_color=axons_color, + material_type=material_type) + + +#################################################################################################### +# @create_to_scale_neuron_mesh_in_circuit +#################################################################################################### +def create_to_scale_neuron_mesh_in_circuit( + circuit, gid, + basal_branching_order=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, + apical_branching_order=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, + axon_branching_order=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, + soma_color=nmv.enums.Color.SOMA, + basal_dendrites_color=nmv.enums.Color.BASAL_DENDRITES, + apical_dendrites_color=nmv.enums.Color.APICAL_DENDRITES, + axons_color=nmv.enums.Color.AXONS, + material_type=nmv.enums.Shader.LAMBERT_WARD,): + """Creates a to-scale mesh of a neuron, specified by a GID in a given circuit. + The branches preserve the actual diameters as specified in the morphology. + + :param circuit: + BBP circuit. + :param gid: + Neuron GID. + :param color: + Neuron RGB color in a Vector. + :param material_type: + The type of the material to be applied to the mesh. + :param basal_branching_order: + The maximum branching order of the basal dendrites. + :param apical_branching_order: + The maximum branching order of the apical dendrites. + :param axon_branching_order: + The maximum branching order of the axons. + :return: + A reference to the created mesh object. + """ + + return create_neuron_mesh_in_circuit( + circuit=circuit, gid=gid, unified_radius=False, + basal_branching_order=basal_branching_order, + apical_branching_order=apical_branching_order, + axon_branching_order=axon_branching_order, + soma_color=soma_color, + basal_dendrites_color=basal_dendrites_color, + apical_dendrites_color=apical_dendrites_color, + axons_color=axons_color, + material_type=material_type) + + +#################################################################################################### +# @transform_neuron_mesh_to_global_coordinates +#################################################################################################### +def transform_neuron_mesh_to_global_coordinates(circuit, + gid, + neuron_mesh): + """Transforms the mesh of a specific neuron to its global coordinates in the circuit. + + :param circuit: + BBP circuit. + :param gid: + Neuron GID in the circuit. + :param neuron_mesh: + A reference to the neuron mesh. + """ + + # Get the neuron transformation matrix and update that of the mesh accordingly + neuron_mesh.matrix_world = nmv.bbp.get_neuron_transformation_matrix(circuit=circuit, gid=gid) diff --git a/nmv/bbp/spines.py b/nmv/bbp/spines.py new file mode 100644 index 000000000..c92755a89 --- /dev/null +++ b/nmv/bbp/spines.py @@ -0,0 +1,145 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +from mathutils import Vector + + +#################################################################################################### +# @get_spines +#################################################################################################### +def get_spines(circuit, + post_gid): + """Get a list of all the spines on a post synaptic neuron. + + :param circuit: + BBP circuit. + :param post_gid: + The GID of the post-synaptic neuron. + :return: + """ + + import bluepy + # Get the IDs of the afferent synapses of a given GID + afferent_synapses_ids = circuit.connectome.afferent_synapses(post_gid) + + # Get the GIDs of all the pre-synaptic cells + pre_gids = circuit.connectome.synapse_properties( + afferent_synapses_ids, [bluepy.enums.Synapse.PRE_GID]).values + pre_gids = [gid[0] for gid in pre_gids] + + # Get the positions of the incoming synapses at the post synaptic side + post_synaptic_center_positions = circuit.connectome.synapse_positions( + afferent_synapses_ids, 'post', 'center').values.tolist() + + pre_synaptic_center_positions = circuit.connectome.synapse_positions( + afferent_synapses_ids, 'pre', 'center').values.tolist() + + pre_synaptic_contour_positions = circuit.connectome.synapse_positions( + afferent_synapses_ids, 'pre', 'contour').values.tolist() + + import nmv.skeleton + spines = list() + for i in range(len(pre_gids)): + + # Synapse position is the mid-way between the pre- and post-synaptic centers + post_synaptic_center_position = Vector((post_synaptic_center_positions[i][0], + post_synaptic_center_positions[i][1], + post_synaptic_center_positions[i][2])) + + pre_synaptic_center_position = Vector((pre_synaptic_center_positions[i][0], + pre_synaptic_center_positions[i][1], + pre_synaptic_center_positions[i][2])) + + pre_synaptic_contour_position = Vector((pre_synaptic_contour_positions[i][0], + pre_synaptic_contour_positions[i][1], + pre_synaptic_contour_positions[i][2])) + + #position = 0.5 * (post_synaptic_position + pre_synaptic_position) + + spine = nmv.skeleton.Spine() + spine.pre_synaptic_position = pre_synaptic_center_position + spine.post_synaptic_position = post_synaptic_center_position + + # To determine the spine size + spine.size = (pre_synaptic_contour_position - post_synaptic_center_position).length + spines.append(spine) + + # Return the spines list + return spines + + +#################################################################################################### +# @get_spines_for_synaptic_pair +#################################################################################################### +def get_spines_for_synaptic_pair(circuit, + pre_gid, + post_gid): + """Gets the spines for a synaptic pair. + + :param circuit: + BBP circuit. + :param pre_gid: + The GID of the pre-synaptic neuron. + :param post_gid: + The GID of the post-synaptic neuron. + :return: + A list of the resulting synapses. + """ + + import bluepy + + # Get the IDs of the afferent synapses of a given GID + afferent_synapses_ids = circuit.connectome.afferent_synapses(post_gid) + + # Get the GIDs of all the pre-synaptic cells + pre_gids = circuit.connectome.synapse_properties( + afferent_synapses_ids, [bluepy.enums.Synapse.PRE_GID]).values + pre_gids = [gid[0] for gid in pre_gids] + + # Get the positions of the incoming synapses at the post synaptic side + post_synaptic_positions = circuit.connectome.synapse_positions( + afferent_synapses_ids, 'post', 'center').values.tolist() + pre_synaptic_positions = circuit.connectome.synapse_positions( + afferent_synapses_ids, 'pre', 'contour').values.tolist() + + import nmv.skeleton + spines = list() + for i in range(len(pre_gids)): + + # Get only the shared synapses with the pre-synaptic gid + if pre_gid == int(pre_gids[i]): + + # Synapse position is the mid-way between the pre- and post-synaptic centers + post_synaptic_position = Vector((post_synaptic_positions[i][0], + post_synaptic_positions[i][1], + post_synaptic_positions[i][2])) + + pre_synaptic_position = Vector((pre_synaptic_positions[i][0], + pre_synaptic_positions[i][1], + pre_synaptic_positions[i][2])) + + position = 0.5 * (post_synaptic_position + pre_synaptic_position) + + spine = nmv.skeleton.Spine() + spine.pre_synaptic_position = pre_synaptic_position + spine.post_synaptic_position = post_synaptic_position + + spines.append(spine) + + # Return the spines list + return spines diff --git a/nmv/bbp/synapse_group.py b/nmv/bbp/synapse_group.py new file mode 100644 index 000000000..18d00c542 --- /dev/null +++ b/nmv/bbp/synapse_group.py @@ -0,0 +1,49 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +#################################################################################################### +# @SynapseGroup +#################################################################################################### +class SynapseGroup: + """A group of synapses sharing a specific parameter.""" + + ################################################################################################ + # @__int__ + ################################################################################################ + def __init__(self, + name='Synapse Group', + synapses_ids_list=None, + color=None): + """Constructor + + :param name: + The name of the group. + :param synapses_ids_list: + A list of all the IDs of the synapse. + :param color: + RGB color vector of the synapse. + """ + + # Group name + self.name = name + + # A list of the IDs of all the synapses in this group + self.synapses_ids_list = synapses_ids_list + + # The color of the group + self.color = color diff --git a/nmv/bbp/synapses.py b/nmv/bbp/synapses.py new file mode 100644 index 000000000..cf29b3614 --- /dev/null +++ b/nmv/bbp/synapses.py @@ -0,0 +1,459 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import numpy + +# Internal import +import nmv.geometry +import nmv.mesh +import nmv.enums +import nmv.shading +import nmv.utilities +import nmv.consts + + +#################################################################################################### +# @get_excitatory_synapse_group +#################################################################################################### +def get_excitatory_synapse_group(circuit, + gid, + color, + group_name='Excitatory Synapses'): + """Gets a SynapseGroup of all excitatory synapses of the neuron specified by the given GID. + + :param circuit: + A digitally reconstructed circuit. + :param gid: + Neuron GID. + :param color: + A color used to label this synapse group. + :param group_name: + A name to label the group, default Excitatory Synapses. + :return: + A SynapseGroup of all excitatory synapses of the neuron specified by the given GID. + """ + + return nmv.bbp.SynapseGroup( + name=group_name, + color=nmv.utilities.confirm_rgb_color_from_color_string(color), + synapses_ids_list=circuit.get_excitatory_synapses_ids(gid=gid)) + + +#################################################################################################### +# @get_inhibitory_synapse_group +#################################################################################################### +def get_inhibitory_synapse_group(circuit, + gid, + color, + group_name='Inhibitory Synapses'): + """Gets a SynapseGroup of all inhibitory synapses of the neuron specified by the given GID. + + :param circuit: + A digitally reconstructed circuit. + :param gid: + Neuron GID. + :param color: + A color used to label this synapse group. + :param group_name: + A name to label the group, default Inhibitory Synapses. + :return: + A SynapseGroup of all excitatory synapses of the neuron specified by the given GID. + """ + return nmv.bbp.SynapseGroup( + name=group_name, + color=nmv.utilities.confirm_rgb_color_from_color_string(color), + synapses_ids_list=circuit.get_inhibitory_synapses_ids(gid=gid)) + + +#################################################################################################### +# @get_shared_synapses_group_between_two_neurons +#################################################################################################### +def get_shared_synapses_group_between_two_neurons(circuit, + pre_gid, + post_gid, + color): + """Gets a SynapseGroup of the shared synapses between two neurons specified by their GIDs. + + :param circuit: + A digitally reconstructed circuit. + :param pre_gid: + The GID of the pre-synaptic neuron. + :param post_gid: + The GID of the post-synaptic neuron. + :param color: + A color used to label this synapse group. + :return: + A SynapseGroup of the shared synapses between two neurons specified by their GIDs. + """ + + # Get the shared synapses IDs + shared_synapses_ids = circuit.get_shared_synapses_ids_between_two_neurons( + pre_gid=pre_gid, post_gid=post_gid) + + return nmv.bbp.SynapseGroup( + name='Shared Synapses [%s -> %s]' % (str(pre_gid), str(post_gid)), + color=nmv.utilities.confirm_rgb_color_from_color_string(color), + synapses_ids_list=shared_synapses_ids) + + +#################################################################################################### +# @get_afferent_synapse_groups_color_coded_by_pre_mtypes +#################################################################################################### +def get_afferent_synapse_groups_color_coded_by_pre_mtypes(circuit, + gid, + mtype_color_dict): + """Gets a list of SynapseGroup elements that color-code the afferent synapses of a given + neuron based on the mtype of the pre-synaptic neurons. + + :param circuit: + A digitally reconstructed circuit. + :param gid: + Neuron GID. + :param mtype_color_dict: + A color dictionary, where a HEX color defines each mtype. + :return: + A list of SynapseGroup elements that color-code the afferent synapses of a given neuron + based on the mtype of the pre-synaptic neurons. + """ + + # Get the afferent synapses of the given GID + afferent_synapses_ids = circuit.get_afferent_synapses_ids(gid=gid) + + # Get the mtypes of the pre-synaptic cells + afferent_synapses_mtypes = circuit.get_pre_synaptic_mtypes( + afferent_synapses_ids_list=afferent_synapses_ids) + + # Create and return the color coded synapses + return nmv.bbp.create_color_coded_synapse_groups_by_mtype( + circuit=circuit, synapses_ids=afferent_synapses_ids, + synapses_mtypes=afferent_synapses_mtypes, mtype_color_dict=mtype_color_dict) + + +#################################################################################################### +# @get_afferent_synapse_groups_color_coded_by_pre_etypes +#################################################################################################### +def get_afferent_synapse_groups_color_coded_by_pre_etypes(circuit, + gid, + etype_color_dict): + + # Get the afferent synapses of the given GID + afferent_synapses_ids = circuit.get_afferent_synapses_ids(gid=gid) + + # Get the mtypes of the pre-synaptic cells + afferent_synapses_etypes = circuit.get_pre_synaptic_etypes( + afferent_synapses_ids_list=afferent_synapses_ids) + + # Create and return the color coded synapses + return nmv.bbp.create_color_coded_synapse_groups_by_etype( + circuit=circuit, synapses_ids=afferent_synapses_ids, + synapses_etypes=afferent_synapses_etypes, etype_color_dict=etype_color_dict) + + +#################################################################################################### +# @get_efferent_synapse_groups_color_coded_by_post_mtypes +#################################################################################################### +def get_efferent_synapse_groups_color_coded_by_post_mtypes(circuit, + gid, + mtype_color_dict): + + # Get the efferent synapses of the given GID + efferent_synapses_ids = circuit.get_efferent_synapses_ids(gid=gid) + + # Get the mtypes of the post-synaptic cells + efferent_synapses_mtypes = circuit.get_post_synaptic_mtypes( + efferent_synapses_ids_list=efferent_synapses_ids) + + # Create and return the color coded synapses + return nmv.bbp.create_color_coded_synapse_groups_by_mtype( + circuit=circuit, synapses_ids=efferent_synapses_ids, + synapses_mtypes=efferent_synapses_mtypes, mtype_color_dict=mtype_color_dict) + + +#################################################################################################### +# @get_efferent_synapse_groups_color_coded_by_post_etypes +#################################################################################################### +def get_efferent_synapse_groups_color_coded_by_post_etypes(circuit, + gid, + etype_color_dict): + + # Get the efferent synapses of the given GID + efferent_synapses_ids = circuit.get_efferent_synapses_ids(gid=gid) + + # Get the etypes of the post-synaptic cells + efferent_synapses_etypes = circuit.get_post_synaptic_etypes( + efferent_synapses_ids_list=efferent_synapses_ids) + + # Create and return the color coded synapses + return nmv.bbp.create_color_coded_synapse_groups_by_etype( + circuit=circuit, synapses_ids=efferent_synapses_ids, + synapses_etypes=efferent_synapses_etypes, etype_color_dict=etype_color_dict) + + +#################################################################################################### +# @get_excitatory_and_inhibitory_synapse_groups +#################################################################################################### +def get_excitatory_and_inhibitory_synapse_groups(circuit, + gid, + load_excitatory=True, + load_inhibitory=True, + exc_color='#ff0000', + inh_color='#0000ff'): + + # Get the color coded dictionary + color_coded_synapses_dict = nmv.bbp.get_excitatory_and_inhibitory_synapses_color_coded_dict( + circuit=circuit, gid=gid, load_excitatory=load_excitatory, load_inhibitory=load_inhibitory, + exc_color=exc_color, inh_color=inh_color) + + # Construct the groups + synapse_groups = list() + for key in color_coded_synapses_dict: + group_name = key + group_value = color_coded_synapses_dict[key] + group_color = list(group_value.keys())[0] + group_synapses_list = group_value[group_color] + + synapse_group = nmv.bbp.SynapseGroup( + name=group_name, + color=nmv.utilities.confirm_rgb_color_from_color_string(group_color), + synapses_ids_list=group_synapses_list) + synapse_groups.append(synapse_group) + + # Return a reference to the synapse group + return synapse_groups + + +#################################################################################################### +# @get_afferent_synapse_group_for_projection +#################################################################################################### +def get_afferent_synapse_group_for_projection(circuit, + gid, + projection_name, + color): + + return nmv.bbp.SynapseGroup( + name='Afferent Synapses', + color=nmv.utilities.confirm_rgb_color_from_color_string(color), + synapses_ids_list=circuit.get_afferent_synapses_ids_for_projection( + gid=gid, projection_name=projection_name)) + + +#################################################################################################### +# @get_afferent_synapse_group +#################################################################################################### +def get_afferent_synapse_group(circuit, + gid, + color): + + return nmv.bbp.SynapseGroup( + name='Afferent Synapses', + color=nmv.utilities.confirm_rgb_color_from_color_string(color), + synapses_ids_list=circuit.get_afferent_synapses_ids(gid=gid)) + + +#################################################################################################### +# @get_efferent_synapse_group +#################################################################################################### +def get_efferent_synapse_group(circuit, + gid, + color): + + return nmv.bbp.SynapseGroup( + name='Efferent Synapses', + color=nmv.utilities.confirm_rgb_color_from_color_string(color), + synapses_ids_list=circuit.get_efferent_synapses_ids(gid=gid)) + + + + + + + + + + + + + + + + + + + + + + + + +def is_axo_somatic_synapse(circuit, synapse_id): + import bluepy + import morphio + value = circuit.connectome.synapse_properties( + numpy.array([synapse_id]), [bluepy.enums.Synapse.POST_SECTION_ID])\ + [bluepy.enums.Synapse.POST_SECTION_ID].values.tolist()[0] + print(type(value), value) + if value == 0: + + return True + else: + return False + + + + + + + + + + + +#################################################################################################### +# @get_pre_mtype_synapses_mtype_coded_dict +#################################################################################################### +def get_pre_mtype_synapses_mtype_coded_dict(circuit, + post_gid, + color_coded_mtype_dict): + """ + # thats mainly on dendrites + + :param circuit: + :type circuit: + :param post_gid: + :type post_gid: + :return: + :rtype: + """ + + # Get the afferent synapses of the post_gid + afferent_synapses_ids = get_afferent_synapses_ids(circuit=circuit, gid=post_gid) + + # Get the mtypes of the pre-synaptic cells + afferent_synapses_mtypes = get_pre_synaptic_mtypes( + circuit=circuit, afferent_synapses_ids_list=afferent_synapses_ids) + + # Initially, get a dictionary where the lists corresponding to each mtype are empty + mtype_set = nmv.bbp.get_circuit_mtype_strings_set(circuit=circuit) + mtype_dict = dict.fromkeys(mtype_set, dict()) + + # Update the mtypes lists + synapses_mtype_coded_dict = dict.fromkeys(set(color_coded_mtype_dict.values()), list()) + print(synapses_mtype_coded_dict) + for i, mtype in enumerate(afferent_synapses_mtypes): + + mtype_key = afferent_synapses_mtypes[i] + synapse_id = afferent_synapses_ids[i] + + color = color_coded_mtype_dict[mtype_key] + + synapses_ids_list = synapses_mtype_coded_dict[color] + synapses_ids_list.append(synapse_id) + + for key in color_coded_mtype_dict: + mtype = key + color = color_coded_mtype_dict[key] + + print(key, color) + + synapse_list = synapses_mtype_coded_dict[color] + print(len(synapse_list)) + + mtype_dict[mtype] = {color: synapse_list} + + + + + + + + return mtype_dict + + +#################################################################################################### +# @get_color_coded_synapse_dict_from_color_coded_mtype_dict +#################################################################################################### +def get_color_coded_synapse_dict_from_color_coded_mtype_dict(mtype_color_dict, + mtype_synapses_dict): + color_coded_synapse_dict = {} + for key in mtype_color_dict: + color_coded_synapse_dict[key] = mtype_synapses_dict[key] + + + + + + + + +#################################################################################################### +# @get_color_coded_synapse_dict +#################################################################################################### +def get_color_coded_synapse_dict(synapse_json_file): + """Returns a dictionary containing color-coded lists of synapses. The keys represent the + colors in hex format and the values are lists of synapse IDs. + + :param synapse_json_file: + The absolute path to json files containing synapses IDs and their group colors. + :return: + A dictionary containing color-coded lists of synapses. The keys represent the + colors in hex format and the values are lists of synapse IDs. + """ + + # Load the data from the JSON file + try: + f = open(synapse_json_file) + except FileNotFoundError: + print("The synapse json file [%s] is NOT found!" % synapse_json_file) + return None + + # Load all the data from the file + import json + data_dict = json.load(f) + f.close() + + # Return the loaded dictionary + return data_dict + + +#################################################################################################### +# @get_synapse_groups_from_color_coded_json_file +#################################################################################################### +def get_synapse_groups_from_color_coded_json_file(synapse_json_file): + + # Read the file into the dictionary + color_coded_synapses_dict = nmv.bbp.get_color_coded_synapse_dict(synapse_json_file) + + # Construct the groups + synapse_groups = list() + for key in color_coded_synapses_dict: + + group_name = key + group_value = color_coded_synapses_dict[key] + group_color = list(group_value.keys())[0] + group_synapses_list = group_value[group_color] + + synapse_group = nmv.bbp.SynapseGroup( + name=group_name, + color=nmv.utilities.confirm_rgb_color_from_color_string(group_color), + synapses_ids_list=group_synapses_list) + synapse_groups.append(synapse_group) + + # Return a reference to the synapse group + return synapse_groups + + diff --git a/nmv/bbp/synapses_color_coding.py b/nmv/bbp/synapses_color_coding.py new file mode 100644 index 000000000..72c5b58a7 --- /dev/null +++ b/nmv/bbp/synapses_color_coding.py @@ -0,0 +1,159 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.bbp + + +#################################################################################################### +# @create_color_coded_synapse_groups_by_mtype +#################################################################################################### +def create_color_coded_synapse_groups_by_mtype(circuit, + synapses_ids, + synapses_mtypes, + mtype_color_dict): + + # Initially, get a dictionary where the lists corresponding to each mtype are empty + mtypes_set = circuit.get_mtype_strings_set() + + # Create a dictionary, initialized with empty lists + mtype_dict = {} + for key in mtypes_set: + mtype_dict[key] = list() + + # Iterate over the synapses ids and their corresponding mtypes to create a dictionary + for synapse_id, synapse_mtype in zip(synapses_ids, synapses_mtypes): + mtype_dict[synapse_mtype].append(synapse_id) + + # Create the synapses groups + synapse_groups = list() + for mtype in mtype_dict: + + # Create the synapse group for that specific mtype + synapses_ids_list = mtype_dict[mtype] + if len(synapses_ids_list) > 0: + synapse_groups.append(nmv.bbp.SynapseGroup( + name=mtype, synapses_ids_list=synapses_ids_list, color=mtype_color_dict[mtype])) + + # To keep consistency for the indexing over all the types, add an empty group + else: + synapse_groups.append(nmv.bbp.SynapseGroup( + name=mtype, synapses_ids_list=list(), color=mtype_color_dict[mtype])) + + # Return a reference to the synapse groups + return synapse_groups + + +#################################################################################################### +# @create_color_coded_synapse_groups_by_etype +#################################################################################################### +def create_color_coded_synapse_groups_by_etype(circuit, + synapses_ids, + synapses_etypes, + etype_color_dict): + + # Initially, get a dictionary where the lists corresponding to each etype are empty + etypes_set = circuit.get_etype_strings_set() + + # Create a dictionary, initialized with empty lists + etype_dict = {} + for key in etypes_set: + etype_dict[key] = list() + + # Iterate over the synapses ids and their corresponding etypes to create a dictionary + for synapse_id, synapse_etype in zip(synapses_ids, synapses_etypes): + etype_dict[synapse_etype].append(synapse_id) + + # Create the synapses groups + synapse_groups = list() + for etype in etype_dict: + + # Create the synapse group for that specific etype + synapses_ids_list = etype_dict[etype] + if len(synapses_ids_list) > 0: + synapse_groups.append(nmv.bbp.SynapseGroup( + name=etype, synapses_ids_list=synapses_ids_list, color=etype_color_dict[etype])) + + # To keep consistency for the indexing over all the types, add an empty group + else: + synapse_groups.append(nmv.bbp.SynapseGroup( + name=etype, synapses_ids_list=list(), color=etype_color_dict[etype])) + + # Return a reference to the synapse groups + return synapse_groups + + +#################################################################################################### +# @get_excitatory_and_inhibitory_synapses_color_coded_dict +#################################################################################################### +def get_excitatory_and_inhibitory_synapses_color_coded_dict(circuit, + gid, + load_excitatory=True, + load_inhibitory=True, + exc_color='#ff0000', + inh_color='#0000ff'): + """Returns a color-coded dictionary containing the inhibitory and excitatory synapses or either + any of them. + + :param circuit: + BBP circuit. + :param gid: + Neuron GID for which the synapses will be returned. Note that the synapse IDs are unique. + :param load_excitatory: + If this flag is set, the excitatory synapses will be loaded. + :param load_inhibitory: + If this flag is set, the inhibitory synapses will be loaded. + :param exc_color: + A Vector containing the RGB color of the excitatory synapses. + :param inh_color: + A vector containing the RGB color of the inhibitory synapses. + :return: + A color-coded dictionary containing the inhibitory and excitatory synapses. + """ + + # Get the IDs of all the synapses on the neuron + synapse_ids_list = circuit.get_all_synapses_ids(gid=gid) + + # Get the IDs of the types of the synapses + synapse_types_ids = circuit.get_synapse_types_ids(synapse_ids_list=synapse_ids_list) + + # A list that will collect the synapses + excitatory_synapses = list() + inhibitory_synapses = list() + + for i, synapse_type_id in enumerate(synapse_types_ids): + if circuit.is_synapse_inhibitory(synapse_type_id): + inhibitory_synapses.append(synapse_ids_list[i]) + else: + excitatory_synapses.append(synapse_ids_list[i]) + + # Construct and return the dict of the synapses + if load_excitatory and load_inhibitory: + synapses_color_coded_dict = { + 'Excitatory Synapses': {exc_color: excitatory_synapses}, + 'Inhibitory Synapses': {inh_color: inhibitory_synapses}} + return synapses_color_coded_dict + elif load_excitatory and not load_inhibitory: + synapses_color_coded_dict = { + 'Excitatory Synapses': {exc_color: excitatory_synapses}} + return synapses_color_coded_dict + elif not load_excitatory and load_inhibitory: + synapses_color_coded_dict = { + 'Inhibitory Synapses': {inh_color: inhibitory_synapses}} + return synapses_color_coded_dict + + return None diff --git a/nmv/bbp/synapses_generation.py b/nmv/bbp/synapses_generation.py new file mode 100644 index 000000000..d2f94f366 --- /dev/null +++ b/nmv/bbp/synapses_generation.py @@ -0,0 +1,92 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import random + +# Blender imports +from mathutils import Vector + +# Internal imports +import nmv.bmeshi +import nmv.enums +import nmv.geometry +import nmv.mesh +import nmv.shading + + +#################################################################################################### +# @create_color_coded_synapses_particle_system +#################################################################################################### +def create_color_coded_synapses_particle_system(circuit, + synapse_groups, + synapse_radius=2, + synapses_percentage=100, + inverted_transformation=None, + material_type=nmv.enums.Shader.LAMBERT_WARD): + """Creates a color-coded mesh for a given list of synapses. + + :param circuit: + BBP circuit. + :param synapse_groups: + An object of the SynapseGroup class containing the name of the group, its synapses IDs list + and the color of the group in a Vector((R, G, B)) format. + :param synapse_radius: + The radius of the synapse in microns. By default, this value if 2. + :param synapses_percentage: + The percentage of the shown synapses. + :param inverted_transformation: + The inverse transformation to transform the synapses to the origin. If this is None, the + synapses where will be located in the circuit global coordinates. + :param material_type: + The type of the material used to shade the resulting mesh. + :return: + A reference to the created synapse group particle system. + """ + + # For every group in the synapse list, create a mesh and color code it. + for synapse_group in synapse_groups: + + # The post-synaptic position + positions = circuit.get_post_synaptic_synapse_positions( + synapse_ids_list=synapse_group.synapses_ids_list) + + # Update the positions taking into consideration the transformation + for j in range(len(positions)): + position = Vector((positions[j][0], positions[j][1], positions[j][2])) + if inverted_transformation is not None: + position = inverted_transformation @ position + positions[j] = position + + # Create the material + material = nmv.shading.create_material( + name=synapse_group.name, color=synapse_group.color, material_type=material_type) + + # Sample the list + number_synapses = int(len(positions) * synapses_percentage / 100.0) + positions = random.sample(positions, number_synapses) + + # Create the vertices mesh + vertices_mesh = nmv.bmeshi.convert_bmesh_to_mesh( + bmesh_object=nmv.bmeshi.create_vertices(locations=positions), name=synapse_group.name) + + nmv.shading.set_material_to_object(mesh_object=vertices_mesh, material_reference=material) + + # Create the particle system + particle_system = nmv.geometry.create_particle_system_for_vertices( + mesh_object=vertices_mesh, name=synapse_group.name, vertex_radius=synapse_radius, + material=material) diff --git a/nmv/bbp/synapses_visualization.py b/nmv/bbp/synapses_visualization.py new file mode 100644 index 000000000..a5230cb82 --- /dev/null +++ b/nmv/bbp/synapses_visualization.py @@ -0,0 +1,446 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +from mathutils import Vector + +# Internal imports +import nmv.bbox +import nmv.consts +import nmv.enums +import nmv.utilities + + +#################################################################################################### +# @visualize_afferent_synapses +#################################################################################################### +def visualize_afferent_synapses(circuit, + gid, + options, + context=None): + """Visualizes the afferent synapses of a specific neuron in a circuit. + + :param circuit: + BBP circuit. + :param gid: + Neuron GID. + :param options: + NMV options. + :param context: + Blender context, for UI operations. + :return: + A reference to the created synapse group. + """ + + # Create the synapse groups + synapse_groups = list() + + # Select what to visualize based on the selected color coding scheme + color_coding_scheme = options.synaptics.afferent_color_coding + if color_coding_scheme == nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR: + + # Create the afferent synapse group + afferent_group = nmv.bbp.get_afferent_synapse_group( + circuit=circuit, gid=gid, + color=nmv.utilities.rgb_vector_to_hex(options.synaptics.afferent_synapses_color)) + synapse_groups.append(afferent_group) + + # Synapses count + if context is not None: + afferent_synapses_count = len(afferent_group.synapses_ids_list) + context.scene.NMV_SynapticsNumberAfferentSynapses = afferent_synapses_count + + elif color_coding_scheme == nmv.enums.Synaptics.ColorCoding.MTYPE_COLOR_CODED: + + # Get the color-coding dictionary from the UI + color_dict = {} + for i in range(len(nmv.consts.Circuit.MTYPES)): + color_dict[nmv.consts.Circuit.MTYPES[i]] = options.synaptics.mtypes_colors[i] + + # Create the afferent synapse groups + synapse_groups = nmv.bbp.get_afferent_synapse_groups_color_coded_by_pre_mtypes( + circuit=circuit, gid=gid, mtype_color_dict=color_dict) + + if context is not None: + for i in range(len(nmv.consts.Circuit.MTYPES)): + setattr(context.scene, 'NMV_Synaptic_MtypeCount_%d' % i, + len(synapse_groups[i].synapses_ids_list)) + + elif color_coding_scheme == nmv.enums.Synaptics.ColorCoding.ETYPE_COLOR_CODED: + + # Get the color-coding dictionary from the UI + color_dict = {} + for i in range(len(nmv.consts.Circuit.ETYPES)): + color_dict[nmv.consts.Circuit.ETYPES[i]] = options.synaptics.etypes_colors[i] + + # Create the afferent synapse group + synapse_groups = nmv.bbp.get_afferent_synapse_groups_color_coded_by_pre_etypes( + circuit=circuit, gid=gid, etype_color_dict=color_dict) + + if context is not None: + for i in range(len(nmv.consts.Circuit.ETYPES)): + setattr(context.scene, 'NMV_Synaptic_EtypeCount_%d' % i, + len(synapse_groups[i].synapses_ids_list)) + + else: + pass + + nmv.logger.info('Adding synapses to the scene') + nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=options.synaptics.synapses_radius, + synapses_percentage=options.synaptics.percentage, + inverted_transformation=circuit.get_neuron_inverse_transformation_matrix(gid=gid), + material_type=options.synaptics.shader) + + # Return the synapse groups for statistics + return synapse_groups + + +#################################################################################################### +# @visualize_afferent_synapses_for_projection +#################################################################################################### +def visualize_afferent_synapses_for_projection(circuit, + gid, + options, + context=None): + + # Create the synapse groups + synapse_groups = list() + + # Select what to visualize based on the selected color coding scheme + color_coding_scheme = options.synaptics.afferent_color_coding + if color_coding_scheme == nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR: + + # Create the afferent synapse group + afferent_group = nmv.bbp.get_afferent_synapse_group_for_projection( + circuit=circuit, gid=gid, projection_name=options.synaptics.projection_name, + color=nmv.utilities.rgb_vector_to_hex(options.synaptics.afferent_synapses_color)) + synapse_groups.append(afferent_group) + + # Synapses count + if context is not None: + afferent_synapses_count = len(afferent_group.synapses_ids_list) + context.scene.NMV_SynapticsNumberAfferentSynapses = afferent_synapses_count + + elif color_coding_scheme == nmv.enums.Synaptics.ColorCoding.MTYPE_COLOR_CODED: + + # Get the color-coding dictionary from the UI + color_dict = {} + for i in range(len(nmv.consts.Circuit.MTYPES)): + color_dict[nmv.consts.Circuit.MTYPES[i]] = options.synaptics.mtypes_colors[i] + + # Create the afferent synapse groups + synapse_groups = nmv.bbp.get_afferent_synapse_groups_color_coded_by_pre_mtypes( + circuit=circuit, gid=gid, mtype_color_dict=color_dict) + + if context is not None: + for i in range(len(nmv.consts.Circuit.MTYPES)): + setattr(context.scene, 'NMV_Synaptic_MtypeCount_%d' % i, + len(synapse_groups[i].synapses_ids_list)) + + elif color_coding_scheme == nmv.enums.Synaptics.ColorCoding.ETYPE_COLOR_CODED: + + # Get the color-coding dictionary from the UI + color_dict = {} + for i in range(len(nmv.consts.Circuit.ETYPES)): + color_dict[nmv.consts.Circuit.ETYPES[i]] = options.synaptics.etypes_colors[i] + + # Create the afferent synapse group + synapse_groups = nmv.bbp.get_afferent_synapse_groups_color_coded_by_pre_etypes( + circuit=circuit, gid=gid, etype_color_dict=color_dict) + + if context is not None: + for i in range(len(nmv.consts.Circuit.ETYPES)): + setattr(context.scene, 'NMV_Synaptic_EtypeCount_%d' % i, + len(synapse_groups[i].synapses_ids_list)) + + else: + pass + + nmv.logger.info('Adding synapses to the scene') + nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=options.synaptics.synapses_radius, + synapses_percentage=options.synaptics.percentage, + inverted_transformation=circuit.get_neuron_inverse_transformation_matrix(gid=gid), + material_type=options.synaptics.shader) + + # Return the synapse groups for statistics + return synapse_groups + + +#################################################################################################### +# @visualize_efferent_synapses +#################################################################################################### +def visualize_efferent_synapses(circuit, + gid, + options, + context=None): + """Visualizes the efferent synapses of a specific neuron in a circuit. + + :param circuit: + BBP circuit. + :param gid: + Neuron GID. + :param options: + NMV options. + :param context: + Blender context, for UI operations. + :return: + A reference to the created synapse group. + """ + + # Create the synapse groups + synapse_groups = list() + + # Select what to visualize based on the selected color coding scheme + efferent_scheme = options.synaptics.efferent_color_coding + if efferent_scheme == nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR: + + # Create the afferent synapse group + efferent_group = nmv.bbp.get_efferent_synapse_group( + circuit=circuit, gid=gid, + color=nmv.utilities.rgb_vector_to_hex(options.synaptics.efferent_synapses_color)) + synapse_groups.append(efferent_group) + + # Synapses count + if context is not None: + efferent_synapses_count = len(efferent_group.synapses_ids_list) + context.scene.NMV_SynapticsNumberEfferentSynapses = efferent_synapses_count + + elif efferent_scheme == nmv.enums.Synaptics.ColorCoding.MTYPE_COLOR_CODED: + + # Get the color-coding dictionary from the UI + color_dict = {} + for i in range(len(nmv.consts.Circuit.MTYPES)): + color_dict[nmv.consts.Circuit.MTYPES[i]] = options.synaptics.mtypes_colors[i] + + # Create the efferent synapse groups + synapse_groups = nmv.bbp.get_efferent_synapse_groups_color_coded_by_post_mtypes( + circuit=circuit, gid=gid, mtype_color_dict=color_dict) + + if context is not None: + for i in range(len(nmv.consts.Circuit.MTYPES)): + setattr(context.scene, 'NMV_Synaptic_MtypeCount_%d' % i, + len(synapse_groups[i].synapses_ids_list)) + + elif efferent_scheme == nmv.enums.Synaptics.ColorCoding.ETYPE_COLOR_CODED: + + # Get the color-coding dictionary from the UI + color_dict = {} + for i in range(len(nmv.consts.Circuit.ETYPES)): + color_dict[nmv.consts.Circuit.ETYPES[i]] = options.synaptics.etypes_colors[i] + + # Create the efferent synapse groups + synapse_groups = nmv.bbp.get_efferent_synapse_groups_color_coded_by_post_etypes( + circuit=circuit, gid=gid, etype_color_dict=color_dict) + + if context is not None: + for i in range(len(nmv.consts.Circuit.ETYPES)): + setattr(context.scene, 'NMV_Synaptic_EtypeCount_%d' % i, + len(synapse_groups[i].synapses_ids_list)) + + else: + pass + + nmv.logger.info('Adding synapses to the scene') + nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=options.synaptics.synapses_radius, + synapses_percentage=options.synaptics.percentage, + inverted_transformation=circuit.get_neuron_inverse_transformation_matrix(gid=gid), + material_type=options.synaptics.shader) + + # Return the synapse groups for statistics + return synapse_groups + + +#################################################################################################### +# @visualize_afferent_and_efferent_synapses +#################################################################################################### +def visualize_afferent_and_efferent_synapses(circuit, + gid, + options, + visualize_afferent=True, + visualize_efferent=True): + """Visualizes the afferent and/or efferent synapses of a specific neuron in a circuit. + + :param circuit: + BBP circuit. + :param gid: + Neuron GID. + :param options: + NMV options. + :param visualize_afferent: + If this flag is set the afferent synapses will be visualized. + :param visualize_efferent: + If this flag is set the efferent synapses will be visualized. + :return: + A reference to the synapse groups list. + """ + + nmv.logger.info('Loading synapses and creating synapse groups') + synapse_groups = list() + if visualize_afferent: + afferent_group = nmv.bbp.get_afferent_synapse_group( + circuit=circuit, gid=gid, + color=nmv.utilities.rgb_vector_to_hex(options.synaptics.afferent_synapses_color)) + synapse_groups.append(afferent_group) + + if visualize_efferent: + efferent_group = nmv.bbp.get_efferent_synapse_group( + circuit=circuit, gid=gid, + color=nmv.utilities.rgb_vector_to_hex(options.synaptics.efferent_synapses_color)) + synapse_groups.append(efferent_group) + + nmv.logger.info('Adding synapses to the scene') + nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=options.synaptics.synapses_radius, + synapses_percentage=options.synaptics.percentage, + inverted_transformation=circuit.get_neuron_inverse_transformation_matrix(gid=gid), + material_type=options.synaptics.shader) + + # Return the synapse groups for statistics + return synapse_groups + + +#################################################################################################### +# @visualize_excitatory_and_inhibitory_synapses +#################################################################################################### +def visualize_excitatory_and_inhibitory_synapses(circuit, + gid, + options, + visualize_excitatory=True, + visualize_inhibitory=True): + """Visualizes the excitatory and/or inhibitory synapses of a specific neuron in a circuit. + + :param circuit: + BBP circuit. + :param gid: + Neuron GID. + :param options: + NMV options. + :param visualize_excitatory: + If this flag is set, the excitatory synapses will be visualized. + :param visualize_inhibitory: + If this flag is set, the inhibitory synapses will be visualized. + :return: + A reference to the synapse groups list. + """ + + # + nmv.logger.info('Loading synapses and creating synapse groups') + synapse_groups = list() + if visualize_excitatory: + excitatory_group = nmv.bbp.get_excitatory_synapse_group( + circuit=circuit, gid=gid, + color=nmv.utilities.rgb_vector_to_hex(options.synaptics.excitatory_synapses_color)) + synapse_groups.append(excitatory_group) + + if visualize_inhibitory: + inhibitory_group = nmv.bbp.get_inhibitory_synapse_group( + circuit=circuit, gid=gid, + color=nmv.utilities.rgb_vector_to_hex(options.synaptics.inhibitory_synapses_color)) + synapse_groups.append(inhibitory_group) + + # + nmv.logger.info('Adding synapses to the scene') + nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=options.synaptics.synapses_radius, + synapses_percentage=options.synaptics.percentage, + inverted_transformation=circuit.get_neuron_inverse_transformation_matrix(gid=gid), + material_type=options.synaptics.shader) + + # Return the synapse groups for statistics + return synapse_groups + + +#################################################################################################### +# @visualize_excitatory_and_inhibitory_synapses +#################################################################################################### +def visualize_shared_synapses_between_two_neurons(circuit, + pre_gid, + post_gid, + options, + inverse_transformation): + """Visualizes the shared synapses between two neurons in a circuit. + + :param circuit: + BBP circuit. + :param pre_gid: + The GID of the pre-synaptic neuron. + :param post_gid: + The GID of the post-synaptic neuron. + :param options: + NMV options. + :param inverse_transformation: + The inverse transformation used to align the neurons at the origin. + :return: + A reference to the created synapse groups list. + """ + + # Create the shared group + synapse_groups = list() + synapse_groups.append( + nmv.bbp.get_shared_synapses_group_between_two_neurons( + circuit=circuit, pre_gid=pre_gid, post_gid=post_gid, + color=nmv.utilities.rgb_vector_to_hex(options.synaptics.synapses_color))) + + nmv.logger.info('Adding synapses to the scene') + nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=options.synaptics.synapses_radius, + synapses_percentage=options.synaptics.percentage, + inverted_transformation=inverse_transformation, + material_type=options.synaptics.shader) + + # Return the synapse groups for statistics + return synapse_groups + + +#################################################################################################### +# @visualize_synapse_groups +#################################################################################################### +def visualize_synapse_groups(circuit, + synapse_groups, + gid, + options): + """A generic function to visualize a set of synapse groups. + + :param circuit: + BBP circuit. + :param synapse_groups: + A list of synapse groups to be visualized. + :param gid: + The GID of the neuron. + :param options: + NMV options. + """ + + nmv.logger.info('Adding synapses to the scene') + nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=options.synaptics.synapses_radius, + synapses_percentage=options.synaptics.percentage, + inverted_transformation=circuit.get_neuron_inverse_transformation_matrix(gid=gid), + material_type=options.synaptics.shader) + + + diff --git a/nmv/bbp/transformations.py b/nmv/bbp/transformations.py new file mode 100644 index 000000000..e6aabc28b --- /dev/null +++ b/nmv/bbp/transformations.py @@ -0,0 +1,112 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +from mathutils import Vector, Matrix + + +#################################################################################################### +# @get_neuron_translation_vector +#################################################################################################### +def get_neuron_translation_vector(circuit, + gid): + + # Translation + neuron = circuit.cells.get(int(gid)) + return Vector((neuron['x'], neuron['y'], neuron['z'])) + + +#################################################################################################### +# @get_neuron_orientation_matrix +#################################################################################################### +def get_neuron_orientation_matrix(circuit, + gid): + + # Orientation + neuron = circuit.cells.get(int(gid)) + o = neuron['orientation'] + o0 = Vector((o[0][0], o[0][1], o[0][2])) + o1 = Vector((o[1][0], o[1][1], o[1][2])) + o2 = Vector((o[2][0], o[2][1], o[2][2])) + + # Initialize the orientation matrix to I + orientation_matrix = Matrix() + + orientation_matrix[0][0] = o0[0] + orientation_matrix[0][1] = o0[1] + orientation_matrix[0][2] = o0[2] + orientation_matrix[0][3] = 1.0 + + orientation_matrix[1][0] = o1[0] + orientation_matrix[1][1] = o1[1] + orientation_matrix[1][2] = o1[2] + orientation_matrix[1][3] = 1.0 + + orientation_matrix[2][0] = o2[0] + orientation_matrix[2][1] = o2[1] + orientation_matrix[2][2] = o2[2] + orientation_matrix[2][3] = 1.0 + + orientation_matrix[3][0] = 0.0 + orientation_matrix[3][1] = 0.0 + orientation_matrix[3][2] = 0.0 + orientation_matrix[3][3] = 1.0 + + return orientation_matrix + + +#################################################################################################### +# @get_neuron_transformation_matrix +#################################################################################################### +def get_neuron_transformation_matrix(circuit, + gid): + """Get the transformation matrix of a neuron identified by a GID. + :param circuit: + BBP circuit. + :param gid: + Neuron GID. + :return: + The transformation matrix of the neuron in Blender format. + """ + + # Translation + translation_vector = get_neuron_translation_vector(circuit=circuit, gid=gid) + + # Get the orientation and update the translation elements in the orientation matrix + matrix = get_neuron_orientation_matrix(circuit=circuit, gid=gid) + matrix[0][3] = translation_vector[0] + matrix[1][3] = translation_vector[1] + matrix[2][3] = translation_vector[2] + + return matrix + + +#################################################################################################### +# @get_neuron_inverse_transformation_matrix +#################################################################################################### +def get_neuron_inverse_transformation_matrix(circuit, + gid): + """Get the inverse transformation matrix of a neuron identified by a GID. + :param circuit: + BBP circuit. + :param gid: + Neuron GID. + :return: + The transformation matrix of the neuron in Blender format. + """ + + return get_neuron_transformation_matrix(circuit=circuit, gid=gid).inverted() diff --git a/nmv/builders/mesh/voxelization_builder.py b/nmv/builders/mesh/voxelization_builder.py new file mode 100644 index 000000000..cc0236735 --- /dev/null +++ b/nmv/builders/mesh/voxelization_builder.py @@ -0,0 +1,280 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal modules +from .base import MeshBuilderBase +import nmv.builders +import nmv.enums +import nmv.mesh +import nmv.shading +import nmv.skeleton +import nmv.scene +import nmv.utilities + + +#################################################################################################### +# @VoxelizationBuilder +#################################################################################################### +class VoxelizationBuilder(MeshBuilderBase): + """Mesh builder that creates watertight meshes using voxelization. + Notes: + - This builder works with Blender 3.0 and beyond. + - The meshes produced by this builder are watertight. + """ + + ################################################################################################ + # @__init__ + ################################################################################################ + def __init__(self, + morphology, + options): + """Constructor + + :param morphology: + A given morphology skeleton to create the mesh for. + :param options: + Loaded options from NeuroMorphoVis. + """ + + # Initialize the parent with the common parameters + MeshBuilderBase.__init__(self, morphology, options, 'voxelization') + + # Voxelization resolution use for the re-meshing modifier + self.voxelization_resolution = 0.1 + + # Statistics + self.profiling_statistics = 'VoxelizationBuilder Profiling Stats.: \n' + + # Stats. about the mesh + self.mesh_statistics = 'VoxelizationBuilder Mesh: \n' + + ################################################################################################ + # @confirm_single_or_multiple_mesh_objects + ################################################################################################ + def confirm_single_or_multiple_mesh_objects(self): + """The resulting mesh from this builder is always contained in a single object.""" + + self.result_is_single_object_mesh = True + + ################################################################################################ + # @resample_skeleton_adaptive_relaxed + ################################################################################################ + def resample_skeleton_adaptive_relaxed(self): + """Resamples the morphology skeleton using the adaptive relaxed method.""" + + nmv.skeleton.ops.apply_operation_to_morphology( + *[self.morphology, nmv.skeleton.resample_section_adaptively_relaxed]) + + ################################################################################################ + # @initialize_builder + ################################################################################################ + def initialize_builder(self): + """Initializes the different parameters/options of the builder required for building.""" + + # Is it a single object or multiple objects + self.confirm_single_or_multiple_mesh_objects() + + # Modify the morphology skeleton, if required + self.modify_morphology_skeleton() + + # Remove the internal samples of the arbors that are located within the soma extent + self.remove_arbors_samples_inside_soma() + + # Resample the morphology skeleton using the adaptive relaxed method + # self.resample_skeleton_adaptive_relaxed() + + # Verify the connectivity of the arbors to the soma + nmv.skeleton.verify_arbors_connectivity_to_soma(morphology=self.morphology) + + # Computes the smallest radius of the morphology skeleton until the specified branch order + smallest_radius = nmv.skeleton.get_smallest_sample_radius_of_trimmed_morphology( + morphology=self.morphology, + axon_branch_order=self.options.morphology.axon_branch_order, + basal_dendrites_branch_order=self.options.morphology.basal_dendrites_branch_order, + apical_dendrite_branch_order=self.options.morphology.apical_dendrite_branch_order) + + # Get the voxelization resolution + if smallest_radius < 0.1: + self.voxelization_resolution = 0.1 * 0.85 + else: + self.voxelization_resolution = smallest_radius * 0.85 + + # If the spines are loaded, use resolution 0.05 + if self.options.mesh.spines != nmv.enums.Meshing.Spines.Source.IGNORE: + self.voxelization_resolution = 0.05 + + nmv.logger.info('Voxelization Resolution [%f]' % self.voxelization_resolution) + + # Optimized meta-ball soma resolution for the voxelization modifier + self.options.soma.meta_ball_resolution = self.voxelization_resolution + + ################################################################################################ + # @build_proxy_mesh_using_articulated_sections_builder + ################################################################################################ + def build_proxy_mesh_using_articulated_sections_builder(self): + + # Adjust the options to force the articulated sections method + self.options.morphology.reconstruction_method = \ + nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS + + # Adjust the options to use the soma-generation method of the mesh toolbox + self.options.morphology.soma_representation = self.options.mesh.soma_type + + # Adjust the minimum radius to 0.1 to avoid discontinuities + nmv.skeleton.set_smallest_sample_radius_to_value( + morphology=self.morphology, smallest_radius=0.2) + + # Create the morphology builder and generate the morphology skeleton + morphology_builder = nmv.builders.DisconnectedSectionsBuilder( + morphology=self.morphology, options=self.options) + + # Generate a list of objects (currently morphologies) + objects = morphology_builder.draw_morphology_skeleton() + + # Convert the morphologies into meshes, if not meshes + for i_object in objects: + nmv.scene.convert_object_to_mesh(scene_object=i_object) + + # Join all the objects into a single mesh object + self.neuron_mesh = nmv.scene.join_objects(scene_objects=objects) + + ################################################################################################ + # @build_proxy_mesh_using_connected_sections_builder + ################################################################################################ + def build_proxy_mesh_using_connected_sections_builder(self): + + # Adjust the minimum radius to 0.1 to avoid discontinuities + nmv.skeleton.set_smallest_sample_radius_to_value( + morphology=self.morphology, smallest_radius=0.2) + + # Create the proxy mesh builder and generate the morphology skeleton + proxy_mesh_builder = nmv.builders.PiecewiseBuilder( + morphology=self.morphology, options=self.options, this_is_proxy_mesh=True) + + # Reconstruct the proxy mesh + self.neuron_mesh = proxy_mesh_builder.reconstruct_mesh()[0] + + ################################################################################################ + # @apply_voxelization_modifier + ################################################################################################ + def apply_voxelization_modifier(self): + """Apply the voxelization-based re-meshing modifier to create a new mesh.""" + + nmv.logger.info('Applying the voxelization finalization') + + # Apply the modifier + nmv.mesh.apply_voxelization_remeshing_modifier( + mesh_object=self.neuron_mesh, voxel_size=self.voxelization_resolution) + + ################################################################################################ + # @adjust_origin_to_soma_center + ################################################################################################ + def adjust_origin_to_soma_center(self): + nmv.mesh.set_mesh_origin( + mesh_object=self.neuron_mesh, coordinate=self.morphology.soma.centroid) + + ################################################################################################ + # @build_proxy_mesh + ################################################################################################ + def build_proxy_mesh(self): + """Builds the proxy mesh that will be used for the voxelization""" + + # Ensure that we use a high quality cross-sectional bevel + self.options.morphology.bevel_object_sides = 16 + if self.options.mesh.proxy_mesh_method == nmv.enums.Meshing.Proxy.CONNECTED_SECTIONS: + return self.build_proxy_mesh_using_connected_sections_builder() + else: + return self.build_proxy_mesh_using_articulated_sections_builder() + + ################################################################################################ + # @finalize_mesh + ################################################################################################ + def finalize_mesh(self): + """Finalize the resulting mesh to create a clean one.""" + + nmv.logger.info('Mesh finalization') + + # Create the soma material and assign it to the final object + self.create_soma_materials() + self.assign_material_to_single_object_mesh() + + # Adjust the origin of the mesh + self.adjust_origin_to_soma_center() + + # Create a new collection from the created objects of the mesh + nmv.utilities.create_collection_with_objects( + name='Mesh %s' % self.morphology.label, objects_list=self.neuron_meshes) + + ################################################################################################ + # @clean_mesh + ################################################################################################ + def clean_mesh(self): + """Cleans the resulting mesh.""" + + # Remove doubles + if self.options.mesh.remove_small_edges: + nmv.mesh.remove_doubles(mesh_object=self.neuron_mesh, distance=0.01) + + # Smooth vertices to remove any sphere-like shapes + nmv.mesh.smooth_object_vertices(self.neuron_mesh, level=1) + + ################################################################################################ + # @add_surface_roughness + ################################################################################################ + def add_surface_roughness(self): + """Adds surface noise to the mesh to make it looking realistic as seen by microscopes.""" + + if self.options.mesh.surface == nmv.enums.Meshing.Surface.ROUGH: + nmv.logger.info('Adding surface noise') + nmv.mesh.add_surface_noise_to_mesh_using_displacement_modifier( + mesh_object=self.neuron_mesh, strength=1.5, noise_scale=2, noise_depth=2) + + ################################################################################################ + # @reconstruct_mesh + ################################################################################################ + def reconstruct_mesh(self): + + nmv.logger.header('Building Mesh: VoxelizationBuilder') + + # Initialization + result, stats = self.PROFILE(self.initialize_builder) + self.profiling_statistics += stats + + result, stats = self.PROFILE(self.build_proxy_mesh) + self.profiling_statistics += stats + + # Voxelization modifier + result, stats = self.PROFILE(self.apply_voxelization_modifier) + self.profiling_statistics += stats + + # Adjust the origin of the mesh + result, stats = self.PROFILE(self.finalize_mesh) + self.profiling_statistics += stats + + # Surface roughness + result, stats = self.PROFILE(self.add_surface_roughness) + self.profiling_statistics += stats + + # Mesh cleaning + result, stats = self.PROFILE(self.clean_mesh) + self.profiling_statistics += stats + + # Report the statistics of this builder + self.report_builder_statistics() + + # This builder creates a single mesh object + return [self.neuron_mesh] diff --git a/nmv/consts/circuit_consts.py b/nmv/consts/circuit_consts.py new file mode 100644 index 000000000..8ac1f6b29 --- /dev/null +++ b/nmv/consts/circuit_consts.py @@ -0,0 +1,37 @@ +#################################################################################################### +# Copyright (c) 2016 _ 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender_based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +#################################################################################################### +# @Circuit +#################################################################################################### +class Circuit: + """Circuit constants""" + + ################################################################################################ + # @__init__ + ################################################################################################ + def __init__(self): + pass + + # A list of all the morphological types in the circuit. Initially, this variable is set to None + # until a circuit is loaded, and then it is filled with the correct values + MTYPES = None + + # A list of all the electro-physiological types in the circuit. Initially, this variable is set + # to None until a circuit is loaded, and then it is filled with the correct values + ETYPES = None diff --git a/nmv/consts/string_consts.py b/nmv/consts/string_consts.py new file mode 100644 index 000000000..1583444e6 --- /dev/null +++ b/nmv/consts/string_consts.py @@ -0,0 +1,50 @@ +###################################################################Pre################################ +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +#################################################################################################### +# @Strings +#################################################################################################### +class Strings: + """Strings constants""" + + ################################################################################################ + # @__init__ + ################################################################################################ + def __init__(self): + pass + + # The default message in the select file field + SELECT_FILE = "Select File" + + # The default message in the select directory field + SELECT_DIRECTORY = "Select Directory" + + # The default message in the select circuit file field + SELECT_CIRCUIT_FILE = "Select Circuit File" + + # The default message in the add target field + ADD_TARGET = "Add Target" + + # The default message in the add GID field + ADD_GID = "Add GID" + + # The default message in the pre-synaptic GID field + PRE_GID = "Add Pre-Synaptic GID" + + # The default message in the pre-synaptic GID field + POST_GID = "Add Post-Synaptic GID" diff --git a/nmv/interface/ui/ui_globals.py b/nmv/consts/synaptics_consts.py similarity index 81% rename from nmv/interface/ui/ui_globals.py rename to nmv/consts/synaptics_consts.py index 084c34bad..2346b8032 100644 --- a/nmv/interface/ui/ui_globals.py +++ b/nmv/consts/synaptics_consts.py @@ -1,5 +1,5 @@ -#################################################################################################### -# Copyright (c) 2016 - 2021, EPFL / Blue Brain Project +################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project # Marwan Abdellah # # This file is part of NeuroMorphoVis @@ -17,11 +17,10 @@ #################################################################################################### -# @Globals +# @Synaptics #################################################################################################### -class Globals: - """UI globals - """ +class Synaptics: + """Synaptics constants""" ################################################################################################ # @__init__ @@ -29,6 +28,8 @@ class Globals: def __init__(self): pass - # This flag is used to verify if NeuroMorphoVis is already initialized or not - nmv_initialized = False + # The default value of the synapse radius in microns + SYNAPSES_RADIUS = 2.0 + # The default percentage loaded of all the synapses + SYNAPSES_PERCENTAGE = 100 diff --git a/nmv/enums/morphology_enums.py b/nmv/enums/morphology_enums.py new file mode 100644 index 000000000..daecbc90e --- /dev/null +++ b/nmv/enums/morphology_enums.py @@ -0,0 +1,44 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +#################################################################################################### +# @Morphology +#################################################################################################### +class Morphology: + """Morphology enumerators""" + + ################################################################################################ + # @__init__ + ################################################################################################ + def __init__(self): + pass + + ################################################################################################ + # @Format + ################################################################################################ + class Format: + """Morphology format enumerators""" + + # SWC format + SWC = 'SWC_MORPHOLOGY_FORMAT' + + # H5 format + H5 = 'H5_MORPHOLOGY_FORMAT' + + # ASCII format + ASCII = 'ASCII_MORPHOLOGY_FORMAT' diff --git a/nmv/enums/synaptics_enums.py b/nmv/enums/synaptics_enums.py new file mode 100644 index 000000000..328414299 --- /dev/null +++ b/nmv/enums/synaptics_enums.py @@ -0,0 +1,124 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +#################################################################################################### +# @Synaptics +#################################################################################################### +class Synaptics: + """Synaptics enumerators""" + + ################################################################################################ + # @__init__ + ################################################################################################ + def __init__(self): + """Constructor""" + pass + + ################################################################################################ + # @UseCase + ################################################################################################ + class UseCase: + """Synaptics use cases enumerators""" + + ############################################################################################ + # @__init__ + ############################################################################################ + def __init__(self): + """Constructor""" + pass + + # Nothing is selected in the menu, display NOTHING in the panel then + NOT_SELECTED = 'SYNAPSE_VISUALIZATION_USE_CASE_NOT_SELECTED' + + # Visualize the afferent, or incoming, synapses + AFFERENT = 'VISUALIZE_AFFERENT_SYNAPSES' + + # Visualize the efferent, or outgoing, synapses + EFFERENT = 'VISUALIZE_EFFERENT_SYNAPSES' + + # Visualize both the afferent and efferent synapses at the same time + AFFERENT_AND_EFFERENT = 'VISUALIZE_AFFERENT_AND_EFFERENT_SYNAPSES' + + # Visualize the excitatory synapses only + EXCITATORY = 'VISUALIZE_EXCITATORY_SYNAPSES' + + # Visualize the inhibitory synapses only + INHIBITORY = 'VISUALIZE_INHIBITORY_SYNAPSES' + + # Visualize the excitatory and inhibitory synapses + EXCITATORY_AND_INHIBITORY = 'VISUALIZE_EXCITATORY_AND_INHIBITORY_SYNAPSES' + + # Visualize the synaptic pathways with a pre-synaptic cell + PATHWAY_PRE_SYNAPTIC = 'VISUALIZE_SYNAPTIC_PATHWAYS_PRE_SYNAPTIC' + + # Visualize the synaptic pathways with a post-synaptic cell + PATHWAY_POST_SYNAPTIC = 'VISUALIZE_SYNAPTIC_PATHWAYS_POST_SYNAPTIC' + + # Visualize the synapses on a single neuron with a specific color map and specific labels + SPECIFIC_COLOR_CODED_SET = 'VISUALIZE_SPECIFIC_COLOR_CODED_SET' + + # Visualize projection to a cell (afferent projection) + PROJECTION_TO_CELL = 'VISUALIZE_PROJECTIONS_TO_CELL' + + # Visualize targets + TARGETS = 'VISUALIZE_SYNAPTIC_TARGETS' + + ################################################################################################ + # @ColorCoding + ################################################################################################ + class ColorCoding: + """Color-coding enumerators""" + + ############################################################################################ + # @__init__ + ############################################################################################ + def __init__(self): + """Constructor""" + pass + + # Use a single color to label all the synapses + SINGLE_COLOR = 'SYNAPSE_SINGLE_COLOR' + + # Color-code the synapses based on the mtypes of the pre- or the post-synaptic neurons + MTYPE_COLOR_CODED = 'SYNAPSE_MTYPE_COLOR_CODED' + + # Color-code the synapses based on the etype of the pre- or the post-synaptic neurons + ETYPE_COLOR_CODED = 'SYNAPSE_ETYPE_COLOR_CODED' + + ################################################################################################ + # @WhichNeuron + ################################################################################################ + class WhichNeuron: + """Is it a individual neuron, or a pre-synaptic or a post-synaptic neuron""" + + ############################################################################################ + # @__init__ + ############################################################################################ + def __init__(self): + """Constructor""" + pass + + # This is an individual neuron + INDIVIDUAL = 'SYNAPTICS_INDIVIDUAL_NEURON' + + # Pre-synaptic neuron + PRE_SYNAPTIC = 'SYNAPTICS_PRE_NEURON' + + # Post-synaptic neuron + POST_SYNAPTIC = 'SYNAPTICS_POST_NEURON' + diff --git a/nmv/geometry/object/particle_system.py b/nmv/geometry/object/particle_system.py new file mode 100644 index 000000000..003c7520b --- /dev/null +++ b/nmv/geometry/object/particle_system.py @@ -0,0 +1,71 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.mesh +import nmv.shading + + +#################################################################################################### +# @create_particle_system_for_vertices +#################################################################################################### +def create_particle_system_for_vertices(mesh_object, + name='Particle System', + vertex_radius=1, + particle_quality=4, + display_method='RENDER', + material=None): + + base_object = nmv.mesh.create_ico_sphere(name='%s Base' % name, subdivisions=particle_quality) + nmv.mesh.shade_smooth_object(base_object) + nmv.shading.set_material_to_object(mesh_object=base_object, material_reference=material) + + # Construct a particle system modifier + particle_system_modifier = mesh_object.modifiers.new('%s PS' % name, 'PARTICLE_SYSTEM') + + # Create the particle system + particle_system = mesh_object.particle_systems[particle_system_modifier.name] + + # Use a particle count equivalent to the same number of vertices of the mesh object + particle_system.settings.count = len(mesh_object.data.vertices) + + # Timing + particle_system.settings.frame_start = 1 + particle_system.settings.frame_end = 1 + particle_system.settings.lifetime = 1 + + # Emit from the vertices + particle_system.settings.emit_from = 'VERT' + + # Don't use the random emissions to avoid display issues + particle_system.settings.use_emit_random = False + + # Adjust the size of the particle + particle_system.settings.particle_size = vertex_radius + particle_system.settings.display_size = vertex_radius + + # Display dots for the performance + particle_system.settings.display_method = display_method # 'DOT' + + # Render objects since we cannot render Halos in the current version of Blender + particle_system.settings.render_type = 'OBJECT' + + # Create a base object + particle_system.settings.instance_object = base_object + + # Return a reference to the particle system + return particle_system diff --git a/scripts/vasculature/__init__.py b/nmv/interface/common/__init__.py similarity index 82% rename from scripts/vasculature/__init__.py rename to nmv/interface/common/__init__.py index bbd21ba8e..01248e6c9 100644 --- a/scripts/vasculature/__init__.py +++ b/nmv/interface/common/__init__.py @@ -1,5 +1,5 @@ #################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project # Marwan Abdellah # # This file is part of NeuroMorphoVis @@ -15,8 +15,6 @@ # If not, see . #################################################################################################### -from .vasculature_sample import * -from .vasculature_section import * -from .vasculature_loader import * -from .vasculature_skeletonizer import * - +from .file_ops import * +from .rendering_ops import * +from .suffix_ops import * diff --git a/nmv/interface/common/file_ops.py b/nmv/interface/common/file_ops.py new file mode 100644 index 000000000..2016f5896 --- /dev/null +++ b/nmv/interface/common/file_ops.py @@ -0,0 +1,42 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +# Internal imports +import nmv.consts + + +#################################################################################################### +# @verify_output_directory +#################################################################################################### +def verify_output_directory(options, + panel=None): + + # Ensure that there is a valid directory where the images will be written to + if options.io.output_directory is None: + if panel is not None: + panel.report({'ERROR'}, nmv.consts.Messages.PATH_NOT_SET) + return False + + if not nmv.file.ops.path_exists(options.io.output_directory): + if panel is not None: + panel.report({'ERROR'}, nmv.consts.Messages.INVALID_OUTPUT_PATH) + return False + + # Otherwise, the output directory is valid + return True + diff --git a/nmv/interface/common/rendering_ops.py b/nmv/interface/common/rendering_ops.py new file mode 100644 index 000000000..5e004f6c8 --- /dev/null +++ b/nmv/interface/common/rendering_ops.py @@ -0,0 +1,207 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.interface +import nmv.skeleton +import nmv.bbox +import nmv.rendering +import nmv.scene +import nmv.geometry +import nmv.interface + + +#################################################################################################### +# @render_dendrogram +#################################################################################################### +def render_dendrogram(options): + """Creates a rendering of the dendrogram. + + :param options: + NeuroMorphoVis options. + """ + + # Compute the bounding box of the dendrogram (that is a morphology) and stretch it + bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves() + delta = bounding_box.get_largest_dimension() * 0.05 + bounding_box.extend_bbox(delta_x=1.5 * delta, delta_y=delta) + + # Render the dendrogram image, and always use the FRONT view + nmv.rendering.render( + bounding_box=bounding_box, + camera_view=nmv.enums.Camera.View.FRONT, + image_resolution=options.rendering.frame_resolution, + image_name='%s%s' % (options.morphology.label, nmv.consts.Suffix.DENDROGRAM), + image_format=options.rendering.image_format, + image_directory=options.io.images_directory, + keep_camera_in_scene=True) + + +#################################################################################################### +# @render_morphology_relevant_image +#################################################################################################### +def render_morphology_relevant_image(options, + morphology, + camera_view, + image_suffix, + panel=None): + + # Profile the rendering + start_time = time.time() + + # Verify the output directory + nmv.interface.verify_output_directory(options=options, panel=panel) + + # Create the images directory if it does not exist + if not nmv.file.ops.path_exists(options.io.images_directory): + nmv.file.ops.clean_and_create_directory(options.io.images_directory) + + # Report the process starting in the UI + if panel is not None: + panel.report({'INFO'}, 'Rendering morphology ... Wait') + + # If this is a dendrogram rendering, handle it in a very specific way + if options.morphology.reconstruction_method == nmv.enums.Skeleton.Method.DENDROGRAM: + render_dendrogram(options=options) + else: + + # Bounding box computation + if options.rendering.rendering_view == nmv.enums.Rendering.View.CLOSEUP: + bounding_box = nmv.bbox.compute_unified_extent_bounding_box( + extent=options.rendering.close_up_dimensions) + elif options.rendering.rendering_view == nmv.enums.Rendering.View.MID_SHOT: + bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves_and_meshes() + else: + bounding_box = nmv.skeleton.compute_full_morphology_bounding_box(morphology=morphology) + + # Draw the morphology scale bar + scale_bar = None + if options.rendering.render_scale_bar: + scale_bar = nmv.interface.draw_scale_bar( + bounding_box=bounding_box, + material_type=options.shading.morphology_material, + view=camera_view) + + # Resolution basis + if options.rendering.resolution_basis == nmv.enums.Rendering.Resolution.FIXED: + nmv.rendering.render( + bounding_box=bounding_box, + camera_view=camera_view, + image_resolution=options.rendering.frame_resolution, + image_name='%s%s' % (morphology.label, image_suffix), + image_format=options.rendering.image_format, + image_directory=options.io.images_directory, + keep_camera_in_scene=False) + else: + nmv.rendering.render_to_scale( + bounding_box=bounding_box, + camera_view=camera_view, + image_scale_factor=options.rendering.resolution_scale_factor, + image_name='%s%s' % (options.morphology.label, image_suffix), + image_format=options.rendering.image_format, + image_directory=options.io.images_directory, + keep_camera_in_scene=False) + + # Delete the morphology scale bar, if rendered + if scale_bar is not None: + nmv.scene.delete_object_in_scene(scene_object=scale_bar) + + nmv.logger.statistics('Image rendered in [%f] seconds' % (time.time() - start_time)) + + # Report the process termination in the UI + if panel is not None: + panel.report({'INFO'}, 'Rendering Done') + + +#################################################################################################### +# @render_meshes_relevant_image +#################################################################################################### +def render_meshes_relevant_image(options, + morphology, + camera_view, + image_suffix, + panel=None): + + # Profile the rendering + start_time = time.time() + + # Verify the output directory + nmv.interface.verify_output_directory(options=options, panel=panel) + + # Create the images directory if it does not exist + if not nmv.file.ops.path_exists(options.io.images_directory): + nmv.file.ops.clean_and_create_directory(options.io.images_directory) + + # Report the process starting in the UI + if panel is not None: + panel.report({'INFO'}, 'Rendering mesh ... Wait') + + # Bounding box computation + if options.rendering.rendering_view == nmv.enums.Rendering.View.CLOSEUP: + bounding_box = nmv.bbox.compute_unified_extent_bounding_box( + extent=options.rendering.close_up_dimensions) + elif options.rendering.rendering_view == nmv.enums.Rendering.View.MID_SHOT: + bounding_box = nmv.bbox.compute_scene_bounding_box_for_meshes() + else: + bounding_box = nmv.skeleton.compute_full_morphology_bounding_box(morphology=morphology) + + # Draw the morphology scale bar + scale_bar = None + if options.rendering.render_scale_bar: + scale_bar = nmv.interface.draw_scale_bar( + bounding_box=bounding_box, + material_type=options.shading.mesh_material, + view=camera_view) + + # Resolution basis + if options.rendering.resolution_basis == nmv.enums.Rendering.Resolution.FIXED: + nmv.rendering.render( + bounding_box=bounding_box, + camera_view=camera_view, + image_resolution=options.rendering.frame_resolution, + image_name='%s%s' % (morphology.label, image_suffix), + image_format=options.rendering.image_format, + image_directory=options.io.images_directory, + keep_camera_in_scene=False) + else: + nmv.rendering.render_to_scale( + bounding_box=bounding_box, + camera_view=camera_view, + image_scale_factor=options.rendering.resolution_scale_factor, + image_name='%s%s' % (options.morphology.label, image_suffix), + image_format=options.rendering.image_format, + image_directory=options.io.images_directory, + keep_camera_in_scene=False) + + # Delete the morphology scale bar, if rendered + if scale_bar is not None: + nmv.scene.delete_object_in_scene(scene_object=scale_bar) + + nmv.logger.statistics('Image rendered in [%f] seconds' % (time.time() - start_time)) + + # Report the process termination in the UI + if panel is not None: + panel.report({'INFO'}, 'Rendering Done') + + + + diff --git a/nmv/interface/common/suffix_ops.py b/nmv/interface/common/suffix_ops.py new file mode 100644 index 000000000..4266ed029 --- /dev/null +++ b/nmv/interface/common/suffix_ops.py @@ -0,0 +1,131 @@ +#################################################################################################### +# Copyright (c) 2019 - 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.enums +import nmv.consts + + +#################################################################################################### +# @get_morphology_image_suffixes_from_view +#################################################################################################### +def get_morphology_image_suffixes_from_view(camera_view): + """Gets the suffix that will be appended to the morphology image from the camera view. + The result will be in a list to allow rendering multiple views at the same moment even if it + contains a single element. + + :param camera_view: + The camera view. + :return + Image suffix list. + """ + + # Front + if camera_view == nmv.enums.Camera.View.FRONT: + return [nmv.consts.Suffix.MORPHOLOGY_FRONT] + + # Side + elif camera_view == nmv.enums.Camera.View.SIDE: + return [nmv.consts.Suffix.MORPHOLOGY_SIDE] + + # Top + elif camera_view == nmv.enums.Camera.View.TOP: + return [nmv.consts.Suffix.MORPHOLOGY_TOP] + + # All views + elif camera_view == nmv.enums.Camera.View.ALL_VIEWS: + return [nmv.consts.Suffix.MORPHOLOGY_FRONT, + nmv.consts.Suffix.MORPHOLOGY_SIDE, + nmv.consts.Suffix.MORPHOLOGY_TOP] + + # By default, render the front view + else: + return [nmv.consts.Suffix.MORPHOLOGY_FRONT] + + +#################################################################################################### +# @get_mesh_image_suffixes_from_view +#################################################################################################### +def get_mesh_image_suffixes_from_view(camera_view): + """Gets the suffix that will be appended to the mesh image from the camera view. + The result will be in a list to allow rendering multiple views at the same moment even if it + contains a single element. + + :param camera_view: + The camera view. + :return + Image suffix list. + """ + + # Front + if camera_view == nmv.enums.Camera.View.FRONT: + return [nmv.consts.Suffix.MESH_FRONT] + + # Side + elif camera_view == nmv.enums.Camera.View.SIDE: + return [nmv.consts.Suffix.MESH_SIDE] + + # Top + elif camera_view == nmv.enums.Camera.View.TOP: + return [nmv.consts.Suffix.MESH_TOP] + + # All views + elif camera_view == nmv.enums.Camera.View.ALL_VIEWS: + return [nmv.consts.Suffix.MESH_FRONT, + nmv.consts.Suffix.MESH_SIDE, + nmv.consts.Suffix.MESH_TOP] + + # By default, render the front view + else: + return [nmv.consts.Suffix.MESH_FRONT] + + +#################################################################################################### +# @get_soma_image_suffixes_from_view +#################################################################################################### +def get_soma_image_suffixes_from_view(camera_view): + """Gets the suffix that will be appended to the soma image from the camera view. + The result will be in a list to allow rendering multiple views at the same moment even if it + contains a single element. + + :param camera_view: + The camera view. + :return + Image suffix list. + """ + + # Front + if camera_view == nmv.enums.Camera.View.FRONT: + return [nmv.consts.Suffix.SOMA_FRONT] + + # Side + elif camera_view == nmv.enums.Camera.View.SIDE: + return [nmv.consts.Suffix.SOMA_SIDE] + + # Top + elif camera_view == nmv.enums.Camera.View.TOP: + return [nmv.consts.Suffix.SOMA_TOP] + + # All views + elif camera_view == nmv.enums.Camera.View.ALL_VIEWS: + return [nmv.consts.Suffix.SOMA_FRONT, + nmv.consts.Suffix.SOMA_SIDE, + nmv.consts.Suffix.SOMA_TOP] + + # By default, render the front view + else: + return [nmv.consts.Suffix.SOMA_FRONT] diff --git a/nmv/interface/ui/about/about_panel.py b/nmv/interface/ui/about/about_panel.py deleted file mode 100644 index 6cb209e6a..000000000 --- a/nmv/interface/ui/about/about_panel.py +++ /dev/null @@ -1,243 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# System imports -import sys -import os -import subprocess - -# Blender imports -import bpy -import bpy.utils.previews - -# Internal imports -import nmv.enums -import nmv.interface -import nmv.utilities - -nmv_icons = None - - -#################################################################################################### -# @IOPanel -#################################################################################################### -class AboutPanel(bpy.types.Panel): - """NMV About Us panel""" - - ################################################################################################ - # Panel parameters - ################################################################################################ - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' if nmv.utilities.is_blender_280() else 'TOOLS' - bl_idname = "OBJECT_PT_NMV_About" - bl_label = 'About' - bl_category = 'NeuroMorphoVis' - bl_options = {'DEFAULT_CLOSED'} - - ################################################################################################ - # @draw - ################################################################################################ - def draw(self, - context): - """Draw the panel. - - :param context: - Blender context. - """ - - # Get a reference to the panel layout - layout = self.layout - - # Credits - credits_column = layout.column() - credits_column.label(text='Copyrights (c)') - credits_column.label(text='Blue Brain Project (BBP)', icon='PMARKER') - credits_column.label(text='Ecole Polytechnique Federale de Lausanne (EPFL)', icon='PMARKER') - credits_column.separator() - - credits_column.label(text='License') - credits_column.label(text='GPL', icon='UNLOCKED') - credits_column.separator() - - credits_column.label(text='Main Author') - credits_column.label(text='Marwan Abdellah', icon='OUTLINER_DATA_ARMATURE') - credits_column.separator() - credits_column.label(text='Credits') - credits_column.label(text='Juan Hernando', icon='OUTLINER_DATA_ARMATURE') - credits_column.label(text='Caitlin Monney', icon='OUTLINER_DATA_ARMATURE') - credits_column.label(text='Nadir Roman', icon='OUTLINER_DATA_ARMATURE') - credits_column.label(text='Alessandro Foni', icon='OUTLINER_DATA_ARMATURE') - - credits_column.separator() - credits_column.label(text='Advisors') - credits_column.label(text='Ahmet Bilgili', icon='OUTLINER_DATA_ARMATURE') - credits_column.label(text='Stefan Eilemann', icon='OUTLINER_DATA_ARMATURE') - credits_column.label(text='Henry Markram', icon='OUTLINER_DATA_ARMATURE') - credits_column.label(text='Felix Schürmann', icon='OUTLINER_DATA_ARMATURE') - credits_column.separator() - credits_column.label(text='Acknowledgements') - credits_column.label(text='Pawel Podhajski', icon='OUTLINER_DATA_ARMATURE') - credits_column.label(text='Danny Dyer', icon='OUTLINER_DATA_ARMATURE') - credits_column.label(text='Alan Garner', icon='OUTLINER_DATA_ARMATURE') - credits_column.separator() - - # Version - version_column = layout.column() - version = nmv.utilities.get_nmv_version() - version_column.label(text='Version: %d.%d.%d' % (version[0], version[1], version[2])) - - update_button = layout.column() - update_button.operator('update.nmv', emboss=True, icon='NODETREE') - update_button.operator('open.github', emboss=True, icon='SCRIPT') - update_button.operator('open.wiki', emboss=True, icon='URL') - - -#################################################################################################### -# @OpenDocumentation -#################################################################################################### -class OpenDocumentation(bpy.types.Operator): - """Open the Github repository page""" - - # Operator parameters - bl_idname = "open.wiki" - bl_label = "Documentation" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Blender context - :return: - 'FINISHED' - """ - - import webbrowser - webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki') - return {'FINISHED'} - - -#################################################################################################### -# @OpenRepository -#################################################################################################### -class OpenRepository(bpy.types.Operator): - """Open the Github repository page""" - - # Operator parameters - bl_idname = "open.github" - bl_label = "Code" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Blender context - :return: - 'FINISHED' - """ - - import webbrowser - webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis') - return {'FINISHED'} - - -#################################################################################################### -# @UpdateNeuroMorphoVis -#################################################################################################### -class UpdateNeuroMorphoVis(bpy.types.Operator): - """Update NeuroMorphoVis""" - - # Operator parameters - bl_idname = "update.nmv" - bl_label = "Update" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Blender context - :return: - 'FINISHED' - """ - - # git must be installed to update the tool. - if not nmv.utilities.command_exists('git'): - self.report({'INFO'}, 'Cannot update NeuroMorphoVis! git must be installed.') - return {'FINISHED'} - - # TODO: Verify git or use wget or curl. - # TODO: Ignore this option for windows. - - # Get the current path - current_path = os.path.dirname(os.path.realpath(__file__)) - - # Go to the main directory and pull the latest master - os.chdir(current_path) - shell_command = 'git pull origin master' - nmv.logger.log('Updating NeuroMorphoVis ...') - subprocess.call(shell_command, shell=True) - - # Call blender and exit this one - shell_command = '%s &' % bpy.app.binary_path - nmv.logger.log('Restarting Blender with NeuroMorphoVis ...') - subprocess.call(shell_command, shell=True) - - # Exiting blender - exit(0) - - return {'FINISHED'} - - -#################################################################################################### -# @register_panel -#################################################################################################### -def register_panel(): - """Registers all the classes in this panel""" - - # Panel - bpy.utils.register_class(AboutPanel) - - # Buttons - bpy.utils.register_class(UpdateNeuroMorphoVis) - bpy.utils.register_class(OpenRepository) - bpy.utils.register_class(OpenDocumentation) - - -#################################################################################################### -# @unregister_panel -#################################################################################################### -def unregister_panel(): - """Un-registers all the classes in this panel""" - - # Panel - bpy.utils.unregister_class(AboutPanel) - - # Buttons - bpy.utils.unregister_class(UpdateNeuroMorphoVis) - bpy.utils.unregister_class(OpenRepository) - bpy.utils.unregister_class(OpenDocumentation) diff --git a/nmv/interface/ui/about/layout_props.py b/nmv/interface/ui/about/layout_props.py new file mode 100644 index 000000000..665cda9ad --- /dev/null +++ b/nmv/interface/ui/about/layout_props.py @@ -0,0 +1,72 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +# Internal imports +import nmv.utilities + + +#################################################################################################### +# @draw_layout_props +#################################################################################################### +def draw_layout_props(panel, scene, options): + + # Credits + credits_column = panel.layout.column() + credits_column.label(text='Copyrights (c)') + credits_column.label(text='Blue Brain Project (BBP)', icon='PMARKER') + credits_column.label(text='École Polytechnique Fédérale de Lausanne (EPFL)', icon='PMARKER') + credits_column.separator() + + credits_column.label(text='License') + credits_column.label(text='GPL 3.0', icon='UNLOCKED') + credits_column.separator() + + credits_column.label(text='Main Author') + credits_column.label(text='Marwan Abdellah', icon='USER') + credits_column.separator() + credits_column.label(text='Credits') + credits_column.label(text='Juan Hernando', icon='USER') + credits_column.label(text='Caitlin Monney', icon='USER') + credits_column.label(text='Nadir Roman', icon='USER') + credits_column.label(text='Alessandro Foni', icon='USER') + credits_column.label(text='Elvis Boci', icon='USER') + credits_column.separator() + + credits_column.label(text='Advisors') + credits_column.label(text='Ahmet Bilgili', icon='USER') + credits_column.label(text='Stefan Eilemann', icon='USER') + credits_column.label(text='Henry Markram', icon='USER') + credits_column.label(text='Felix Schürmann', icon='USER') + credits_column.separator() + credits_column.label(text='Acknowledgements') + credits_column.label(text='Pawel Podhajski', icon='USER') + credits_column.label(text='Danny Dyer', icon='USER') + credits_column.label(text='Alan Garner', icon='USER') + credits_column.separator() + + # Version + version_column = panel.layout.column() + version = nmv.utilities.get_nmv_version() + version_column.label(text='Version: %d.%d.%d' % (version[0], version[1], version[2])) + + buttons = panel.layout.column() + buttons.operator('nmv.update', emboss=True, icon='NODETREE') + buttons.separator() + buttons.operator('nmv.open_github', emboss=True, icon='SCRIPT') + buttons.separator() + buttons.operator('nmv.open_wiki', emboss=True, icon='URL') diff --git a/nmv/interface/ui/about/ops.py b/nmv/interface/ui/about/ops.py new file mode 100644 index 000000000..2353efa17 --- /dev/null +++ b/nmv/interface/ui/about/ops.py @@ -0,0 +1,117 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import os +import subprocess + +# Blender imports +import bpy + +# Internal imports +import nmv.utilities + + +#################################################################################################### +# @NMV_Update +#################################################################################################### +class NMV_Update(bpy.types.Operator): + """Update NeuroMorphoVis and pull the latest changes to the master branch on GitHub""" + + # Operator parameters + bl_idname = "nmv.update" + bl_label = "Update" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # git must be installed to update the tool. + if not nmv.utilities.command_exists('git'): + self.report({'INFO'}, 'Cannot update NeuroMorphoVis! git must be installed.') + return {'FINISHED'} + + if 'posix' in os.name: + nmv.logger('Updating NeuroMorphoVis on a unix-based OS') + else: + self.report({'ERROR'}, 'Cannot update NeuroMorphoVis on Windows! ' + 'Please download the latest version from the GitHub ' + 'Release page.') + return {'FINISHED'} + + # Get the current path + current_path = os.path.dirname(os.path.realpath(__file__)) + + # Go to the main directory and pull the latest master + os.chdir(current_path) + shell_command = 'git pull origin master' + nmv.logger.log('Updating NeuroMorphoVis ...') + subprocess.call(shell_command, shell=True) + + # Call blender and exit this one + shell_command = '%s &' % bpy.app.binary_path + nmv.logger.log('Restarting Blender with NeuroMorphoVis ...') + subprocess.call(shell_command, shell=True) + + # Exiting blender + exit(0) + + +#################################################################################################### +# @NMV_OpenDocumentation +#################################################################################################### +class NMV_OpenDocumentation(bpy.types.Operator): + """Open the documentation page on GitHub""" + + # Operator parameters + bl_idname = "nmv.open_wiki" + bl_label = "Documentation" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + try: + import webbrowser + webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki') + return {'FINISHED'} + except ImportError: + self.report({'ERROR'}, 'The package webbrowser must be installed!') + return {'FINISHED'} + + +#################################################################################################### +# @NMV_OpenRepository +#################################################################################################### +class NMV_OpenRepository(bpy.types.Operator): + """Open the GitHub repository page""" + + # Operator parameters + bl_idname = "nmv.open_github" + bl_label = "Code" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + import webbrowser + webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis') + return {'FINISHED'} + diff --git a/nmv/interface/ui/about/panel.py b/nmv/interface/ui/about/panel.py new file mode 100644 index 000000000..1ea036001 --- /dev/null +++ b/nmv/interface/ui/about/panel.py @@ -0,0 +1,47 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.utilities +from .layout_props import draw_layout_props + + +#################################################################################################### +# @NMV_AboutPanel +#################################################################################################### +class NMV_AboutPanel(bpy.types.Panel): + """NMV About panel""" + + ################################################################################################ + # Panel parameters + ################################################################################################ + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_idname = "OBJECT_PT_NMV_About" + bl_label = 'About' + bl_category = 'NeuroMorphoVis' + bl_options = {'DEFAULT_CLOSED'} + + ################################################################################################ + # @draw + ################################################################################################ + def draw(self, context): + + draw_layout_props(panel=self, scene=context.scene, options=nmv.interface.ui_options) diff --git a/nmv/interface/ui/about/registration.py b/nmv/interface/ui/about/registration.py new file mode 100644 index 000000000..0dd4af7b9 --- /dev/null +++ b/nmv/interface/ui/about/registration.py @@ -0,0 +1,60 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @register_panel +#################################################################################################### +def register_panel(): + """Registers all the classes in this panel""" + + from .panel import NMV_AboutPanel + from .ops import NMV_Update + from .ops import NMV_OpenDocumentation + from .ops import NMV_OpenRepository + + # Panel + bpy.utils.register_class(NMV_AboutPanel) + + # Buttons + bpy.utils.register_class(NMV_Update) + bpy.utils.register_class(NMV_OpenRepository) + bpy.utils.register_class(NMV_OpenDocumentation) + + +#################################################################################################### +# @unregister_panel +#################################################################################################### +def unregister_panel(): + """Un-registers all the classes in this panel""" + + from .panel import NMV_AboutPanel + from .panel import NMV_AboutPanel + from .ops import NMV_Update + from .ops import NMV_OpenDocumentation + from .ops import NMV_OpenRepository + + # Panel + bpy.utils.unregister_class(NMV_AboutPanel) + + # Buttons + bpy.utils.unregister_class(NMV_Update) + bpy.utils.unregister_class(NMV_OpenRepository) + bpy.utils.unregister_class(NMV_OpenDocumentation) diff --git a/nmv/interface/ui/analysis/analysis_panel_ops.py b/nmv/interface/ui/analysis/analysis_panel_ops.py deleted file mode 100644 index f65bda897..000000000 --- a/nmv/interface/ui/analysis/analysis_panel_ops.py +++ /dev/null @@ -1,428 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# System imports -import copy - -# Blender imports -import bpy -from bpy.props import BoolProperty - -# Internal imports -import nmv.analysis -import nmv.builders -import nmv.scene -import nmv.enums -import nmv.consts - - -#################################################################################################### -# @get_arbor_label_with_spaces_from_prefix -#################################################################################################### -def get_label_from_prefix(prefix): - """Gets a nice label for the UI of a component from a given prefix. - - Examples: - * Prefix: 'ApicalDendrite' -> Label: 'Apical Dendrite' - * Prefix: 'BasalDendrite0' -> Label: 'Basal Dendrite 0' - - :param prefix: - Component prefix. - :return: - Component label that can be used to tag a UI element. - """ - - if 'ApicalDendrite' in prefix: - return 'Apical Dendrite' - elif 'BasalDendrite' in prefix: - i = prefix.split('BasalDendrite')[1] - return 'Basal Dendrite %s' % i - elif 'Axon' in prefix: - return 'Axon' - elif 'Morphology' in prefix: - return 'Morphology' - else: - return 'ERROR' - - -#################################################################################################### -# @register_group_checkbox -#################################################################################################### -def register_group_checkbox(prefix, - description, - label=''): - """For each arbor in the morphology, there will be a checkbox to show and hide the analysis - data group. - - This feature is added to reduce any clutter if the number of analysis entries are huge. - Note that the morphology group will be checked by default, in contrast to the arbors. - - :param prefix: - The prefix 'in string format' that is used to tag or identify the arbor. - :param label: - The label of the arbor. - :param description: - The tooltip description of the checkbox. - """ - - # By default show the morphology analysis group (set its checkbox) - if 'Morphology' in prefix: - setattr(bpy.types.Scene, '%s' % prefix, - BoolProperty(name='Morphology', description=description, default=True)) - - # This one is for the soma - elif 'Soma' in prefix: - setattr(bpy.types.Scene, '%s' % prefix, - BoolProperty(name='Soma', description=description, default=False)) - - # By default hide the arbors analysis groups (unset their checkboxes) - else: - setattr(bpy.types.Scene, '%s' % prefix, - BoolProperty(name=label, description=description, - default=False)) - - -#################################################################################################### -# @register_analysis_groups -#################################################################################################### -def register_analysis_groups(morphology): - """Registers the analysis groups of the morphology. - - :param morphology: - Loaded morphology. - """ - - # Register the checkbox of the 'Morphology' group - register_group_checkbox(prefix='Morphology', - description='Show the analysis data of the entire morphology') - - # Register the checkbox of the 'Soma' group - register_group_checkbox(prefix='Soma', description='Show the analysis data of the soma') - - # Register the group checkbox of the 'Apical Dendrites', if exist - if morphology.has_apical_dendrites(): - for arbor in morphology.apical_dendrites: - register_group_checkbox( - prefix=arbor.tag, - description='Show the analysis data of %s' % arbor.label, - label=arbor.label) - - # Register the group checkboxes of the Basal Dendrites, if exist - if morphology.has_basal_dendrites(): - for arbor in morphology.basal_dendrites: - register_group_checkbox( - prefix=arbor.tag, - description='Show the analysis data of %s' % arbor.label, - label=arbor.label) - - # Register the group checkboxes of the Axons, if exist - if morphology.has_axons(): - for arbor in morphology.axons: - register_group_checkbox( - prefix=arbor.tag, - description='Show the analysis data of %s' % arbor.label, - label=arbor.label) - - -#################################################################################################### -# @add_analysis_group_to_panel -#################################################################################################### -def add_analysis_group_to_panel(prefix, - layout, - context): - """Adds the results of analysis of each arbor to the UI. - - :param prefix: - The prefix 'in string format' that is used to tag or identify the arbor. - :param layout: - UI panel layout. - :param context: - Blender context. - """ - - # Create a column outline in the panel - outline = layout.column() - - # Add the checkbox that was registered before to show/hide the group @register_group_checkbox - outline.prop(context.scene, prefix) - - # If the checkbox is checked - if getattr(context.scene, prefix): - - # Create a sub-column that aligns the analysis data from the original outline - analysis_area = outline.column(align=True) - - # In case of showing the analysis results of the entire morphology, add the - # results of the global analysis before the common ones - if 'Morphology' in prefix: - for item in nmv.analysis.ui_global_analysis_items: - analysis_area.prop(context.scene, '%s' % item.variable) - - # Update the analysis area with all the filters, that are common - for item in nmv.analysis.ui_per_arbor_analysis_items: - analysis_area.prop(context.scene, '%s%s' % (prefix, item.variable)) - - # Disable editing the analysis area - analysis_area.enabled = False - - -#################################################################################################### -# @add_analysis_groups_to_panel -#################################################################################################### -def add_analysis_groups_to_panel(morphology, - layout, - context): - """Adds the results of the morphology analysis to the UI. - - :param morphology: - Loaded morphology. - :param layout: - UI panel layout. - :param context: - Blender context. - """ - - # Bounding box information - add_bounding_box_information_to_panel(morphology=morphology, layout=layout, scene=context.scene) - - # Morphology - add_analysis_group_to_panel(prefix='Morphology', layout=layout, context=context) - - # Add the analysis results of the Apical Dendrites to the panel - if morphology.has_apical_dendrites(): - for arbor in morphology.apical_dendrites: - add_analysis_group_to_panel(prefix=arbor.tag, layout=layout, context=context) - - # Add the analysis results of the Basal Dendrites to the panel - if morphology.basal_dendrites is not None: - for arbor in morphology.basal_dendrites: - add_analysis_group_to_panel(prefix=arbor.tag, layout=layout, context=context) - - # Add the analysis results of the Axons to the panel - if morphology.has_axons(): - for arbor in morphology.axons: - add_analysis_group_to_panel(prefix=arbor.tag, layout=layout, context=context) - - -#################################################################################################### -# @add_bounding_box_information_to_panel -#################################################################################################### -def add_bounding_box_information_to_panel(morphology, - layout, - scene): - """Computes the bounding box information of the morphology and adds them to the analysis panel. - - :param morphology: - The input morphology being analyzed. - :param layout: - Panel layout. - :param scene: - Context scene. - """ - - # Draw the bounding box - bounding_box_p_row = layout.row() - bounding_box_p_min_row = bounding_box_p_row.column(align=True) - bounding_box_p_min_row.label(text='BBox PMin:') - bounding_box_p_min_row.prop(scene, 'NMV_BBoxPMinX') - bounding_box_p_min_row.prop(scene, 'NMV_BBoxPMinY') - bounding_box_p_min_row.prop(scene, 'NMV_BBoxPMinZ') - bounding_box_p_min_row.enabled = False - - bounding_box_p_max_row = bounding_box_p_row.column(align=True) - bounding_box_p_max_row.label(text='BBox PMax:') - bounding_box_p_max_row.prop(scene, 'NMV_BBoxPMaxX') - bounding_box_p_max_row.prop(scene, 'NMV_BBoxPMaxY') - bounding_box_p_max_row.prop(scene, 'NMV_BBoxPMaxZ') - bounding_box_p_max_row.enabled = False - - bounding_box_data_row = layout.row() - bounding_box_center_row = bounding_box_data_row.column(align=True) - bounding_box_center_row.label(text='BBox Center:') - bounding_box_center_row.prop(scene, 'NMV_BBoxCenterX') - bounding_box_center_row.prop(scene, 'NMV_BBoxCenterY') - bounding_box_center_row.prop(scene, 'NMV_BBoxCenterZ') - bounding_box_center_row.enabled = False - - bounding_box_bounds_row = bounding_box_data_row.column(align=True) - bounding_box_bounds_row.label(text='BBox Bounds:') - bounding_box_bounds_row.prop(scene, 'NMV_BoundsX') - bounding_box_bounds_row.prop(scene, 'NMV_BoundsY') - bounding_box_bounds_row.prop(scene, 'NMV_BoundsZ') - bounding_box_bounds_row.enabled = False - - -#################################################################################################### -# @analyze_bounding_box -#################################################################################################### -def analyze_bounding_box(morphology, - scene): - """Analyzes the bounding nox of the morphology and copies the results to the context variables. - - :param morphology: - Given morphology to be analyzed. - :param scene: - Context scene. - """ - - # Computes the bounding box to double confirm the results - morphology.compute_bounding_box() - - # Copy the values to the context variables - scene.NMV_BBoxPMinX = morphology.bounding_box.p_min[0] - scene.NMV_BBoxPMinY = morphology.bounding_box.p_min[1] - scene.NMV_BBoxPMinZ = morphology.bounding_box.p_min[2] - scene.NMV_BBoxPMaxX = morphology.bounding_box.p_max[0] - scene.NMV_BBoxPMaxY = morphology.bounding_box.p_max[1] - scene.NMV_BBoxPMaxZ = morphology.bounding_box.p_max[2] - scene.NMV_BBoxCenterX = morphology.bounding_box.center[0] - scene.NMV_BBoxCenterY = morphology.bounding_box.center[1] - scene.NMV_BBoxCenterZ = morphology.bounding_box.center[2] - scene.NMV_BoundsX = morphology.bounding_box.bounds[0] - scene.NMV_BoundsY = morphology.bounding_box.bounds[1] - scene.NMV_BoundsZ = morphology.bounding_box.bounds[2] - - -#################################################################################################### -# @analyze_morphology -#################################################################################################### -def analyze_morphology(morphology, - context=None): - """Registers the different analysis components and then analyze the morphology. - - :param morphology: - A given morphology to analyse. - :param context: - A bpy.context. - :return: - True if the morphology is analyzed, and False if not. - """ - - try: - # Register the different analysis groups - register_analysis_groups(morphology=morphology) - - # Register the global morphology variables to be able to show and update them on the UI - for item in nmv.analysis.ui_global_analysis_items: - item.register_global_analysis_variables(morphology=morphology) - - # Register the per-arbor variables to be able to show and update them on the UI - for item in nmv.analysis.ui_per_arbor_analysis_items: - item.register_per_arbor_analysis_variables(morphology=morphology) - - # Apply the global analysis filters and update the results - for item in nmv.analysis.ui_global_analysis_items: - item.apply_global_analysis_kernel(morphology=morphology, context=context) - - # Apply the per-arbor analysis filters and update the results - for item in nmv.analysis.ui_per_arbor_analysis_items: - item.apply_per_arbor_analysis_kernel(morphology=morphology, context=context) - - # Analyze the bounding box information - if context is not None: - analyze_bounding_box(morphology=morphology, scene=context.scene) - - # Morphology is analyzed - return True - - except ValueError: - - # Morphology could not be analyzed - return False - - -#################################################################################################### -# @sketch_morphology_skeleton_guide -#################################################################################################### -def sketch_morphology_skeleton_guide(morphology, - options): - """Sketches the morphology skeleton in a very raw or basic format to correlate the analysis - results with it. - - :param morphology: - Morphology skeleton. - :param options: - Instance of NMV options, but it will be modified here to account for the changes we must do. - """ - - # Set the morphology options to the default after they have been already initialized - options.morphology.set_default() - - # Clear the scene - nmv.scene.clear_scene() - - # Create a skeletonizer object to build the morphology skeleton - options_clone = copy.deepcopy(options) - options_clone.morphology.branching = nmv.enums.Skeleton.Branching.RADII - builder = nmv.builders.ConnectedSectionsBuilder(morphology, options_clone) - - # Draw the morphology skeleton and return a list of all the reconstructed objects - nmv.interface.ui_reconstructed_skeleton = builder.draw_morphology_skeleton() - - -#################################################################################################### -# @export_analysis_results -#################################################################################################### -def export_analysis_results(morphology, - options): - """Export the analysis results into a file. - - :param morphology: - The morphology that is analysed. - :param options: - NMV options. - """ - - # Create a specific directory per morphology - morphology_analysis_directory = '%s/%s' % (options.io.analysis_directory, morphology.label) - if not nmv.file.ops.path_exists(morphology_analysis_directory): - nmv.file.ops.clean_and_create_directory(morphology_analysis_directory) - - # Analysis results - analysis_results_string = '*' * 80 + '\n' - analysis_results_string += 'WARNING: AUTO-GENERATED FILE FROM NEUROMORPHOVIS \n' - analysis_results_string += '*' * 80 + '\n' - analysis_results_string += '* Analysis results for the morphology [%s] \n\n' % morphology.label - - analysis_results_string += '- Contents \n' - analysis_results_string += '\t* Soma: ' + 'Found \n' \ - if morphology.soma is not None else 'Not Found \n' - - if morphology.has_apical_dendrites(): - analysis_results_string += '\t* Apical Dendrites: %d \n' % len(morphology.apical_dendrites) - else: - analysis_results_string += '\t* Apical Dendrites: 0 \n' - - if morphology.has_basal_dendrites(): - analysis_results_string += '\t* Basal Dendrites: %d \n' % len(morphology.basal_dendrites) - else: - analysis_results_string += '\t* Basal Dendrites: 0 \n' - - if morphology.has_axons(): - analysis_results_string += '\t* Axons: %d \n\n' % len(morphology.axons) - else: - analysis_results_string += '\t* Axons: 0 \n\n' - - # Register the morphology variables to be able to show and update them on the UI - for item in nmv.analysis.ui_per_arbor_analysis_items: - analysis_results_string += item.write_analysis_results_to_string(morphology=morphology) - - # Write the text to file - analysis_results_file = open('%s/%s.txt' % (morphology_analysis_directory, - nmv.consts.Analysis.ANALYSIS_FILE_NAME), 'w') - analysis_results_file.write(analysis_results_string) - analysis_results_file.close() diff --git a/nmv/interface/ui/analysis/layout_props.py b/nmv/interface/ui/analysis/layout_props.py new file mode 100644 index 000000000..eb23abfa7 --- /dev/null +++ b/nmv/interface/ui/analysis/layout_props.py @@ -0,0 +1,237 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import copy + +# Blender imports +import bpy +from bpy.props import BoolProperty + +# Internal imports +import nmv.analysis +import nmv.builders +import nmv.scene +import nmv.enums +import nmv.consts + + +#################################################################################################### +# @draw_morphology_label +#################################################################################################### +def draw_morphology_label(layout, morphology): + + row = layout.row() + row.label(text='Name: %s' % morphology.label) + + +#################################################################################################### +# @draw_morphology_file_format +#################################################################################################### +def draw_morphology_file_format(layout, morphology): + + row = layout.row() + row.label(text='Format: %s' % morphology.file_format) + + +#################################################################################################### +# @draw_load_morphology_for_analysis_message +#################################################################################################### +def draw_load_morphology_for_analysis_message(layout): + + row = layout.row() + row.label(text='Load Morphology to Get Analyzed!') + + +#################################################################################################### +# @draw_export_analysis_header +#################################################################################################### +def draw_export_analysis_header(layout): + + row = layout.row() + row.label(text='Export Analysis Results', icon='MESH_UVSPHERE') + + +#################################################################################################### +# @add_analysis_group_to_panel +#################################################################################################### +def add_analysis_group_to_panel(prefix, + layout, + scene): + """Adds the results of analysis of each arbor to the UI. + + :param prefix: + The prefix 'in string format' that is used to tag or identify the arbor. + :param layout: + UI panel layout. + :param scene: + Blender scene. + """ + + # Create a column outline in the panel + outline = layout.column() + + # Add the checkbox that was registered before to show/hide the group @register_group_checkbox + outline.prop(scene, prefix) + + # If the checkbox is checked + if getattr(scene, prefix): + + # Create a sub-column that aligns the analysis data from the original outline + analysis_area = outline.column() + + # In case of showing the analysis results of the entire morphology, add the + # results of the global analysis before the common ones + if 'Morphology' in prefix: + analysis_area.label(text='Soma') + soma_area = analysis_area.column(align=True) + for item in nmv.analysis.ui_soma_analysis_items: + soma_area.prop(scene, '%s' % item.variable) + + analysis_area.label(text='Arbors') + arbors_area = analysis_area.column(align=True) + for item in nmv.analysis.ui_global_analysis_items: + arbors_area.prop(scene, '%s' % item.variable) + + # Update the analysis area with all the filters, that are common + for item in nmv.analysis.ui_per_arbor_analysis_items: + analysis_area.prop(scene, '%s%s' % (prefix, item.variable)) + + # Disable editing the analysis area + analysis_area.enabled = False + + +#################################################################################################### +# @add_analysis_groups_to_panel +#################################################################################################### +def add_analysis_groups_to_panel(layout, + scene, + morphology): + """Adds the results of the morphology analysis to the UI. + + :param morphology: + Loaded morphology. + :param layout: + UI panel layout. + :param scene: + Blender scene. + """ + + # Bounding box information + add_bounding_box_information_to_panel(morphology=morphology, layout=layout, scene=scene) + + # Morphology + add_analysis_group_to_panel(prefix='Morphology', layout=layout, scene=scene) + + # Add the analysis results of the Apical Dendrites to the panel + if morphology.has_apical_dendrites(): + for arbor in morphology.apical_dendrites: + add_analysis_group_to_panel(prefix=arbor.tag, layout=layout, scene=scene) + + # Add the analysis results of the Basal Dendrites to the panel + if morphology.basal_dendrites is not None: + for arbor in morphology.basal_dendrites: + add_analysis_group_to_panel(prefix=arbor.tag, layout=layout, scene=scene) + + # Add the analysis results of the Axons to the panel + if morphology.has_axons(): + for arbor in morphology.axons: + add_analysis_group_to_panel(prefix=arbor.tag, layout=layout, scene=scene) + + +#################################################################################################### +# @add_bounding_box_information_to_panel +#################################################################################################### +def add_bounding_box_information_to_panel(morphology, + layout, + scene): + """Computes the bounding box information of the morphology and adds them to the analysis panel. + + :param morphology: + The input morphology being analyzed. + :param layout: + Panel layout. + :param scene: + Context scene. + """ + + # Draw the bounding box + bounding_box_p_row = layout.row() + bounding_box_p_min_row = bounding_box_p_row.column(align=True) + bounding_box_p_min_row.label(text='BBox PMin:') + bounding_box_p_min_row.prop(scene, 'NMV_BBoxPMinX') + bounding_box_p_min_row.prop(scene, 'NMV_BBoxPMinY') + bounding_box_p_min_row.prop(scene, 'NMV_BBoxPMinZ') + bounding_box_p_min_row.enabled = False + + bounding_box_p_max_row = bounding_box_p_row.column(align=True) + bounding_box_p_max_row.label(text='BBox PMax:') + bounding_box_p_max_row.prop(scene, 'NMV_BBoxPMaxX') + bounding_box_p_max_row.prop(scene, 'NMV_BBoxPMaxY') + bounding_box_p_max_row.prop(scene, 'NMV_BBoxPMaxZ') + bounding_box_p_max_row.enabled = False + + bounding_box_data_row = layout.row() + bounding_box_center_row = bounding_box_data_row.column(align=True) + bounding_box_center_row.label(text='BBox Center:') + bounding_box_center_row.prop(scene, 'NMV_BBoxCenterX') + bounding_box_center_row.prop(scene, 'NMV_BBoxCenterY') + bounding_box_center_row.prop(scene, 'NMV_BBoxCenterZ') + bounding_box_center_row.enabled = False + + bounding_box_bounds_row = bounding_box_data_row.column(align=True) + bounding_box_bounds_row.label(text='BBox Bounds:') + bounding_box_bounds_row.prop(scene, 'NMV_BoundsX') + bounding_box_bounds_row.prop(scene, 'NMV_BoundsY') + bounding_box_bounds_row.prop(scene, 'NMV_BoundsZ') + bounding_box_bounds_row.enabled = False + + +#################################################################################################### +# @draw_layout_props +#################################################################################################### +def draw_layout_props(panel, scene, options, morphology): + + # The morphology must be loaded to the UI and analyzed to be able to draw the analysis + # components based on its arbors count + if morphology is not None: + + draw_morphology_label(layout=panel.layout, morphology=morphology) + + draw_morphology_file_format(layout=panel.layout, morphology=morphology) + + # If the morphology is analyzed, then add the results to the analysis panel + add_analysis_groups_to_panel(layout=panel.layout, scene=scene, morphology=morphology) + + draw_export_analysis_header(layout=panel.layout) + + export_analysis_row = panel.layout.row() + export_analysis_row.operator('nmv.export_analysis_results', icon='MESH_DATA') + + create_plots_row = panel.layout.row() + create_plots_row.operator('nmv.create_neuron_card', icon='MESH_DATA') + + if nmv.interface.ui_morphology_analyzed: + morphology_stats_row = panel.layout.row() + morphology_stats_row.label(text='Stats:', icon='RECOVER_LAST') + analysis_time_row = panel.layout.row() + analysis_time_row.prop(scene, 'NMV_MorphologyAnalysisTime') + analysis_time_row.enabled = False + + # Load a morphology file to get analyzed ! + else: + draw_load_morphology_for_analysis_message(layout=panel.layout) \ No newline at end of file diff --git a/nmv/interface/ui/analysis/analysis_panel.py b/nmv/interface/ui/analysis/ops_export.py similarity index 51% rename from nmv/interface/ui/analysis/analysis_panel.py rename to nmv/interface/ui/analysis/ops_export.py index 288382508..1da3e7dd3 100644 --- a/nmv/interface/ui/analysis/analysis_panel.py +++ b/nmv/interface/ui/analysis/ops_export.py @@ -1,5 +1,5 @@ #################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project # Marwan Abdellah # # This file is part of NeuroMorphoVis @@ -16,103 +16,23 @@ #################################################################################################### # System imports -import copy import time # Blender imports import bpy -import nmv.consts +# Internal imports import nmv.analysis -import nmv.builders +import nmv.consts import nmv.enums -import nmv.file import nmv.interface -import nmv.skeleton -import nmv.scene import nmv.utilities -from .analysis_panel_options import * - -# Is the morphology analyzed or not -is_morphology_analyzed = False - - -#################################################################################################### -# @AnalysisPanel -#################################################################################################### -class AnalysisPanel(bpy.types.Panel): - """Analysis panel""" - - ################################################################################################ - # Panel parameters - ################################################################################################ - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' if nmv.utilities.is_blender_280() else 'TOOLS' - bl_idname = "OBJECT_PT_NMV_Analysis" - bl_label = 'Morphology Analysis' - bl_category = 'NeuroMorphoVis' - bl_options = {'DEFAULT_CLOSED'} - - ################################################################################################ - # @draw - ################################################################################################ - def draw(self, - context): - """Draw the panel - - :param context: - Blender context - """ - - # Get a reference to the panel layout - layout = self.layout - - # The morphology must be loaded to the UI and analyzed to be able to draw the analysis - # components based on its arbors count - if nmv.interface.ui_morphology is not None: - - morphology_name_row = layout.row() - morphology_name_row.label(text='Name: %s' % nmv.interface.ui_morphology.label) - - morphology_format_row = layout.row() - morphology_format_row.label(text='Format: %s' % nmv.interface.ui_morphology.file_format) - - # If the morphology is analyzed, then add the results to the analysis panel - nmv.interface.add_analysis_groups_to_panel( - morphology=nmv.interface.ui_morphology, layout=layout, context=context) - - # Export analysis button - export_row = layout.row() - export_row.label(text='Export Analysis Results:', icon='MESH_UVSPHERE') - - export_analysis_row = layout.row() - export_analysis_row.operator('nmv.export_analysis_results', icon='MESH_DATA') - - create_plots_row = layout.row() - create_plots_row.operator('nmv.create_neuron_card', icon='MESH_DATA') - - global is_morphology_analyzed - if is_morphology_analyzed: - morphology_stats_row = layout.row() - morphology_stats_row.label(text='Stats:', icon='RECOVER_LAST') - analysis_time_row = layout.row() - analysis_time_row.prop(context.scene, 'NMV_MorphologyAnalysisTime') - analysis_time_row.enabled = False - - # Load a morphology file to get analyzed ! - else: - simple_message_row = layout.row() - simple_message_row.label(text='Load Morphology to Get Analyzed!',) - - # Enable or disable the layout - nmv.interface.enable_or_disable_layout(layout) - #################################################################################################### -# @cExportAnalysisResults +# @NMV_ExportAnalysisResults #################################################################################################### -class ExportAnalysisResults(bpy.types.Operator): +class NMV_ExportAnalysisResults(bpy.types.Operator): """Export the analysis results into a file""" # Operator parameters @@ -122,15 +42,7 @@ class ExportAnalysisResults(bpy.types.Operator): ################################################################################################ # @execute ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Blender context - :return: - 'FINISHED' - """ + def execute(self, context): # Ensure that there is a valid directory where the images will be written to if nmv.interface.ui_options.io.output_directory is None: @@ -157,9 +69,9 @@ def execute(self, #################################################################################################### -# @cExportAnalysisResults +# @NMV_CreateNeuronCard #################################################################################################### -class CreateNeuronCard(bpy.types.Operator): +class NMV_CreateNeuronCard(bpy.types.Operator): """Export the analysis results into a file""" # Operator parameters @@ -206,8 +118,8 @@ def execute(self, start_time = time.time() # Export the analysis results - nmv.interface.ui.export_analysis_results( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + #nmv.interface.ui.export_analysis_results( + # morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) # Analysis plots nmv.analysis.plot_analysis_results( @@ -215,40 +127,10 @@ def execute(self, # Morphology analyzed analysis_time = time.time() - global is_morphology_analyzed - is_morphology_analyzed = True + + nmv.interface.ui_morphology_analyzed = True context.scene.NMV_MorphologyAnalysisTime = analysis_time - start_time nmv.logger.info('Morphology skeleton analyzed in [%f] seconds' % context.scene.NMV_MorphologyAnalysisTime) - return {'FINISHED'} - - -#################################################################################################### -# @register_panel -#################################################################################################### -def register_panel(): - """Registers all the classes in this panel. - """ - - # Morphology analysis panel - bpy.utils.register_class(AnalysisPanel) - - # Export analysis button - bpy.utils.register_class(CreateNeuronCard) - bpy.utils.register_class(ExportAnalysisResults) - - -#################################################################################################### -# @unregister_panel -#################################################################################################### -def unregister_panel(): - """Un-registers all the classes in this panel. - """ - - # Morphology analysis panel - bpy.utils.unregister_class(AnalysisPanel) - - # Export analysis button - bpy.utils.unregister_class(CreateNeuronCard) - bpy.utils.unregister_class(ExportAnalysisResults) + return {'FINISHED'} \ No newline at end of file diff --git a/nmv/interface/ui/analysis/panel.py b/nmv/interface/ui/analysis/panel.py new file mode 100644 index 000000000..2d842479d --- /dev/null +++ b/nmv/interface/ui/analysis/panel.py @@ -0,0 +1,58 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import copy +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.interface +from .layout_props import draw_layout_props + + +#################################################################################################### +# @NMV_AnalysisPanel +#################################################################################################### +class NMV_AnalysisPanel(bpy.types.Panel): + """Analysis panel""" + + ################################################################################################ + # Panel parameters + ################################################################################################ + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_idname = "OBJECT_PT_NMV_Analysis" + bl_label = 'Morphology Analysis Tools' + bl_category = 'NeuroMorphoVis' + bl_options = {'DEFAULT_CLOSED'} + + ################################################################################################ + # @draw + ################################################################################################ + def draw(self, context): + + draw_layout_props(panel=self, scene=context.scene, + options=nmv.interface.ui_options, morphology=nmv.interface.ui_morphology) + + # Enable or disable the layout + nmv.interface.enable_or_disable_layout(self.layout) + + + diff --git a/nmv/interface/ui/analysis/panel_ops.py b/nmv/interface/ui/analysis/panel_ops.py new file mode 100644 index 000000000..2804b1507 --- /dev/null +++ b/nmv/interface/ui/analysis/panel_ops.py @@ -0,0 +1,199 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import copy + +# Internal imports +import nmv.analysis +import nmv.builders +import nmv.scene +import nmv.enums +import nmv.consts +import nmv.interface + + +#################################################################################################### +# @analyze_bounding_box +#################################################################################################### +def analyze_bounding_box(morphology, + scene): + """Analyzes the bounding nox of the morphology and copies the results to the context variables. + + :param morphology: + Given morphology to be analyzed. + :param scene: + Context scene. + """ + + # Computes the bounding box to double confirm the results + morphology.compute_bounding_box() + + # Copy the values to the context variables + scene.NMV_BBoxPMinX = morphology.bounding_box.p_min[0] + scene.NMV_BBoxPMinY = morphology.bounding_box.p_min[1] + scene.NMV_BBoxPMinZ = morphology.bounding_box.p_min[2] + scene.NMV_BBoxPMaxX = morphology.bounding_box.p_max[0] + scene.NMV_BBoxPMaxY = morphology.bounding_box.p_max[1] + scene.NMV_BBoxPMaxZ = morphology.bounding_box.p_max[2] + scene.NMV_BBoxCenterX = morphology.bounding_box.center[0] + scene.NMV_BBoxCenterY = morphology.bounding_box.center[1] + scene.NMV_BBoxCenterZ = morphology.bounding_box.center[2] + scene.NMV_BoundsX = morphology.bounding_box.bounds[0] + scene.NMV_BoundsY = morphology.bounding_box.bounds[1] + scene.NMV_BoundsZ = morphology.bounding_box.bounds[2] + + +#################################################################################################### +# @analyze_morphology +#################################################################################################### +def analyze_morphology(morphology, + context=None): + """Registers the different analysis components and then analyze the morphology. + + :param morphology: + A given morphology to analyse. + :param context: + A bpy.context. + :return: + True if the morphology is analyzed, and False if not. + """ + + from .registration import register_analysis_groups + + try: + # Register the different analysis groups + register_analysis_groups(morphology=morphology) + + # Register the global morphology variables to be able to show and update them on the UI + for item in nmv.analysis.ui_soma_analysis_items: + item.register_global_analysis_variables(morphology=morphology) + + # Register the global morphology variables to be able to show and update them on the UI + for item in nmv.analysis.ui_global_analysis_items: + item.register_global_analysis_variables(morphology=morphology) + + # Register the per-arbor variables to be able to show and update them on the UI + for item in nmv.analysis.ui_per_arbor_analysis_items: + item.register_per_arbor_analysis_variables(morphology=morphology) + + # Apply the global analysis filters and update the results + for item in nmv.analysis.ui_soma_analysis_items: + item.apply_global_analysis_kernel(morphology=morphology, context=context) + + # Apply the global analysis filters and update the results + for item in nmv.analysis.ui_global_analysis_items: + item.apply_global_analysis_kernel(morphology=morphology, context=context) + + # Apply the per-arbor analysis filters and update the results + for item in nmv.analysis.ui_per_arbor_analysis_items: + item.apply_per_arbor_analysis_kernel(morphology=morphology, context=context) + + # Analyze the bounding box information + if context is not None: + analyze_bounding_box(morphology=morphology, scene=context.scene) + + # Morphology is analyzed + return True + + except ValueError: + + # Morphology could not be analyzed + return False + + +#################################################################################################### +# @sketch_morphology_skeleton_guide +#################################################################################################### +def sketch_morphology_skeleton_guide(morphology, + options): + """Sketches the morphology skeleton in a very raw or basic format to correlate the analysis + results with it. + + :param morphology: + Morphology skeleton. + :param options: + Instance of NMV options, but it will be modified here to account for the changes we must do. + """ + + # Set the morphology options to the default after they have been already initialized + options.morphology.set_default() + + # Clear the scene + nmv.scene.clear_scene() + + # Create a skeletonizer object to build the morphology skeleton + options_clone = copy.deepcopy(options) + options_clone.morphology.branching = nmv.enums.Skeleton.Branching.RADII + builder = nmv.builders.ConnectedSectionsBuilder(morphology, options_clone) + + # Draw the morphology skeleton and return a list of all the reconstructed objects + nmv.interface.ui_reconstructed_skeleton = builder.draw_morphology_skeleton() + + +#################################################################################################### +# @export_analysis_results +#################################################################################################### +def export_analysis_results(morphology, + options): + """Export the analysis results into a file. + + :param morphology: + The morphology that is analysed. + :param options: + NMV options. + """ + + # Create a specific directory per morphology + morphology_analysis_directory = '%s/%s' % (options.io.analysis_directory, morphology.label) + if not nmv.file.ops.path_exists(morphology_analysis_directory): + nmv.file.ops.clean_and_create_directory(morphology_analysis_directory) + + # Analysis results + analysis_results_string = '*' * 80 + '\n' + analysis_results_string += 'WARNING: AUTO-GENERATED FILE FROM NEUROMORPHOVIS \n' + analysis_results_string += '*' * 80 + '\n' + analysis_results_string += '* Analysis results for the morphology [%s] \n\n' % morphology.label + + analysis_results_string += '- Contents \n' + analysis_results_string += '\t* Soma: ' + 'Found \n' \ + if morphology.soma is not None else 'Not Found \n' + + if morphology.has_apical_dendrites(): + analysis_results_string += '\t* Apical Dendrites: %d \n' % len(morphology.apical_dendrites) + else: + analysis_results_string += '\t* Apical Dendrites: 0 \n' + + if morphology.has_basal_dendrites(): + analysis_results_string += '\t* Basal Dendrites: %d \n' % len(morphology.basal_dendrites) + else: + analysis_results_string += '\t* Basal Dendrites: 0 \n' + + if morphology.has_axons(): + analysis_results_string += '\t* Axons: %d \n\n' % len(morphology.axons) + else: + analysis_results_string += '\t* Axons: 0 \n\n' + + # Register the morphology variables to be able to show and update them on the UI + for item in nmv.analysis.ui_per_arbor_analysis_items: + analysis_results_string += item.write_analysis_results_to_string(morphology=morphology) + + # Write the text to file + analysis_results_file = open('%s/%s.txt' % (morphology_analysis_directory, + nmv.consts.Analysis.ANALYSIS_FILE_NAME), 'w') + analysis_results_file.write(analysis_results_string) + analysis_results_file.close() diff --git a/nmv/interface/ui/analysis/analysis_panel_options.py b/nmv/interface/ui/analysis/panel_props.py similarity index 99% rename from nmv/interface/ui/analysis/analysis_panel_options.py rename to nmv/interface/ui/analysis/panel_props.py index 734360888..39f61d0dc 100644 --- a/nmv/interface/ui/analysis/analysis_panel_options.py +++ b/nmv/interface/ui/analysis/panel_props.py @@ -25,46 +25,57 @@ name="X", description="X-coordinate of PMin", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BBoxPMinY = bpy.props.FloatProperty( name="Y", description="Y-coordinate of PMin", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BBoxPMinZ = bpy.props.FloatProperty( name="Z", description="Z-coordinate of PMin", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BBoxPMaxX = bpy.props.FloatProperty( name="X", description="X-coordinate of PMax", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BBoxPMaxY = bpy.props.FloatProperty( name="Y", description="Y-coordinate of PMax", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BBoxPMaxZ = bpy.props.FloatProperty( name="Z", description="Z-coordinate of PMax", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BBoxCenterX = bpy.props.FloatProperty( name="X", description="X-coordinate of center of the morphology", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BBoxCenterY = bpy.props.FloatProperty( name="Y", description="Y-coordinate of center of the morphology", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BBoxCenterZ = bpy.props.FloatProperty( name="Z", description="Z-coordinate of center of the morphology", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BoundsX = bpy.props.FloatProperty( name="X", description="Morphology width", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BoundsY = bpy.props.FloatProperty( name="Y", description="Morphology height", min=-1e10, max=1e10, subtype='FACTOR') + bpy.types.Scene.NMV_BoundsZ = bpy.props.FloatProperty( name="Z", description="Morphology depth", diff --git a/nmv/interface/ui/analysis/registration.py b/nmv/interface/ui/analysis/registration.py new file mode 100644 index 000000000..f40cd468b --- /dev/null +++ b/nmv/interface/ui/analysis/registration.py @@ -0,0 +1,133 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @register_group_checkbox +#################################################################################################### +def register_group_checkbox(prefix, + description, + label=''): + """For each arbor in the morphology, there will be a checkbox to show and hide the analysis + data group. + + This feature is added to reduce any clutter if the number of analysis entries are huge. + Note that the morphology group will be checked by default, in contrast to the arbors. + + :param prefix: + The prefix 'in string format' that is used to tag or identify the arbor. + :param label: + The label of the arbor. + :param description: + The tooltip description of the checkbox. + """ + + # By default, show the morphology analysis group (set its checkbox) + if 'Morphology' in prefix: + setattr(bpy.types.Scene, '%s' % prefix, + bpy.props.BoolProperty(name='Morphology', description=description, default=True)) + + # Soma analysis + elif 'Soma' in prefix: + setattr(bpy.types.Scene, '%s' % prefix, + bpy.props.BoolProperty(name='Soma', description=description, default=False)) + + # By default, hide the arbors analysis groups (unset their checkboxes) + else: + setattr(bpy.types.Scene, '%s' % prefix, + bpy.props.BoolProperty(name=label, description=description, default=False)) + + +#################################################################################################### +# @register_analysis_groups +#################################################################################################### +def register_analysis_groups(morphology): + """Registers the analysis groups of the morphology. + + :param morphology: + Loaded morphology. + """ + + # Register the checkbox of the 'Morphology' group + register_group_checkbox(prefix='Morphology', + description='Show the analysis data of the entire morphology') + + # Register the checkbox of the 'Soma' group + register_group_checkbox(prefix='Soma', description='Show the analysis data of the soma') + + # Register the group checkbox of the 'Apical Dendrites', if exist + if morphology.has_apical_dendrites(): + for arbor in morphology.apical_dendrites: + register_group_checkbox( + prefix=arbor.tag, + description='Show the analysis data of %s' % arbor.label, + label=arbor.label) + + # Register the group checkboxes of the Basal Dendrites, if exist + if morphology.has_basal_dendrites(): + for arbor in morphology.basal_dendrites: + register_group_checkbox( + prefix=arbor.tag, + description='Show the analysis data of %s' % arbor.label, + label=arbor.label) + + # Register the group checkboxes of the Axons, if exist + if morphology.has_axons(): + for arbor in morphology.axons: + register_group_checkbox( + prefix=arbor.tag, + description='Show the analysis data of %s' % arbor.label, + label=arbor.label) + + +#################################################################################################### +# @register_panel +#################################################################################################### +def register_panel(): + """Registers all the classes in this panel""" + + from .panel import NMV_AnalysisPanel + from .ops_export import NMV_ExportAnalysisResults + from .ops_export import NMV_CreateNeuronCard + + # Morphology analysis panel + bpy.utils.register_class(NMV_AnalysisPanel) + + # Export analysis button + bpy.utils.register_class(NMV_CreateNeuronCard) + bpy.utils.register_class(NMV_ExportAnalysisResults) + + +#################################################################################################### +# @unregister_panel +#################################################################################################### +def unregister_panel(): + """Un-registers all the classes in this panel""" + + from .panel import NMV_AnalysisPanel + from .ops_export import NMV_ExportAnalysisResults + from .ops_export import NMV_CreateNeuronCard + + # Morphology analysis panel + bpy.utils.unregister_class(NMV_AnalysisPanel) + + # Export analysis button + bpy.utils.unregister_class(NMV_CreateNeuronCard) + bpy.utils.unregister_class(NMV_ExportAnalysisResults) diff --git a/nmv/interface/ui/common.py b/nmv/interface/ui/common.py deleted file mode 100644 index cb6fbb354..000000000 --- a/nmv/interface/ui/common.py +++ /dev/null @@ -1,619 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# System imports -import os -import time - -# Blender imports -import bpy - -# Internal imports -import nmv.consts -import nmv.enums -import nmv.interface -import nmv.skeleton -import nmv.bbox -import nmv.rendering -import nmv.scene -import nmv.geometry -import nmv.interface - -# Global variables to notify us if a new morphology has been loaded to the system or not -current_morphology_label = None -current_morphology_path = None -current_coordinate = None - - -#################################################################################################### -# @load_morphology -#################################################################################################### -def load_icons(): - """Loads the external icons. - """ - nmv.interface.ui_icons = bpy.utils.previews.new() - images_path = '%s/../../../data/images' % os.path.dirname(os.path.realpath(__file__)) - nmv.interface.ui_icons.load("github", os.path.join(images_path, "github-logo.png"), 'IMAGE') - nmv.interface.ui_icons.load("bbp", os.path.join(images_path, "bbp-logo.png"), 'IMAGE') - nmv.interface.ui_icons.load("epfl", os.path.join(images_path, "epfl-logo.png"), 'IMAGE') - nmv.interface.ui_icons.load("nmv", os.path.join(images_path, "nmv-logo.png"), 'IMAGE') - - -#################################################################################################### -# @load_morphology -#################################################################################################### -def unload_icons(): - """Unloads the external icons, after loading them to Blender. - """ - - # Remove the icons - bpy.utils.previews.remove(nmv.interface.ui_icons) - - -#################################################################################################### -# @load_morphology -#################################################################################################### -def load_fonts(): - """Loads all the fonts to the add-on. - """ - - # Get all the font files in the fonts directory - font_files = nmv.file.get_files_in_directory( - directory=nmv.consts.Paths.FONTS_DIRECTORY, file_extension='ttf') - - # Load fonts - for font_file in font_files: - font = '%s/%s' % (nmv.consts.Paths.FONTS_DIRECTORY, font_file) - bpy.data.fonts.load(font) - - -#################################################################################################### -# @load_morphology -#################################################################################################### -def enable_or_disable_layout(layout): - """Activates or deactivates the layout based on the status of the morphology. - - :param layout: - A given layout to enable or disable. - """ - if nmv.interface.ui_morphology is None: - layout.enabled = False - else: - layout.enabled = True - - -#################################################################################################### -# @load_morphology -#################################################################################################### -def load_morphology(panel_object, - context_scene): - """Load a given morphology from file. - - :param panel_object: - An object of a UI panel. - - :param context_scene: - Current scene in the rendering context. - """ - - global current_morphology_label - global current_morphology_path - global current_coordinate - - # Alias, to make lines shorter than 100 characters - options = nmv.interface.ui_options - - # Read the data from a given morphology file either in .h5 or .swc formats - if bpy.context.scene.NMV_InputSource == nmv.enums.Input.H5_SWC_FILE: - - try: - # Pass options from UI to system - options.morphology.morphology_file_path = context_scene.NMV_MorphologyFile - - # Ensure that a file has been selected - if 'Select File' in context_scene.NMV_MorphologyFile: - return None - - # If no morphologies are loaded - if current_morphology_path is None: - - # Update the morphology label - options.morphology.label = nmv.file.ops.get_file_name_from_path( - context_scene.NMV_MorphologyFile) - - # Load the morphology file - # Load the morphology from the file - loading_flag, morphology_object = nmv.file.readers.read_morphology_from_file( - options=options) - - # Verify the loading operation - if loading_flag: - - # Update the morphology - nmv.interface.ui_morphology = morphology_object - - # Update the current morphology path - current_morphology_path = context_scene.NMV_MorphologyFile - - # Update the coordinate - current_coordinate = context_scene.NMV_CenterMorphologyAtOrigin - - # New morphology loaded - return 'NEW_MORPHOLOGY_LOADED' - - # Otherwise, report an ERROR - else: - - # Report the issue - panel_object.report({'ERROR'}, 'Invalid Morphology File') - - # None - return None - - # If there is file that is loaded - else: - - # If the same path, and same coordinates, then return ALREADY_LOADED - if current_morphology_path == options.morphology.morphology_file_path: - return 'ALREADY_LOADED' - - # Load the new morphology file - else: - - # Update the morphology label - nmv.interface.ui_options.morphology.label = \ - nmv.file.ops.get_file_name_from_path(context_scene.NMV_MorphologyFile) - - # Load the morphology from file - loading_flag, morphology_object = nmv.file.readers.read_morphology_from_file( - options=nmv.interface.ui_options) - - # Verify the loading operation - if loading_flag: - - # Update the morphology - nmv.interface.ui_morphology = morphology_object - - # Update the current morphology path - current_morphology_path = context_scene.NMV_MorphologyFile - - # New morphology loaded - return 'NEW_MORPHOLOGY_LOADED' - - # Otherwise, report an ERROR - else: - - # Report the issue - panel_object.report({'ERROR'}, 'Invalid Morphology File') - - # None - return None - - # Invalid morphology file - except ValueError: - # Report the issue - panel_object.report({'ERROR'}, 'CANNOT load. Invalid Morphology File') - - # None - return None - - # Read the data from a specific gid in a given circuit - elif bpy.context.scene.NMV_InputSource == nmv.enums.Input.CIRCUIT_GID: - - # Pass options from UI to system - nmv.interface.ui_options.morphology.blue_config = context_scene.NMV_CircuitFile - nmv.interface.ui_options.morphology.gid = context_scene.NMV_Gid - - # Update the morphology label - nmv.interface.ui_options.morphology.label = 'neuron_' + str(context_scene.NMV_Gid) - - # Check if the morphology is loaded before or not - if current_morphology_label is None: - current_morphology_label = nmv.interface.ui_options.morphology.label - else: - if current_morphology_label == nmv.interface.ui_options.morphology.label: - return 'ALREADY_LOADED' - - # Load the morphology from the circuit - loading_flag, morphology_object = nmv.file.readers.BBPReader.load_morphology_from_circuit( - blue_config=nmv.interface.ui_options.morphology.blue_config, - gid=nmv.interface.ui_options.morphology.gid) - - # Verify the loading operation - if loading_flag: - - # Update the morphology - nmv.interface.ui_morphology = morphology_object - - # Otherwise, report an ERROR - else: - panel_object.report({'ERROR'}, 'Cannot Load Morphology from Circuit') - - # None - return None - - else: - # Report an invalid input source - panel_object.report({'ERROR'}, 'Invalid Input Source') - - # None - return None - - return 'NEW_MORPHOLOGY_LOADED' - - -#################################################################################################### -# @configure_output_directory -#################################################################################################### -def configure_output_directory(options, - context=None): - """Configures the output directory after loading the data. - - :param options: - System options. - :param context: - Context. - """ - - # If the output directory is not set - if options.io.output_directory is None: - - # Suggest an output directory at the home folder - suggested_output_folder = '%s/neuromorphovis-output' % os.path.expanduser('~') - - # Check if the output directory already exists or not - if os.path.exists(suggested_output_folder): - - # Update the system options - nmv.interface.ui_options.io.output_directory = suggested_output_folder - - # Update the UI - context.scene.NMV_OutputDirectory = suggested_output_folder - - # Otherwise, create it - else: - - # Try to create the directory there - try: - - # Create the directory - os.mkdir(suggested_output_folder) - - # Update the system options - nmv.interface.ui_options.io.output_directory = suggested_output_folder - - # Update the UI - context.scene.NMV_OutputDirectory = suggested_output_folder - - # Voila - except ValueError: - pass - - -#################################################################################################### -# @validate_output_directory -#################################################################################################### -def validate_output_directory(panel_object, - context_scene): - """Validates the existence of the output directory. - - :param panel_object: - An object of a UI panel. - - :param context_scene: - Current scene in the rendering context. - - :return - True if the output directory is valid or False otherwise. - """ - - # Ensure that there is a valid directory where the images will be written to - if nmv.interface.ui_options.io.output_directory is None: - panel_object.report({'ERROR'}, nmv.consts.Messages.PATH_NOT_SET) - return False - - if not nmv.file.ops.path_exists(context_scene.NMV_OutputDirectory): - panel_object.report({'ERROR'}, nmv.consts.Messages.INVALID_OUTPUT_PATH) - return False - - # The output directory is valid - return True - - -#################################################################################################### -# @render_morphology_image -#################################################################################################### -def render_morphology_image(panel_object, - context_scene, - view, - image_format=nmv.enums.Image.Extension.PNG): - """Renders an image of the morphology reconstructed in the scene. - - :param panel_object: - UI Panel. - :param context_scene: - A reference to the Blender scene. - :param view: - Rendering view. - :param image_format: - Image extension or file format, by default .PNG. - """ - - nmv.logger.header('Rendering Image') - - # Start - start_time = time.time() - - # Validate the output directory - if not nmv.interface.ui.validate_output_directory( - panel_object=panel_object, context_scene=context_scene): - return - - # Create the images directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.images_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.images_directory) - - # Report the process starting in the UI - panel_object.report({'INFO'}, 'Rendering ... Wait') - - # Update the image file format - bpy.context.scene.render.image_settings.file_format = image_format - - # If this is a dendrogram rendering, handle it in a very specific way. - if nmv.interface.ui_options.morphology.reconstruction_method == \ - nmv.enums.Skeleton.Method.DENDROGRAM: - - # Compute the bounding box of the dendrogram and stretch it - bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves() - delta = bounding_box.get_largest_dimension() * 0.05 - bounding_box.extend_bbox(delta_x=1.5 * delta, delta_y=delta) - - # Render the image - nmv.rendering.render( - bounding_box=bounding_box, - camera_view=nmv.enums.Camera.View.FRONT, - image_resolution=context_scene.NMV_MorphologyFrameResolution, - image_name='%s_dendrogram' % nmv.interface.ui_options.morphology.label, - image_format=image_format, - image_directory=nmv.interface.ui_options.io.images_directory, - keep_camera_in_scene=False) - - # All other cases are okay - else: - - # Compute the bounding box for a close up view - if context_scene.NMV_MorphologyRenderingView == \ - nmv.enums.Rendering.View.CLOSE_UP: - - # Compute the bounding box for a close up view - bounding_box = nmv.bbox.compute_unified_extent_bounding_box( - extent=context_scene.NMV_MorphologyCloseUpDimensions) - - # Compute the bounding box for a mid shot view - elif context_scene.NMV_MorphologyRenderingView == \ - nmv.enums.Rendering.View.MID_SHOT: - - # Compute the bounding box for the available curves and meshes - bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves_and_meshes() - - # Compute the bounding box for the wide shot view that correspond to the whole morphology - else: - - # Compute the full morphology bounding box - bounding_box = nmv.skeleton.compute_full_morphology_bounding_box( - morphology=nmv.interface.ui_morphology) - - # Get the image suffix - if view == nmv.enums.Camera.View.FRONT: - suffix = nmv.consts.Suffix.MORPHOLOGY_FRONT - elif view == nmv.enums.Camera.View.SIDE: - suffix = nmv.consts.Suffix.MORPHOLOGY_SIDE - elif view == nmv.enums.Camera.View.TOP: - suffix = nmv.consts.Suffix.MORPHOLOGY_TOP - else: - suffix = nmv.consts.Suffix.MORPHOLOGY_FRONT - - # Draw the morphology scale bar - if context_scene.NMV_RenderMorphologyScaleBar: - scale_bar = nmv.interface.draw_scale_bar( - bounding_box=bounding_box, - material_type=nmv.interface.ui_options.shading.morphology_material, - view=view) - - # Render at a specific resolution - if context_scene.NMV_RenderingType == nmv.enums.Rendering.Resolution.FIXED: - - # Render the image - nmv.rendering.render( - bounding_box=bounding_box, - camera_view=view, - image_resolution=context_scene.NMV_MorphologyFrameResolution, - image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), - image_format=image_format, - image_directory=nmv.interface.ui_options.io.images_directory, - keep_camera_in_scene=False) - - # Render at a specific scale factor - else: - - # Render the image - nmv.rendering.render_to_scale( - bounding_box=bounding_box, - camera_view=view, - image_scale_factor=context_scene.NMV_MorphologyFrameScaleFactor, - image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), - image_format=image_format, - image_directory=nmv.interface.ui_options.io.images_directory, - keep_camera_in_scene=False) - - # Delete the morphology scale bar, if rendered - if context_scene.NMV_RenderMorphologyScaleBar: - nmv.scene.delete_object_in_scene(scene_object=scale_bar) - - nmv.logger.statistics('Image rendered in [%f] seconds' % (time.time() - start_time)) - - # Report the process termination in the UI - panel_object.report({'INFO'}, 'Rendering Done') - - -#################################################################################################### -# @render_mesh_image -#################################################################################################### -def render_mesh_image(panel_object, - context_scene, - view, - image_format=nmv.enums.Image.Extension.PNG): - """Renders an image of a mesh in the scene. - - :param panel_object: - UI Panel. - :param context_scene: - A reference to the Blender scene. - :param view: - Rendering view. - :param image_format: - Image extension or file format, by default .PNG. - """ - - nmv.logger.header('Rendering Image') - - # Start - start_time = time.time() - - # Validate the output directory - if not nmv.interface.ui.validate_output_directory( - panel_object=panel_object, context_scene=context_scene): - return - - # Create the images directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.images_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.images_directory) - - # Report the process starting in the UI - panel_object.report({'INFO'}, 'Rendering ... Wait') - - # Update the image file format - bpy.context.scene.render.image_settings.file_format = image_format - - # Compute the bounding box for a close up view - if context_scene.NMV_MeshRenderingView == nmv.enums.Rendering.View.CLOSE_UP: - - # Compute the bounding box for a close up view - bounding_box = nmv.bbox.compute_unified_extent_bounding_box( - extent=context_scene.NMV_MeshCloseUpSize) - - # Compute the bounding box for a mid shot view - elif context_scene.NMV_MeshRenderingView == nmv.enums.Rendering.View.MID_SHOT: - - # Compute the bounding box for the available meshes only - bounding_box = nmv.bbox.compute_scene_bounding_box_for_meshes() - - # Compute the bounding box for the wide shot view that correspond to the whole morphology - else: - - # Compute the full morphology bounding box - bounding_box = nmv.skeleton.compute_full_morphology_bounding_box( - morphology=nmv.interface.ui_morphology) - - # Get the image suffix - if view == nmv.enums.Camera.View.FRONT: - suffix = nmv.consts.Suffix.MESH_FRONT - elif view == nmv.enums.Camera.View.SIDE: - suffix = nmv.consts.Suffix.MESH_SIDE - elif view == nmv.enums.Camera.View.TOP: - suffix = nmv.consts.Suffix.MESH_TOP - else: - suffix = nmv.consts.Suffix.MESH_FRONT - - # Draw the morphology scale bar - if context_scene.NMV_RenderMeshScaleBar: - scale_bar = nmv.interface.draw_scale_bar( - bounding_box=bounding_box, - material_type=nmv.interface.ui_options.shading.mesh_material, - view=view) - - # Render at a specific resolution - if context_scene.NMV_MeshRenderingResolution == nmv.enums.Rendering.Resolution.FIXED: - - # Render the image - nmv.rendering.render( - bounding_box=bounding_box, - camera_view=view, - image_resolution=context_scene.NMV_MeshFrameResolution, - image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), - image_format=image_format, - image_directory=nmv.interface.ui_options.io.images_directory) - - # Render at a specific scale factor - else: - - # Render the image - nmv.rendering.render_to_scale( - bounding_box=bounding_box, - camera_view=view, - image_scale_factor=context_scene.NMV_MeshFrameScaleFactor, - image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), - image_format=image_format, - image_directory=nmv.interface.ui_options.io.images_directory) - - # Delete the morphology scale bar, if rendered - if context_scene.NMV_RenderMeshScaleBar: - nmv.scene.delete_object_in_scene(scene_object=scale_bar) - - nmv.logger.statistics('Image rendered in [%f] seconds' % (time.time() - start_time)) - - # Report the process termination in the UI - panel_object.report({'INFO'}, 'Rendering Done') - - -#################################################################################################### -# @render_morphology_image -#################################################################################################### -def render_morphology_image_for_catalogue(resolution_scale_factor=10, - view='FRONT'): - """Renders an image of the morphology reconstructed in the scene. - - :param resolution_scale_factor: - The scale factor that will determine the resolution. - :param view: - Rendering view. - """ - - # Create the images directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.images_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.images_directory) - - # Compute the bounding box for the available curves and meshes - bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves_and_meshes() - - # Get the view prefix - if view == nmv.enums.Camera.View.FRONT: - view_prefix = 'FRONT' - elif view == nmv.enums.Camera.View.SIDE: - view_prefix = 'SIDE' - elif view == nmv.enums.Camera.View.TOP: - view_prefix = 'TOP' - else: - view_prefix = '' - - # Render the image - nmv.rendering.render_to_scale( - bounding_box=bounding_box, - camera_view=view, - image_scale_factor=resolution_scale_factor, - image_name='MORPHOLOGY_%s_%s' % (view_prefix, nmv.interface.ui_options.morphology.label), - image_directory=nmv.interface.ui_options.io.analysis_directory) diff --git a/nmv/builders/mesh/common.py b/nmv/interface/ui/common/__init__.py similarity index 78% rename from nmv/builders/mesh/common.py rename to nmv/interface/ui/common/__init__.py index eb19d0962..8b7aa3509 100644 --- a/nmv/builders/mesh/common.py +++ b/nmv/interface/ui/common/__init__.py @@ -1,5 +1,5 @@ #################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project # Marwan Abdellah # # This file is part of NeuroMorphoVis @@ -15,32 +15,10 @@ # If not, see . #################################################################################################### -# System imports -import random -import os - -# Blender imports -import bpy - -# Internal imports -import nmv.scene -import nmv.enums -import nmv.geometry -import nmv.mesh -import nmv.shading -import nmv.skeleton -import nmv.utilities - - - - - - - - - - - - - - +from .layout_rendering_props import * +from .rendering_options import * +from .rendering_operators import * +from .file_system_ops import * +from .fonts_ops import * +from .icons_ops import * +from .commonxx import * diff --git a/nmv/interface/ui/common/commonxx.py b/nmv/interface/ui/common/commonxx.py new file mode 100644 index 000000000..bafdfae10 --- /dev/null +++ b/nmv/interface/ui/common/commonxx.py @@ -0,0 +1,680 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import os +import time + +# Blender imports +import bpy +from mathutils import Vector + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.interface +import nmv.skeleton +import nmv.bbox +import nmv.rendering +import nmv.scene +import nmv.geometry +import nmv.interface + + + + +#################################################################################################### +# @enable_or_disable_layout +#################################################################################################### +def enable_or_disable_layout(layout): + """Activates or deactivates the layout based on the status of the morphology. + + :param layout: + A given layout to enable or disable. + """ + + layout.enabled = False if nmv.interface.ui_morphology is None else True + + +#################################################################################################### +# @load_morphology_from_file +#################################################################################################### +def load_morphology_from_file(panel, + scene, + options): + """Loads an individual morphology file. Note that the path information is available in @options. + NOTE: In case of failure, a specific error is reported and a None is returned to terminate + the loading operation. + + :param panel: + Current I/O panel. + :param scene: + Blender scene. + :param options: + A reference to the NeuroMorphoVis options. + :return: + A reference to the morphology object if successfully loaded, or None otherwise. + """ + + # If no file is given from the user, handle the error + if nmv.consts.Strings.SELECT_FILE in options.morphology.morphology_file_path: + panel.report({'ERROR'}, 'To load a morphology, please select a valid morphology file') + return None + + # If the given morphology path is not valid, handle the error + if not os.path.isfile(options.morphology.morphology_file_path): + panel.report({'ERROR'}, 'The given morphology path is not a valid file. ' + 'Please select a valid file morphology file with an existing path') + return None + + # Load the morphology object + morphology_object = nmv.file.readers.read_morphology_from_file(options=options, panel=panel) + + # If the loaded morphology object is not None, update the global references + if morphology_object is None: + panel.report({'ERROR'}, 'The selected morphology cannot be loaded!') + return None + else: + options.morphology.label = nmv.file.ops.get_file_name_from_path(scene.NMV_MorphologyFile) + nmv.interface.ui_morphology = morphology_object + return 'VALID_MORPHOLOGY' + + +#################################################################################################### +# @load_morphology_from_circuit +#################################################################################################### +def load_morphology_from_circuit(panel, + scene, + options): + """Loads the morphology from a digitally reconstructed circuit. Note that the circuit + information is available in @options. + NOTE: In case of failure, a specific error is reported and a None is returned to terminate + the loading operation. + + :param panel: + Current I/O panel. + :param scene: + Blender scene. + :param options: + A reference to the NeuroMorphoVis options. + :return: + A reference to the morphology object if successfully loaded, or None otherwise. + """ + + # In case users do not select a circuit file or give a wrong circuit file, handle the error + if nmv.consts.Strings.SELECT_CIRCUIT_FILE in options.morphology.blue_config or \ + not os.path.isfile(options.morphology.blue_config): + panel.report({'ERROR'}, 'Please select a valid circuit') + return None + + # In case a non-valid GID is provided + if nmv.consts.Strings.ADD_GID in str(options.morphology.gid): + panel.report({'ERROR'}, 'Please provide a valid GID') + return None + + # If the given GID contains non-integer characters + try: + int(options.morphology.gid) + except ArithmeticError: + panel.report({'ERROR'}, 'The provided GID must be an integer') + return None + + # Load the morphology from the circuit + morphology_object = nmv.file.readers.read_morphology_from_circuit( + options=nmv.interface.ui_options) + + # If the loaded morphology object is not None, update the global references + if morphology_object is None: + panel.report({'ERROR'}, 'The selected morphology cannot be loaded!') + return None + else: + options.morphology.label = str(scene.NMV_Gid) + nmv.interface.ui_morphology = morphology_object + return 'VALID_MORPHOLOGY' + + +#################################################################################################### +# @disable_circuit_options +#################################################################################################### +def disable_circuit_options(): + """Disables all the circuit related parameters""" + + nmv.interface.ui_circuit = None + nmv.interface.ui_synaptics_file_loaded = False + nmv.interface.ui_synaptics_reconstructed = False + nmv.interface.ui_synaptics_rendered = False + + +#################################################################################################### +# @load_morphology +#################################################################################################### +def load_morphology(panel, + scene): + """Loads a morphology into NeuroMorphoVis from a specific input source. + + :param panel: + Current I/O panel. + :param scene: + Blender scene. + """ + + # Load the morphology either from an individual file or from a digitally reconstructed circuit + if bpy.context.scene.NMV_InputSource == nmv.enums.Input.MORPHOLOGY_FILE: + + # Disable all the options for the circuit + disable_circuit_options() + return load_morphology_from_file( + panel=panel, scene=scene, options=nmv.interface.ui_options) + + elif bpy.context.scene.NMV_InputSource == nmv.enums.Input.CIRCUIT_GID: + return load_morphology_from_circuit( + panel=panel, scene=scene, options=nmv.interface.ui_options) + else: + panel.report({'ERROR'}, 'Invalid Input Source') + return None + + +#################################################################################################### +# @render_morphology_image +#################################################################################################### +def render_morphology_image___(panel, + context_scene, + view, + image_format=nmv.enums.Image.Extension.PNG): + """Renders an image of the morphology reconstructed in the scene. + + :param panel: + UI Panel. + :param context_scene: + A reference to the Blender scene. + :param view: + Rendering view. + :param image_format: + Image extension or file format, by default .PNG. + """ + + nmv.logger.header('Rendering Image') + + # Start + start_time = time.time() + + # Validate the output directory + if not nmv.interface.ui.validate_output_directory( + panel=panel, context_scene=context_scene): + return + + # Create the images directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.images_directory): + nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.images_directory) + + # Report the process starting in the UI + panel.report({'INFO'}, 'Rendering ... Wait') + + # Update the image file format + bpy.context.scene.render.image_settings.file_format = image_format + + # If this is a dendrogram rendering, handle it in a very specific way. + if nmv.interface.ui_options.morphology.reconstruction_method == \ + nmv.enums.Skeleton.Method.DENDROGRAM: + + # Compute the bounding box of the dendrogram and stretch it + bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves() + delta = bounding_box.get_largest_dimension() * 0.05 + bounding_box.extend_bbox(delta_x=1.5 * delta, delta_y=delta) + + # Render the image + nmv.rendering.render( + bounding_box=bounding_box, + camera_view=nmv.enums.Camera.View.FRONT, + image_resolution=context_scene.NMV_MorphologyFrameResolution, + image_name='%s_dendrogram' % nmv.interface.ui_options.morphology.label, + image_format=image_format, + image_directory=nmv.interface.ui_options.io.images_directory, + keep_camera_in_scene=False) + + # All other cases are okay + else: + + # Compute the bounding box for a close up view + if context_scene.NMV_MorphologyRenderingView == \ + nmv.enums.Rendering.View.CLOSEUP: + + # Compute the bounding box for a close up view + bounding_box = nmv.bbox.compute_unified_extent_bounding_box( + extent=context_scene.NMV_MorphologyCloseUpDimensions) + + # Compute the bounding box for a mid shot view + elif context_scene.NMV_MorphologyRenderingView == \ + nmv.enums.Rendering.View.MID_SHOT: + + # Compute the bounding box for the available curves and meshes + bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves_and_meshes() + + # Compute the bounding box for the wide shot view that correspond to the whole morphology + else: + + # Compute the full morphology bounding box + bounding_box = nmv.skeleton.compute_full_morphology_bounding_box( + morphology=nmv.interface.ui_morphology) + + # Get the image suffix + if view == nmv.enums.Camera.View.FRONT: + suffix = nmv.consts.Suffix.MORPHOLOGY_FRONT + elif view == nmv.enums.Camera.View.SIDE: + suffix = nmv.consts.Suffix.MORPHOLOGY_SIDE + elif view == nmv.enums.Camera.View.TOP: + suffix = nmv.consts.Suffix.MORPHOLOGY_TOP + else: + suffix = nmv.consts.Suffix.MORPHOLOGY_FRONT + + # Draw the morphology scale bar + if context_scene.NMV_RenderMorphologyScaleBar: + scale_bar = nmv.interface.draw_scale_bar( + bounding_box=bounding_box, + material_type=nmv.interface.ui_options.shading.morphology_material, + view=view) + + # Render at a specific resolution + if context_scene.NMV_RenderingType == nmv.enums.Rendering.Resolution.FIXED: + + # Render the image + nmv.rendering.render( + bounding_box=bounding_box, + camera_view=view, + image_resolution=context_scene.NMV_MorphologyFrameResolution, + image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), + image_format=image_format, + image_directory=nmv.interface.ui_options.io.images_directory, + keep_camera_in_scene=False) + + # Render at a specific scale factor + else: + + # Render the image + nmv.rendering.render_to_scale( + bounding_box=bounding_box, + camera_view=view, + image_scale_factor=context_scene.NMV_MorphologyFrameScaleFactor, + image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), + image_format=image_format, + image_directory=nmv.interface.ui_options.io.images_directory, + keep_camera_in_scene=False) + + # Delete the morphology scale bar, if rendered + if context_scene.NMV_RenderMorphologyScaleBar: + nmv.scene.delete_object_in_scene(scene_object=scale_bar) + + nmv.logger.statistics('Image rendered in [%f] seconds' % (time.time() - start_time)) + + # Report the process termination in the UI + panel.report({'INFO'}, 'Rendering Done') + + +#################################################################################################### +# @render_mesh_image +#################################################################################################### +def render_mesh_image(panel, + context_scene, + view, + image_format=nmv.enums.Image.Extension.PNG): + """Renders an image of a mesh in the scene. + + :param panel: + UI Panel. + :param context_scene: + A reference to the Blender scene. + :param view: + Rendering view. + :param image_format: + Image extension or file format, by default .PNG. + """ + + nmv.logger.header('Rendering Image') + + # Start + start_time = time.time() + + # Validate the output directory + if not nmv.interface.ui.validate_output_directory(panel=panel, context_scene=context_scene): + return + + # Create the images directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.images_directory): + nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.images_directory) + + # Report the process starting in the UI + panel.report({'INFO'}, 'Rendering ... Wait') + + # Update the image file format + bpy.context.scene.render.image_settings.file_format = image_format + + # Compute the bounding box for a close up view + if context_scene.NMV_MeshRenderingView == nmv.enums.Rendering.View.CLOSEUP: + + # Compute the bounding box for a close up view + bounding_box = nmv.bbox.compute_unified_extent_bounding_box( + extent=context_scene.NMV_MeshCloseUpSize) + + # Compute the bounding box for a mid shot view + elif context_scene.NMV_MeshRenderingView == nmv.enums.Rendering.View.MID_SHOT: + + # Compute the bounding box for the available meshes only + bounding_box = nmv.bbox.compute_scene_bounding_box_for_meshes() + + # Compute the bounding box for the wide shot view that correspond to the whole morphology + else: + + # Compute the full morphology bounding box + bounding_box = nmv.skeleton.compute_full_morphology_bounding_box( + morphology=nmv.interface.ui_morphology) + + # Get the image suffix + if view == nmv.enums.Camera.View.FRONT: + suffix = nmv.consts.Suffix.MESH_FRONT + elif view == nmv.enums.Camera.View.SIDE: + suffix = nmv.consts.Suffix.MESH_SIDE + elif view == nmv.enums.Camera.View.TOP: + suffix = nmv.consts.Suffix.MESH_TOP + else: + suffix = nmv.consts.Suffix.MESH_FRONT + + # Draw the morphology scale bar + if context_scene.NMV_RenderMeshScaleBar: + scale_bar = nmv.interface.draw_scale_bar( + bounding_box=bounding_box, + material_type=nmv.interface.ui_options.shading.mesh_material, + view=view) + + # Render at a specific resolution + if context_scene.NMV_MeshRenderingResolution == nmv.enums.Rendering.Resolution.FIXED: + + # Render the image + nmv.rendering.render( + bounding_box=bounding_box, + camera_view=view, + image_resolution=context_scene.NMV_MeshFrameResolution, + image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), + image_format=image_format, + image_directory=nmv.interface.ui_options.io.images_directory) + + # Render at a specific scale factor + else: + + # Render the image + nmv.rendering.render_to_scale( + bounding_box=bounding_box, + camera_view=view, + image_scale_factor=context_scene.NMV_MeshFrameScaleFactor, + image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), + image_format=image_format, + image_directory=nmv.interface.ui_options.io.images_directory) + + # Delete the morphology scale bar, if rendered + if context_scene.NMV_RenderMeshScaleBar: + nmv.scene.delete_object_in_scene(scene_object=scale_bar) + + nmv.logger.statistics('Image rendered in [%f] seconds' % (time.time() - start_time)) + + # Report the process termination in the UI + panel.report({'INFO'}, 'Rendering Done') + + +#################################################################################################### +# @render_morphology_image +#################################################################################################### +def render_morphology_image_for_catalogue(resolution_scale_factor=10, + view='FRONT'): + """Renders an image of the morphology reconstructed in the scene. + + :param resolution_scale_factor: + The scale factor that will determine the resolution. + :param view: + Rendering view. + """ + + # Create the images directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.images_directory): + nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.images_directory) + + # Compute the bounding box for the available curves and meshes + bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves_and_meshes() + + # Get the view prefix + if view == nmv.enums.Camera.View.FRONT: + view_prefix = 'FRONT' + elif view == nmv.enums.Camera.View.SIDE: + view_prefix = 'SIDE' + elif view == nmv.enums.Camera.View.TOP: + view_prefix = 'TOP' + else: + view_prefix = '' + + # Render the image + nmv.rendering.render_to_scale( + bounding_box=bounding_box, + camera_view=view, + image_scale_factor=resolution_scale_factor, + image_name='MORPHOLOGY_%s_%s' % (view_prefix, nmv.interface.ui_options.morphology.label), + image_directory=nmv.interface.ui_options.io.analysis_directory) + + +#################################################################################################### +# @initialize_synaptic_colors +#################################################################################################### +def initialize_synaptic_colors(): + + import random + + # UI color elements for the color map + if nmv.consts.Circuit.MTYPES is not None: + for i in range(len(nmv.consts.Circuit.MTYPES)): + r = random.uniform(0, 1) + g = random.uniform(0, 1) + b = random.uniform(0, 1) + + setattr(bpy.types.Scene, 'NMV_MtypeColor_%d' % i, + bpy.props.FloatVectorProperty( + name='%s' % nmv.consts.Circuit.MTYPES[i], + subtype='COLOR', default=Vector((r, g, b)), min=0.0, max=1.0, + description='')) + + setattr(bpy.types.Scene, 'NMV_Synaptic_MtypeCount_%d' % i, + bpy.props.IntProperty( + name="Count", + description="The number of synapses of this specific morphological type", + default=0, min=0, max=1000000)) + + # UI color elements for the color map + if nmv.consts.Circuit.ETYPES is not None: + for i in range(len(nmv.consts.Circuit.ETYPES)): + r = random.uniform(0, 1) + g = random.uniform(0, 1) + b = random.uniform(0, 1) + setattr(bpy.types.Scene, 'NMV_EtypeColor_%d' % i, + bpy.props.FloatVectorProperty( + name='%s' % nmv.consts.Circuit.ETYPES[i], + subtype='COLOR', default=Vector((r, g, b)), min=0.0, max=1.0, + description='')) + + setattr(bpy.types.Scene, 'NMV_Synaptic_EtypeCount_%d' % i, + bpy.props.IntProperty( + name="Count", + description="The number of synapses of this specific electrical type", + default=0, min=0, max=1000000)) + + +#################################################################################################### +# @initialize_relevant_parameters +#################################################################################################### +def initialize_relevant_parameters(scene): + + initialize_synaptic_colors() + + +#################################################################################################### +# @compute_scene_bounding_box_for_meshes_based_on_view +#################################################################################################### +def compute_scene_bounding_box_for_meshes_based_on_view(options): + + # Compute the bounding box, depending on the selected view + if options.rendering.rendering_view == nmv.enums.Rendering.View.CLOSEUP: + return nmv.bbox.compute_unified_extent_bounding_box( + extent=options.rendering.close_up_dimensions) + else: + return nmv.bbox.compute_scene_bounding_box_for_meshes() + + +#################################################################################################### +# @get_image_suffix_for_morphologies +#################################################################################################### +def get_image_suffix_for_morphologies(view): + + # Get the image suffix + if view == nmv.enums.Camera.View.FRONT: + return nmv.consts.Suffix.MORPHOLOGY_FRONT + elif view == nmv.enums.Camera.View.SIDE: + return nmv.consts.Suffix.MORPHOLOGY_SIDE + elif view == nmv.enums.Camera.View.TOP: + return nmv.consts.Suffix.MORPHOLOGY_TOP + return nmv.consts.Suffix.MORPHOLOGY_FRONT + + +#################################################################################################### +# @get_image_suffix_for_meshes +#################################################################################################### +def get_image_suffix_for_meshes(view): + + # Get the image suffix + if view == nmv.enums.Camera.View.FRONT: + return nmv.consts.Suffix.MESH_FRONT + elif view == nmv.enums.Camera.View.SIDE: + return nmv.consts.Suffix.MESH_SIDE + elif view == nmv.enums.Camera.View.TOP: + return nmv.consts.Suffix.MESH_TOP + return nmv.consts.Suffix.MESH_FRONT + + +#################################################################################################### +# @get_image_suffix_for_synaptics +#################################################################################################### +def get_image_suffix_for_synaptics(view): + + # Get the image suffix + if view == nmv.enums.Camera.View.FRONT: + return nmv.consts.Suffix.SYNAPTICS_FRONT + elif view == nmv.enums.Camera.View.SIDE: + return nmv.consts.Suffix.SYNAPTICS_SIDE + elif view == nmv.enums.Camera.View.TOP: + return nmv.consts.Suffix.SYNAPTICS_TOP + return nmv.consts.Suffix.SYNAPTICS_FRONT + + +#################################################################################################### +# @get_use_case_suffix_for_synaptics +#################################################################################################### +def get_use_case_suffix_for_synaptics(options): + + if options.synaptics.use_case == nmv.enums.Synaptics.UseCase.AFFERENT: + return '%s_afferent' % str(options.morphology.gid) + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.EFFERENT: + return '%s_efferent' % str(options.morphology.gid) + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.AFFERENT_AND_EFFERENT: + return '%s_afferent_efferent' % str(options.morphology.gid) + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.EXCITATORY: + return '%s_excitatory' % str(options.morphology.gid) + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.INHIBITORY: + return '%s_inhibitory' % str(options.morphology.gid) + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.EXCITATORY_AND_INHIBITORY: + return '%s_excitatory_inhibitory' % str(options.morphology.gid) + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.PATHWAY_PRE_SYNAPTIC: + return '%s_%s_pre_pathway' % (str(options.synaptics.post_synaptic_gid), + str(options.morphology.gid)) + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.PATHWAY_POST_SYNAPTIC: + return '%s_%s_post_pathway' % (str(options.morphology.gid), + str(options.synaptics.post_synaptic_gid)) + else: + return '' + + +#################################################################################################### +# @render_synaptics_image +#################################################################################################### +def render_synaptics_image(panel, + scene, + view, + options): + + # Validate the output directory + if not nmv.interface.ui.validate_output_directory(panel=panel, context_scene=scene): + return + + # Report the process starting in the UI + start_time = time.time() + panel.report({'INFO'}, 'Rendering ... Wait') + + # Compute the bounding box, depending on the shot + bounding_box = compute_scene_bounding_box_for_meshes_based_on_view(options=options) + + # Draw the morphology scale bar + scale_bar = None + if options.rendering.render_scale_bar: + scale_bar = nmv.interface.draw_scale_bar( + bounding_box=compute_scene_bounding_box_for_meshes_based_on_view(options=options), + material_type=options.synaptics.shader, + view=view) + + # Get the image label suffixes + view_suffix = get_image_suffix_for_synaptics(view) + use_case_suffix = get_use_case_suffix_for_synaptics(options=options) + image_name = '%s%s' % (use_case_suffix, view_suffix) + + # Render at a specific resolution + if options.rendering.resolution_basis == nmv.enums.Rendering.Resolution.FIXED: + nmv.rendering.render( + bounding_box=bounding_box, + camera_view=view, + image_resolution=options.rendering.frame_resolution, + image_name=image_name, + image_format=options.rendering.image_format, + image_directory=options.io.images_directory) + + # Render at a specific scale factor + else: + + # Render the image + nmv.rendering.render_to_scale( + bounding_box=bounding_box, + camera_view=view, + image_scale_factor=options.rendering.resolution_scale_factor, + image_name=image_name, + image_format=options.rendering.image_format, + image_directory=options.io.images_directory) + + # Delete the morphology scale bar, if rendered + if scale_bar is not None: + nmv.scene.delete_object_in_scene(scene_object=scale_bar) + + rendering_time = time.time() - start_time + nmv.logger.statistics('Image rendered in [%f] seconds' % rendering_time) + + # Report the process termination in the UI + panel.report({'INFO'}, 'Rendering Done') + + return rendering_time + + + diff --git a/nmv/interface/ui/common/file_system_ops.py b/nmv/interface/ui/common/file_system_ops.py new file mode 100644 index 000000000..a242ecb6f --- /dev/null +++ b/nmv/interface/ui/common/file_system_ops.py @@ -0,0 +1,104 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys +import os + +# Blender imports +import bpy + +# Internal imports +import nmv.consts + + +#################################################################################################### +# @configure_output_directory +#################################################################################################### +def configure_output_directory(options, + context=None): + """Configures the output directory after loading the data. + + :param options: + NeuroMorphoVis options + :param context: + Context. + """ + + # If the output directory is not set + if options.io.output_directory is None: + + # Suggest an output directory at the home folder + suggested_output_folder = '%s/neuromorphovis-output' % os.path.expanduser('~') + + # Check if the output directory already exists or not + if os.path.exists(suggested_output_folder): + + # Update the system options + options.io.output_directory = suggested_output_folder + + # Update the UI + context.scene.NMV_OutputDirectory = suggested_output_folder + + # Otherwise, create it + else: + + # Try to create the directory there + try: + + # Create the directory + os.mkdir(suggested_output_folder) + + # Update the system options + options.io.output_directory = suggested_output_folder + + # Update the UI + context.scene.NMV_OutputDirectory = suggested_output_folder + + # Voila + except ValueError: + pass + + +#################################################################################################### +# @validate_output_directory +#################################################################################################### +def validate_output_directory(panel, + context_scene): + """Validates the existence of the output directory. + + :param panel: + An object of a UI panel. + + :param context_scene: + Current scene in the rendering context. + + :return + True if the output directory is valid or False otherwise. + """ + + # Ensure that there is a valid directory where the images will be written to + if nmv.interface.ui_options.io.output_directory is None: + panel.report({'ERROR'}, nmv.consts.Messages.PATH_NOT_SET) + return False + + if not nmv.file.ops.path_exists(context_scene.NMV_OutputDirectory): + panel.report({'ERROR'}, nmv.consts.Messages.INVALID_OUTPUT_PATH) + return False + + # The output directory is valid + return True diff --git a/nmv/interface/ui/common/fonts_ops.py b/nmv/interface/ui/common/fonts_ops.py new file mode 100644 index 000000000..85b46c3aa --- /dev/null +++ b/nmv/interface/ui/common/fonts_ops.py @@ -0,0 +1,36 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts + + +#################################################################################################### +# @load_fonts +#################################################################################################### +def load_fonts(): + """Loads all the fonts to the Blender system to be able to use them for the plotting.""" + + # Get all the font files in the fonts directory and load them into the scene + font_files = nmv.file.get_files_in_directory( + directory=nmv.consts.Paths.FONTS_DIRECTORY, file_extension='ttf') + for font_file in font_files: + font = '%s/%s' % (nmv.consts.Paths.FONTS_DIRECTORY, font_file) + bpy.data.fonts.load(font) diff --git a/nmv/interface/ui/common/icons_ops.py b/nmv/interface/ui/common/icons_ops.py new file mode 100644 index 000000000..544e822ab --- /dev/null +++ b/nmv/interface/ui/common/icons_ops.py @@ -0,0 +1,48 @@ +################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import os + +# Blender imports +import bpy + +# Internal imports +import nmv.interface + + +#################################################################################################### +# @load_icons +#################################################################################################### +def load_icons(): + """Loads the external icons to Blender to be able to use them on the panel interface.""" + + nmv.interface.ui_icons = bpy.utils.previews.new() + images_path = '%s/../../../../data/images' % os.path.dirname(os.path.realpath(__file__)) + nmv.interface.ui_icons.load("github", os.path.join(images_path, "github-logo.png"), 'IMAGE') + nmv.interface.ui_icons.load("bbp", os.path.join(images_path, "bbp-logo.png"), 'IMAGE') + nmv.interface.ui_icons.load("epfl", os.path.join(images_path, "epfl-logo.png"), 'IMAGE') + nmv.interface.ui_icons.load("nmv", os.path.join(images_path, "nmv-logo.png"), 'IMAGE') + + +#################################################################################################### +# @unload_icons +#################################################################################################### +def unload_icons(): + """Unloads the external icons, after loading them to Blender.""" + + bpy.utils.previews.remove(nmv.interface.ui_icons) \ No newline at end of file diff --git a/nmv/interface/ui/common/layout_rendering_props.py b/nmv/interface/ui/common/layout_rendering_props.py new file mode 100644 index 000000000..c9839320b --- /dev/null +++ b/nmv/interface/ui/common/layout_rendering_props.py @@ -0,0 +1,181 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + + +#################################################################################################### +# @draw_rendering_header +#################################################################################################### +def draw_rendering_header(layout, scene, options): + + row = layout.row() + row.label(text='Rendering Options', icon='RENDER_STILL') + + +#################################################################################################### +# @draw_morphology_rendering_view_option +#################################################################################################### +def draw_morphology_rendering_view_option(layout, scene, options): + + row = layout.row() + row.label(text='Rendering View') + row.prop(scene, 'NMV_MorphologyRenderingView', expand=True) + options.rendering.rendering_view = scene.NMV_MorphologyRenderingView + + +#################################################################################################### +# @draw_morphology_rendering_view_option_for +#################################################################################################### +def draw_synaptics_rendering_view_option(layout, scene, options): + + row = layout.row() + row.label(text='Rendering View') + row.prop(scene, 'NMV_SynapticsRenderingView', expand=True) + options.rendering.rendering_view = scene.NMV_SynapticsRenderingView + + +#################################################################################################### +# @draw_resolution_basis_option +#################################################################################################### +def draw_resolution_basis_option(layout, scene, options): + + row = layout.row() + row.label(text='Resolution Basis') + row.prop(scene, 'NMV_ResolutionBasis', expand=True) + options.rendering.resolution_basis = scene.NMV_ResolutionBasis + + +#################################################################################################### +# @draw_closeup_size_option +#################################################################################################### +def draw_closeup_size_option(layout, scene, options): + + row = layout.row() + row.label(text='Closeup Size') + row.prop(scene, 'NMV_CloseupDimensions') + options.rendering.close_up_dimensions = scene.NMV_CloseupDimensions + + +#################################################################################################### +# @draw_frame_resolution_option +#################################################################################################### +def draw_frame_resolution_option(layout, scene, options): + + resolution_row = layout.row() + resolution_row.label(text='Frame Resolution') + resolution_row.prop(scene, 'NMV_FrameResolution') + options.rendering.frame_resolution = scene.NMV_FrameResolution + + +#################################################################################################### +# @draw_frame_scale_factor_options +#################################################################################################### +def draw_frame_scale_factor_options(layout, scene, options): + + scale_factor_row = layout.row() + scale_factor_row.label(text='Resolution Scale Factor') + scale_factor_row.prop(scene, 'NMV_ResolutionScaleFactor') + options.rendering.resolution_scale_factor = scene.NMV_ResolutionScaleFactor + + +#################################################################################################### +# @draw_frame_resolution_basis_options +#################################################################################################### +def draw_image_format_option(layout, scene, options): + + row = layout.row() + row.label(text='Image Format:') + row.prop(scene, 'NMV_ImageFormat') + options.rendering.image_format = scene.NMV_ImageFormat + + +#################################################################################################### +# @draw_scale_bar_option +#################################################################################################### +def draw_scale_bar_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_RenderScaleBar') + options.rendering.render_scale_bar = scene.NMV_RenderScaleBar + + +#################################################################################################### +# @draw_frame_resolution_basis_options +#################################################################################################### +def draw_frame_resolution_basis_options(layout, scene, options): + + if options.rendering.resolution_basis == nmv.enums.Rendering.Resolution.FIXED: + draw_frame_resolution_option(layout=layout, scene=scene, options=options) + else: + draw_frame_scale_factor_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_closeup_rendering_options +#################################################################################################### +def draw_wide_shot_rendering_options(layout, scene, options): + + if options.rendering.resolution_basis == nmv.enums.Rendering.Resolution.FIXED: + draw_frame_resolution_option(layout=layout, scene=scene, options=options) + else: + draw_frame_scale_factor_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_frame_resolution_basis_options +#################################################################################################### +def draw_full_view_rendering_options(layout, scene, options): + + draw_frame_resolution_basis_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_resolution_options +#################################################################################################### +def draw_resolution_options(layout, scene, options): + + if options.rendering.rendering_view == nmv.enums.Rendering.View.CLOSEUP: + draw_closeup_size_option(layout=layout, scene=scene, options=options) + + if options.rendering.resolution_basis == nmv.enums.Rendering.Resolution.FIXED: + draw_wide_shot_rendering_options(layout=layout, scene=scene, options=options) + else: + draw_full_view_rendering_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_rendering_buttons +#################################################################################################### +def draw_rendering_buttons(panel, scene, options): + + # Rendering view + view_row = panel.layout.row() + view_row.label(text='Render View:', icon='RESTRICT_RENDER_OFF') + buttons_row = panel.layout.row(align=True) + buttons_row.operator('nmv.render_front_view_button', icon='AXIS_FRONT') + buttons_row.operator('nmv.render_side_view_button', icon='AXIS_SIDE') + buttons_row.operator('nmv.render_top_view_button', icon='AXIS_TOP') + + diff --git a/nmv/interface/ui/common/ops_documentation.py b/nmv/interface/ui/common/ops_documentation.py new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/nmv/interface/ui/common/ops_documentation.py @@ -0,0 +1,2 @@ + + diff --git a/nmv/interface/ui/common/rendering_operators.py b/nmv/interface/ui/common/rendering_operators.py new file mode 100644 index 000000000..38e1dfee9 --- /dev/null +++ b/nmv/interface/ui/common/rendering_operators.py @@ -0,0 +1,112 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +# Blender imports +import bpy + + +#################################################################################################### +# @NMV_RenderFrontView +#################################################################################################### +class NMV_RenderFrontView(bpy.types.Operator): + """Render front view of the reconstructed scene in NeuroMorphoVis""" + + # Operator parameters + bl_idname = "nmv.render_front_view_button" + bl_label = "Front" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + + # Render the image and report the rendering time + #context.scene.NMV_SynapticsRenderingTime = nmv.interface.ui.render_synaptics_image( + # self, scene=context.scene, options=nmv.interface.ui_options, + # view=nmv.enums.Camera.View.FRONT) + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderSideView +#################################################################################################### +class NMV_RenderSideView(bpy.types.Operator): + """Render side view of the reconstructed scene in NeuroMorphoVis""" + + # Operator parameters + bl_idname = "nmv.render_side_view_button" + bl_label = "Side" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Render the image and report the rendering time + #context.scene.NMV_SynapticsRenderingTime = nmv.interface.ui.render_synaptics_image( + # self, scene=context.scene, options=nmv.interface.ui_options, + # view=nmv.enums.Camera.View.FRONT) + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderTopView +#################################################################################################### +class NMV_RenderTopView(bpy.types.Operator): + """Render top view of the reconstructed scene in NeuroMorphoVis""" + + # Operator parameters + bl_idname = "nmv.render_top_view_button" + bl_label = "Top" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Render the image and report the rendering time + #context.scene.NMV_SynapticsRenderingTime = nmv.interface.ui.render_synaptics_image( + # self, scene=context.scene, options=nmv.interface.ui_options, + # view=nmv.enums.Camera.View.FRONT) + return {'FINISHED'} + + +#################################################################################################### +# @register_common_operators +#################################################################################################### +def register_common_operators(): + """Registers all the common operators""" + + # Buttons + bpy.utils.register_class(NMV_RenderFrontView) + bpy.utils.register_class(NMV_RenderSideView) + bpy.utils.register_class(NMV_RenderTopView) + + +#################################################################################################### +# @unregister_common_operators +#################################################################################################### +def unregister_common_operators(): + """Un-registers all the common operators""" + + # Buttons + bpy.utils.unregister_class(NMV_RenderFrontView) + bpy.utils.unregister_class(NMV_RenderSideView) + bpy.utils.unregister_class(NMV_RenderTopView) + diff --git a/nmv/interface/ui/common/rendering_ops.py b/nmv/interface/ui/common/rendering_ops.py new file mode 100644 index 000000000..3594961bd --- /dev/null +++ b/nmv/interface/ui/common/rendering_ops.py @@ -0,0 +1,159 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import os +import time + +# Blender imports +import bpy +from mathutils import Vector + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.interface +import nmv.skeleton +import nmv.bbox +import nmv.rendering +import nmv.scene +import nmv.geometry +import nmv.interface + + +#################################################################################################### +# @render_dendrogram +#################################################################################################### +def render_dendrogram(options): + + # Compute the bounding box of the dendrogram (that is a morphology) and stretch it + bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves() + delta = bounding_box.get_largest_dimension() * 0.05 + bounding_box.extend_bbox(delta_x=1.5 * delta, delta_y=delta) + + # Render the dendrogram image, and always use the FRONT view + nmv.rendering.render( + bounding_box=bounding_box, + camera_view=nmv.enums.Camera.View.FRONT, + image_resolution=options.rendering.frame_resolution, + image_name='%s%s' % (options.morphology.label, nmv.consts.Suffix.DENDROGRAM), + image_format=options.rendering.image_format, + image_directory=options.io.images_directory, + keep_camera_in_scene=False) + + +#################################################################################################### +# @render_morphology_image +#################################################################################################### +def render_morphology_image(panel, + options, + view): + + nmv.logger.header('Rendering Image') + + # Start + start_time = time.time() + + # Validate the output directory + #if not nmv.interface.ui.validate_output_directory( + # panel=panel, context_scene=context_scene): + # return + + # Create the images directory if it does not exist + if not nmv.file.ops.path_exists(options.io.images_directory): + nmv.file.ops.clean_and_create_directory(options.io.images_directory) + + # Report the process starting in the UI + panel.report({'INFO'}, 'Rendering ... Wait') + + # If this is a dendrogram rendering, handle it in a very specific way + if options.morphology.reconstruction_method == nmv.enums.Skeleton.Method.DENDROGRAM: + render_dendrogram(options=options) + else: + + # Compute the bounding box for a close up view + if options.rendering_view == nmv.enums.Rendering.View.CLOSEUP: + + # Compute the bounding box for a close up view + bounding_box = nmv.bbox.compute_unified_extent_bounding_box( + extent=context_scene.NMV_MorphologyCloseUpDimensions) + + # Compute the bounding box for a mid shot view + elif context_scene.NMV_MorphologyRenderingView == \ + nmv.enums.Rendering.View.MID_SHOT: + + # Compute the bounding box for the available curves and meshes + bounding_box = nmv.bbox.compute_scene_bounding_box_for_curves_and_meshes() + + # Compute the bounding box for the wide shot view that correspond to the whole morphology + else: + + # Compute the full morphology bounding box + bounding_box = nmv.skeleton.compute_full_morphology_bounding_box( + morphology=nmv.interface.ui_morphology) + + # Get the image suffix + if view == nmv.enums.Camera.View.FRONT: + suffix = nmv.consts.Suffix.MORPHOLOGY_FRONT + elif view == nmv.enums.Camera.View.SIDE: + suffix = nmv.consts.Suffix.MORPHOLOGY_SIDE + elif view == nmv.enums.Camera.View.TOP: + suffix = nmv.consts.Suffix.MORPHOLOGY_TOP + else: + suffix = nmv.consts.Suffix.MORPHOLOGY_FRONT + + # Draw the morphology scale bar + if context_scene.NMV_RenderMorphologyScaleBar: + scale_bar = nmv.interface.draw_scale_bar( + bounding_box=bounding_box, + material_type=nmv.interface.ui_options.shading.morphology_material, + view=view) + + # Render at a specific resolution + if context_scene.NMV_RenderingType == nmv.enums.Rendering.Resolution.FIXED: + + # Render the image + nmv.rendering.render( + bounding_box=bounding_box, + camera_view=view, + image_resolution=context_scene.NMV_MorphologyFrameResolution, + image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), + image_format=image_format, + image_directory=nmv.interface.ui_options.io.images_directory, + keep_camera_in_scene=False) + + # Render at a specific scale factor + else: + + # Render the image + nmv.rendering.render_to_scale( + bounding_box=bounding_box, + camera_view=view, + image_scale_factor=context_scene.NMV_MorphologyFrameScaleFactor, + image_name='%s%s' % (nmv.interface.ui_options.morphology.label, suffix), + image_format=image_format, + image_directory=nmv.interface.ui_options.io.images_directory, + keep_camera_in_scene=False) + + # Delete the morphology scale bar, if rendered + if context_scene.NMV_RenderMorphologyScaleBar: + nmv.scene.delete_object_in_scene(scene_object=scale_bar) + + nmv.logger.statistics('Image rendered in [%f] seconds' % (time.time() - start_time)) + + # Report the process termination in the UI + panel.report({'INFO'}, 'Rendering Done') \ No newline at end of file diff --git a/nmv/interface/ui/common/rendering_options.py b/nmv/interface/ui/common/rendering_options.py new file mode 100644 index 000000000..14c2bc82d --- /dev/null +++ b/nmv/interface/ui/common/rendering_options.py @@ -0,0 +1,91 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.utilities + +# Rendering resolution +bpy.types.Scene.NMV_ResolutionBasis = bpy.props.EnumProperty( + items=[(nmv.enums.Rendering.Resolution.FIXED, + 'Fixed', + 'Render an image at a specific resolution, for example: 1024 or 2048 pixels'), + (nmv.enums.Rendering.Resolution.TO_SCALE, + 'To Scale', + 'The resolution of the final image is a multiple factor of the exact scale of the ' + 'morphology in microns')], + name='Type', + default=nmv.enums.Rendering.Resolution.FIXED) + +# Rendering view, for the Morphology and Mesh Reconstruction panels +bpy.types.Scene.NMV_MorphologyRenderingView = bpy.props.EnumProperty( + items=[(nmv.enums.Rendering.View.WIDE_SHOT, + 'Wide Shot', + 'Renders an image of the full view'), + (nmv.enums.Rendering.View.MID_SHOT, + 'Mid Shot', + 'Renders an image of the reconstructed arbors only'), + (nmv.enums.Rendering.View.CLOSEUP, + 'Close Up', + 'Renders a close up image the focuses on the soma')], + name='View', + default=nmv.enums.Rendering.View.MID_SHOT) + +# Rendering view, for the Synaptics +bpy.types.Scene.NMV_SynapticsRenderingView = bpy.props.EnumProperty( + items=[(nmv.enums.Rendering.View.WIDE_SHOT, + 'Wide Shot', + 'Renders an image of the full view'), + (nmv.enums.Rendering.View.CLOSEUP, + 'Closeup', + 'Renders a closeup image the focuses on the soma of the chosen morphology')], + name='View', + default=nmv.enums.Rendering.View.WIDE_SHOT) + +# Render the corresponding scale bar on the resulting image +bpy.types.Scene.NMV_RenderScaleBar = bpy.props.BoolProperty( + name='Add Scale Bar', + description='Render the scale bar on the rendered image', + default=False) + +# Image format +bpy.types.Scene.NMV_ImageFormat = bpy.props.EnumProperty( + items=nmv.enums.Image.Extension.IMAGE_EXTENSION_ITEMS, + name='', + default=nmv.enums.Image.Extension.PNG) + +# Image resolution +bpy.types.Scene.NMV_FrameResolution = bpy.props.IntProperty( + name='Resolution', + description='The resolution of the image to be rendered', + default=nmv.consts.Image.DEFAULT_RESOLUTION, min=128, max=1024 * 10) + +# Frame scale factor 'for rendering to scale option' +bpy.types.Scene.NMV_ResolutionScaleFactor = bpy.props.FloatProperty( + name="Scale", default=1.0, min=1.0, max=100.0, + description="The scale factor for rendering a scene to scale") + +# The dimensions of the closeup view in microns +bpy.types.Scene.NMV_CloseupDimensions = bpy.props.FloatProperty( + name='Size', + description='The dimensions of the closeup view in microns', + default=20, min=5, max=100) + diff --git a/nmv/interface/ui/data.py b/nmv/interface/ui/data.py deleted file mode 100644 index c2f39bd88..000000000 --- a/nmv/interface/ui/data.py +++ /dev/null @@ -1,50 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# Internal imports -import nmv.options - -# A global variable for the system options. -# All the parameters of the system are stored in this global variable and updated following the -# execution of an active element in the GUI. -# You can access all the parameters of the system as follows: -# ui_options.options.io.VARIABLE : for the input/output directories -# ui_options.options.soma.VARIABLE : for the soma options -# ui_options.options.morphology.VARIABLE : for the morphology options -# ui_options.options.mesh.VARIABLE : for the mesh options -# ui_options.options.analysis.VARIABLE : for the analysis options -# ui_options.options.rendering.VARIABLE : for the rendering options -# ui_options.options.shading.VARIABLE : for the shading options -ui_options = nmv.options.NeuroMorphoVisOptions() - -# The morphology skeleton object loaded after UI interaction -ui_morphology = None - -# All the icons loaded for the UI -ui_icons = None - -# The reconstructed soma mesh object -ui_soma_mesh = None - -# A list of all the objects that correspond to the reconstructed morphology skeleton -ui_reconstructed_skeleton = list() - -# A list of all the objects that correspond to the reconstructed mesh of the neuron -# NOTE: If this list contains a single mesh object, then it accounts for the entire mesh after -# joining all the mesh objects together -ui_reconstructed_mesh = list() - diff --git a/nmv/interface/ui/edit/edit_panel.py b/nmv/interface/ui/edit/edit_panel.py deleted file mode 100644 index 3e5c5d5d4..000000000 --- a/nmv/interface/ui/edit/edit_panel.py +++ /dev/null @@ -1,391 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - - -# System imports -import copy - -# Blender imports -import bpy -from bpy.props import BoolProperty - -# Internal imports -import nmv.edit -import nmv.interface -import nmv.scene -import nmv.consts -import nmv.utilities -import nmv.enums - -# Globals -# Morphology editor -morphology_editor = None - -# A flag to indicate that the morphology has been edited and ready for update -is_skeleton_edited = False -in_edit_mode = False - - -#################################################################################################### -# @EditPanel -#################################################################################################### -class EditPanel(bpy.types.Panel): - """Edit panel""" - - ################################################################################################ - # Panel parameters - ################################################################################################ - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' if nmv.utilities.is_blender_280() else 'TOOLS' - bl_idname = "OBJECT_PT_NMV_MorphologyEditing" - bl_label = 'Morphology Editing' - bl_category = 'NeuroMorphoVis' - bl_options = {'DEFAULT_CLOSED'} - - # Register a variable that indicates that the morphology is sketched to be able to update the UI - bpy.types.Scene.NMV_MorphologySketched = BoolProperty(default=False) - bpy.types.Scene.NMV_MorphologyCoordinatesEdited = BoolProperty(default=False) - - ################################################################################################ - # @draw - ################################################################################################ - def draw(self, - context): - """Draw the panel - - :param context: - Rendering context - """ - - # Get a reference to the panel layout - layout = self.layout - - # Documentation button - documentation_button = layout.column() - documentation_button.operator('nmv.documentation_editing', icon='URL') - documentation_button.separator() - - # Morphology sketching button - if not in_edit_mode: - sketching_morphology_column = layout.column(align=True) - sketching_morphology_column.operator('sketch.skeleton', icon='PARTICLE_POINT') - - # Reconstruction options - edit_coordinates_row = layout.row() - edit_coordinates_row.label(text='Editing Samples Coordinates:', - icon='OUTLINER_OB_EMPTY') - - global is_skeleton_edited - if not is_skeleton_edited: - - # Morphology edit button - edit_morphology_column = layout.column(align=True) - edit_morphology_column.operator('edit.morphology_coordinates', icon='MESH_DATA') - - else: - - # Morphology update buttons - update_morphology_column = layout.column(align=True) - update_morphology_column.operator('update.morphology_coordinates', icon='MESH_DATA') - - # Saving morphology buttons - if not in_edit_mode: - - # Saving morphology options - save_morphology_row = layout.row() - save_morphology_row.label(text='Save Morphology As:', icon='MESH_UVSPHERE') - - save_morphology_buttons_column = layout.column(align=True) - save_morphology_buttons_column.operator('export_morphology.swc', icon='GROUP_VERTEX') - - # Enable or disable the layout - nmv.interface.enable_or_disable_layout(layout) - - -#################################################################################################### -# @SketchSkeleton -#################################################################################################### -class SketchSkeleton(bpy.types.Operator): - """Repair the morphology skeleton, detect the artifacts and fix them - """ - - # Operator parameters - bl_idname = "sketch.skeleton" - bl_label = "Sketch Skeleton" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Rendering context - :return: - 'FINISHED' - """ - - # Clear the scene - nmv.scene.ops.clear_scene() - - # Load the morphology file - loading_result = nmv.interface.ui.load_morphology(self, context.scene) - - # If the result is None, report the issue - if loading_result is None: - self.report({'ERROR'}, 'Please select a morphology file') - return {'FINISHED'} - - # Clone the options to avoid messing the other panels - options_clone = copy.deepcopy(nmv.interface.ui_options) - options_clone.shading.set_default() - - # Plot the morphology (whatever issues it contains) - nmv.interface.ui.sketch_morphology_skeleton_guide( - morphology=nmv.interface.ui_morphology, - options=options_clone) - - return {'FINISHED'} - - -#################################################################################################### -# @UpdateMorphologyCoordinates -#################################################################################################### -class EditMorphologyCoordinates(bpy.types.Operator): - """Update the morphology coordinates following to the repair process - """ - - # Operator parameters - bl_idname = "edit.morphology_coordinates" - bl_label = "Edit Coordinates" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Rendering context - :return: - 'FINISHED """ - - if nmv.interface.ui_morphology is None: - self.report({'ERROR'}, 'Please select a morphology file') - return {'FINISHED'} - - # Create an object of the repairer - global morphology_editor - - # Clear the scene - nmv.scene.ops.clear_scene() - - # Sketch the guide to make sure users can see something - options_clone = copy.deepcopy(nmv.interface.ui_options) - options_clone.morphology.soma_representation = nmv.enums.Soma.Representation.META_BALLS - nmv.interface.ui.sketch_morphology_skeleton_guide( - morphology=nmv.interface.ui_morphology, options=options_clone) - - # Sketch the morphological skeleton for repair - morphology_editor = nmv.edit.MorphologyEditor( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - morphology_editor.sketch_morphology_skeleton() - - # Switch to the wire-frame mode - nmv.scene.switch_interface_to_edit_mode() - - # Switch to edit mode, to be able to export the mesh - bpy.ops.object.mode_set(mode='EDIT') - - # The morphology is edited - global is_skeleton_edited - is_skeleton_edited = True - - # Update the edit mode - global in_edit_mode - in_edit_mode = True - - return {'FINISHED'} - - -#################################################################################################### -# @UpdateMorphologyCoordinates -#################################################################################################### -class UpdateMorphologyCoordinates(bpy.types.Operator): - """Update the morphology coordinates following to the repair process. - """ - - # Operator parameters - bl_idname = "update.morphology_coordinates" - bl_label = "Update Coordinates" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Rendering context - :return: - 'FINISHED' - """ - - # Switch back to the solid mode - nmv.scene.switch_interface_to_visualization_mode() - - # Create an object of the repairer - global morphology_editor - - # Create the morphological skeleton - if morphology_editor is not None: - - # Switch back to object mode, to be able to export the mesh - bpy.ops.object.mode_set(mode='OBJECT') - - # Update the morphology skeleton - morphology_editor.update_skeleton_coordinates() - - global is_skeleton_edited - is_skeleton_edited = False - - # Clear the scene - nmv.scene.ops.clear_scene() - - # Plot the morphology (whatever issues it contains) - nmv.interface.ui.sketch_morphology_skeleton_guide( - morphology=nmv.interface.ui_morphology, - options=copy.deepcopy(nmv.interface.ui_options)) - - # Update the edit mode - global in_edit_mode - in_edit_mode = False - - # Switch back from the analysis mode to the visualization mode - nmv.scene.switch_interface_to_visualization_mode() - - return {'FINISHED'} - - -#################################################################################################### -# @ExportMorphologySWC -#################################################################################################### -class ExportMorphologySWC(bpy.types.Operator): - """Export the reconstructed morphology in an SWC file""" - - # Operator parameters - bl_idname = "export_morphology.swc" - bl_label = "SWC (.swc)" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """ - Executes the operator. - - :param context: Operator context. - :return: {'FINISHED'} - """ - - # Ensure that there is a valid directory where the meshes will be written to - if nmv.interface.ui_options.io.output_directory is None: - self.report({'ERROR'}, nmv.consts.Messages.PATH_NOT_SET) - return {'FINISHED'} - - if not nmv.file.ops.file_ops.path_exists(context.scene.NMV_OutputDirectory): - self.report({'ERROR'}, nmv.consts.Messages.INVALID_OUTPUT_PATH) - return {'FINISHED'} - - # Create the meshes directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.morphologies_directory): - nmv.file.ops.clean_and_create_directory( - nmv.interface.ui_options.io.morphologies_directory) - - # Export the reconstructed morphology as an .swc file - nmv.file.write_morphology_to_swc_file( - nmv.interface.ui_morphology, nmv.interface.ui_options.io.morphologies_directory) - - return {'FINISHED'} - - -#################################################################################################### -# @MorphologyEditingDocumentation -#################################################################################################### -class MorphologyEditingDocumentation(bpy.types.Operator): - """Open the online documentation page of the Morphology Editing panel.""" - - # Operator parameters - bl_idname = "nmv.documentation_editing" - bl_label = "Online User Guide" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Blender context - :return: - 'FINISHED' - """ - - import webbrowser - webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki/Morphology-Editing') - return {'FINISHED'} - - -#################################################################################################### -# @register_panel -#################################################################################################### -def register_panel(): - """Registers all the classes in this panel. - """ - - # Morphology analysis panel - bpy.utils.register_class(EditPanel) - - # Buttons - bpy.utils.register_class(MorphologyEditingDocumentation) - bpy.utils.register_class(SketchSkeleton) - bpy.utils.register_class(EditMorphologyCoordinates) - bpy.utils.register_class(UpdateMorphologyCoordinates) - bpy.utils.register_class(ExportMorphologySWC) - - -#################################################################################################### -# @unregister_panel -#################################################################################################### -def unregister_panel(): - """Un-registers all the classes in this panel. - """ - - # Morphology analysis panel - bpy.utils.unregister_class(EditPanel) - - # Buttons - bpy.utils.unregister_class(MorphologyEditingDocumentation) - bpy.utils.unregister_class(SketchSkeleton) - bpy.utils.unregister_class(EditMorphologyCoordinates) - bpy.utils.unregister_class(UpdateMorphologyCoordinates) - bpy.utils.unregister_class(ExportMorphologySWC) diff --git a/nmv/interface/ui/edit/layout_props.py b/nmv/interface/ui/edit/layout_props.py new file mode 100644 index 000000000..ff016b1e0 --- /dev/null +++ b/nmv/interface/ui/edit/layout_props.py @@ -0,0 +1,77 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +#################################################################################################### +# @draw_documentation_button +#################################################################################################### +def draw_documentation_button(layout): + + row = layout.column() + row.operator('nmv.documentation_editing', icon='URL') + row.separator() + + +#################################################################################################### +# @draw_sketch_skeleton_button +#################################################################################################### +def draw_sketch_skeleton_button(layout): + + row = layout.column(align=True) + row.operator('sketch.skeleton', icon='PARTICLE_POINT') + + +#################################################################################################### +# @draw_skeleton_edit_header +#################################################################################################### +def draw_skeleton_edit_header(layout): + + row = layout.row() + row.label(text='Editing Samples Coordinates', icon='OUTLINER_OB_EMPTY') + + +#################################################################################################### +# @draw_edit_skeleton_button +#################################################################################################### +def draw_edit_skeleton_button(layout): + + draw_skeleton_edit_header(layout=layout) + + row = layout.column(align=True) + row.operator('edit.morphology_coordinates', icon='MESH_DATA') + + +#################################################################################################### +# @draw_update_skeleton_button +#################################################################################################### +def draw_update_skeleton_button(layout): + + draw_skeleton_edit_header(layout=layout) + + row = layout.column(align=True) + row.operator('update.morphology_coordinates', icon='MESH_DATA') + + +#################################################################################################### +# @draw_morphology_export_button +#################################################################################################### +def draw_morphology_export_button(layout): + row = layout.row() + row.label(text='Export Morphology', icon='MESH_UVSPHERE') + + row = layout.column(align=True) + row.operator('export_morphology.swc', icon='GROUP_VERTEX') \ No newline at end of file diff --git a/nmv/interface/ui/edit/ops_documentation.py b/nmv/interface/ui/edit/ops_documentation.py new file mode 100644 index 000000000..b36b58f15 --- /dev/null +++ b/nmv/interface/ui/edit/ops_documentation.py @@ -0,0 +1,39 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @NMV_MorphologyEditingDocumentation +#################################################################################################### +class NMV_MorphologyEditingDocumentation(bpy.types.Operator): + """Open the online documentation page of the Morphology Editing panel""" + + # Operator parameters + bl_idname = "nmv.documentation_editing" + bl_label = "Online User Guide" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + import webbrowser + webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki/Morphology-Editing') + return {'FINISHED'} diff --git a/nmv/interface/ui/edit/ops_edit.py b/nmv/interface/ui/edit/ops_edit.py new file mode 100644 index 000000000..96eb6bdd3 --- /dev/null +++ b/nmv/interface/ui/edit/ops_edit.py @@ -0,0 +1,163 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import copy + +# Blender imports +import bpy + +# Internal imports +import nmv.edit +import nmv.interface +import nmv.scene +import nmv.enums + + +#################################################################################################### +# @NMV_SketchSkeleton +#################################################################################################### +class NMV_SketchSkeleton(bpy.types.Operator): + """Repair the morphology skeleton, detect the artifacts and fix them""" + + # Operator parameters + bl_idname = "sketch.skeleton" + bl_label = "Sketch Skeleton" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Clear the scene + nmv.scene.ops.clear_scene() + + # If no morphology file is loaded, load the morphology file + if nmv.interface.ui_morphology is None: + loading_result = nmv.interface.ui.load_morphology(self, context.scene) + if loading_result is None: + self.report({'ERROR'}, 'Please select a morphology file') + return {'FINISHED'} + + # Clone the options to avoid messing the other panels + options_clone = copy.deepcopy(nmv.interface.ui_options) + options_clone.shading.set_default() + + # Plot the morphology (whatever issues it contains) + nmv.interface.ui.sketch_morphology_skeleton_guide( + morphology=nmv.interface.ui_morphology, + options=options_clone) + + # Done + return {'FINISHED'} + + +#################################################################################################### +# @NMV_EditMorphologyCoordinates +#################################################################################################### +class NMV_EditMorphologyCoordinates(bpy.types.Operator): + """Update the morphology coordinates following to the repair process""" + + # Operator parameters + bl_idname = "edit.morphology_coordinates" + bl_label = "Edit Coordinates" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + if nmv.interface.ui_morphology is None: + self.report({'ERROR'}, 'Please select a morphology file') + return {'FINISHED'} + + # Clear the scene + nmv.scene.ops.clear_scene() + + # Sketch the guide to make sure users can see something + options_clone = copy.deepcopy(nmv.interface.ui_options) + options_clone.morphology.soma_representation = nmv.enums.Soma.Representation.META_BALLS + nmv.interface.ui.sketch_morphology_skeleton_guide( + morphology=nmv.interface.ui_morphology, options=options_clone) + + # Sketch the morphological skeleton for repair + nmv.interface.ui_morphology_editor = nmv.edit.MorphologyEditor( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + nmv.interface.ui_morphology_editor.sketch_morphology_skeleton() + + # Switch to the wire-frame mode + nmv.scene.switch_interface_to_edit_mode() + + # Switch to edit mode, to be able to export the mesh + bpy.ops.object.mode_set(mode='EDIT') + + # The morphology is edited + nmv.interface.ui_is_skeleton_edited = True + + # Update the edit mode + nmv.interface.ui_in_edit_mode = True + + # Done + return {'FINISHED'} + + +#################################################################################################### +# @NMV_UpdateMorphologyCoordinates +#################################################################################################### +class NMV_UpdateMorphologyCoordinates(bpy.types.Operator): + """Update the morphology coordinates following to the repair process""" + + # Operator parameters + bl_idname = "update.morphology_coordinates" + bl_label = "Update Coordinates" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Switch back to the solid mode + nmv.scene.switch_interface_to_visualization_mode() + + # Create the morphological skeleton + if nmv.interface.ui_morphology_editor is not None: + + # Switch back to object mode, to be able to export the mesh + bpy.ops.object.mode_set(mode='OBJECT') + + # Update the morphology skeleton + nmv.interface.ui_morphology_editor.update_skeleton_coordinates() + + # Update the state + nmv.interface.ui_is_skeleton_edited = False + + # Clear the scene + nmv.scene.ops.clear_scene() + + # Plot the morphology (whatever issues it contains) + nmv.interface.ui.sketch_morphology_skeleton_guide( + morphology=nmv.interface.ui_morphology, + options=copy.deepcopy(nmv.interface.ui_options)) + + # Update the edit mode + nmv.interface.ui_in_edit_mode = False + + # Switch back from the analysis mode to the visualization mode + nmv.scene.switch_interface_to_visualization_mode() + + # Done + return {'FINISHED'} diff --git a/nmv/interface/ui/edit/ops_export.py b/nmv/interface/ui/edit/ops_export.py new file mode 100644 index 000000000..8cb7a39d9 --- /dev/null +++ b/nmv/interface/ui/edit/ops_export.py @@ -0,0 +1,59 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.interface +import nmv.consts + + +#################################################################################################### +# @NMV_ExportMorphologySWC +#################################################################################################### +class NMV_ExportMorphologySWC(bpy.types.Operator): + """Export the reconstructed morphology to SWC file""" + + # Operator parameters + bl_idname = "export_morphology.swc" + bl_label = "SWC (.swc)" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Ensure that there is a valid directory where the meshes will be written to + if nmv.interface.ui_options.io.output_directory is None: + self.report({'ERROR'}, nmv.consts.Messages.PATH_NOT_SET) + return {'FINISHED'} + + if not nmv.file.ops.file_ops.path_exists(context.scene.NMV_OutputDirectory): + self.report({'ERROR'}, nmv.consts.Messages.INVALID_OUTPUT_PATH) + return {'FINISHED'} + + # Create the meshes directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.morphologies_directory): + nmv.file.ops.clean_and_create_directory( + nmv.interface.ui_options.io.morphologies_directory) + + # Export the reconstructed morphology as an .swc file + nmv.file.write_morphology_to_swc_file( + nmv.interface.ui_morphology, nmv.interface.ui_options.io.morphologies_directory) + + return {'FINISHED'} \ No newline at end of file diff --git a/nmv/interface/ui/edit/panel.py b/nmv/interface/ui/edit/panel.py new file mode 100644 index 000000000..58603384b --- /dev/null +++ b/nmv/interface/ui/edit/panel.py @@ -0,0 +1,74 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.interface +from .layout_props import * + + +#################################################################################################### +# @NMV_EditPanel +#################################################################################################### +class NMV_EditPanel(bpy.types.Panel): + """Edit panel""" + + ################################################################################################ + # Panel parameters + ################################################################################################ + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_idname = "OBJECT_PT_NMV_MorphologyEditing" + bl_label = 'Morphology Editing' + bl_category = 'NeuroMorphoVis' + bl_options = {'DEFAULT_CLOSED'} + + # Register a variable that indicates that the morphology is sketched to be able to update the UI + bpy.types.Scene.NMV_MorphologySketched = bpy.props.BoolProperty(default=False) + bpy.types.Scene.NMV_MorphologyCoordinatesEdited = bpy.props.BoolProperty(default=False) + + ################################################################################################ + # @draw + ################################################################################################ + def draw(self, context): + + # Draw the documentation button + draw_documentation_button(layout=self.layout) + self.layout.separator() + + # Draw the morphology sketching button if the edit mode is not activated + if not nmv.interface.ui_in_edit_mode: + draw_sketch_skeleton_button(layout=self.layout) + self.layout.separator() + + # If the skeleton is not being edited, draw the edit button, otherwise the update one + if not nmv.interface.ui_is_skeleton_edited: + draw_edit_skeleton_button(layout=self.layout) + self.layout.separator() + else: + draw_update_skeleton_button(layout=self.layout) + self.layout.separator() + + # Draw the morphology export button(s) + if not nmv.interface.ui_in_edit_mode: + draw_morphology_export_button(layout=self.layout) + self.layout.separator() + + # Enable or disable the layout + nmv.interface.enable_or_disable_layout(self.layout) diff --git a/nmv/interface/ui/edit/registration.py b/nmv/interface/ui/edit/registration.py new file mode 100644 index 000000000..366ecf72c --- /dev/null +++ b/nmv/interface/ui/edit/registration.py @@ -0,0 +1,67 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @register_panel +#################################################################################################### +def register_panel(): + """Registers all the classes in this panel""" + + from .panel import NMV_EditPanel + from .ops_documentation import NMV_MorphologyEditingDocumentation + from .ops_edit import NMV_SketchSkeleton + from .ops_edit import NMV_EditMorphologyCoordinates + from .ops_edit import NMV_UpdateMorphologyCoordinates + from .ops_export import NMV_ExportMorphologySWC + + # Morphology analysis panel + bpy.utils.register_class(NMV_EditPanel) + + # Buttons + bpy.utils.register_class(NMV_MorphologyEditingDocumentation) + bpy.utils.register_class(NMV_SketchSkeleton) + bpy.utils.register_class(NMV_EditMorphologyCoordinates) + bpy.utils.register_class(NMV_UpdateMorphologyCoordinates) + bpy.utils.register_class(NMV_ExportMorphologySWC) + + +#################################################################################################### +# @unregister_panel +#################################################################################################### +def unregister_panel(): + """Un-registers all the classes in this panel""" + + from .panel import NMV_EditPanel + from .ops_documentation import NMV_MorphologyEditingDocumentation + from .ops_edit import NMV_SketchSkeleton + from .ops_edit import NMV_EditMorphologyCoordinates + from .ops_edit import NMV_UpdateMorphologyCoordinates + from .ops_export import NMV_ExportMorphologySWC + + # Morphology analysis panel + bpy.utils.unregister_class(NMV_EditPanel) + + # Buttons + bpy.utils.unregister_class(NMV_MorphologyEditingDocumentation) + bpy.utils.unregister_class(NMV_SketchSkeleton) + bpy.utils.unregister_class(NMV_EditMorphologyCoordinates) + bpy.utils.unregister_class(NMV_UpdateMorphologyCoordinates) + bpy.utils.unregister_class(NMV_ExportMorphologySWC) diff --git a/nmv/interface/ui/globals.py b/nmv/interface/ui/globals.py new file mode 100644 index 000000000..aab0ea868 --- /dev/null +++ b/nmv/interface/ui/globals.py @@ -0,0 +1,125 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.options + +# If this flag is set, this means that the system has been initialized and all the icons, fonts +# and other relevant data have been loaded into the system. +nmv_initialized = False + +# NeuroMorphoVisOptions ############################################################################ +# A global variable for the system options. +# All the parameters of the system are stored in this global variable and updated following the +# execution of an active element in the GUI. +# You can access all the parameters of the system as follows: +# ui.globals.options.io.VARIABLE : for the input/output directories +# ui.globals.options.soma.VARIABLE : for the soma options +# ui.globals.options.morphology.VARIABLE : for the morphology options +# ui.globals.options.mesh.VARIABLE : for the mesh options +# ui.globals.options.analysis.VARIABLE : for the analysis options +# ui.globals.options.rendering.VARIABLE : for the rendering options +# ui.globals.options.shading.VARIABLE : for the shading options +# ui.globals.options.synaptics.VARIABLE : for the synaptics options +ui_options = nmv.options.NeuroMorphoVisOptions() + +#################################################################################################### +# Morphology IO #################################################################################### +#################################################################################################### +# The morphology skeleton object loaded after UI interaction. +ui_morphology = None + +# A reference to the circuit loaded in NMV, if any. +ui_circuit = None + +# All the icons loaded for the UI. +ui_icons = None + +#################################################################################################### +# Morphology Analysis ############################################################################## +#################################################################################################### +# This flag is set after the analysis of the morphology. +ui_morphology_analyzed = False + +#################################################################################################### +# Morphology Editing ############################################################################### +#################################################################################################### +# A reference to the editor used to edit and modify the skeleton of the neuronal morphology. +ui_morphology_editor = None + +# A flag to indicate that the morphology has been edited and ready for update +ui_is_skeleton_edited = False + +# A flag that indicates if we are in the edit mode or not +ui_in_edit_mode = False + +#################################################################################################### +# Soma Meshing ##################################################################################### +#################################################################################################### +# A reference to the mesh object of the reconstructed soma. +ui_soma_mesh = None + +# This flag is set to True if the soma mesh is reconstructed in the UI. +ui_soma_reconstructed = False + +# This flag is set if the soma is rendered. +ui_soma_rendered = False + +#################################################################################################### +# Morphology Reconstruction ######################################################################## +#################################################################################################### +# A reference to the morphology builder used to build the morphology skeleton. +ui_morphology_builder = None + +# A list of all the objects that correspond to the reconstructed morphology skeleton +ui_reconstructed_skeleton = list() + +# If this flag is set, this means that a morphology is already reconstructed, and we can display the +# morphology export options to be able to save the morphology to disk. If this flag is not set, +# this means that the morphology reconstruction operator has never been executed. +ui_morphology_reconstructed = False + +# If this flag is set, this means that the reconstructed morphology is rendered at least once. +ui_morphology_rendered = False + +#################################################################################################### +# Synaptics ######################################################################################## +#################################################################################################### +# This flag is set if a valid synaptics file is loaded in the UI. +ui_synaptics_file_loaded = False + +# This flag is set if a synaptics scene is reconstructed. +ui_synaptics_reconstructed = False + +# This flag is set if a synaptics scene is rendered. +ui_synaptics_rendered = False + +#################################################################################################### +# Mesh Reconstruction ############################################################################## +#################################################################################################### +# A list of all the objects that correspond to the reconstructed mesh of the neuron +# NOTE: If this list contains a single mesh object, then it accounts for the entire mesh after +# joining all the mesh objects together +ui_reconstructed_mesh = list() + +# If this flag is set, this means that a mesh is already reconstructed, and we can display the +# mesh export options to be able to export the mesh. If this flag is not set, this means that the +# mesh reconstruction operator has never been executed. +ui_mesh_reconstructed = False + +# If this flag is set, this means that the reconstructed mesh is rendered at least once. +ui_mesh_rendered = False diff --git a/nmv/interface/ui/io/io_panel.py b/nmv/interface/ui/io/io_panel.py deleted file mode 100644 index f0bef11e6..000000000 --- a/nmv/interface/ui/io/io_panel.py +++ /dev/null @@ -1,393 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# System imports -import copy -import time - -# Blender imports -import bpy - -# Internal imports -import nmv.consts -import nmv.enums -import nmv.interface -import nmv.scene -import nmv.utilities -from .io_panel_options import * - - -#################################################################################################### -# @IOPanel -#################################################################################################### -class IOPanel(bpy.types.Panel): - """Input and output data panel""" - - ################################################################################################ - # Panel parameters - ################################################################################################ - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' if nmv.utilities.is_blender_280() else 'TOOLS' - bl_idname = "OBJECT_PT_NMV_IO" - bl_label = 'Input / Output' - bl_category = 'NeuroMorphoVis' - - ################################################################################################ - # @draw - ################################################################################################ - def draw(self, context): - """Draw the panel. - - :param context: - Panel context. - """ - - # Get a reference to the panel layout - layout = self.layout - - # Get a reference to the scene - scene = context.scene - - # Documentation button - documentation_button = layout.column() - documentation_button.operator('nmv.documentation_io', icon='URL') - documentation_button.separator() - - # Input data options - input_data_options_row = layout.row() - input_data_options_row.label(text='Input Data Options:', icon='LIBRARY_DATA_DIRECT') - - # Input source - input_source_row = layout.row() - input_source_row.prop(scene, 'NMV_InputSource') - - # Read the data from a given morphology file either in .h5 or .swc formats - if bpy.context.scene.NMV_InputSource == nmv.enums.Input.H5_SWC_FILE: - morphology_file_row = layout.row() - morphology_file_row.prop(scene, 'NMV_MorphologyFile') - - # Read the data from a specific gid in a given circuit - elif bpy.context.scene.NMV_InputSource == nmv.enums.Input.CIRCUIT_GID: - - blue_config_row = layout.row() - blue_config_row.prop(scene, 'NMV_CircuitFile') - gid_row = layout.row() - gid_row.prop(scene, 'NMV_Gid') - - # Otherwise, ERROR - else: - - # Report an invalid input source - self.report({'ERROR'}, 'Invalid Input Source') - - # Center the morphology at the origin - centering_check_box = layout.row() - centering_check_box.prop(scene, 'NMV_CenterMorphologyAtOrigin') - nmv.interface.ui_options.morphology.center_at_origin = scene.NMV_CenterMorphologyAtOrigin - - import_button = layout.column() - import_button.operator('nmv.load_morphology', icon='ANIM_DATA') - import_button.separator() - - # Stats - if nmv.interface.ui_morphology is not None: - morphology_stats_row = layout.row() - morphology_stats_row.label(text='Stats:', icon='RECOVER_LAST') - - loading_time_row = layout.row() - loading_time_row.prop(scene, 'NMV_MorphologyLoadingTime') - loading_time_row.enabled = False - - drawing_time_row = layout.row() - drawing_time_row.prop(scene, 'NMV_MorphologyDrawingTime') - drawing_time_row.enabled = False - - # Output options - output_data_options_row = layout.row() - output_data_options_row.label(text='Output Options:', icon='SCRIPT') - - # Output directory - output_directory_row = layout.row() - output_directory_row.prop(scene, 'NMV_OutputDirectory') - - # Default paths - default_paths_row = layout.row() - default_paths_row.prop(scene, 'NMV_DefaultArtifactsRelativePath') - - # Images path - images_path_row = layout.row() - images_path_row.prop(scene, 'NMV_ImagesPath') - - # Sequences path - sequences_path_row = layout.row() - sequences_path_row.prop(scene, 'NMV_SequencesPath') - - # Meshes path - meshes_path_row = layout.row() - meshes_path_row.prop(scene, 'NMV_MeshesPath') - - # Morphologies path - morphologies_path_row = layout.row() - morphologies_path_row.prop(scene, 'NMV_MorphologiesPath') - - # Analysis path - analysis_path_row = layout.row() - analysis_path_row.prop(scene, 'NMV_AnalysisPath') - - # Stats. path - stats_path_row = layout.row() - stats_path_row.prop(scene, 'NMV_StatisticsPath') - - # Disable the default paths selection if the use default paths flag is set - if scene.NMV_DefaultArtifactsRelativePath: - images_path_row.enabled = False - sequences_path_row.enabled = False - meshes_path_row.enabled = False - morphologies_path_row.enabled = False - analysis_path_row.enabled = False - stats_path_row.enabled = False - - # Pass options from UI to system - if 'Select Directory' in scene.NMV_OutputDirectory: - nmv.interface.ui_options.io.output_directory = None - else: - nmv.interface.ui_options.io.output_directory = \ - scene.NMV_OutputDirectory - nmv.interface.ui_options.io.images_directory = \ - '%s/%s' % (scene.NMV_OutputDirectory, scene.NMV_ImagesPath) - nmv.interface.ui_options.io.sequences_directory = \ - '%s/%s' % (scene.NMV_OutputDirectory, scene.NMV_SequencesPath) - nmv.interface.ui_options.io.morphologies_directory = \ - '%s/%s' % (scene.NMV_OutputDirectory, scene.NMV_MorphologiesPath) - nmv.interface.ui_options.io.meshes_directory = \ - '%s/%s' % (scene.NMV_OutputDirectory, scene.NMV_MeshesPath) - nmv.interface.ui_options.io.analysis_directory = \ - '%s/%s' % (scene.NMV_OutputDirectory, scene.NMV_AnalysisPath) - nmv.interface.ui_options.io.statistics_directory = \ - '%s/%s' % (scene.NMV_OutputDirectory, scene.NMV_StatisticsPath) - - -#################################################################################################### -# @SketchSkeleton -#################################################################################################### -class LoadMorphology(bpy.types.Operator): - """Loads morphology - """ - - # Operator parameters - bl_idname = "nmv.load_morphology" - bl_label = "Load" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Rendering context - :return: - 'FINISHED' - """ - - # Clear the scene - import nmv.scene - nmv.scene.reset_scene() - nmv.scene.clear_scene() - - # By default, set the background to transparent - nmv.scene.set_transparent_background() - - # Load the images - logo_tex = bpy.data.textures.new("nmv-logo", "IMAGE") - logo_tex.image = bpy.data.images.load( - "%s/%s" % (nmv.consts.Paths.IMAGES_PATH, 'nmv-logo.png')) - logo_tex.extension = 'CLIP' - - # Initialize all the operations that needs to run once and for all - import nmv.interface - if not nmv.interface.ui.Globals.nmv_initialized: - nmv.interface.load_fonts() - - # Load the morphology file - start_time = time.time() - loading_result = nmv.interface.ui.load_morphology(self, context.scene) - loading_time = time.time() - context.scene.NMV_MorphologyLoadingTime = loading_time - start_time - - # If the result is None, report the issue - if loading_result is None: - self.report({'ERROR'}, 'Please select a morphology file') - return {'FINISHED'} - - nmv.logger.header('Loading Morphology') - nmv.logger.info('Morphology: %s' % nmv.interface.ui_morphology.label) - nmv.logger.info('Morphology loaded in [%f] seconds' % - context.scene.NMV_MorphologyLoadingTime) - - # Clear the scene - import nmv.scene - nmv.scene.clear_scene() - - # Create a builder object to build the morphology skeleton - import nmv.builders - - # Always use meta builder to reconstruct the initial soma - options = copy.deepcopy(nmv.interface.ui_options) - options.morphology.set_default() - options.shading.set_default() - - # Use branching order of 2 for the axons to ensure that we can see the whole morphology, - # while use the entire branching order for the astrocytes to ensure that it is connected - if nmv.interface.ui_morphology.is_astrocyte: - options.morphology.axon_branch_order = nmv.consts.Skeleton.MAX_BRANCHING_ORDER - else: - options.morphology.axon_branch_order = 2 - - # Create the builder - builder = nmv.builders.DisconnectedSectionsBuilder( - morphology=nmv.interface.ui_morphology, options=options, force_meta_ball_soma=False) - nmv.interface.ui_reconstructed_skeleton = builder.draw_morphology_skeleton() - - drawing_time = time.time() - context.scene.NMV_MorphologyDrawingTime = drawing_time - loading_time - - nmv.logger.header('Stats.') - nmv.logger.info('Morphology drawn in [%f] seconds' % - context.scene.NMV_MorphologyDrawingTime) - - # Switch to the top view - nmv.scene.view_axis() - - # View all the objects in the scene - # if not nmv.interface.ui.Globals.nmv_initialized: - nmv.scene.ops.view_all_scene() - - # Configure the output directory - nmv.interface.configure_output_directory(options=nmv.interface.ui_options, context=context) - - # Use the event timer to update the UI during the soma building - wm = context.window_manager - # self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) - wm.modal_handler_add(self) - - # - if not nmv.interface.ui.Globals.nmv_initialized: - nmv.interface.ui.Globals.nmv_initialized = True - - # Modal - return {'RUNNING_MODAL'} - - ############################################################################################### - # @modal - ################################################################################################ - def modal(self, - context, - event): - """ - Threading and non-blocking handling. - - :param context: - Panel context. - :param event: - A given event for the panel. - """ - - # Analyze the morphology once loaded as well - analysis_time = time.time() - nmv.interface.analyze_morphology(morphology=nmv.interface.ui_morphology, context=context) - nmv.logger.info_done('Morphology analyzed in [%f] seconds' % (time.time() - analysis_time)) - - # Done - return {'FINISHED'} - - ################################################################################################ - # @cancel - ################################################################################################ - def cancel(self, context): - """ - Cancel the panel processing and return to the interaction mode. - - :param context: - Panel context. - """ - - # Finished - return {'FINISHED'} - - -#################################################################################################### -# @InputOutputDocumentation -#################################################################################################### -class InputOutputDocumentation(bpy.types.Operator): - """Open the online documentation page of the IO panel.""" - - # Operator parameters - bl_idname = "nmv.documentation_io" - bl_label = "Online User Guide" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Blender context - :return: - 'FINISHED' - """ - - import webbrowser - webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki/Input-&-Output') - return {'FINISHED'} - - -#################################################################################################### -# @register_panel -#################################################################################################### -def register_panel(): - """Registers all the classes in this panel""" - - # Once loaded activate the mode - nmv.scene.activate_neuromorphovis_mode() - - # InputOutput data - bpy.utils.register_class(IOPanel) - - # Buttons - bpy.utils.register_class(InputOutputDocumentation) - bpy.utils.register_class(LoadMorphology) - - -#################################################################################################### -# @unregister_panel -#################################################################################################### -def unregister_panel(): - """Un-registers all the classes in this panel""" - - # Get back to the original theme - nmv.scene.deactivate_neuromorphovis_mode() - - # InputOutput data - bpy.utils.unregister_class(IOPanel) - - # Buttons - bpy.utils.unregister_class(InputOutputDocumentation) - bpy.utils.unregister_class(LoadMorphology) diff --git a/nmv/interface/ui/io/layout_props.py b/nmv/interface/ui/io/layout_props.py new file mode 100644 index 000000000..1cf20cc0a --- /dev/null +++ b/nmv/interface/ui/io/layout_props.py @@ -0,0 +1,261 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums + + +#################################################################################################### +# @draw_io_documentation_button +#################################################################################################### +def draw_io_documentation_button(panel): + + row = panel.layout.column() + row.operator('nmv.documentation_io', icon='URL') + row.separator() + + +#################################################################################################### +# @draw_morphology_loading_button +#################################################################################################### +def draw_morphology_loading_button(panel): + + row = panel.layout.column() + row.operator('nmv.load_morphology', icon='ANIM_DATA') + + +#################################################################################################### +# @draw_input_data_header +#################################################################################################### +def draw_input_data_header(panel): + + row = panel.layout.row() + row.label(text='Input Data Options', icon='LIBRARY_DATA_DIRECT') + + +#################################################################################################### +# @draw_input_source +#################################################################################################### +def draw_input_source(panel, scene, options): + + row = panel.layout.row() + row.prop(scene, 'NMV_InputSource') + options.io.input_source = scene.NMV_InputSource + + +#################################################################################################### +# @draw_morphology_file_path_option +#################################################################################################### +def draw_morphology_file_path_option(panel, scene, options): + + row = panel.layout.row() + row.prop(scene, 'NMV_MorphologyFile') + options.morphology.morphology_file_path = scene.NMV_MorphologyFile + + +#################################################################################################### +# @draw_circuit_file_path_option +#################################################################################################### +def draw_circuit_file_path_option(panel, scene, options): + + row = panel.layout.row() + row.prop(scene, 'NMV_CircuitFile') + options.morphology.blue_config = scene.NMV_CircuitFile + + +#################################################################################################### +# @draw_morphology_gid_option +#################################################################################################### +def draw_morphology_gid_option(panel, scene, options): + + row = panel.layout.row() + row.prop(scene, 'NMV_Gid') + options.morphology.gid = scene.NMV_Gid + + +#################################################################################################### +# @draw_morphology_centering_option +#################################################################################################### +def draw_morphology_centering_option(panel, scene, options): + + row = panel.layout.row() + row.prop(scene, 'NMV_CenterMorphologyAtOrigin') + options.morphology.center_at_origin = scene.NMV_CenterMorphologyAtOrigin + + +#################################################################################################### +# @draw_input_data_options +#################################################################################################### +def draw_input_data_options(panel, scene, options): + + draw_input_data_header(panel=panel) + draw_input_source(panel=panel, scene=scene, options=options) + + if options.io.input_source == nmv.enums.Input.MORPHOLOGY_FILE: + draw_morphology_file_path_option(panel=panel, scene=scene, options=options) + + elif options.io.input_source == nmv.enums.Input.CIRCUIT_GID: + draw_circuit_file_path_option(panel=panel, scene=scene, options=options) + draw_morphology_gid_option(panel=panel, scene=scene, options=options) + + else: + panel.report({'ERROR'}, 'Invalid Input Source') + nmv.logger.log('UI_ERROR: draw_input_output_panel_options') + + draw_morphology_centering_option(panel=panel, scene=scene, options=options) + + +#################################################################################################### +# @draw_morphology_loading_statistics +#################################################################################################### +def draw_morphology_loading_statistics(panel, scene, morphology): + + # Display the loading statistics after loading the morphology + if morphology is not None: + + # Loading time + row = panel.layout.row() + row.prop(scene, 'NMV_MorphologyLoadingTime') + row.enabled = False + + # Drawing time + row = panel.layout.row() + row.prop(scene, 'NMV_MorphologyDrawingTime') + row.enabled = False + + +#################################################################################################### +# @draw_output_data_header +#################################################################################################### +def draw_output_data_header(panel): + + row = panel.layout.row() + row.label(text='Output Options', icon='SCRIPT') + + +#################################################################################################### +# @draw_output_directory_option +#################################################################################################### +def draw_output_directory_option(panel, scene, options): + + row = panel.layout.row() + row.prop(scene, 'NMV_OutputDirectory') + options.io.output_directory = scene.NMV_OutputDirectory + + +#################################################################################################### +# @draw_default_paths_option +#################################################################################################### +def draw_default_paths_option(panel, scene, options): + + row = panel.layout.row() + row.prop(scene, 'NMV_DefaultArtifactsRelativePath') + options.io.use_default_path_for_artifacts = scene.NMV_DefaultArtifactsRelativePath + + +#################################################################################################### +# @draw_images_path_option +#################################################################################################### +def draw_images_path_option(panel, scene, enabled): + row = panel.layout.row() + row.prop(scene, 'NMV_ImagesPath') + row.enabled = enabled + + +#################################################################################################### +# @draw_sequences_path_option +#################################################################################################### +def draw_sequences_path_option(panel, scene, enabled): + + row = panel.layout.row() + row.prop(scene, 'NMV_SequencesPath') + row.enabled = enabled + + +#################################################################################################### +# @draw_meshes_path_option +#################################################################################################### +def draw_meshes_path_option(panel, scene, enabled): + + row = panel.layout.row() + row.prop(scene, 'NMV_MeshesPath') + row.enabled = enabled + + +#################################################################################################### +# @draw_morphologies_path_option +#################################################################################################### +def draw_morphologies_path_option(panel, scene, enabled): + + row = panel.layout.row() + row.prop(scene, 'NMV_MorphologiesPath') + row.enabled = enabled + + +#################################################################################################### +# @draw_analysis_path_option +#################################################################################################### +def draw_analysis_path_option(panel, scene, enabled): + + row = panel.layout.row() + row.prop(scene, 'NMV_AnalysisPath') + row.enabled = enabled + + +#################################################################################################### +# @draw_stats_path_option +#################################################################################################### +def draw_stats_path_option(panel, scene, enabled): + + row = panel.layout.row() + row.prop(scene, 'NMV_StatisticsPath') + row.enabled = enabled + + +#################################################################################################### +# @draw_output_options +#################################################################################################### +def draw_output_data_options(panel, scene, options): + + draw_output_data_header(panel=panel) + draw_output_directory_option(panel=panel, scene=scene, options=options) + draw_default_paths_option(panel=panel, scene=scene, options=options) + + enabled = not options.io.use_default_path_for_artifacts + draw_images_path_option(panel=panel, scene=scene, enabled=enabled) + draw_sequences_path_option(panel=panel, scene=scene, enabled=enabled) + draw_meshes_path_option(panel=panel, scene=scene, enabled=enabled) + draw_morphologies_path_option(panel=panel, scene=scene, enabled=enabled) + draw_analysis_path_option(panel=panel, scene=scene, enabled=enabled) + draw_stats_path_option(panel=panel, scene=scene, enabled=enabled) + + # Pass options from UI to system + if 'Select Directory' in scene.NMV_OutputDirectory: + options.io.output_directory = None + else: + output_dir = options.io.output_directory + options.io.output_directory = output_dir + options.io.images_directory = '%s/%s' % (output_dir, scene.NMV_ImagesPath) + options.io.sequences_directory = '%s/%s' % (output_dir, scene.NMV_SequencesPath) + options.io.morphologies_directory = '%s/%s' % (output_dir, scene.NMV_MorphologiesPath) + options.io.meshes_directory = '%s/%s' % (output_dir, scene.NMV_MeshesPath) + options.io.analysis_directory = '%s/%s' % (output_dir, scene.NMV_AnalysisPath) + options.io.statistics_directory = '%s/%s' % (output_dir, scene.NMV_StatisticsPath) \ No newline at end of file diff --git a/nmv/interface/ui/io/ops.py b/nmv/interface/ui/io/ops.py new file mode 100644 index 000000000..456510c2d --- /dev/null +++ b/nmv/interface/ui/io/ops.py @@ -0,0 +1,163 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time +import copy + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.scene + + +#################################################################################################### +# @NMV_LoadMorphology +#################################################################################################### +class NMV_LoadMorphology(bpy.types.Operator): + """Loads the morphology into the Blender scene""" + + # Operator parameters + bl_idname = "nmv.load_morphology" + bl_label = "Load Morphology" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + import nmv.interface + import nmv.builders + + # Load the morphology + start_time = time.time() + loading_result = nmv.interface.ui.load_morphology(panel=self, scene=context.scene) + loading_time = time.time() + context.scene.NMV_MorphologyLoadingTime = loading_time - start_time + + # If the loading result is None, terminate the loading process, ERROR handling is done + # within the scope of the function @nmv.interface.ui.load_morphology + if loading_result is None: + return {'FINISHED'} + + # If the morphology is successfully loaded + nmv.logger.header('Loading Morphology') + nmv.logger.info('Morphology: %s' % nmv.interface.ui_morphology.label) + nmv.logger.info('Morphology loaded in [%f] seconds' % + context.scene.NMV_MorphologyLoadingTime) + + # Get ready to draw the morphology to the scene, therefore clear the scene + nmv.scene.clear_scene() + + # By default, set the background to transparent + nmv.scene.set_transparent_background() + + # Initialize all the operations that needs to run once and for all + if not nmv.interface.ui.globals.nmv_initialized: + nmv.interface.load_fonts() + + # Always use meta builder to reconstruct the initial soma + options = copy.deepcopy(nmv.interface.ui_options) + options.morphology.set_default() + options.shading.set_default() + + # Use branching order of 2 for the axons to ensure that we can see the whole morphology, + # while use the entire branching order for the astrocytes to ensure that it is connected + if nmv.interface.ui_morphology.is_astrocyte: + options.morphology.axon_branch_order = nmv.consts.Skeleton.MAX_BRANCHING_ORDER + else: + options.morphology.axon_branch_order = 2 + + # Create the builder + builder = nmv.builders.DisconnectedSectionsBuilder( + morphology=nmv.interface.ui_morphology, options=options, force_meta_ball_soma=False) + nmv.interface.ui_reconstructed_skeleton = builder.draw_morphology_skeleton() + + drawing_time = time.time() + context.scene.NMV_MorphologyDrawingTime = drawing_time - loading_time + + nmv.logger.header('Stats.') + nmv.logger.info('Morphology drawn in [%f] seconds' % + context.scene.NMV_MorphologyDrawingTime) + + # Switch to the top view, to make the user realize that this is a new morphology + nmv.scene.view_axis() + + # View all the objects in the scene + # if not nmv.interface.ui.Globals.nmv_initialized: + nmv.scene.ops.view_all_scene() + + # Configure the output directory + nmv.interface.configure_output_directory(options=nmv.interface.ui_options, context=context) + + # Use the event timer to update the UI during the soma building + wm = context.window_manager + wm.modal_handler_add(self) + + # Switch the initialization flag to be able to use the add-on in the rest of the panel + if not nmv.interface.ui.globals.nmv_initialized: + nmv.interface.ui.globals.nmv_initialized = True + + # Initialize other relevant information that could be required later + nmv.interface.initialize_relevant_parameters(scene=context.scene) + + # Modal + return {'RUNNING_MODAL'} + + ############################################################################################### + # @modal + ################################################################################################ + def modal(self, context, event): + + # Analyze the morphology once loaded as well + analysis_time = time.time() + nmv.interface.analyze_morphology(morphology=nmv.interface.ui_morphology, context=context) + nmv.logger.info_done('Morphology analyzed in [%f] seconds' % (time.time() - analysis_time)) + + # Done + return {'FINISHED'} + + ################################################################################################ + # @cancel + ################################################################################################ + def cancel(self, context): + + # Finished + return {'FINISHED'} + + +#################################################################################################### +# @NMV_InputOutputDocumentation +#################################################################################################### +class NMV_InputOutputDocumentation(bpy.types.Operator): + """Open the online documentation page of the IO panel.""" + + # Operator parameters + bl_idname = "nmv.documentation_io" + bl_label = "Online User Guide" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + import webbrowser + webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki/Input-&-Output') + return {'FINISHED'} diff --git a/nmv/interface/ui/io/panel.py b/nmv/interface/ui/io/panel.py new file mode 100644 index 000000000..6d1fd7701 --- /dev/null +++ b/nmv/interface/ui/io/panel.py @@ -0,0 +1,72 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import copy +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.scene +import nmv.utilities + +from .layout_props import * + + +#################################################################################################### +# @NMV_IOPanel +#################################################################################################### +class NMV_IOPanel(bpy.types.Panel): + """The input and output data panel of NeuroMorphoVis""" + + ################################################################################################ + # Panel parameters + ################################################################################################ + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_idname = "OBJECT_PT_NMV_IO" + bl_label = 'Input / Output' + bl_category = 'NeuroMorphoVis' + + ################################################################################################ + # @draw + ################################################################################################ + def draw(self, context): + + draw_io_documentation_button(panel=self) + self.layout.separator() + + draw_input_data_options( + panel=self, scene=context.scene, options=nmv.interface.ui_options) + self.layout.separator() + + draw_morphology_loading_button(panel=self) + self.layout.separator() + + draw_morphology_loading_statistics( + panel=self, scene=context.scene, morphology=nmv.interface.ui_morphology) + self.layout.separator() + + draw_output_data_options( + panel=self, scene=context.scene, options=nmv.interface.ui_options) + + + diff --git a/nmv/interface/ui/io/io_panel_options.py b/nmv/interface/ui/io/panel_props.py similarity index 64% rename from nmv/interface/ui/io/io_panel_options.py rename to nmv/interface/ui/io/panel_props.py index f492e8b97..ca0c4c039 100644 --- a/nmv/interface/ui/io/io_panel_options.py +++ b/nmv/interface/ui/io/panel_props.py @@ -1,5 +1,5 @@ #################################################################################################### -# Copyright (c) 2019, EPFL / Blue Brain Project +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project # Marwan Abdellah # # This file is part of NeuroMorphoVis @@ -15,64 +15,77 @@ # If not, see . #################################################################################################### + # Blender imports import bpy # Internal imports +import nmv.consts import nmv.enums -import nmv.scene # Input options (what is the input source) bpy.types.Scene.NMV_InputSource = bpy.props.EnumProperty( - items=[(nmv.enums.Input.H5_SWC_FILE, - 'H5, SWC or ASC File', - 'Load individual .h5, .swc or .asc file without a circuit'), + items=[(nmv.enums.Input.MORPHOLOGY_FILE, + '.SWC, .ASC or .H5 File', + 'Load individual .SWC, .ASC or .H5 morphology file of a neuron or an astrocyte. ' + 'For the astrocyte file format in H5 files, please refer to the paper by ' + 'Abdellah et al. 2021'), (nmv.enums.Input.CIRCUIT_GID, - 'BBP Circuit (GID)', - 'Load a specific GID from the circuit config')], + 'Circuit (GID)', + 'Load the morphology of either a neuron or an astrocyte with a specific GID from ' + 'a digitally reconstructed circuit using a circuit configuration file. The current ' + 'version of NeuroMorphoVis supports loading libSonata circuits. For details, please ' + 'refer to the software suits at https://github.com/BlueBrain')], name="Input Source", - default=nmv.enums.Input.H5_SWC_FILE) + default=nmv.enums.Input.MORPHOLOGY_FILE) # Center the loaded morphology at the origin bpy.types.Scene.NMV_CenterMorphologyAtOrigin = bpy.props.BoolProperty( name='Center At Origin', - description='Center the loaded morphology at the origin irrespective to its actual center', + description='Center the loaded morphology at the origin irrespective to its actual center. ' + 'If the soma is already located at the origin, then checking this checkbox is ' + 'irrelevant', default=True) # Morphology file bpy.types.Scene.NMV_MorphologyFile = bpy.props.StringProperty( name="Morphology File", description="Select a specific morphology to load, visualize or to mesh", - default='Select File', maxlen=2048, subtype='FILE_PATH') + default=nmv.consts.Strings.SELECT_FILE, maxlen=2048, subtype='FILE_PATH') # Morphology directory bpy.types.Scene.NMV_MorphologyDirectory = bpy.props.StringProperty( name="Morphology Directory", - description="Select a directory to mesh all the morphologies in it", - default="Select Directory", maxlen=2048, subtype='DIR_PATH') + description="Select a directory to that contains multiple morphology files in it", + default=nmv.consts.Strings.SELECT_DIRECTORY, maxlen=2048, subtype='DIR_PATH') -# Circuit file or BlueConfig +# A circuit configuration file bpy.types.Scene.NMV_CircuitFile = bpy.props.StringProperty( name="Circuit File", - description="Select a BBP circuit file (or blue config)", - default="Select Circuit File", maxlen=2048, subtype='FILE_PATH') + description="Select a circuit file. The current version of NeuroMorphoVis supports loading " + "BBP circuits that can be loaded with BluePy or recent circuits that can be loaded " + "with libSonata", + default=nmv.consts.Strings.SELECT_CIRCUIT_FILE, maxlen=2048, subtype='FILE_PATH') # Circuit target bpy.types.Scene.NMV_Target = bpy.props.StringProperty( name="Target", - description="Select a specific target that must exist in the circuit", - default="Add Target", maxlen=1024) + description="Select a specific circuit target that must exist in the circuit and contain " + "more than a single GID, or at least a single GID", + default=nmv.consts.Strings.ADD_TARGET, maxlen=1024) # Neuron GID bpy.types.Scene.NMV_Gid = bpy.props.StringProperty( name="GID", description="Select a specific GID in the circuit", - default="Add a GID", maxlen=1024) + default=nmv.consts.Strings.ADD_GID, maxlen=1024) # Loading time bpy.types.Scene.NMV_MorphologyLoadingTime = bpy.props.FloatProperty( name="Loading Morphology (Sec)", - description="The time it takes to load the morphology from file and draw it to the viewport", + description="The time to load the morphology from file and draw it to viewport. If the neuron " + "is loaded from a circuit, it might takes several second depending on the " + "network performance to load from a remote file system", default=0, min=0, max=1000000) # Drawing time @@ -84,7 +97,8 @@ # Output directory bpy.types.Scene.NMV_OutputDirectory = bpy.props.StringProperty( name="Output Directory", - description="Select a directory where the results will be generated", + description="Select a directory where the results (analysis, images, movies, meshes, etc.) " + "will be generated", default="Select Directory", maxlen=5000, subtype='DIR_PATH') # Use default paths for the artifacts diff --git a/nmv/interface/ui/io/registration.py b/nmv/interface/ui/io/registration.py new file mode 100644 index 000000000..c4d3333f6 --- /dev/null +++ b/nmv/interface/ui/io/registration.py @@ -0,0 +1,64 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.scene + + +#################################################################################################### +# @register_panel +#################################################################################################### +def register_panel(): + """Registers all the classes in this panel""" + + # Once loaded, activate the mode rendering mode in the viewport + nmv.scene.activate_neuromorphovis_mode() + + from .panel import NMV_IOPanel + from .ops import NMV_InputOutputDocumentation + from .ops import NMV_LoadMorphology + + # Input/Output panel + bpy.utils.register_class(NMV_IOPanel) + + # Buttons + bpy.utils.register_class(NMV_InputOutputDocumentation) + bpy.utils.register_class(NMV_LoadMorphology) + + +#################################################################################################### +# @unregister_panel +#################################################################################################### +def unregister_panel(): + """Un-registers all the classes in this panel""" + + # Get back to the original theme + nmv.scene.deactivate_neuromorphovis_mode() + + from .panel import NMV_IOPanel + from .ops import NMV_InputOutputDocumentation + from .ops import NMV_LoadMorphology + + # Input/Output panel + bpy.utils.unregister_class(NMV_IOPanel) + + # Buttons + bpy.utils.unregister_class(NMV_InputOutputDocumentation) + bpy.utils.unregister_class(NMV_LoadMorphology) diff --git a/nmv/interface/ui/mesh/layout_buttons.py b/nmv/interface/ui/mesh/layout_buttons.py new file mode 100644 index 000000000..d208bc070 --- /dev/null +++ b/nmv/interface/ui/mesh/layout_buttons.py @@ -0,0 +1,116 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + + +#################################################################################################### +# @draw_documentation_button +#################################################################################################### +def draw_documentation_button(layout): + + row = layout.row() + row.operator('nmv.documentation_mesh', icon='URL') + + +#################################################################################################### +# @draw_mesh_reconstruction_button +#################################################################################################### +def draw_mesh_reconstruction_button(panel, scene): + + # Mesh quick reconstruction options + row = panel.layout.row() + row.label(text='Quick Reconstruction', icon='PARTICLE_POINT') + + # Mesh reconstruction options + row = panel.layout.row() + row.operator('nmv.reconstruct_mesh', icon='MESH_DATA') + + if nmv.interface.ui_mesh_reconstructed: + row = panel.layout.row() + row.prop(scene, 'NMV_MeshReconstructionTime') + row.enabled = False + + +#################################################################################################### +# @draw_mesh_rendering_buttons +#################################################################################################### +def draw_mesh_rendering_buttons(panel): + + view_row = panel.layout.row() + view_row.label(text='Render View', icon='RESTRICT_RENDER_OFF') + buttons_row = panel.layout.row(align=True) + buttons_row.operator('nmv.render_mesh_front', icon='AXIS_FRONT') + buttons_row.operator('nmv.render_mesh_side', icon='AXIS_SIDE') + buttons_row.operator('nmv.render_mesh_top', icon='AXIS_TOP') + + if nmv.interface.ui_mesh_reconstructed: + view_row.enabled = True + buttons_row.enabled = True + else: + view_row.enabled = False + buttons_row.enabled = False + + +#################################################################################################### +# @draw_export_components_option +#################################################################################################### +def draw_export_components_option(panel, scene, options): + + # The objects must be disconnected + if options.mesh.neuron_objects_connection == nmv.enums.Meshing.ObjectsConnection.DISCONNECTED: + + # It does work only with OBJ files + if scene.NMV_ExportedMeshFormat == nmv.enums.Meshing.ExportFormat.OBJ: + + # Only for the piecewise-watertight and skinning-based meshing + if scene.NMV_MeshingTechnique == nmv.enums.Meshing.Technique.PIECEWISE_WATERTIGHT or \ + scene.NMV_MeshingTechnique == nmv.enums.Meshing.Technique.SKINNING: + export_individual_row = panel.layout.row() + export_individual_row.prop(scene, 'NMV_ExportIndividuals') + + +#################################################################################################### +# @draw_mesh_export_options +#################################################################################################### +def draw_mesh_export_options(panel, scene, options): + + # Mesh export + save_neuron_mesh_row = panel.layout.row() + save_neuron_mesh_row.label(text='Export Mesh', icon='MESH_UVSPHERE') + + export_format_row = panel.layout.row() + export_format_row.prop(scene, 'NMV_ExportedMeshFormat', icon='GROUP_VERTEX') + + # Display the export components option + draw_export_components_option(panel=panel, scene=scene, options=options) + + # Save button + save_neuron_mesh_buttons_column = panel.layout.column(align=True) + save_neuron_mesh_buttons_column.operator('nmv.export_mesh', icon='MESH_DATA') + + if nmv.interface.ui_mesh_reconstructed: + export_format_row.enabled = True + save_neuron_mesh_buttons_column.enabled = True + else: + export_format_row.enabled = False + save_neuron_mesh_buttons_column.enabled = False + diff --git a/nmv/interface/ui/mesh/layout_color_props.py b/nmv/interface/ui/mesh/layout_color_props.py new file mode 100644 index 000000000..61c2e5a36 --- /dev/null +++ b/nmv/interface/ui/mesh/layout_color_props.py @@ -0,0 +1,203 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +from mathutils import Vector + +# Internal imports +import nmv.consts +import nmv.enums + + +#################################################################################################### +# @draw_mesh_colors_header +#################################################################################################### +def draw_mesh_colors_header(layout): + + row = layout.row() + row.label(text='Mesh Colors', icon='COLOR') + + +#################################################################################################### +# @draw_mesh_shading_option +#################################################################################################### +def draw_mesh_shading_option(layout, scene, options): + + row = layout.row() + row.label(text='Shading') + row.prop(scene, 'NMV_MeshMaterial') + options.shading.mesh_material = scene.NMV_MeshMaterial + + +#################################################################################################### +# @draw_soma_color_option +#################################################################################################### +def draw_soma_color_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SomaColor') + rgb = scene.NMV_SomaColor + options.shading.mesh_soma_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_axons_color_option +#################################################################################################### +def draw_axons_color_option(layout, scene, options, morphology): + + if morphology.has_axons(): + row = layout.row() + row.prop(scene, 'NMV_AxonColor') + rgb = scene.NMV_AxonColor + options.shading.mesh_axons_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_basal_dendrites_color_option +#################################################################################################### +def draw_basal_dendrites_color_option(layout, scene, options, morphology): + + if morphology.has_basal_dendrites(): + row = layout.row() + row.prop(scene, 'NMV_BasalDendritesColor') + rgb = scene.NMV_BasalDendritesColor + options.shading.mesh_basal_dendrites_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_apical_dendrites_color_option +#################################################################################################### +def draw_apical_dendrites_color_option(layout, scene, options, morphology): + + if morphology.has_apical_dendrites(): + row = layout.row() + row.prop(scene, 'NMV_ApicalDendriteColor') + rgb = scene.NMV_ApicalDendriteColor + options.shading.mesh_apical_dendrites_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_apical_dendrites_color_option +#################################################################################################### +def draw_spines_color_option(layout, scene, options, morphology): + + if options.mesh.spines != nmv.enums.Meshing.Spines.Source.IGNORE: + row = layout.row() + row.prop(scene, 'NMV_SpinesMeshColor') + rgb = scene.NMV_SpinesMeshColor + options.shading.mesh_spines_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_endfeet_color_option +#################################################################################################### +def draw_endfeet_color_option(layout, scene, options, morphology): + + if morphology.has_endfeet(): + row = layout.row() + row.prop(scene, 'NMV_EndfeetMeshColor') + rgb = scene.NMV_EndfeetColor + options.shading.mesh_endfeet_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_mesh_demo_color_options +#################################################################################################### +def draw_mesh_demo_color_options(layout, scene, options): + + # Axons + axons_color_row = layout.row() + axons_color_row.prop(scene, 'NMV_AxonColor') + + # Apical dendrites + basal_dendrites_color_row = layout.row() + basal_dendrites_color_row.prop(scene, 'NMV_BasalDendritesColor') + + # Apical dendrites + apical_dendrites_color_row = layout.row() + apical_dendrites_color_row.prop(scene, 'NMV_ApicalDendriteColor') + + +#################################################################################################### +# @draw_multi_component_mesh_color_options +#################################################################################################### +def draw_multi_component_mesh_color_options(layout, scene, options, morphology): + + draw_soma_color_option(layout=layout, scene=scene, options=options) + + draw_axons_color_option( + layout=layout, scene=scene, options=options, morphology=morphology) + draw_basal_dendrites_color_option( + layout=layout, scene=scene, options=options, morphology=morphology) + draw_apical_dendrites_color_option( + layout=layout, scene=scene, options=options, morphology=morphology) + draw_spines_color_option( + layout=layout, scene=scene, options=options, morphology=morphology) + draw_endfeet_color_option( + layout=layout, scene=scene, options=options, morphology=morphology) + + +#################################################################################################### +# @draw_multi_component_mesh_color_options +#################################################################################################### +def draw_single_component_mesh_color_options(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_NeuronMeshColor') + rgb = scene.NMV_NeuronMeshColor + options.shading.mesh_axons_color = Vector((rgb.r, rgb.g, rgb.b)) + + # Affirm + options.shading.mesh_soma_color = Vector((rgb.r, rgb.g, rgb.b)) + options.shading.mesh_axons_color = Vector((rgb.r, rgb.g, rgb.b)) + options.shading.mesh_basal_dendrites_color = Vector((rgb.r, rgb.g, rgb.b)) + options.shading.mesh_apical_dendrites_color = Vector((rgb.r, rgb.g, rgb.b)) + options.shading.mesh_spines_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_mesh_color_options +#################################################################################################### +def draw_mesh_color_options(layout, scene, options, morphology): + + draw_mesh_colors_header(layout=layout) + + # The morphology must be loaded to be able to draw these options, otherwise draw demo + if morphology is not None: + + draw_mesh_shading_option(layout=layout, scene=scene, options=options) + + if options.mesh.meshing_technique == nmv.enums.Meshing.Technique.VOXELIZATION: + draw_single_component_mesh_color_options( + layout=layout, scene=scene, options=options) + elif options.mesh.meshing_technique == nmv.enums.Meshing.Technique.UNION: + draw_single_component_mesh_color_options( + layout=layout, scene=scene, options=options) + elif options.mesh.meshing_technique == nmv.enums.Meshing.Technique.META_OBJECTS: + draw_single_component_mesh_color_options( + layout=layout, scene=scene, options=options) + elif options.mesh.meshing_technique == nmv.enums.Meshing.Technique.PIECEWISE_WATERTIGHT: + draw_multi_component_mesh_color_options( + layout=layout, scene=scene, options=options, morphology=morphology) + elif options.mesh.meshing_technique == nmv.enums.Meshing.Technique.SKINNING: + draw_multi_component_mesh_color_options( + layout=layout, scene=scene, options=options, morphology=morphology) + else: + nmv.logger.log('UI_ERROR: draw_mesh_color_options') + + else: + draw_mesh_demo_color_options(layout=layout, scene=scene, options=options) diff --git a/nmv/interface/ui/mesh/layout_reconstruction_props.py b/nmv/interface/ui/mesh/layout_reconstruction_props.py new file mode 100644 index 000000000..f632f2de1 --- /dev/null +++ b/nmv/interface/ui/mesh/layout_reconstruction_props.py @@ -0,0 +1,281 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + + +#################################################################################################### +# @draw_mesh_reconstruction_header +#################################################################################################### +def draw_mesh_reconstruction_header(layout): + + row = layout.row() + row.label(text='Mesh Reconstruction Options', icon='SURFACE_DATA') + + +#################################################################################################### +# @draw_meshing_technique_option +#################################################################################################### +def draw_meshing_technique_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_MeshingTechnique', icon='OUTLINER_OB_EMPTY') + options.mesh.meshing_technique = scene.NMV_MeshingTechnique + + +#################################################################################################### +# @draw_soma_type_option +#################################################################################################### +def draw_soma_type_option(layout, scene, options): + + row = layout.row() + row.label(text='Soma:') + row.prop(scene, 'NMV_MeshingSomaType', expand=True) + options.mesh.soma_type = scene.NMV_MeshingSomaType + + +#################################################################################################### +# @draw_meta_soma_option +#################################################################################################### +def draw_meta_soma_option(layout, scene, options): + + row = layout.row() + row.label(text='Soma:') + row.prop(scene, 'NMV_MeshingMetaSoma', expand=True) + options.mesh.soma_type = scene.NMV_MeshingMetaSoma + + +#################################################################################################### +# @draw_mesh_edges_option +#################################################################################################### +def draw_mesh_edges_option(layout, scene, options): + + row = layout.row() + row.label(text='Edges:') + row.prop(scene, 'NMV_MeshSmoothing', expand=True) + options.mesh.edges = scene.NMV_MeshSmoothing + + +#################################################################################################### +# @draw_small_edges_removal_option +#################################################################################################### +def draw_small_edges_removal_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_RemoveSmallEdges', expand=True) + options.mesh.remove_small_edges = scene.NMV_RemoveSmallEdges + + +#################################################################################################### +# @draw_mesh_surface_roughness_option +#################################################################################################### +def draw_mesh_surface_roughness_option(layout, scene, options): + + if options.mesh.meshing_technique == nmv.enums.Meshing.Technique.UNION: + if options.mesh.edges == nmv.enums.Meshing.Edges.SMOOTH: + row = layout.row() + row.label(text='Surface:') + row.prop(scene, 'NMV_SurfaceRoughness', expand=True) + options.mesh.surface = scene.NMV_SurfaceRoughness + else: + options.mesh.surface = nmv.enums.Meshing.Surface.SMOOTH + else: + row = layout.row() + row.label(text='Surface:') + row.prop(scene, 'NMV_SurfaceRoughness', expand=True) + options.mesh.surface = scene.NMV_SurfaceRoughness + + +#################################################################################################### +# @draw_mesh_connectivity_options +#################################################################################################### +def draw_mesh_connectivity_options(layout, scene, options): + + row = layout.row() + row.label(text='Mesh Objects:') + row.prop(scene, 'NMV_MeshObjectsConnection', expand=True) + options.mesh.neuron_objects_connection = scene.NMV_MeshObjectsConnection + + +#################################################################################################### +# @draw_proxy_mesh_option +#################################################################################################### +def draw_proxy_mesh_option(layout, scene, options): + + row = layout.row() + row.label(text='Proxy Mesh') + row.prop(scene, 'NMV_ProxyMeshes', expand=True) + options.mesh.proxy_mesh_method = scene.NMV_ProxyMeshes + + +#################################################################################################### +# @draw_tessellation_option +#################################################################################################### +def draw_tessellation_option(layout, scene, options): + + tess_level_row = layout.row() + tess_level_row.prop(scene, 'NMV_TessellateMesh') + tess_level_column = tess_level_row.column() + tess_level_column.prop(scene, 'NMV_MeshTessellationLevel') + + # Disable the tessellation + if not scene.NMV_TessellateMesh: + options.mesh.tessellate_mesh = False + options.mesh.tessellation_level = 1.0 + tess_level_column.enabled = False + else: + options.mesh.tessellate_mesh = scene.NMV_TessellateMesh + options.mesh.tessellation_level = scene.NMV_MeshTessellationLevel + + +#################################################################################################### +# @draw_spine_options_header +#################################################################################################### +def draw_spine_options_header(layout): + + row = layout.row() + row.label(text='Spine Options', icon='MOD_WAVE') + + +#################################################################################################### +# @draw_spines_source_option +#################################################################################################### +def draw_spines_source_option(layout, scene, options): + + # If the input neuron is from a circuit, show the circuit options, otherwise use random spines + row = layout.row() + row.label(text='Source:') + + if scene.NMV_InputSource == nmv.enums.Input.CIRCUIT_GID: + row.prop(scene, 'NMV_SpinesSourceCircuit', expand=True) + options.mesh.spines = scene.NMV_SpinesSourceCircuit + else: + row.prop(scene, 'NMV_SpinesSourceRandom', expand=True) + options.mesh.spines = scene.NMV_SpinesSourceRandom + + +#################################################################################################### +# @draw_spines_percentage_option +#################################################################################################### +def draw_spines_percentage_option(layout, scene, options): + + if options.mesh.spines == nmv.enums.Meshing.Spines.Source.RANDOM: + row = layout.row() + row.label(text='Random Density:') + row.prop(scene, 'NMV_NumberSpinesPerMicron') + options.mesh.number_spines_per_micron = scene.NMV_NumberSpinesPerMicron + + +#################################################################################################### +# @draw_spines_options +#################################################################################################### +def draw_spines_options(layout, scene, options): + + draw_spine_options_header(layout=layout) + draw_spines_source_option(layout=layout, scene=scene, options=options) + draw_spines_percentage_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_piecewise_watertight_meshing_options +#################################################################################################### +def draw_piecewise_watertight_meshing_options(layout, scene, options): + + draw_soma_type_option(layout=layout, scene=scene, options=options) + draw_mesh_connectivity_options(layout=layout, scene=scene, options=options) + draw_tessellation_option(layout=layout, scene=scene, options=options) + draw_spines_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_voxelization_meshing_options +#################################################################################################### +def draw_voxelization_meshing_options(layout, scene, options): + + draw_soma_type_option(layout=layout, scene=scene, options=options) + draw_proxy_mesh_option(layout=layout, scene=scene, options=options) + draw_mesh_surface_roughness_option(layout=layout, scene=scene, options=options) + draw_small_edges_removal_option(layout=layout, scene=scene, options=options) + draw_spines_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_skinning_meshing_options +#################################################################################################### +def draw_skinning_meshing_options(layout, scene, options): + + draw_soma_type_option(layout=layout, scene=scene, options=options) + draw_mesh_connectivity_options(layout=layout, scene=scene, options=options) + draw_mesh_surface_roughness_option(layout=layout, scene=scene, options=options) + draw_tessellation_option(layout=layout, scene=scene, options=options) + draw_spines_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_union_operators_meshing_options +#################################################################################################### +def draw_union_operators_meshing_options(layout, scene, options): + + draw_meta_soma_option(layout=layout, scene=scene, options=options) + draw_mesh_edges_option(layout=layout, scene=scene, options=options) + draw_mesh_surface_roughness_option(layout=layout, scene=scene, options=options) + draw_tessellation_option(layout=layout, scene=scene, options=options) + draw_spines_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_meta_objects_meshing_options +#################################################################################################### +def draw_meta_objects_meshing_options(layout, scene, options): + + draw_meta_soma_option(layout=layout, scene=scene, options=options) + draw_mesh_surface_roughness_option(layout=layout, scene=scene, options=options) + draw_small_edges_removal_option(layout=layout, scene=scene, options=options) + draw_tessellation_option(layout=layout, scene=scene, options=options) + draw_spines_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_mesh_reconstruction_options +#################################################################################################### +def draw_mesh_reconstruction_options(panel, scene, options, morphology): + + draw_mesh_reconstruction_header(layout=panel.layout) + draw_meshing_technique_option(layout=panel.layout, scene=scene, options=options) + + if options.mesh.meshing_technique == nmv.enums.Meshing.Technique.PIECEWISE_WATERTIGHT: + draw_piecewise_watertight_meshing_options(layout=panel.layout, scene=scene, options=options) + + elif options.mesh.meshing_technique == nmv.enums.Meshing.Technique.VOXELIZATION: + draw_voxelization_meshing_options(layout=panel.layout, scene=scene, options=options) + + elif options.mesh.meshing_technique == nmv.enums.Meshing.Technique.SKINNING: + draw_skinning_meshing_options(layout=panel.layout, scene=scene, options=options) + + elif options.mesh.meshing_technique == nmv.enums.Meshing.Technique.UNION: + draw_union_operators_meshing_options(layout=panel.layout, scene=scene, options=options) + + elif options.mesh.meshing_technique == nmv.enums.Meshing.Technique.META_OBJECTS: + draw_meta_objects_meshing_options(layout=panel.layout, scene=scene, options=options) + + else: + nmv.logger.log('UI_ERROR: draw_mesh_reconstruction_options') + diff --git a/nmv/interface/ui/mesh/layout_rendering_props.py b/nmv/interface/ui/mesh/layout_rendering_props.py new file mode 100644 index 000000000..5f5e39f27 --- /dev/null +++ b/nmv/interface/ui/mesh/layout_rendering_props.py @@ -0,0 +1,99 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + + +from .layout_buttons import draw_mesh_rendering_buttons + + +#################################################################################################### +# draw_still_frame_rendering_options +#################################################################################################### +def draw_still_frame_rendering_options(panel, scene, options, show_stats=False): + + nmv.interface.ui.common.draw_rendering_header( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_morphology_rendering_view_option( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_resolution_basis_option( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_resolution_options( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_image_format_option( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_scale_bar_option( + layout=panel.layout, scene=scene, options=options) + + draw_mesh_rendering_buttons(panel=panel) + + if show_stats: + row = panel.layout.row() + row.prop(scene, 'NMV_MeshRenderingTime') + row.enabled = False + + +#################################################################################################### +# @draw_animated_360_rendering_buttons +#################################################################################################### +def draw_animated_360_rendering_buttons(panel, scene): + + # Animation row + animation_row = panel.layout.row() + animation_row.label(text='Render Animation (Front View - XY)', icon='CAMERA_DATA') + + # Buttons + buttons_row = panel.layout.row(align=True) + buttons_row.operator('nmv.render_mesh_360', icon='FORCE_MAGNETIC') + buttons_row.enabled = True + + # Progress bar + progress_bar_row = panel.layout.row() + progress_bar_row.prop(scene, 'NMV_MeshRenderingProgress') + progress_bar_row.enabled = False + + if nmv.interface.ui_mesh_reconstructed: + animation_row.enabled = True + buttons_row.enabled = True + else: + animation_row.enabled = False + buttons_row.enabled = False + + +#################################################################################################### +# draw_rendering_options +#################################################################################################### +def draw_rendering_options(panel, scene, options, show_stats=False): + + draw_still_frame_rendering_options( + panel=panel, scene=scene, options=options, show_stats=show_stats) + panel.layout.separator() + + draw_animated_360_rendering_buttons(panel=panel, scene=scene) + diff --git a/nmv/interface/ui/mesh/mesh_panel.py b/nmv/interface/ui/mesh/mesh_panel.py deleted file mode 100644 index 2c802464f..000000000 --- a/nmv/interface/ui/mesh/mesh_panel.py +++ /dev/null @@ -1,648 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# System imports -import time - -# Blender imports -import bpy - -# Internal imports -import nmv.bbox -import nmv.consts -import nmv.builders -import nmv.consts -import nmv.enums -import nmv.file -import nmv.interface -import nmv.mesh -import nmv.rendering -import nmv.scene -import nmv.skeleton -import nmv.utilities - -from .mesh_panel_options import * -from .mesh_panel_ops import * - -# Is the mesh reconstructed or not -is_mesh_reconstructed = False - -# Is the mesh rendered or not -is_mesh_rendered = False - - -#################################################################################################### -# @MeshPanel -#################################################################################################### -class MeshPanel(bpy.types.Panel): - """MeshPanel class""" - - ################################################################################################ - # Panel parameters - ################################################################################################ - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' if nmv.utilities.is_blender_280() else 'TOOLS' - bl_idname = "OBJECT_PT_NMV_MeshingToolBox" - bl_label = 'Mesh Toolbox' - bl_category = 'NeuroMorphoVis' - bl_options = {'DEFAULT_CLOSED'} - - ################################################################################################ - # @draw - ################################################################################################ - def __init__(self): - """Constructor - """ - - # Shown / Hidden rows - # A list of rows that will be activated or deactivated based on availability of the mesh - self.shown_hidden_rows = list() - - ################################################################################################ - # @draw - ################################################################################################ - def draw(self, - context): - """Draw the panel - - :param context: - Rendering context. - """ - - # Documentation button - documentation_button = self.layout.column() - documentation_button.operator('nmv.documentation_mesh', icon='URL') - documentation_button.separator() - - # Meshing options - draw_meshing_options(panel=self, scene=context.scene) - - # Color options - draw_color_options(panel=self, scene=context.scene) - - # Mesh reconstruction button - draw_mesh_reconstruction_button(panel=self, scene=context.scene) - - # Profiling - if is_mesh_reconstructed: - morphology_stats_row = self.layout.row() - morphology_stats_row.label(text='Stats:', icon='RECOVER_LAST') - reconstruction_time_row = self.layout.row() - reconstruction_time_row.prop(context.scene, 'NMV_MeshReconstructionTime') - reconstruction_time_row.enabled = False - - # Rendering options - draw_rendering_options(panel=self, scene=context.scene) - - global is_mesh_rendered - if is_mesh_rendered: - morphology_stats_row = self.layout.row() - morphology_stats_row.label(text='Stats:', icon='RECOVER_LAST') - rendering_time_row = self.layout.row() - rendering_time_row.prop(context.scene, 'NMV_MeshRenderingTime') - rendering_time_row.enabled = False - - # Mesh export options - draw_mesh_export_options(panel=self, scene=context.scene) - - # Enable or disable the layout - nmv.interface.enable_or_disable_layout(self.layout) - - -#################################################################################################### -# @ReconstructNeuronMesh -#################################################################################################### -class ReconstructNeuronMesh(bpy.types.Operator): - """Reconstructs the mesh of the neuron""" - - # Operator parameters - bl_idname = "nmv.reconstruct_neuron_mesh" - bl_label = "Reconstruct Mesh" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Executes the operator - - Keyword arguments: - :param context: - Operator context. - :return: - {'FINISHED'} - """ - - import time - - # Reset the scene - nmv.scene.reset_scene() - - # Clear the scene - nmv.scene.ops.clear_scene() - - # Load the morphology file - loading_result = nmv.interface.ui.load_morphology(self, context.scene) - - # If the result is None, report the issue - if loading_result is None: - self.report({'ERROR'}, 'Please select a morphology file') - return {'FINISHED'} - - # Meshing technique - meshing_technique = nmv.interface.ui_options.mesh.meshing_technique - - # Start reconstruction - start_time = time.time() - - # Piece-wise watertight meshing - if meshing_technique == nmv.enums.Meshing.Technique.PIECEWISE_WATERTIGHT: - mesh_builder = nmv.builders.PiecewiseBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - nmv.interface.ui_reconstructed_mesh = mesh_builder.reconstruct_mesh() - - # Union - elif meshing_technique == nmv.enums.Meshing.Technique.UNION: - mesh_builder = nmv.builders.UnionBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - nmv.interface.ui_reconstructed_mesh = mesh_builder.reconstruct_mesh() - - # Skinning - elif meshing_technique == nmv.enums.Meshing.Technique.SKINNING: - mesh_builder = nmv.builders.SkinningBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - nmv.interface.ui_reconstructed_mesh = mesh_builder.reconstruct_mesh() - - # Meta Balls - elif meshing_technique == nmv.enums.Meshing.Technique.META_OBJECTS: - mesh_builder = nmv.builders.MetaBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - nmv.interface.ui_reconstructed_mesh = mesh_builder.reconstruct_mesh() - - else: - - # Invalid method - self.report({'ERROR'}, 'Invalid Meshing Technique') - return {'FINISHED'} - - # Mesh reconstructed - reconstruction_time = time.time() - global is_mesh_reconstructed - is_mesh_reconstructed = True - context.scene.NMV_MeshReconstructionTime = reconstruction_time - start_time - nmv.logger.statistics('Mesh reconstructed in [%f] seconds' % - context.scene.NMV_MeshReconstructionTime) - - return {'FINISHED'} - - -#################################################################################################### -# @RenderMeshFront -#################################################################################################### -class RenderMeshFront(bpy.types.Operator): - """Render front view of the reconstructed mesh""" - - # Operator parameters - bl_idname = "nmv.render_mesh_front" - bl_label = "Front" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator - - :param context: - Rendering Context. - :return: - 'FINISHED' - """ - - # Timer - start_time = time.time() - - # Render the image - nmv.interface.ui.render_mesh_image( - self, context_scene=context.scene, view=nmv.enums.Camera.View.FRONT, - image_format=nmv.interface.ui_options.mesh.image_format) - - # Stats. - rendering_time = time.time() - global is_mesh_rendered - is_mesh_rendered = True - context.scene.NMV_MeshRenderingTime = rendering_time - start_time - nmv.logger.statistics('Morphology rendered in [%f] seconds' % - context.scene.NMV_MeshRenderingTime) - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderMeshSide -#################################################################################################### -class RenderMeshSide(bpy.types.Operator): - """Render side view of the reconstructed mesh""" - - # Operator parameters - bl_idname = "nmv.render_mesh_side" - bl_label = "Side" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Execute the operator - - :param context: - Rendering context. - :return: - 'FINISHED' - """ - - # Timer - start_time = time.time() - - # Render the image - nmv.interface.ui.render_mesh_image( - self, context_scene=context.scene, view=nmv.enums.Camera.View.SIDE, - image_format=nmv.interface.ui_options.mesh.image_format) - - # Stats. - rendering_time = time.time() - global is_mesh_rendered - is_mesh_rendered = True - context.scene.NMV_MeshRenderingTime = rendering_time - start_time - nmv.logger.statistics('Morphology rendered in [%f] seconds' % - context.scene.NMV_MeshRenderingTime) - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderMeshTop -#################################################################################################### -class RenderMeshTop(bpy.types.Operator): - """Render top view of the reconstructed mesh""" - - # Operator parameters - bl_idname = "nmv.render_mesh_top" - bl_label = "Top" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Execute the operator - - :param context: - Rendering context. - :return: - 'FINISHED'. - """ - - # Timer - start_time = time.time() - - # Render the image - nmv.interface.ui.render_mesh_image( - self, context_scene=context.scene, view=nmv.enums.Camera.View.TOP, - image_format=nmv.interface.ui_options.mesh.image_format) - - # Stats. - rendering_time = time.time() - global is_mesh_rendered - is_mesh_rendered = True - context.scene.NMV_MeshRenderingTime = rendering_time - start_time - nmv.logger.statistics('Morphology rendered in [%f] seconds' % - context.scene.NMV_MeshRenderingTime) - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderMesh360 -#################################################################################################### -class RenderMesh360(bpy.types.Operator): - """Render a 360 view of the reconstructed mesh""" - - # Operator parameters - bl_idname = "nmv.render_mesh_360" - bl_label = "360" - - # Timer parameters - start_time = 0 - event_timer = None - timer_limits = 0 - - # Collect a list of the scene objects (meshes) to be rendered before starting the rendering loop - scene_objects = list() - - # 360 bounding box - bounding_box_360 = None - - # Output data - output_directory = None - - ################################################################################################ - # @modal - ################################################################################################ - def modal(self, - context, - event): - """Threading and non-blocking handling. - - :param context: - Panel context. - :param event: - A given event for the panel. - """ - - # Cancelling event, if using right click or exceeding the time limit of the simulation - if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > 360: - - # Reset the orientation of the mesh - nmv.scene.reset_orientation_of_objects(scene_objects=self.scene_objects) - - # Stats. - rendering_time = time.time() - global is_mesh_rendered - is_mesh_rendered = True - context.scene.NMV_MeshRenderingTime = rendering_time - self.start_time - nmv.logger.statistics('Morphology rendered in [%f] seconds' % - context.scene.NMV_MeshRenderingTime) - - # Reset the timer limits - self.timer_limits = 0 - - # Refresh the panel context - self.cancel(context) - - # Done - return {'FINISHED'} - - # Timer event, where the function is executed here on a per-frame basis - if event.type == 'TIMER': - - # Set the frame name - image_name = '%s/%s' % ( - self.output_directory, '{0:05d}'.format(self.timer_limits)) - - # Render at a specific resolution - if context.scene.NMV_MeshRenderingResolution == \ - nmv.enums.Rendering.Resolution.FIXED: - - # Render the image - nmv.rendering.renderer.render_at_angle( - scene_objects=self.scene_objects, - angle=self.timer_limits, - bounding_box=self.bounding_box_360, - camera_view=nmv.enums.Camera.View.FRONT_360, - image_resolution=context.scene.NMV_MeshFrameResolution, - image_name=image_name) - - # Render at a specific scale factor - else: - - # Render the image - nmv.rendering.renderer.render_at_angle_to_scale( - scene_objects=self.scene_objects, - angle=self.timer_limits, - bounding_box=self.bounding_box_360, - camera_view=nmv.enums.Camera.View.FRONT_360, - image_scale_factor=context.scene.NMV_MeshFrameScaleFactor, - image_name=image_name) - - # Update the progress shell - nmv.utilities.show_progress('Rendering', self.timer_limits, 360) - - # Update the progress bar - context.scene.NMV_NeuronMeshRenderingProgress = int(100 * self.timer_limits / 360.0) - - # Upgrade the timer limits - self.timer_limits += 1 - - # Next frame - return {'PASS_THROUGH'} - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Execute the operator - - :param context: - Panel context. - """ - - # Timer - self.start_time = time.time() - - # If no morphology is loaded, report it - if nmv.interface.ui_morphology is None: - self.report({'ERROR'}, 'Please select a morphology file') - return {'FINISHED'} - - # Validate the output directory - nmv.interface.ui.validate_output_directory( - panel_object=self, context_scene=context.scene) - - # Get a list of all the meshes in the scene - self.scene_objects = nmv.scene.get_list_of_meshes_in_scene() - - # Create the sequences directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): - nmv.file.ops.clean_and_create_directory( - nmv.interface.ui_options.io.sequences_directory) - - # Compute the bounding box for a close up view - if context.scene.NMV_MeshRenderingView == nmv.enums.Rendering.View.CLOSE_UP: - - # Compute the bounding box for a close up view - rendering_bbox = nmv.bbox.compute_unified_extent_bounding_box( - extent=context.scene.NMV_MeshCloseUpSize) - - # Compute the bounding box for a mid shot view - elif context.scene.NMV_MeshRenderingView == nmv.enums.Rendering.View.MID_SHOT: - - # Compute the bounding box for the available meshes only - rendering_bbox = nmv.bbox.compute_scene_bounding_box_for_meshes() - - # Compute the bounding box for the wide shot view that correspond to the whole morphology - else: - - # Compute the full morphology bounding box - rendering_bbox = nmv.skeleton.compute_full_morphology_bounding_box( - morphology=nmv.interface.ui_morphology) - - # Compute a 360 bounding box to fit the arbors - self.bounding_box_360 = nmv.bbox.compute_360_bounding_box( - rendering_bbox, nmv.interface.ui_morphology.soma.centroid) - - # Stretch the bounding box by few microns - self.bounding_box_360.extend_bbox_uniformly(delta=nmv.consts.Image.GAP_DELTA) - - # Create a specific directory for this mesh - self.output_directory = '%s/%s%s' % ( - nmv.interface.ui_options.io.sequences_directory, - nmv.interface.ui_options.morphology.label, - nmv.consts.Suffix.MESH_360) - nmv.file.ops.clean_and_create_directory(self.output_directory) - - # Use the event timer to update the UI during the soma building - wm = context.window_manager - self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) - wm.modal_handler_add(self) - - # Done - return {'RUNNING_MODAL'} - - ################################################################################################ - # @cancel - ################################################################################################ - def cancel(self, context): - """ - Cancel the panel processing and return to the interaction mode. - - :param context: Panel context. - """ - - # Multi-threading - wm = context.window_manager - wm.event_timer_remove(self.event_timer) - - # Report the process termination in the UI - self.report({'INFO'}, 'Morphology Rendering Done') - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @SaveNeuronMeshBLEND -#################################################################################################### -class ExportMesh(bpy.types.Operator): - """Export neuron mesh""" - - # Operator parameters - bl_idname = "nmv.export_neuron_mesh" - bl_label = "Export" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Executes the operator - - :param context: - Rendering context. - :return: - 'FINISHED' - """ - - # If no morphology is loaded, report it - if nmv.interface.ui_morphology is None: - self.report({'ERROR'}, 'Please select a morphology file') - return {'FINISHED'} - - # Validate the output directory - nmv.interface.ui.validate_output_directory(panel_object=self, context_scene=context.scene) - - # Create the meshes directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.meshes_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.meshes_directory) - - # Get a list of all the meshes in the scene - mesh_objects = nmv.scene.get_list_of_meshes_in_scene() - - # Export - nmv.file.export_mesh_objects_to_file(mesh_objects, - nmv.interface.ui_options.io.meshes_directory, - nmv.interface.ui_morphology.label, - context.scene.NMV_ExportedMeshFormat, - context.scene.NMV_ExportIndividuals) - - return {'FINISHED'} - - -#################################################################################################### -# @InputOutputDocumentation -#################################################################################################### -class MeshReconstructionDocumentation(bpy.types.Operator): - """Open the online documentation page of the Mesh Reconstruction panel.""" - - # Operator parameters - bl_idname = "nmv.documentation_mesh" - bl_label = "Online User Guide" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Blender context - :return: - 'FINISHED' - """ - - import webbrowser - webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki/Mesh-Reconstruction') - return {'FINISHED'} - - -#################################################################################################### -# @register_panel -#################################################################################################### -def register_panel(): - """Register all the classes in this panel""" - - # Mesh reconstruction panel - bpy.utils.register_class(MeshPanel) - - # Buttons - bpy.utils.register_class(MeshReconstructionDocumentation) - bpy.utils.register_class(ReconstructNeuronMesh) - bpy.utils.register_class(RenderMeshFront) - bpy.utils.register_class(RenderMeshSide) - bpy.utils.register_class(RenderMeshTop) - bpy.utils.register_class(RenderMesh360) - bpy.utils.register_class(ExportMesh) - - -#################################################################################################### -# @unregister_panel -#################################################################################################### -def unregister_panel(): - """Un-register all the classes in this panel""" - - # Mesh reconstruction panel - bpy.utils.unregister_class(MeshPanel) - - # Buttons - bpy.utils.unregister_class(MeshReconstructionDocumentation) - bpy.utils.unregister_class(ReconstructNeuronMesh) - bpy.utils.unregister_class(RenderMeshFront) - bpy.utils.unregister_class(RenderMeshSide) - bpy.utils.unregister_class(RenderMeshTop) - bpy.utils.unregister_class(RenderMesh360) - bpy.utils.unregister_class(ExportMesh) - diff --git a/nmv/interface/ui/mesh/ops_documentation.py b/nmv/interface/ui/mesh/ops_documentation.py new file mode 100644 index 000000000..e88006201 --- /dev/null +++ b/nmv/interface/ui/mesh/ops_documentation.py @@ -0,0 +1,39 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +import bpy + + +#################################################################################################### +# @NMV_MeshReconstructionDocumentation +#################################################################################################### +class NMV_MeshReconstructionDocumentation(bpy.types.Operator): + """Open the online documentation page of the Mesh Reconstruction panel""" + + # Operator parameters + bl_idname = "nmv.documentation_mesh" + bl_label = "Online User Guide" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + import webbrowser + webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki/Mesh-Reconstruction') + return {'FINISHED'} diff --git a/nmv/interface/ui/mesh/ops_exports.py b/nmv/interface/ui/mesh/ops_exports.py new file mode 100644 index 000000000..33bbf4628 --- /dev/null +++ b/nmv/interface/ui/mesh/ops_exports.py @@ -0,0 +1,66 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.interface +import nmv.file +import nmv.scene + + +#################################################################################################### +# @NMV_ExportMesh +#################################################################################################### +class NMV_ExportMesh(bpy.types.Operator): + """Exports the reconstructed mesh""" + + # Operator parameters + bl_idname = "nmv.export_mesh" + bl_label = "Export" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # If no morphology is loaded, report it + if nmv.interface.ui_morphology is None: + self.report({'ERROR'}, 'Please select a morphology file') + return {'FINISHED'} + + # Validate the output directory + nmv.interface.ui.validate_output_directory(panel=self, context_scene=context.scene) + + # Create the meshes directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.meshes_directory): + nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.meshes_directory) + + # Get a list of all the meshes in the scene + mesh_objects = nmv.scene.get_list_of_meshes_in_scene() + + # Export + nmv.file.export_mesh_objects_to_file(mesh_objects, + nmv.interface.ui_options.io.meshes_directory, + nmv.interface.ui_morphology.label, + context.scene.NMV_ExportedMeshFormat, + context.scene.NMV_ExportIndividuals) + + return {'FINISHED'} + + diff --git a/nmv/interface/ui/mesh/ops_reconstruction.py b/nmv/interface/ui/mesh/ops_reconstruction.py new file mode 100644 index 000000000..87831dfbd --- /dev/null +++ b/nmv/interface/ui/mesh/ops_reconstruction.py @@ -0,0 +1,113 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.scene +import nmv.enums +import nmv.builders +import nmv.interface + + +#################################################################################################### +# @NMV_ReconstructNeuronMesh +#################################################################################################### +class NMV_ReconstructNeuronMesh(bpy.types.Operator): + """Reconstructs the mesh of the neuron""" + + # Operator parameters + bl_idname = "nmv.reconstruct_mesh" + bl_label = "Reconstruct Mesh" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Clear the scene + nmv.scene.clear_scene(deep_delete=True) + + # If no morphology file is loaded, load the morphology file + if nmv.interface.ui_morphology is None: + loading_result = nmv.interface.ui.load_morphology(self, context.scene) + if loading_result is None: + self.report({'ERROR'}, 'Please select a morphology file') + return {'FINISHED'} + + # Meshing technique + meshing_technique = nmv.interface.ui_options.mesh.meshing_technique + + # Start reconstruction + start_time = time.time() + + # Piece-wise watertight meshing + if meshing_technique == nmv.enums.Meshing.Technique.PIECEWISE_WATERTIGHT: + mesh_builder = nmv.builders.PiecewiseBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + nmv.interface.ui_reconstructed_mesh = mesh_builder.reconstruct_mesh() + + elif meshing_technique == nmv.enums.Meshing.Technique.VOXELIZATION: + mesh_builder = nmv.builders.VoxelizationBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + nmv.interface.ui_reconstructed_mesh = mesh_builder.reconstruct_mesh() + + # Union + elif meshing_technique == nmv.enums.Meshing.Technique.UNION: + mesh_builder = nmv.builders.UnionBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + nmv.interface.ui_reconstructed_mesh = mesh_builder.reconstruct_mesh() + + # Skinning + elif meshing_technique == nmv.enums.Meshing.Technique.SKINNING: + mesh_builder = nmv.builders.SkinningBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + nmv.interface.ui_reconstructed_mesh = mesh_builder.reconstruct_mesh() + + # Meta Balls + elif meshing_technique == nmv.enums.Meshing.Technique.META_OBJECTS: + mesh_builder = nmv.builders.MetaBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + nmv.interface.ui_reconstructed_mesh = mesh_builder.reconstruct_mesh() + + else: + + # Invalid method + self.report({'ERROR'}, 'Invalid Meshing Technique') + return {'FINISHED'} + + # Update the timing + reconstruction_time = time.time() + nmv.interface.ui_mesh_reconstructed = True + context.scene.NMV_MeshReconstructionTime = reconstruction_time - start_time + nmv.logger.statistics('Mesh reconstructed in [%f] seconds' % + context.scene.NMV_MeshReconstructionTime) + + # Deselect everything in the scene to be able to see the morphology + nmv.scene.deselect_all() + + # Operation done + return {'FINISHED'} + + + + + diff --git a/nmv/interface/ui/mesh/ops_render_360.py b/nmv/interface/ui/mesh/ops_render_360.py new file mode 100644 index 000000000..4abc00396 --- /dev/null +++ b/nmv/interface/ui/mesh/ops_render_360.py @@ -0,0 +1,219 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.bbox +import nmv.consts +import nmv.builders +import nmv.consts +import nmv.enums +import nmv.file +import nmv.interface +import nmv.mesh +import nmv.rendering +import nmv.scene +import nmv.skeleton +import nmv.utilities + + +#################################################################################################### +# @NMV_RenderMesh360 +#################################################################################################### +class NMV_RenderMesh360(bpy.types.Operator): + """Render a 360 view of the reconstructed mesh""" + + # Operator parameters + bl_idname = "nmv.render_mesh_360" + bl_label = "360" + + # Timer parameters + start_time = 0 + event_timer = None + timer_limits = 0 + + # Collect a list of the scene objects (meshes) to be rendered before starting the rendering loop + scene_objects = list() + + # 360 bounding box + bounding_box_360 = None + + # Output data + output_directory = None + + ################################################################################################ + # @modal + ################################################################################################ + def modal(self, context, event): + + # Cancelling event, if using right click or exceeding the time limit of the simulation + if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > 360: + + # Reset the orientation of the mesh + nmv.scene.reset_orientation_of_objects(scene_objects=self.scene_objects) + + # Stats. + rendering_time = time.time() + global is_mesh_rendered + is_mesh_rendered = True + context.scene.NMV_MeshRenderingTime = rendering_time - self.start_time + nmv.logger.statistics('Morphology rendered in [%f] seconds' % + context.scene.NMV_MeshRenderingTime) + + # Reset the timer limits + self.timer_limits = 0 + + # Refresh the panel context + self.cancel(context) + + # Done + return {'FINISHED'} + + # Timer event, where the function is executed here on a per-frame basis + if event.type == 'TIMER': + + # Set the frame name + image_name = '%s/%s' % ( + self.output_directory, '{0:05d}'.format(self.timer_limits)) + + # Render at a specific resolution + if context.scene.NMV_MeshRenderingResolution == \ + nmv.enums.Rendering.Resolution.FIXED: + + # Render the image + nmv.rendering.renderer.render_at_angle( + scene_objects=self.scene_objects, + angle=self.timer_limits, + bounding_box=self.bounding_box_360, + camera_view=nmv.enums.Camera.View.FRONT_360, + image_resolution=context.scene.NMV_MeshFrameResolution, + image_name=image_name) + + # Render at a specific scale factor + else: + + # Render the image + nmv.rendering.renderer.render_at_angle_to_scale( + scene_objects=self.scene_objects, + angle=self.timer_limits, + bounding_box=self.bounding_box_360, + camera_view=nmv.enums.Camera.View.FRONT_360, + image_scale_factor=context.scene.NMV_MeshFrameScaleFactor, + image_name=image_name) + + # Update the progress shell + nmv.utilities.show_progress('Rendering', self.timer_limits, 360) + + # Update the progress bar + context.scene.NMV_MeshRenderingProgress = int(100 * self.timer_limits / 360.0) + + # Upgrade the timer limits + self.timer_limits += 1 + + # Next frame + return {'PASS_THROUGH'} + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Timer + self.start_time = time.time() + + # If no morphology is loaded, report it + if nmv.interface.ui_morphology is None: + self.report({'ERROR'}, 'Please select a morphology file') + return {'FINISHED'} + + # Validate the output directory + nmv.interface.ui.validate_output_directory(panel=self, context_scene=context.scene) + + # Get a list of all the meshes in the scene + self.scene_objects = nmv.scene.get_list_of_meshes_in_scene() + + # Create the sequences directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): + nmv.file.ops.clean_and_create_directory( + nmv.interface.ui_options.io.sequences_directory) + + # Compute the bounding box for a close up view + if context.scene.NMV_MeshRenderingView == nmv.enums.Rendering.View.CLOSEUP: + + # Compute the bounding box for a close up view + rendering_bbox = nmv.bbox.compute_unified_extent_bounding_box( + extent=context.scene.NMV_MeshCloseUpSize) + + # Compute the bounding box for a mid shot view + elif context.scene.NMV_MeshRenderingView == nmv.enums.Rendering.View.MID_SHOT: + + # Compute the bounding box for the available meshes only + rendering_bbox = nmv.bbox.compute_scene_bounding_box_for_meshes() + + # Compute the bounding box for the wide shot view that correspond to the whole morphology + else: + + # Compute the full morphology bounding box + rendering_bbox = nmv.skeleton.compute_full_morphology_bounding_box( + morphology=nmv.interface.ui_morphology) + + # Compute a 360 bounding box to fit the arbors + self.bounding_box_360 = nmv.bbox.compute_360_bounding_box( + rendering_bbox, nmv.interface.ui_morphology.soma.centroid) + + # Stretch the bounding box by few microns + self.bounding_box_360.extend_bbox_uniformly(delta=nmv.consts.Image.GAP_DELTA) + + # Create a specific directory for this mesh + self.output_directory = '%s/%s%s' % ( + nmv.interface.ui_options.io.sequences_directory, + nmv.interface.ui_options.morphology.label, + nmv.consts.Suffix.MESH_360) + nmv.file.ops.clean_and_create_directory(self.output_directory) + + # Use the event timer to update the UI during the soma building + wm = context.window_manager + self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) + wm.modal_handler_add(self) + + # Done + return {'RUNNING_MODAL'} + + ################################################################################################ + # @cancel + ################################################################################################ + def cancel(self, context): + """ + Cancel the panel processing and return to the interaction mode. + + :param context: Panel context. + """ + + # Multi-threading + wm = context.window_manager + wm.event_timer_remove(self.event_timer) + + # Report the process termination in the UI + self.report({'INFO'}, 'Mesh Rendering is Cancelled!') + + # Confirm operation done + return {'FINISHED'} diff --git a/nmv/interface/ui/mesh/ops_render_view.py b/nmv/interface/ui/mesh/ops_render_view.py new file mode 100644 index 000000000..6fb7f2d02 --- /dev/null +++ b/nmv/interface/ui/mesh/ops_render_view.py @@ -0,0 +1,125 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.interface + + +#################################################################################################### +# @NMV_RenderMeshFront +#################################################################################################### +class NMV_RenderMeshFront(bpy.types.Operator): + """Render front view of the reconstructed mesh""" + + # Operator parameters + bl_idname = "nmv.render_mesh_front" + bl_label = "Front" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Render the frame + start_time = time.time() + nmv.interface.render_meshes_relevant_image( + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology, + camera_view=nmv.enums.Camera.View.FRONT, + image_suffix=nmv.consts.Suffix.MESH_FRONT) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_mesh_rendered = True + context.scene.NMV_MeshRenderingTime = rendering_time - start_time + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderMeshSide +#################################################################################################### +class NMV_RenderMeshSide(bpy.types.Operator): + """Render side view of the reconstructed mesh""" + + # Operator parameters + bl_idname = "nmv.render_mesh_side" + bl_label = "Side" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + """Execute the operator + + :param context: + Rendering context. + :return: + 'FINISHED' + """ + + # Render the frame + start_time = time.time() + nmv.interface.render_meshes_relevant_image( + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology, + camera_view=nmv.enums.Camera.View.SIDE, + image_suffix=nmv.consts.Suffix.MESH_SIDE) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_mesh_rendered = True + context.scene.NMV_MeshRenderingTime = rendering_time - start_time + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderMeshTop +#################################################################################################### +class NMV_RenderMeshTop(bpy.types.Operator): + """Render top view of the reconstructed mesh""" + + # Operator parameters + bl_idname = "nmv.render_mesh_top" + bl_label = "Top" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Render the frame + start_time = time.time() + nmv.interface.render_meshes_relevant_image( + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology, + camera_view=nmv.enums.Camera.View.TOP, + image_suffix=nmv.consts.Suffix.MESH_TOP) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_mesh_rendered = True + context.scene.NMV_MeshRenderingTime = rendering_time - start_time + return {'FINISHED'} + diff --git a/nmv/interface/ui/mesh/panel.py b/nmv/interface/ui/mesh/panel.py new file mode 100644 index 000000000..381152948 --- /dev/null +++ b/nmv/interface/ui/mesh/panel.py @@ -0,0 +1,96 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.interface +import nmv.scene + +# Layout +from .layout_buttons import * +from .layout_color_props import * +from .layout_reconstruction_props import * +from .layout_rendering_props import * + + +#################################################################################################### +# @NMV_MeshPanel +#################################################################################################### +class NMV_MeshPanel(bpy.types.Panel): + """Meshing Tools Panel""" + + ################################################################################################ + # Panel parameters + ################################################################################################ + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_idname = "OBJECT_PT_NMV_MeshingToolBox" + bl_label = 'Meshing Toolbox' + bl_category = 'NeuroMorphoVis' + bl_options = {'DEFAULT_CLOSED'} + + ################################################################################################ + # @draw + ################################################################################################ + def __init__(self): + """Constructor + """ + + ################################################################################################ + # @draw + ################################################################################################ + def draw(self, context): + + # Verify the presence of a reconstructed mesh in the scene + if len(nmv.interface.ui_reconstructed_mesh) > 0: + if nmv.scene.verify_objects_list_in_scene(nmv.interface.ui_reconstructed_mesh): + nmv.interface.ui_mesh_reconstructed = True + else: + nmv.interface.ui_mesh_reconstructed = False + else: + nmv.interface.ui_mesh_reconstructed = False + + draw_documentation_button(layout=self.layout) + self.layout.separator() + + draw_mesh_reconstruction_options( + panel=self, scene=context.scene, + options=nmv.interface.ui_options, morphology=nmv.interface.ui_morphology) + self.layout.separator() + + draw_mesh_color_options(layout=self.layout, scene=context.scene, + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology) + self.layout.separator() + + draw_mesh_reconstruction_button(panel=self, scene=context.scene) + self.layout.separator() + + draw_rendering_options(panel=self, scene=context.scene, options=nmv.interface.ui_options) + self.layout.separator() + + draw_mesh_export_options( + panel=self, scene=context.scene, options=nmv.interface.ui_options) + self.layout.separator() + + # Enable or disable the layout + nmv.interface.enable_or_disable_layout(self.layout) diff --git a/nmv/interface/ui/mesh/mesh_panel_options.py b/nmv/interface/ui/mesh/panel_props.py similarity index 93% rename from nmv/interface/ui/mesh/mesh_panel_options.py rename to nmv/interface/ui/mesh/panel_props.py index 93e3be8d2..070ccd22c 100644 --- a/nmv/interface/ui/mesh/mesh_panel_options.py +++ b/nmv/interface/ui/mesh/panel_props.py @@ -37,6 +37,18 @@ name='Soma', default=nmv.enums.Soma.Representation.META_BALLS) +# Just displaying that there is a single option for the soma meshing +bpy.types.Scene.NMV_MeshingMetaSoma = bpy.props.EnumProperty( + items=nmv.enums.Soma.Representation.META_SOMA_FOR_MESHING, + name='Soma', + default=nmv.enums.Soma.Representation.META_BALLS) + +# The method that is used to create the proxy meshes +bpy.types.Scene.NMV_ProxyMeshes = bpy.props.EnumProperty( + items=nmv.enums.Meshing.Proxy.PROXY_MESHES_METHODS, + name='Proxy Meshes', + default=nmv.enums.Meshing.Proxy.CONNECTED_SECTIONS) + # Is the soma connected to the first order branches or not ! bpy.types.Scene.NMV_SomaArborsConnection = bpy.props.EnumProperty( items=[(nmv.enums.Meshing.SomaConnection.CONNECTED, @@ -137,6 +149,13 @@ description='Tessellate the reconstructed mesh to reduce the geometry complexity', default=False) +# Removal of small edges flag +bpy.types.Scene.NMV_RemoveSmallEdges = bpy.props.BoolProperty( + name='Remove Small Edges', + description='Removes the small edges in the mesh to improve its quality', + default=False) + + # Mesh tessellation level bpy.types.Scene.NMV_MeshTessellationLevel = bpy.props.FloatProperty( name='Factor', @@ -151,7 +170,7 @@ bpy.types.Scene.NMV_MeshMaterial = bpy.props.EnumProperty( items=nmv.enums.Shader.MATERIAL_ITEMS, - name="Shading", + name="", default=nmv.enums.Shader.LAMBERT_WARD) # Use single color for the all the objects in the mesh @@ -227,7 +246,7 @@ (nmv.enums.Rendering.View.MID_SHOT, 'Mid Shot', 'Renders an image of the reconstructed arbors only'), - (nmv.enums.Rendering.View.CLOSE_UP, + (nmv.enums.Rendering.View.CLOSEUP, 'Close Up', 'Renders a close up image the focuses on the soma')], name='View', default=nmv.enums.Rendering.View.MID_SHOT) @@ -255,14 +274,14 @@ name="Scale", default=1.0, min=1.0, max=100.0, description="The scale factor for rendering a mesh to scale") -# Mesh rendering close up size +# Mesh rendering closeup size bpy.types.Scene.NMV_MeshCloseUpSize = bpy.props.FloatProperty( name='Size', description='The size of the view that will be rendered in microns', default=20, min=5, max=100,) # Soma rendering progress bar -bpy.types.Scene.NMV_NeuronMeshRenderingProgress = bpy.props.IntProperty( +bpy.types.Scene.NMV_MeshRenderingProgress = bpy.props.IntProperty( name="Rendering Progress", default=0, min=0, max=100, subtype='PERCENTAGE') diff --git a/nmv/interface/ui/mesh/registration.py b/nmv/interface/ui/mesh/registration.py new file mode 100644 index 000000000..23e491924 --- /dev/null +++ b/nmv/interface/ui/mesh/registration.py @@ -0,0 +1,75 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @register_panel +#################################################################################################### +def register_panel(): + """Registers all the classes in this panel""" + + from .panel import NMV_MeshPanel + from .ops_documentation import NMV_MeshReconstructionDocumentation + from .ops_reconstruction import NMV_ReconstructNeuronMesh + from .ops_render_view import NMV_RenderMeshFront + from .ops_render_view import NMV_RenderMeshSide + from .ops_render_view import NMV_RenderMeshTop + from .ops_render_360 import NMV_RenderMesh360 + from .ops_exports import NMV_ExportMesh + + # Mesh reconstruction panel + bpy.utils.register_class(NMV_MeshPanel) + + # Buttons + bpy.utils.register_class(NMV_MeshReconstructionDocumentation) + bpy.utils.register_class(NMV_ReconstructNeuronMesh) + bpy.utils.register_class(NMV_RenderMeshFront) + bpy.utils.register_class(NMV_RenderMeshSide) + bpy.utils.register_class(NMV_RenderMeshTop) + bpy.utils.register_class(NMV_RenderMesh360) + bpy.utils.register_class(NMV_ExportMesh) + + +#################################################################################################### +# @unregister_panel +#################################################################################################### +def unregister_panel(): + """Un-registers all the classes in this panel""" + + from .panel import NMV_MeshPanel + from .ops_documentation import NMV_MeshReconstructionDocumentation + from .ops_reconstruction import NMV_ReconstructNeuronMesh + from .ops_render_view import NMV_RenderMeshFront + from .ops_render_view import NMV_RenderMeshSide + from .ops_render_view import NMV_RenderMeshTop + from .ops_render_360 import NMV_RenderMesh360 + from .ops_exports import NMV_ExportMesh + + # Mesh reconstruction panel + bpy.utils.unregister_class(NMV_MeshPanel) + + # Buttons + bpy.utils.unregister_class(NMV_MeshReconstructionDocumentation) + bpy.utils.unregister_class(NMV_ReconstructNeuronMesh) + bpy.utils.unregister_class(NMV_RenderMeshFront) + bpy.utils.unregister_class(NMV_RenderMeshSide) + bpy.utils.unregister_class(NMV_RenderMeshTop) + bpy.utils.unregister_class(NMV_RenderMesh360) + bpy.utils.unregister_class(NMV_ExportMesh) diff --git a/nmv/interface/ui/morphology/layout_buttons.py b/nmv/interface/ui/morphology/layout_buttons.py new file mode 100644 index 000000000..de8d0ac88 --- /dev/null +++ b/nmv/interface/ui/morphology/layout_buttons.py @@ -0,0 +1,115 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.interface + + +#################################################################################################### +# @draw_documentation_button +#################################################################################################### +def draw_documentation_button(layout): + + row = layout.row() + row.operator('nmv.documentation_morphology', icon='URL') + + +#################################################################################################### +# @draw_morphology_reconstruction_button +#################################################################################################### +def draw_morphology_reconstruction_button(layout, + scene, + label=bpy.types.Scene.NMV_MorphologyButtonLabel, + show_stats=False): + + row = layout.row() + row.label(text='Quick Reconstruction', icon='PARTICLE_POINT') + button_row = layout.row() + button_row.operator('nmv.reconstruct_morphology', text=label, icon='RNA_ADD') + button_row.enabled = True + + if show_stats: + row = layout.row() + row.prop(scene, 'NMV_MorphologyReconstructionTime') + row.enabled = False + + +#################################################################################################### +# @draw_morphology_rendering_buttons +#################################################################################################### +def draw_morphology_rendering_buttons(panel, scene, show_stats=False): + + view_row = panel.layout.row() + view_row.label(text='Render View', icon='RESTRICT_RENDER_OFF') + buttons_row = panel.layout.row(align=True) + buttons_row.operator('nmv.render_morphology_front', icon='AXIS_FRONT') + buttons_row.operator('nmv.render_morphology_side', icon='AXIS_SIDE') + buttons_row.operator('nmv.render_morphology_top', icon='AXIS_TOP') + + +#################################################################################################### +# @draw_dendrogram_rendering_button +#################################################################################################### +def draw_dendrogram_rendering_button(panel, scene): + + row = panel.layout.row() + row.label(text='Render Dendrogram', icon='RESTRICT_RENDER_OFF') + buttons_row = panel.layout.row() + buttons_row.operator('nmv.render_morphology_front', icon='AXIS_FRONT') + + +#################################################################################################### +# @draw_animated_morphology_rendering_buttons +#################################################################################################### +def draw_animated_morphology_rendering_buttons(panel, scene): + + animation_row = panel.layout.row() + animation_row.label(text='Render Animation (Front View - XY)', icon='CAMERA_DATA') + buttons_row = panel.layout.row(align=True) + buttons_row.operator('nmv.render_morphology_360', icon='FORCE_MAGNETIC') + buttons_row.operator('nmv.render_morphology_progressive', icon='FORCE_HARMONIC') + buttons_row.enabled = True + + # Progress bar + progress_bar_row = panel.layout.row() + progress_bar_row.prop(scene, 'NMV_MorphologyRenderingProgress') + progress_bar_row.enabled = False + + +#################################################################################################### +# draw_morphology_export_options +#################################################################################################### +def draw_morphology_export_options(panel): + + save_neuron_morphology_row = panel.layout.row() + save_neuron_morphology_row.label(text='Export Morphology', icon='MESH_UVSPHERE') + + save_neuron_morphology_buttons_column = panel.layout.column(align=True) + save_neuron_morphology_buttons_column.operator('nmv.export_mesh', icon='MESH_DATA') + + save_neuron_morphology_buttons_column.operator('nmv.save_morphology_blend', icon='MESH_MONKEY') + save_neuron_morphology_buttons_column.operator('nmv.save_morphology_swc', icon='GRAPH') + save_neuron_morphology_buttons_column.operator('nmv.save_morphology_segments', icon='NOCURVE') + + if nmv.interface.ui_morphology_reconstructed: + save_neuron_morphology_buttons_column.enabled = True + else: + save_neuron_morphology_buttons_column.enabled = False + diff --git a/nmv/interface/ui/morphology/layout_color_props.py b/nmv/interface/ui/morphology/layout_color_props.py new file mode 100644 index 000000000..9e0ec8b07 --- /dev/null +++ b/nmv/interface/ui/morphology/layout_color_props.py @@ -0,0 +1,355 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy +from mathutils import Vector + +# Internal imports +import nmv.consts +import nmv.enums + + +#################################################################################################### +# @draw_soma_color_option +#################################################################################################### +def draw_soma_color_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SomaColor') + row.enabled = True if scene.NMV_BuildSoma else False + rgb = scene.NMV_SomaColor + options.shading.morphology_soma_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_axons_color_option +#################################################################################################### +def draw_axons_color_option(layout, scene, options, morphology): + + if morphology.has_axons(): + row = layout.row() + row.prop(scene, 'NMV_AxonColor') + row.enabled = True if scene.NMV_BuildAxon else False + rgb = scene.NMV_AxonColor + options.shading.morphology_axons_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_basal_dendrites_color_option +#################################################################################################### +def draw_basal_dendrites_color_option(layout, scene, options, morphology): + + if morphology.has_basal_dendrites(): + row = layout.row() + row.prop(scene, 'NMV_BasalDendritesColor') + row.enabled = True if scene.NMV_BuildBasalDendrites else False + rgb = scene.NMV_BasalDendritesColor + options.shading.morphology_basal_dendrites_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_apical_dendrites_color_option +#################################################################################################### +def draw_apical_dendrites_color_option(layout, scene, options, morphology): + + if morphology.has_apical_dendrites(): + row = layout.row() + row.prop(scene, 'NMV_ApicalDendriteColor') + row.enabled = True if scene.NMV_BuildApicalDendrite else False + rgb = scene.NMV_ApicalDendriteColor + options.shading.morphology_apical_dendrites_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_endfeet_color_option +#################################################################################################### +def draw_endfeet_color_option(layout, scene, options, morphology): + + row = layout.row() + row.prop(scene, 'NMV_EndfeetColor') + rgb = scene.NMV_EndfeetColor + options.shading.morphology_endfeet_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_articulation_color_option +#################################################################################################### +def draw_articulation_color_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_ArticulationColor') + rgb = scene.NMV_ArticulationColor + options.shading.morphology_articulation_color = Vector((rgb.r, rgb.g, rgb.b)) + + +#################################################################################################### +# @draw_morphology_demo_color_options +#################################################################################################### +def draw_morphology_demo_color_options(layout, scene, options): + + # Axons + axons_color_row = layout.row() + axons_color_row.prop(scene, 'NMV_AxonColor') + + # Apical dendrites + basal_dendrites_color_row = layout.row() + basal_dendrites_color_row.prop(scene, 'NMV_BasalDendritesColor') + + # Apical dendrites + apical_dendrites_color_row = layout.row() + apical_dendrites_color_row.prop(scene, 'NMV_ApicalDendriteColor') + + # Articulation color option + articulation_color_row = layout.row() + articulation_color_row.prop(scene, 'NMV_ArticulationColor') + + +#################################################################################################### +# @draw_default_morphology_color_options +#################################################################################################### +def draw_default_morphology_color_options(layout, scene, options, morphology): + + # Soma color option + draw_soma_color_option(layout=layout, scene=scene, options=options) + + # The morphology must be loaded to be able to draw these options, otherwise draw demo + if morphology is not None: + draw_axons_color_option( + layout=layout, scene=scene, options=options, morphology=morphology) + draw_basal_dendrites_color_option( + layout=layout, scene=scene, options=options, morphology=morphology) + draw_apical_dendrites_color_option( + layout=layout, scene=scene, options=options, morphology=morphology) + + # Technique-specific options + technique = scene.NMV_MorphologyReconstructionTechnique + if technique == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS: + draw_articulation_color_option(layout=layout, scene=scene, options=options) + + # Is the loaded morphology has any endfeet, i.e. astrocytes + if nmv.interface.ui_morphology.has_endfeet(): + draw_endfeet_color_option( + layout=layout, scene=scene, options=options, morphology=morphology) + else: + draw_morphology_demo_color_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_homogeneous_color_option +#################################################################################################### +def draw_homogeneous_color_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_MorphologyColor') + rgb = scene.NMV_MorphologyColor + homogeneous_color = Vector((rgb.r, rgb.g, rgb.b)) + options.shading.morphology_soma_color = homogeneous_color + options.shading.morphology_axons_color = homogeneous_color + options.shading.morphology_basal_dendrites_color = homogeneous_color + options.shading.morphology_apical_dendrites_color = homogeneous_color + options.shading.morphology_articulation_color = homogeneous_color + options.shading.morphology_endfeet_color = homogeneous_color + + +#################################################################################################### +# @draw_alternating_colors_option +#################################################################################################### +def draw_alternating_colors_option(layout, scene, options): + + draw_soma_color_option(layout=layout, scene=scene, options=options) + + # Color 1 + row_1 = layout.row() + row_1.prop(scene, 'NMV_MorphologyColor1') + options.shading.morphology_alternating_color_1 = scene.NMV_MorphologyColor1 + + # Color 2 + row_2 = layout.row() + row_2.prop(scene, 'NMV_MorphologyColor2') + options.shading.morphology_alternating_color_2 = scene.NMV_MorphologyColor2 + + # Technique-specific options + technique = scene.NMV_MorphologyReconstructionTechnique + if technique == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS: + draw_articulation_color_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_colormap_options +#################################################################################################### +def draw_colormap_options(layout, scene, options): + + # Color map + color_map_row = layout.row() + color_map_row.label(text='Color Map') + color_map_row.prop(scene, 'NMV_ColorMap') + color_map_row.prop(scene, 'NMV_InvertColorMap') + + # Clear the color map passed to VMV if it is full + if len(nmv.interface.ui_options.shading.morphology_colormap_list) > 0: + nmv.interface.ui_options.shading.morphology_colormap_list.clear() + + # Soma + draw_soma_color_option(layout=layout, scene=scene, options=options) + + # Fill list of colors + for i in range(nmv.consts.Color.COLORMAP_RESOLUTION): + + # Add the colormap element to the UI + colors = layout.row() + colormap_element = colors.column() + colormap_element.prop(scene, 'NMV_Color%d' % i) + + # Colormap range values + values = colors.row() + values.prop(scene, 'NMV_R0_Value%d' % i) + values.prop(scene, 'NMV_R1_Value%d' % i) + values.enabled = False + + # Get the color value from the panel + color = getattr(scene, 'NMV_Color%d' % i) + nmv.interface.ui_options.shading.morphology_colormap_list.append(color) + + +#################################################################################################### +# @draw_per_section_color_coding_options +#################################################################################################### +def draw_per_section_color_coding_options(layout, scene, options, morphology): + + row = layout.row() + row.label(text='Color Coding') + row.prop(scene, 'NMV_PerSectionColorCodingBasis') + options.shading.morphology_coloring_scheme = scene.NMV_PerSectionColorCodingBasis + + if options.shading.morphology_coloring_scheme == nmv.enums.ColorCoding.DEFAULT_SCHEME: + draw_default_morphology_color_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif options.shading.morphology_coloring_scheme == nmv.enums.ColorCoding.HOMOGENEOUS_COLOR: + draw_homogeneous_color_option(layout=layout, scene=scene, options=options) + + elif options.shading.morphology_coloring_scheme == nmv.enums.ColorCoding.ALTERNATING_COLORS: + draw_alternating_colors_option(layout=layout, scene=scene, options=options) + + else: + draw_colormap_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_per_segment_color_coding_options +#################################################################################################### +def draw_per_segment_color_coding_options(layout, scene, options, morphology): + + row = layout.row() + row.label(text='Color Coding') + row.prop(scene, 'NMV_PerSegmentColorCodingBasis') + options.shading.morphology_coloring_scheme = scene.NMV_PerSegmentColorCodingBasis + + if options.shading.morphology_coloring_scheme == nmv.enums.ColorCoding.DEFAULT_SCHEME: + draw_default_morphology_color_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif options.shading.morphology_coloring_scheme == nmv.enums.ColorCoding.HOMOGENEOUS_COLOR: + draw_homogeneous_color_option(layout=layout, scene=scene, options=options) + + elif options.shading.morphology_coloring_scheme == nmv.enums.ColorCoding.ALTERNATING_COLORS: + draw_alternating_colors_option(layout=layout, scene=scene, options=options) + + else: + draw_colormap_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_connected_object_color_coding_options +#################################################################################################### +def draw_connected_object_color_coding_options(layout, scene, options, morphology): + + row = layout.row() + row.label(text='Color Coding') + row.prop(scene, 'NMV_ConnectedObjectColorCodingBasis') + options.shading.morphology_coloring_scheme = scene.NMV_ConnectedObjectColorCodingBasis + + if options.shading.morphology_coloring_scheme == nmv.enums.ColorCoding.DEFAULT_SCHEME: + draw_default_morphology_color_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif options.shading.morphology_coloring_scheme == nmv.enums.ColorCoding.HOMOGENEOUS_COLOR: + draw_homogeneous_color_option(layout=layout, scene=scene, options=options) + + else: + nmv.logger.log('UI_ERROR: draw_connected_object_color_coding_options') + + +#################################################################################################### +# @draw_morphology_colors_header +#################################################################################################### +def draw_morphology_colors_header(layout): + + row = layout.row() + row.label(text='Morphology Colors', icon='COLOR') + + +#################################################################################################### +# @draw_morphology_shading_option +#################################################################################################### +def draw_morphology_shading_option(layout, scene, options): + + row = layout.row() + row.label(text='Shading') + row.prop(scene, 'NMV_MorphologyMaterial') + options.shading.morphology_material = scene.NMV_MorphologyMaterial + + +#################################################################################################### +# @draw_morphology_color_options +#################################################################################################### +def draw_morphology_color_options(layout, scene, options, morphology): + + draw_morphology_colors_header(layout=layout) + draw_morphology_shading_option(layout=layout, scene=scene, options=options) + + method = options.morphology.reconstruction_method + + # Each SECTION has a different color + if method == nmv.enums.Skeleton.Method.DISCONNECTED_SECTIONS: + draw_per_section_color_coding_options( + layout=layout, scene=scene, options=options, morphology=morphology) + elif method == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS: + draw_per_section_color_coding_options( + layout=layout, scene=scene, options=options, morphology=morphology) + elif method == nmv.enums.Skeleton.Method.PROGRESSIVE: + draw_per_section_color_coding_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + # Each SEGMENT has a different color + elif method == nmv.enums.Skeleton.Method.DISCONNECTED_SEGMENTS: + draw_per_segment_color_coding_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + # The morphology is created as a single connected object + elif method == nmv.enums.Skeleton.Method.CONNECTED_SECTIONS: + draw_connected_object_color_coding_options( + layout=layout, scene=scene, options=options, morphology=morphology) + elif method == nmv.enums.Skeleton.Method.SAMPLES: + draw_connected_object_color_coding_options( + layout=layout, scene=scene, options=options, morphology=morphology) + elif method == nmv.enums.Skeleton.Method.DENDROGRAM: + draw_connected_object_color_coding_options( + layout=layout, scene=scene, options=options, morphology=morphology) + else: + nmv.logger.log('UI_ERROR: add_morphology_color_options') diff --git a/nmv/interface/ui/morphology/layout_reconstruction_props.py b/nmv/interface/ui/morphology/layout_reconstruction_props.py new file mode 100644 index 000000000..43744ebac --- /dev/null +++ b/nmv/interface/ui/morphology/layout_reconstruction_props.py @@ -0,0 +1,375 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + + +#################################################################################################### +# @draw_morphology_reconstruction_header +#################################################################################################### +def draw_morphology_reconstruction_header(layout): + + row = layout.row() + row.label(text='Reconstruction Options', icon='OUTLINER_OB_EMPTY') + + +#################################################################################################### +# @draw_morphology_reconstruction_technique_option +#################################################################################################### +def draw_morphology_reconstruction_technique_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_MorphologyReconstructionTechnique', icon='FORCE_CURVE') + options.morphology.reconstruction_method = scene.NMV_MorphologyReconstructionTechnique + + +#################################################################################################### +# @draw_arbor_style_option +#################################################################################################### +def draw_arbor_style_option(layout, scene, options): + + row = layout.row() + row.label(text='Arbor Style') + row.prop(scene, 'NMV_ArborsStyle', icon='WPAINT_HLT') + options.morphology.arbor_style = scene.NMV_ArborsStyle + + +#################################################################################################### +# @draw_branching_option +#################################################################################################### +def draw_branching_option(layout, scene, options): + + row = layout.row() + row.label(text='Branching') + row.prop(scene, 'NMV_MorphologyBranching', expand=True) + options.morphology.branching = scene.NMV_MorphologyBranching + + +#################################################################################################### +# @draw_arbor_to_soma_connection_option +#################################################################################################### +def draw_arbor_to_soma_connection_option(layout, scene, options): + + row = layout.row() + row.label(text='Arbors to Soma Connection') + row.prop(scene, 'NMV_SomaConnectionToRoot') + options.morphology.arbors_to_soma_connection = scene.NMV_SomaConnectionToRoot + + +#################################################################################################### +# @draw_dendrogram_type_option +#################################################################################################### +def draw_dendrogram_type_option(layout, scene, options): + + row = layout.row() + row.label(text='Dendrogram Type') + row.prop(scene, 'NMV_DendrogramType', expand=True) + options.morphology.dendrogram_type = scene.NMV_DendrogramType + + +#################################################################################################### +# @draw_arbors_radii_option +#################################################################################################### +def draw_arbors_radii_option(layout, scene, options): + + row = layout.row() + row.label(text='Arbors Radii:') + row.prop(scene, 'NMV_SectionsRadii', icon='SURFACE_NCURVE') + options.morphology.arbors_radii = scene.NMV_SectionsRadii + + +#################################################################################################### +# @draw_original_arbors_radii_option +#################################################################################################### +def draw_original_arbors_radii_option(layout, scene, options): + + options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.ORIGINAL + options.morphology.scale_sections_radii = False + options.morphology.unify_sections_radii = False + options.morphology.sections_radii_scale = 1.0 + + +#################################################################################################### +# @draw_unified_radii_option +#################################################################################################### +def draw_unified_radii_option(layout, scene, options): + + row = layout.row() + row.label(text='Fixed Radius Value:') + row.prop(scene, 'NMV_UnifiedRadiusValue') + + options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.UNIFIED + options.morphology.scale_sections_radii = False + options.morphology.unify_sections_radii = True + options.morphology.samples_unified_radii_value = scene.NMV_UnifiedRadiusValue + + +#################################################################################################### +# @draw_unified_radii_per_arbor_option +#################################################################################################### +def draw_unified_radii_per_arbor_option(layout, scene, options, morphology): + + options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.UNIFIED_PER_ARBOR_TYPE + options.morphology.scale_sections_radii = False + options.morphology.unify_sections_radii = True + + if morphology.has_axons(): + axons_row = layout.row() + axons_row.label(text='Axons Radius:') + axons_row.prop(scene, 'NMV_AxonUnifiedRadiusValue') + options.morphology.axon_samples_unified_radii_value = scene.NMV_AxonUnifiedRadiusValue + + if morphology.has_apical_dendrites(): + apicals_row = layout.row() + apicals_row.label(text='Apical Dendrites Radius:') + apicals_row.prop(scene, 'NMV_ApicalDendriteUnifiedRadiusValue') + options.morphology.apical_dendrite_samples_unified_radii_value = \ + scene.NMV_ApicalDendriteUnifiedRadiusValue + + if morphology.has_basal_dendrites(): + basals_row = layout.row() + basals_row.label(text='Basal Dendrites Radius:') + basals_row.prop(scene, 'NMV_BasalDendritesUnifiedRadiusValue') + options.morphology.basal_dendrites_samples_unified_radii_value = \ + scene.NMV_BasalDendritesUnifiedRadiusValue + + +#################################################################################################### +# @draw_scaled_radii_option +#################################################################################################### +def draw_scaled_radii_option(layout, scene, options): + + row = layout.row() + row.label(text='Radius Scale Factor:') + row.prop(scene, 'NMV_RadiusScaleValue') + + # Affirm + options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.SCALED + options.morphology.unify_sections_radii = False + options.morphology.scale_sections_radii = True + options.morphology.sections_radii_scale = scene.NMV_RadiusScaleValue + + +#################################################################################################### +# @draw_filtered_radii_option +#################################################################################################### +def draw_filtered_radii_option(layout, scene, options): + + minimum_row = layout.row() + minimum_row.label(text='Minimum Radius:') + minimum_row.prop(scene, 'NMV_MinimumRadiusThreshold') + + maximum_row = layout.row() + maximum_row.label(text='Maximum Radius:') + maximum_row.prop(scene, 'NMV_MaximumRadiusThreshold') + + # Affirm + options.morphology.unify_sections_radii = False + options.morphology.scale_sections_radii = False + options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.FILTERED + options.morphology.minimum_threshold_radius = scene.NMV_MinimumRadiusThreshold + options.morphology.maximum_threshold_radius = scene.NMV_MaximumRadiusThreshold + + +#################################################################################################### +# @draw_arbors_radii_options +#################################################################################################### +def draw_arbors_radii_options(layout, scene, options, morphology): + + draw_arbors_radii_option(layout=layout, scene=scene, options=options) + if options.morphology.arbors_radii == nmv.enums.Skeleton.Radii.ORIGINAL: + draw_original_arbors_radii_option(layout=layout, scene=scene, options=options) + + elif options.morphology.arbors_radii == nmv.enums.Skeleton.Radii.UNIFIED: + draw_unified_radii_option(layout=layout, scene=scene, options=options) + + elif options.morphology.arbors_radii == nmv.enums.Skeleton.Radii.UNIFIED_PER_ARBOR_TYPE: + draw_unified_radii_per_arbor_option( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif options.morphology.arbors_radii == nmv.enums.Skeleton.Radii.SCALED: + draw_scaled_radii_option(layout=layout, scene=scene, options=options) + + elif options.morphology.arbors_radii == nmv.enums.Skeleton.Radii.FILTERED: + draw_filtered_radii_option(layout=layout, scene=scene, options=options) + + else: + nmv.logger.log('UI_ERROR: draw_arbors_radii_options') + + +#################################################################################################### +# @draw_arbor_quality_option +#################################################################################################### +def draw_arbor_quality_option(layout, scene, options): + + row = layout.row() + row.label(text='Arbor Quality:') + row.prop(scene, 'NMV_ArborQuality') + options.morphology.bevel_object_sides = scene.NMV_ArborQuality + + +#################################################################################################### +# @draw_morphology_resampling_method_option +#################################################################################################### +def draw_morphology_resampling_method_option(layout, scene, options): + + row = layout.row() + row.label(text='Resampling') + row.prop(scene, 'NMV_MorphologyResampling') + options.morphology.resampling_method = scene.NMV_MorphologyResampling + + +#################################################################################################### +# @draw_fixed_resampling_step_option +#################################################################################################### +def draw_fixed_resampling_step_option(layout, scene, options): + + row = layout.row() + row.label(text='Resampling Step') + row.prop(scene, 'NMV_MorphologyResamplingStep') + options.morphology.resampling_step = scene.NMV_MorphologyResamplingStep + + +#################################################################################################### +# @draw_morphology_resampling_options +#################################################################################################### +def draw_morphology_resampling_options(layout, scene, options): + + draw_morphology_resampling_method_option(layout=layout, scene=scene, options=options) + if options.morphology.resampling_method == nmv.enums.Skeleton.Resampling.FIXED_STEP: + draw_fixed_resampling_step_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_disconnected_segments_options +#################################################################################################### +def draw_disconnected_segments_options(layout, scene, options, morphology): + + draw_morphology_resampling_options(layout=layout, scene=scene, options=options) + draw_arbors_radii_options(layout=layout, scene=scene, options=options, morphology=morphology) + draw_arbor_quality_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_disconnected_sections_options +#################################################################################################### +def draw_disconnected_sections_options(layout, scene, options, morphology): + + draw_morphology_resampling_options(layout=layout, scene=scene, options=options) + draw_arbors_radii_options(layout=layout, scene=scene, options=options, morphology=morphology) + draw_arbor_quality_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_samples_options +#################################################################################################### +def draw_samples_options(layout, scene, options, morphology): + + draw_morphology_resampling_options(layout=layout, scene=scene, options=options) + draw_arbors_radii_options(layout=layout, scene=scene, options=options, morphology=morphology) + + +#################################################################################################### +# @draw_progressive_options +#################################################################################################### +def draw_progressive_options(layout, scene, options, morphology): + + draw_morphology_resampling_options(layout=layout, scene=scene, options=options) + draw_arbors_radii_options(layout=layout, scene=scene, options=options, morphology=morphology) + draw_arbor_quality_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_articulated_sections_options +#################################################################################################### +def draw_articulated_sections_options(layout, scene, options, morphology): + + draw_morphology_resampling_options(layout=layout, scene=scene, options=options) + draw_arbors_radii_options(layout=layout, scene=scene, options=options, morphology=morphology) + draw_arbor_quality_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_connected_sections_options +#################################################################################################### +def draw_connected_sections_options(layout, scene, options, morphology): + + draw_arbor_style_option(layout=layout, scene=scene, options=options) + draw_branching_option(layout=layout, scene=scene, options=options) + draw_arbor_to_soma_connection_option(layout=layout, scene=scene, options=options) + draw_morphology_resampling_options(layout=layout, scene=scene, options=options) + draw_arbors_radii_options(layout=layout, scene=scene, options=options, morphology=morphology) + draw_arbor_quality_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_dendrogram_options +#################################################################################################### +def draw_dendrogram_options(layout, scene, options): + + draw_dendrogram_type_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_morphology_reconstruction_options +#################################################################################################### +def draw_morphology_reconstruction_options(layout, scene, options, morphology): + + draw_morphology_reconstruction_header(layout=layout) + draw_morphology_reconstruction_technique_option(layout=layout, scene=scene, options=options) + + method = options.morphology.reconstruction_method + if method == nmv.enums.Skeleton.Method.CONNECTED_SECTIONS: + draw_connected_sections_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif method == nmv.enums.Skeleton.Method.DISCONNECTED_SECTIONS: + draw_disconnected_sections_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif method == nmv.enums.Skeleton.Method.DISCONNECTED_SEGMENTS: + draw_disconnected_segments_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif method == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS: + draw_articulated_sections_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif method == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS: + draw_articulated_sections_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif method == nmv.enums.Skeleton.Method.PROGRESSIVE: + draw_progressive_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif method == nmv.enums.Skeleton.Method.SAMPLES: + draw_samples_options( + layout=layout, scene=scene, options=options, morphology=morphology) + + elif method == nmv.enums.Skeleton.Method.DENDROGRAM: + draw_dendrogram_options(layout=layout, scene=scene, options=options) + + else: + nmv.logger.log('UI_ERROR: draw_morphology_reconstruction_options') + + + diff --git a/nmv/interface/ui/morphology/layout_rendering_props.py b/nmv/interface/ui/morphology/layout_rendering_props.py new file mode 100644 index 000000000..50df1fb3b --- /dev/null +++ b/nmv/interface/ui/morphology/layout_rendering_props.py @@ -0,0 +1,102 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + +from .layout_buttons import draw_morphology_rendering_buttons +from .layout_buttons import draw_dendrogram_rendering_button +from .layout_buttons import draw_animated_morphology_rendering_buttons + + +#################################################################################################### +# draw_still_frame_rendering_options +#################################################################################################### +def draw_still_frame_rendering_options(panel, scene, options, show_stats=False): + + nmv.interface.ui.common.draw_rendering_header( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_morphology_rendering_view_option( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_resolution_basis_option( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_resolution_options( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_image_format_option( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_scale_bar_option( + layout=panel.layout, scene=scene, options=options) + + draw_morphology_rendering_buttons(panel=panel, scene=scene) + + if show_stats: + row = panel.layout.row() + row.prop(scene, 'NMV_MorphologyRenderingTime') + row.enabled = False + + +#################################################################################################### +# draw_dendrogram_rendering_options +#################################################################################################### +def draw_dendrogram_rendering_options(panel, scene, options, show_stats): + + nmv.interface.ui.common.draw_rendering_header( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_resolution_options( + layout=panel.layout, scene=scene, options=options) + + draw_dendrogram_rendering_button(panel=panel, scene=scene) + + if show_stats: + row = panel.layout.row() + row.prop(scene, 'NMV_MorphologyRenderingTime') + row.enabled = False + + +#################################################################################################### +# draw_animated_sequences_rendering_options +#################################################################################################### +def draw_animated_sequences_rendering_options(panel, scene, options): + + draw_animated_morphology_rendering_buttons(panel=panel, scene=scene) + + +#################################################################################################### +# draw_rendering_options +#################################################################################################### +def draw_rendering_options(panel, scene, options, show_stats=False): + + if options.morphology.reconstruction_method == nmv.enums.Skeleton.Method.DENDROGRAM: + draw_dendrogram_rendering_options( + panel=panel, scene=scene, options=options, show_stats=show_stats) + else: + draw_still_frame_rendering_options( + panel=panel, scene=scene, options=options, show_stats=show_stats) + draw_animated_sequences_rendering_options( + panel=panel, scene=scene, options=options) diff --git a/nmv/interface/ui/morphology/layout_skeleton_props.py b/nmv/interface/ui/morphology/layout_skeleton_props.py new file mode 100644 index 000000000..1cbaa8b9c --- /dev/null +++ b/nmv/interface/ui/morphology/layout_skeleton_props.py @@ -0,0 +1,143 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.enums + + +#################################################################################################### +# @draw_morphology_skeleton_display_header +#################################################################################################### +def draw_morphology_skeleton_display_header(layout): + + row = layout.row() + row.label(text='Morphology Skeleton Display Options', icon='QUESTION') + + +#################################################################################################### +# @draw_soma_building_option +#################################################################################################### +def draw_soma_building_option(layout, scene, options): + + row = layout.row() + row.label(text='Display Soma As') + row.prop(scene, 'NMV_BuildSoma') + options.morphology.soma_representation = scene.NMV_BuildSoma + + +#################################################################################################### +# @draw_axons_building_option +#################################################################################################### +def draw_axons_building_option(layout, scene, options, morphology): + + if morphology.has_axons(): + axon_row = layout.row() + axon_row.prop(scene, 'NMV_BuildAxon') + axon_order_row = axon_row.column() + axon_order_row.prop(scene, 'NMV_AxonsBranchingOrder') + axon_order_row.enabled = True if scene.NMV_BuildAxon else False + options.morphology.ignore_axons = not scene.NMV_BuildAxon + options.morphology.axon_branch_order = scene.NMV_AxonsBranchingOrder + + +#################################################################################################### +# @draw_basal_dendrites_building_option +#################################################################################################### +def draw_basal_dendrites_building_option(layout, scene, options, morphology): + + if morphology.has_basal_dendrites(): + basal_dendrites_row = layout.row() + basal_dendrites_row.prop(scene, 'NMV_BuildBasalDendrites') + basal_dendrites_order_row = basal_dendrites_row.column() + basal_dendrites_order_row.prop(scene, 'NMV_BasalDendritesBranchingOrder') + basal_dendrites_order_row.enabled = True if scene.NMV_BuildBasalDendrites else False + options.morphology.ignore_basal_dendrites = not scene.NMV_BuildBasalDendrites + options.morphology.basal_dendrites_branch_order = scene.NMV_BasalDendritesBranchingOrder + + +#################################################################################################### +# @draw_apical_dendrites_building_option +#################################################################################################### +def draw_apical_dendrites_building_option(layout, scene, options, morphology): + + if morphology.has_apical_dendrites(): + apical_dendrites_row = layout.row() + apical_dendrites_row.prop(scene, 'NMV_BuildApicalDendrite') + apical_dendrites_order_row = apical_dendrites_row.column() + apical_dendrites_order_row.prop(scene, 'NMV_ApicalDendritesBranchingOrder') + apical_dendrites_order_row.enabled = True if scene.NMV_BuildApicalDendrite else False + options.morphology.ignore_apical_dendrites = not scene.NMV_BuildApicalDendrite + options.morphology.apical_dendrite_branch_order = scene.NMV_ApicalDendritesBranchingOrder + + +#################################################################################################### +# @draw_demo_options +#################################################################################################### +def draw_demo_options(layout, scene): + + # Axons + axons_row = layout.row() + axons_row.prop(scene, 'NMV_BuildAxon') + axons_order_row = axons_row.column() + axons_order_row.prop(scene, 'NMV_AxonsBranchingOrder') + + # Basal dendrites + basal_dendrites_row = layout.row() + basal_dendrites_row.prop(scene, 'NMV_BuildBasalDendrites') + basal_dendrites_order_row = basal_dendrites_row.column() + basal_dendrites_order_row.prop(scene, 'NMV_BasalDendritesBranchingOrder') + + # Apical Dendrites + apical_dendrites_row = layout.row() + apical_dendrites_row.prop(scene, 'NMV_BuildApicalDendrite') + apical_dendrites_order_row = apical_dendrites_row.column() + apical_dendrites_order_row.prop(scene, 'NMV_ApicalDendritesBranchingOrder') + + +#################################################################################################### +# @draw_morphology_skeleton_display_options +#################################################################################################### +def draw_morphology_skeleton_display_options(layout, scene, options, morphology): + + draw_morphology_skeleton_display_header(layout=layout) + + # The morphology must be loaded to be able to draw these options, otherwise draw a demo + if morphology is not None: + + if options.morphology.reconstruction_method == nmv.enums.Skeleton.Method.DENDROGRAM: + draw_axons_building_option( + layout=layout, scene=scene, options=options, morphology=morphology) + + draw_basal_dendrites_building_option( + layout=layout, scene=scene, options=options, morphology=morphology) + + draw_apical_dendrites_building_option( + layout=layout, scene=scene, options=options, morphology=morphology) + else: + + draw_soma_building_option(layout=layout, scene=scene, options=options) + + draw_axons_building_option( + layout=layout, scene=scene, options=options, morphology=morphology) + + draw_basal_dendrites_building_option( + layout=layout, scene=scene, options=options, morphology=morphology) + + draw_apical_dendrites_building_option( + layout=layout, scene=scene, options=options, morphology=morphology) + else: + draw_demo_options(layout=layout, scene=scene) diff --git a/nmv/interface/ui/morphology/morphology_panel.py b/nmv/interface/ui/morphology/morphology_panel.py deleted file mode 100644 index bdb7c7efd..000000000 --- a/nmv/interface/ui/morphology/morphology_panel.py +++ /dev/null @@ -1,979 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2021, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# System imports -import time -import copy - -# Blender imports -import bpy - -# Internal imports -import nmv.bbox -import nmv.builders -import nmv.consts -import nmv.enums -import nmv.file -import nmv.interface -import nmv.shading -import nmv.scene -import nmv.skeleton -import nmv.utilities -import nmv.rendering -import nmv.geometry -from .morphology_panel_options import * - -# Is the morphology reconstructed or not -is_morphology_reconstructed = False - -# Is the morphology rendered or not -is_morphology_rendered = False - -# What is the selected morphology builder -morphology_builder = None - - -#################################################################################################### -# @MorphologyPanel -#################################################################################################### -class MorphologyPanel(bpy.types.Panel): - """Morphology tools panel""" - - ################################################################################################ - # Panel parameters - ################################################################################################ - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' if nmv.utilities.is_blender_280() else 'TOOLS' - bl_idname = "OBJECT_PT_NMV_MorphologyToolBox" - bl_label = 'Morphology Toolbox' - bl_category = 'NeuroMorphoVis' - bl_options = {'DEFAULT_CLOSED'} - - ################################################################################################ - # @update_ui_colors - ################################################################################################ - def update_ui_colors(self, context): - - # Get a list of initial colors from the selected colormap - colors = nmv.utilities.create_colormap_from_hex_list( - nmv.enums.ColorMaps.get_hex_color_list(context.scene.NMV_ColorMap), - nmv.consts.Color.COLORMAP_RESOLUTION) - - # Invert the colormap - if context.scene.NMV_InvertColorMap: - colors.reverse() - - # Update the colormap in the UI - for color_index in range(nmv.consts.Color.COLORMAP_RESOLUTION): - setattr(context.scene, 'NMV_Color%d' % color_index, colors[color_index]) - - # A list of all the color maps available in NeuroMorphoVis - # Note that once a new colormap is selected, the corresponding colors will be set in the UI - bpy.types.Scene.NMV_ColorMap = bpy.props.EnumProperty( - items=nmv.enums.ColorMaps.COLOR_MAPS, - name='', - default=nmv.enums.ColorMaps.GNU_PLOT, - update=update_ui_colors) - - bpy.types.Scene.NMV_InvertColorMap = bpy.props.BoolProperty( - name='Invert', - description='Invert the selected colormap', - default=False, - update=update_ui_colors) - - # Create a list of colors from the selected colormap - colors = nmv.utilities.create_colormap_from_hex_list( - nmv.enums.ColorMaps.get_hex_color_list(bpy.types.Scene.NMV_ColorMap), - nmv.consts.Color.COLORMAP_RESOLUTION) - - # Update the UI color elements from the color map list - for index in range(nmv.consts.Color.COLORMAP_RESOLUTION): - setattr(bpy.types.Scene, 'NMV_Color%d' % index, bpy.props.FloatVectorProperty( - name='', subtype='COLOR', default=colors[index], min=0.0, max=1.0, description='')) - - ################################################################################################ - # @draw - ################################################################################################ - def draw(self, - context): - """Draw the panel. - - :param context: - Panel context. - """ - - # Get a reference to the layout of the panel - layout = self.layout - - # Get a reference to the scene - current_scene = context.scene - - # Documentation button - documentation_button = layout.column() - documentation_button.operator('nmv.documentation_morphology', icon='URL') - documentation_button.separator() - - # Set the reconstruction options - nmv.interface.ui.morphology_panel_ops.set_reconstruction_options( - layout=layout, scene=current_scene, options=nmv.interface.ui_options) - - # Set the skeleton options - nmv.interface.ui.morphology_panel_ops.set_skeleton_options( - layout=layout, scene=current_scene, options=nmv.interface.ui_options) - - # Set the color options - nmv.interface.ui.morphology_panel_ops.add_color_options( - layout=layout, scene=current_scene, options=nmv.interface.ui_options) - - # Reconstruction button - quick_reconstruction_row = layout.row() - quick_reconstruction_row.label(text='Quick Reconstruction:', icon='PARTICLE_POINT') - reconstruct_morphology_button_row = layout.row() - reconstruct_morphology_button_row.operator('nmv.reconstruct_morphology', - text=bpy.types.Scene.NMV_MorphologyButtonLabel, - icon='RNA_ADD') - reconstruct_morphology_button_row.enabled = True - - global is_morphology_reconstructed - if is_morphology_reconstructed: - morphology_stats_row = layout.row() - morphology_stats_row.label(text='Stats:', icon='RECOVER_LAST') - reconstruction_time_row = layout.row() - reconstruction_time_row.prop(context.scene, 'NMV_MorphologyReconstructionTime') - reconstruction_time_row.enabled = False - - # Set the rendering options - nmv.interface.ui.morphology_panel_ops.set_rendering_options( - layout=layout, scene=current_scene, options=nmv.interface.ui_options) - - global is_morphology_rendered - if is_morphology_rendered: - morphology_stats_row = layout.row() - morphology_stats_row.label(text='Stats:', icon='RECOVER_LAST') - rendering_time_row = layout.row() - rendering_time_row.prop(context.scene, 'NMV_MorphologyRenderingTime') - rendering_time_row.enabled = False - - # Set the rendering options - nmv.interface.ui.morphology_panel_ops.set_export_options( - layout=layout, scene=current_scene, options=nmv.interface.ui_options) - - # Enable or disable the layout - nmv.interface.enable_or_disable_layout(layout) - - -#################################################################################################### -# ReconstructMorphologyOperator -#################################################################################################### -class ReconstructMorphologyOperator(bpy.types.Operator): - """Morphology reconstruction operator""" - - # Operator parameters - bl_idname = "nmv.reconstruct_morphology" - bl_label = bpy.types.Scene.NMV_MorphologyButtonLabel - - ################################################################################################ - # @load_morphology - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Context. - :return: - 'FINISHED' - """ - - # Reset the scene - nmv.scene.reset_scene() - - # Clear the scene - nmv.scene.clear_scene() - - # Load the morphology file - loading_result = nmv.interface.ui.load_morphology(self, context.scene) - - # If the result is None, report the issue - if loading_result is None: - self.report({'ERROR'}, 'Please select a valid morphology file') - return {'FINISHED'} - - # Start reconstruction - start_time = time.time() - - global morphology_builder - # Create a skeleton builder object to build the morphology skeleton - method = nmv.interface.ui_options.morphology.reconstruction_method - if method == nmv.enums.Skeleton.Method.DISCONNECTED_SEGMENTS: - morphology_builder = nmv.builders.DisconnectedSegmentsBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - - # Draw the morphology as a set of disconnected tubes, where each SECTION is a tube - elif method == nmv.enums.Skeleton.Method.DISCONNECTED_SECTIONS or \ - method == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS: - morphology_builder = nmv.builders.DisconnectedSectionsBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - - # Draw the morphology as a set of spheres, where each SPHERE represents a sample - elif method == nmv.enums.Skeleton.Method.SAMPLES: - morphology_builder = nmv.builders.SamplesBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - - elif method == nmv.enums.Skeleton.Method.CONNECTED_SECTIONS: - morphology_builder = nmv.builders.ConnectedSectionsBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - - elif method == nmv.enums.Skeleton.Method.PROGRESSIVE: - morphology_builder = nmv.builders.ProgressiveBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - - elif method == nmv.enums.Skeleton.Method.DENDROGRAM: - morphology_builder = nmv.builders.DendrogramBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - - # Default: DisconnectedSectionsBuilder - else: - morphology_builder = nmv.builders.DisconnectedSectionsBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - - # Draw the morphology skeleton and return a list of all the reconstructed objects - nmv.interface.ui_reconstructed_skeleton = morphology_builder.draw_morphology_skeleton( - context=context) - - # Interpolations - scale = float(context.scene.NMV_MaximumValue) - float(context.scene.NMV_MinimumValue) - delta = scale / float(nmv.consts.Color.COLORMAP_RESOLUTION) - - # Fill the list of colors - for color_index in range(nmv.consts.Color.COLORMAP_RESOLUTION): - r0_value = float(context.scene.NMV_MinimumValue) + (color_index * delta) - r1_value = float(context.scene.NMV_MinimumValue) + ((color_index + 1) * delta) - setattr(context.scene, 'NMV_R0_Value%d' % color_index, r0_value) - setattr(context.scene, 'NMV_R1_Value%d' % color_index, r1_value) - - # Morphology reconstructed - reconstruction_time = time.time() - global is_morphology_reconstructed - is_morphology_reconstructed = True - context.scene.NMV_MorphologyReconstructionTime = reconstruction_time - start_time - nmv.logger.statistics('Morphology reconstructed in [%f] seconds' % - context.scene.NMV_MorphologyReconstructionTime) - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderMorphologyFront -#################################################################################################### -class RenderMorphologyFront(bpy.types.Operator): - """Render front view of the reconstructed morphology""" - - # Operator parameters - bl_idname = "nmv.render_morphology_front" - bl_label = "Front" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Rendering Context. - :return: - 'FINISHED'. - """ - - # Timer - start_time = time.time() - - # Render the image - nmv.interface.ui.render_morphology_image( - self, context_scene=context.scene, view=nmv.enums.Camera.View.FRONT, - image_format=nmv.interface.ui_options.morphology.image_format) - - # Stats. - rendering_time = time.time() - global is_morphology_rendered - is_morphology_rendered = True - context.scene.NMV_MorphologyRenderingTime = rendering_time - start_time - nmv.logger.statistics('Morphology rendered in [%f] seconds' % - context.scene.NMV_MorphologyRenderingTime) - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderMorphologySide -#################################################################################################### -class RenderMorphologySide(bpy.types.Operator): - """Render side view of the reconstructed morphology""" - - # Operator parameters - bl_idname = "nmv.render_morphology_side" - bl_label = "Side" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Execute the operator. - - :param context: - Rendering context. - :return: - 'FINISHED'. - """ - - # Timer - start_time = time.time() - - # Render the image - nmv.interface.ui.render_morphology_image( - self, context_scene=context.scene, view=nmv.enums.Camera.View.SIDE, - image_format=nmv.interface.ui_options.morphology.image_format) - - # Stats. - rendering_time = time.time() - global is_morphology_rendered - is_morphology_rendered = True - context.scene.NMV_MorphologyRenderingTime = rendering_time - start_time - nmv.logger.statistics('Morphology rendered in [%f] seconds' % - context.scene.NMV_MorphologyRenderingTime) - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderMorphologyTop -#################################################################################################### -class RenderMorphologyTop(bpy.types.Operator): - """Render top view of the reconstructed morphology""" - - # Operator parameters - bl_idname = "nmv.render_morphology_top" - bl_label = "Top" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Execute the operator. - - :param context: - Rendering context. - :return: - 'FINISHED'. - """ - - # Timer - start_time = time.time() - - # Render the image - nmv.interface.ui.render_morphology_image( - self, context_scene=context.scene, view=nmv.enums.Camera.View.TOP, - image_format=nmv.interface.ui_options.morphology.image_format) - - # Stats. - rendering_time = time.time() - global is_morphology_rendered - is_morphology_rendered = True - context.scene.NMV_MorphologyRenderingTime = rendering_time - start_time - nmv.logger.statistics('Morphology rendered in [%f] seconds' % - context.scene.NMV_MorphologyRenderingTime) - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderMorphology360 -#################################################################################################### -class RenderMorphology360(bpy.types.Operator): - """Render a 360 view of the reconstructed morphology""" - - # Operator parameters - bl_idname = "nmv.render_morphology_360" - bl_label = "360" - - # Timer parameters - event_timer = None - timer_limits = 0 - start_time = 0 - - # Output data - output_directory = None - - ################################################################################################ - # @modal - ################################################################################################ - def modal(self, context, event): - """ - Threading and non-blocking handling. - - :param context: Panel context. - :param event: A given event for the panel. - """ - - # Get a reference to the scene - scene = context.scene - - # Cancelling event, if using right click or exceeding the time limit of the simulation - if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > 360: - - # Reset the orientation of the mesh - nmv.scene.reset_orientation_of_objects( - scene_objects=nmv.interface.ui_reconstructed_skeleton) - - # Stats. - rendering_time = time.time() - global is_morphology_rendered - is_morphology_rendered = True - context.scene.NMV_MorphologyRenderingTime = rendering_time - self.start_time - nmv.logger.statistics('Morphology rendered in [%f] seconds' % - context.scene.NMV_MorphologyRenderingTime) - - # Reset the timer limits - self.timer_limits = 0 - - # Refresh the panel context - self.cancel(context) - - # Done - return {'FINISHED'} - - # Timer event, where the function is executed here on a per-frame basis - if event.type == 'TIMER': - - # Set the frame name - image_name = '%s/%s' % ( - self.output_directory, '{0:05d}'.format(self.timer_limits)) - - # Compute the bounding box for a close up view - if context.scene.NMV_MorphologyRenderingView == \ - nmv.enums.Rendering.View.CLOSE_UP: - - # Compute the bounding box for a close up view - rendering_bbox = nmv.bbox.compute_unified_extent_bounding_box( - extent=context.scene.NMV_MorphologyCloseUpDimensions) - - # Compute the bounding box for a mid-shot view - elif context.scene.NMV_MorphologyRenderingView == \ - nmv.enums.Rendering.View.MID_SHOT: - - # Compute the bounding box for the available meshes only - rendering_bbox = nmv.bbox.compute_scene_bounding_box_for_curves() - - # Compute the bounding box for the wide-shot view that corresponds to the whole - # morphology - else: - - # Compute the full morphology bounding box - rendering_bbox = nmv.skeleton.compute_full_morphology_bounding_box( - morphology=nmv.interface.ui_morphology) - - # Compute a 360 bounding box to fit the arbors - bounding_box_360 = nmv.bbox.compute_360_bounding_box( - rendering_bbox, nmv.interface.ui_morphology.soma.centroid) - - # Stretch the bounding box by few microns - bounding_box_360.extend_bbox_uniformly(delta=nmv.consts.Image.GAP_DELTA) - - # Render a frame - nmv.rendering.renderer.render_at_angle( - scene_objects=nmv.interface.ui_reconstructed_skeleton, - angle=self.timer_limits, - bounding_box=bounding_box_360, - camera_view=nmv.enums.Camera.View.FRONT, - image_resolution=context.scene.NMV_MorphologyFrameResolution, - image_name=image_name) - - # Update the progress shell - nmv.utilities.show_progress('Rendering', self.timer_limits, 360) - - # Update the progress bar - context.scene.NMV_MorphologyRenderingProgress = int(100 * self.timer_limits / 360.0) - - # Upgrade the timer limits - self.timer_limits += 1 - - # Next frame - return {'PASS_THROUGH'} - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """ - Execute the operator. - - :param context: Panel context. - """ - - # If this is a dendrogram rendering, handle it in a very specific way. - if nmv.interface.ui_options.morphology.reconstruction_method == \ - nmv.enums.Skeleton.Method.DENDROGRAM: - - # Cannot render a 360 of the dendrogram - self.report({'INFO'}, 'Cannot render a 360 of the dendrogram') - return {'FINISHED'} - - # Timer - self.start_time = time.time() - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the sequences directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): - nmv.file.ops.clean_and_create_directory( - nmv.interface.ui_options.io.sequences_directory) - - # Create a specific directory for this mesh - self.output_directory = '%s/%s%s' % \ - (nmv.interface.ui_options.io.sequences_directory, - nmv.interface.ui_options.morphology.label, - nmv.consts.Suffix.MORPHOLOGY_360) - nmv.file.ops.clean_and_create_directory(self.output_directory) - - # Use the event timer to update the UI during the soma building - wm = context.window_manager - self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) - wm.modal_handler_add(self) - - # Done - return {'RUNNING_MODAL'} - - ################################################################################################ - # @cancel - ################################################################################################ - def cancel(self, context): - """ - Cancel the panel processing and return to the interaction mode. - - :param context: Panel context. - """ - - # Multi-threading - wm = context.window_manager - wm.event_timer_remove(self.event_timer) - - # Report the process termination in the UI - self.report({'INFO'}, 'Morphology Rendering Done') - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderMorphologyProgressive -#################################################################################################### -class RenderMorphologyProgressive(bpy.types.Operator): - """Render a progressive sequence of the reconstruction procedure (time-consuming)""" - - # Operator parameters - bl_idname = "nmv.render_morphology_progressive" - bl_label = "Progressive" - - # Timer parameters - start_time = 0 - event_timer = None - timer_limits = 0 - - # Output data - output_directory = None - - # The bounding box of the morphology - morphology_bounding_box = None - - ################################################################################################ - # @modal - ################################################################################################ - def modal(self, context, event): - """ - Threading and non-blocking handling. - - :param context: Panel context. - :param event: A given event for the panel. - """ - - # Cancelling event, if using right click or exceeding the time limit of the simulation - if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > bpy.context.scene.frame_end: - - # Stats. - rendering_time = time.time() - global is_morphology_rendered - is_morphology_rendered = True - context.scene.NMV_MorphologyRenderingTime = rendering_time - self.start_time - nmv.logger.statistics('Morphology rendered in [%f] seconds' % - context.scene.NMV_MorphologyRenderingTime) - - # Reset the timer limits - self.timer_limits = 0 - - # Refresh the panel context - self.cancel(context) - - # Done - return {'FINISHED'} - - # Timer event, where the function is executed here on a per-frame basis - if event.type == 'TIMER': - - # Update the frame number - bpy.context.scene.frame_set(self.timer_limits) - - # Set the frame name - image_name = '%s' % '{0:05d}'.format(self.timer_limits) - - # Render a frame - nmv.rendering.renderer.render( - bounding_box=self.morphology_bounding_box, - camera_view=nmv.enums.Camera.View.FRONT, - image_resolution=context.scene.NMV_MorphologyFrameResolution, - image_name=image_name, - image_directory=self.output_directory) - - # Update the progress shell - nmv.utilities.show_progress('Rendering', self.timer_limits, bpy.context.scene.frame_end) - - # Update the progress bar - context.scene.NMV_MorphologyRenderingProgress = \ - int(100 * self.timer_limits / float(bpy.context.scene.frame_end)) - - # Upgrade the timer limits - self.timer_limits += 1 - - # Next frame - return {'PASS_THROUGH'} - - ################################################################################################ - # @compute_morphology_bounding_box_for_progressive_reconstruction - ################################################################################################ - def compute_morphology_bounding_box_for_progressive_reconstruction(self, - context): - """Computes the bounding box of the reconstructed morphology from the progressive builder. - - :param context: - Blender context. - """ - - # Move to the last frame to get the bounding box of all the drawn objects - bpy.context.scene.frame_set(bpy.context.scene.frame_end) - - # Morphology view - view = context.scene.NMV_MorphologyRenderingView - - # Compute the bounding box for a close up view - if view == nmv.enums.Rendering.View.CLOSE_UP: - self.morphology_bounding_box = nmv.bbox.compute_unified_extent_bounding_box( - extent=context.scene.NMV_MorphologyCloseUpDimensions) - - # Compute the bounding box for a mid-shot view - elif view == nmv.enums.Rendering.View.MID_SHOT: - self.morphology_bounding_box = copy.deepcopy( - nmv.bbox.compute_scene_bounding_box_for_curves_and_meshes()) - - # The bounding box for the wide-shot view that corresponds to the whole morphology - else: - self.morphology_bounding_box = nmv.skeleton.compute_full_morphology_bounding_box( - morphology=nmv.interface.ui_morphology) - - # Stretch the bounding box by few microns - self.morphology_bounding_box.extend_bbox_uniformly(delta=nmv.consts.Image.GAP_DELTA) - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """ - Execute the operator. - - :param context: Panel context. - """ - - # If this is a dendrogram rendering, handle it in a very specific way. - if nmv.interface.ui_options.morphology.reconstruction_method == \ - nmv.enums.Skeleton.Method.DENDROGRAM: - # Cannot render a 360 of the dendrogram - self.report({'INFO'}, 'Cannot render a progressive reconstruction of the dendrogram') - return {'FINISHED'} - - # Timer - self.start_time = time.time() - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the sequences directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): - nmv.file.ops.clean_and_create_directory( - nmv.interface.ui_options.io.sequences_directory) - - # Create a specific directory for this mesh - self.output_directory = '%s/%s%s' % \ - (nmv.interface.ui_options.io.sequences_directory, - nmv.interface.ui_options.morphology.label, - nmv.consts.Suffix.MORPHOLOGY_PROGRESSIVE) - nmv.file.ops.clean_and_create_directory(self.output_directory) - - # Clear the scene - nmv.scene.clear_scene() - - # Reconstruct the morphology using the progressive builder - progressive_builder = nmv.builders.ProgressiveBuilder( - morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) - progressive_builder.draw_morphology_skeleton() - - # Compute the bounding box of the morphology directly after the reconstruction - self.compute_morphology_bounding_box_for_progressive_reconstruction(context=context) - - # Use the event timer to update the UI during the soma building - wm = context.window_manager - self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) - wm.modal_handler_add(self) - - # Done - return {'RUNNING_MODAL'} - - ################################################################################################ - # @cancel - ################################################################################################ - def cancel(self, context): - """ - Cancel the panel processing and return to the interaction mode. - - :param context: Panel context. - """ - - # Multi-threading - wm = context.window_manager - wm.event_timer_remove(self.event_timer) - - # Report the process termination in the UI - self.report({'INFO'}, 'Morphology Rendering Done') - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @SaveMorphologySWC -#################################################################################################### -class SaveMorphologySWC(bpy.types.Operator): - """Save the reconstructed morphology in an SWC file""" - - # Operator parameters - bl_idname = "nmv.save_morphology_swc" - bl_label = "SWC (.swc)" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """ - Executes the operator. - - :param context: Operator context. - :return: {'FINISHED'} - """ - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the meshes directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.morphologies_directory): - nmv.file.ops.clean_and_create_directory( - nmv.interface.ui_options.io.morphologies_directory) - - # Export the reconstructed morphology as an .blend file - # NOTE: Since we don't have meshes, then the mesh_object argument will be set to None and - # the exported blender file will contain all the morphology objects. - global morphology_builder - nmv.file.write_morphology_to_swc_file( - morphology_builder.morphology, nmv.interface.ui_options.io.morphologies_directory) - - return {'FINISHED'} - - -#################################################################################################### -# @SaveMorphologySegments -#################################################################################################### -class SaveMorphologySegments(bpy.types.Operator): - """Save the reconstructed morphology as a list of segments into file""" - - # Operator parameters - bl_idname = "nmv.save_morphology_segments" - bl_label = "Segments (.segments)" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """ - Executes the operator. - - :param context: Operator context. - :return: {'FINISHED'} - """ - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the meshes directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.morphologies_directory): - nmv.file.ops.clean_and_create_directory( - nmv.interface.ui_options.io.morphologies_directory) - - # Export the reconstructed morphology as an .blend file - # NOTE: Since we don't have meshes, then the mesh_object argument will be set to None and - # the exported blender file will contain all the morphology objects. - nmv.file.write_morphology_to_segments_file( - nmv.interface.ui_morphology, nmv.interface.ui_options.io.morphologies_directory) - - return {'FINISHED'} - - -#################################################################################################### -# @SaveMorphologyBLEND -#################################################################################################### -class SaveMorphologyBLEND(bpy.types.Operator): - """Save the reconstructed morphology in a blender file""" - - # Operator parameters - bl_idname = "nmv.save_morphology_blend" - bl_label = "Blender Format (.blend)" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """ - Executes the operator. - - :param context: Operator context. - :return: {'FINISHED'} - """ - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the meshes directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.morphologies_directory): - nmv.file.ops.clean_and_create_directory( - nmv.interface.ui_options.io.morphologies_directory) - - # Export the reconstructed morphology as an .blend file - # NOTE: Since we don't have meshes, then the mesh_object argument will be set to None and - # the exported blender file will contain all the morphology objects. - nmv.file.export_scene_to_blend_file( - output_directory=nmv.interface.ui_options.io.morphologies_directory, - output_file_name=nmv.interface.ui_morphology.label) - - return {'FINISHED'} - - -#################################################################################################### -# @MorphologyReconstructionDocumentation -#################################################################################################### -class MorphologyReconstructionDocumentation(bpy.types.Operator): - """Open the online documentation page of the Morphology Reconstruction panel.""" - - # Operator parameters - bl_idname = "nmv.documentation_morphology" - bl_label = "Online User Guide" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Blender context - :return: - 'FINISHED' - """ - - import webbrowser - webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki/Morphology-Reconstruction') - return {'FINISHED'} - - -#################################################################################################### -# @register_panel -#################################################################################################### -def register_panel(): - """Registers all the classes in this panel""" - - # ColorMap - # bpy.utils.register_class(ColorMapOperator) - - # Soma reconstruction panel - bpy.utils.register_class(MorphologyPanel) - - # Buttons - bpy.utils.register_class(MorphologyReconstructionDocumentation) - bpy.utils.register_class(ReconstructMorphologyOperator) - bpy.utils.register_class(RenderMorphologyFront) - bpy.utils.register_class(RenderMorphologySide) - bpy.utils.register_class(RenderMorphologyTop) - bpy.utils.register_class(RenderMorphology360) - bpy.utils.register_class(RenderMorphologyProgressive) - bpy.utils.register_class(SaveMorphologyBLEND) - bpy.utils.register_class(SaveMorphologySWC) - bpy.utils.register_class(SaveMorphologySegments) - - -#################################################################################################### -# @unregister_panel -#################################################################################################### -def unregister_panel(): - """Un-registers all the classes in this panel""" - - # ColorMap - # bpy.utils.unregister_class(ColorMapOperator) - - # Morphology reconstruction panel - bpy.utils.unregister_class(MorphologyPanel) - - # Buttons - bpy.utils.unregister_class(MorphologyReconstructionDocumentation) - bpy.utils.unregister_class(ReconstructMorphologyOperator) - bpy.utils.unregister_class(RenderMorphologyTop) - bpy.utils.unregister_class(RenderMorphologySide) - bpy.utils.unregister_class(RenderMorphologyFront) - bpy.utils.unregister_class(RenderMorphology360) - bpy.utils.unregister_class(RenderMorphologyProgressive) - bpy.utils.unregister_class(SaveMorphologyBLEND) - bpy.utils.unregister_class(SaveMorphologySWC) - bpy.utils.unregister_class(SaveMorphologySegments) - diff --git a/nmv/interface/ui/morphology/morphology_panel_color_ops.py b/nmv/interface/ui/morphology/morphology_panel_color_ops.py deleted file mode 100644 index 5806a34ba..000000000 --- a/nmv/interface/ui/morphology/morphology_panel_color_ops.py +++ /dev/null @@ -1,505 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2022, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# Blender imports -import bpy -from mathutils import Vector - -# Internal imports -import nmv.consts -import nmv.enums - - -#################################################################################################### -# @add_soma_coloring_option -#################################################################################################### -def add_soma_coloring_option(layout, - scene, - options): - """Adds the coloring options of the soma. The soma coloring options are always added except for - homogeneous coloring scheme. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Soma color option - soma_color_row = layout.row() - soma_color_row.prop(scene, 'NMV_SomaColor') - - # Make sure to build the soma, otherwise disable the wor - if not scene.NMV_BuildSoma: - soma_color_row.enabled = False - - # Pass options from UI to system - options.shading.morphology_soma_color = Vector((scene.NMV_SomaColor.r, - scene.NMV_SomaColor.g, - scene.NMV_SomaColor.b)) - - -#################################################################################################### -# @add_axons_coloring_options -#################################################################################################### -def add_axons_coloring_options(layout, - scene, - options): - """Adds the coloring options of the axons. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - # The axon must be present - if nmv.interface.ui_morphology.has_axons(): - - # Axon color option - axons_color_row = layout.row() - axons_color_row.prop(scene, 'NMV_AxonColor') - - # Make sure that the user want to visualize the axons - if not scene.NMV_BuildAxon: - axons_color_row.enabled = False - - # Pass options from UI to system - options.shading.morphology_axons_color = Vector(( - scene.NMV_AxonColor.r, scene.NMV_AxonColor.g, scene.NMV_AxonColor.b)) - - -#################################################################################################### -# @add_basal_dendrites_coloring_options -#################################################################################################### -def add_basal_dendrites_coloring_options(layout, - scene, - options): - """Adds the coloring options of the basal dendrites. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # The morphology must have basal dendrites - if nmv.interface.ui_morphology.has_basal_dendrites(): - - # Basal dendrites color option - basal_dendrites_color_row = layout.row() - basal_dendrites_color_row.prop(scene, 'NMV_BasalDendritesColor') - - # Make sure that the user want to visualize the basal dendrites - if not scene.NMV_BuildBasalDendrites: - basal_dendrites_color_row.enabled = False - - # Pass options from UI to system - options.shading.morphology_basal_dendrites_color = Vector(( - scene.NMV_BasalDendritesColor.r, - scene.NMV_BasalDendritesColor.g, - scene.NMV_BasalDendritesColor.b)) - - -#################################################################################################### -# @add_apical_dendrites_coloring_options -#################################################################################################### -def add_apical_dendrites_coloring_options(layout, - scene, - options): - """Adds the coloring options of the apical dendrites. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - # The morphology must have apical dendrites - if nmv.interface.ui_morphology.has_apical_dendrites(): - - # Apical dendrite color option - apical_dendrites_color_row = layout.row() - apical_dendrites_color_row.prop(scene, 'NMV_ApicalDendriteColor') - - # Make sure that the user want to visualize the apical dendrites - if not scene.NMV_BuildApicalDendrite: - apical_dendrites_color_row.enabled = False - - # Pass options from UI to system - options.shading.morphology_apical_dendrites_color = Vector(( - scene.NMV_ApicalDendriteColor.r, - scene.NMV_ApicalDendriteColor.g, - scene.NMV_ApicalDendriteColor.b)) - - -#################################################################################################### -# @add_articulation_coloring_options -#################################################################################################### -def add_endfeet_coloring_options(layout, - scene, - options): - """Adds the coloring options of the endfeet in case of loaded astrocytes. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Endfeet color option - endfeet_color_row = layout.row() - endfeet_color_row.prop(scene, 'NMV_EndfeetColor') - - # Pass options from UI to system - options.shading.morphology_endfeet_color = Vector(( - scene.NMV_EndfeetColor.r, - scene.NMV_EndfeetColor.g, - scene.NMV_EndfeetColor.b)) - - -#################################################################################################### -# @add_articulation_coloring_options -#################################################################################################### -def add_articulation_coloring_options(layout, - scene, - options): - """Adds the coloring options of the articulations in case of articulated sections mode. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Articulation color option - articulation_color_row = layout.row() - articulation_color_row.prop(scene, 'NMV_ArticulationColor') - - # Pass options from UI to system - options.shading.morphology_articulation_color = Vector(( - scene.NMV_ArticulationColor.r, - scene.NMV_ArticulationColor.g, - scene.NMV_ArticulationColor.b)) - - -#################################################################################################### -# @add_default_coloring_option -#################################################################################################### -def add_default_coloring_option(layout, - scene, - options): - """Adds to the UI the single arbor color elements. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Soma color option - add_soma_coloring_option(layout=layout, scene=scene, options=options) - - # The morphology must be loaded to be able to draw these options - if nmv.interface.ui_morphology is not None: - - # Axons color option - add_axons_coloring_options(layout=layout, scene=scene, options=options) - - # Basal dendrites color option - add_basal_dendrites_coloring_options(layout=layout, scene=scene, options=options) - - # Apical dendrites color option - add_apical_dendrites_coloring_options(layout=layout, scene=scene, options=options) - - # Articulation color option - technique = scene.NMV_MorphologyReconstructionTechnique - if technique == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS: - add_articulation_coloring_options(layout=layout, scene=scene, options=options) - - # Endfeet color option - if nmv.interface.ui_morphology.has_endfeet(): - add_endfeet_coloring_options(layout=layout, scene=scene, options=options) - - # Only a simple UI - else: - - # Axons - axons_color_row = layout.row() - axons_color_row.prop(scene, 'NMV_AxonColor') - - # Apical dendrites - basal_dendrites_color_row = layout.row() - basal_dendrites_color_row.prop(scene, 'NMV_BasalDendritesColor') - - # Apical dendrites - apical_dendrites_color_row = layout.row() - apical_dendrites_color_row.prop(scene, 'NMV_ApicalDendriteColor') - - # Articulation color option - articulation_color_row = layout.row() - articulation_color_row.prop(scene, 'NMV_ArticulationColor') - - -#################################################################################################### -# @add_homogeneous_color_option -#################################################################################################### -def add_homogeneous_color_option(layout, - scene, - options): - """Adds to the UI the homogeneous color elements. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # The homogeneous color of the morphology - color_row = layout.row() - color_row.prop(scene, 'NMV_MorphologyColor') - color = scene.NMV_MorphologyColor - options.shading.morphology_soma_color = Vector((color.r, color.g, color.b)) - options.shading.morphology_axons_color = Vector((color.r, color.g, color.b)) - options.shading.morphology_basal_dendrites_color = Vector((color.r, color.g, color.b)) - options.shading.morphology_apical_dendrites_color = Vector((color.r, color.g, color.b)) - options.shading.morphology_articulation_color = Vector((color.r, color.g, color.b)) - options.shading.morphology_endfeet_color = Vector((color.r, color.g, color.b)) - - -#################################################################################################### -# @add_alternating_colors_option -#################################################################################################### -def add_alternating_colors_option(layout, - scene, - options): - """Adds alternating coloring options. Simply two colors where we can see different patterns in - the morphology. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Soma options - add_soma_coloring_option(layout=layout, scene=scene, options=options) - - # Color 1 - color_1_row = layout.row() - color_1_row.prop(scene, 'NMV_MorphologyColor1') - options.shading.morphology_alternating_color_1 = scene.NMV_MorphologyColor1 - - # Color 2 - color_2_row = layout.row() - color_2_row.prop(scene, 'NMV_MorphologyColor2') - options.shading.morphology_alternating_color_2 = scene.NMV_MorphologyColor2 - - # Articulation color option - technique = scene.NMV_MorphologyReconstructionTechnique - if technique == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS: - add_articulation_coloring_options(layout=layout, scene=scene, options=options) - - -#################################################################################################### -# @add_colormap_options -#################################################################################################### -def add_colormap_options(layout, - scene, - options): - """Adds the coloring options that gives extra components to assign a colormap to the morphology. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Color map - color_map_row = layout.row() - color_map_row.label(text='Color Map:') - color_map_row.prop(scene, 'NMV_ColorMap') - color_map_row.prop(scene, 'NMV_InvertColorMap') - - # Clear the color map passed to VMV if it is full - if len(nmv.interface.ui_options.shading.morphology_colormap_list) > 0: - nmv.interface.ui_options.shading.morphology_colormap_list.clear() - - # Soma options - add_soma_coloring_option(layout=layout, scene=scene, options=options) - - # Fill list of colors - for i in range(nmv.consts.Color.COLORMAP_RESOLUTION): - - # Add the colormap element to the UI - colors = layout.row() - colormap_element = colors.column() - colormap_element.prop(scene, 'NMV_Color%d' % i) - - # Colormap range values - values = colors.row() - values.prop(scene, 'NMV_R0_Value%d' % i) - values.prop(scene, 'NMV_R1_Value%d' % i) - values.enabled = False - - # Get the color value from the panel - color = getattr(scene, 'NMV_Color%d' % i) - nmv.interface.ui_options.shading.morphology_colormap_list.append(color) - - -#################################################################################################### -# @add_per_section_color_coding_options -#################################################################################################### -def add_per_section_color_coding_options(layout, - scene, - options): - """Adds the coloring options of the per-section coloring scheme. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Set the color coding scheme - nmv.interface.ui_options.shading.morphology_coloring_scheme = \ - scene.NMV_PerSectionColorCodingBasis - - # Default coloring scheme - if scene.NMV_PerSectionColorCodingBasis == nmv.enums.ColorCoding.DEFAULT_SCHEME: - add_default_coloring_option(layout=layout, scene=scene, options=options) - - # Homogeneous color - elif scene.NMV_PerSectionColorCodingBasis == nmv.enums.ColorCoding.HOMOGENEOUS_COLOR: - add_homogeneous_color_option(layout=layout, scene=scene, options=options) - - # Alternating colors - elif scene.NMV_PerSectionColorCodingBasis == nmv.enums.ColorCoding.ALTERNATING_COLORS: - add_alternating_colors_option(layout=layout, scene=scene, options=options) - - # Using a colormap - else: - add_colormap_options(layout=layout, scene=scene, options=options) - - -#################################################################################################### -# @add_per_segment_color_coding_options -#################################################################################################### -def add_per_segment_color_coding_options(layout, - scene, - options): - """Adds the coloring options of the per-segment coloring scheme. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Color coding scheme - nmv.interface.ui_options.shading.morphology_coloring_scheme = \ - scene.NMV_PerSegmentColorCodingBasis - - # Single arbor color - if scene.NMV_PerSegmentColorCodingBasis == nmv.enums.ColorCoding.DEFAULT_SCHEME: - add_default_coloring_option(layout=layout, scene=scene, options=options) - - # Homogeneous color - elif scene.NMV_PerSegmentColorCodingBasis == nmv.enums.ColorCoding.HOMOGENEOUS_COLOR: - add_homogeneous_color_option(layout=layout, scene=scene, options=options) - - # Alternating colors - elif scene.NMV_PerSegmentColorCodingBasis == nmv.enums.ColorCoding.ALTERNATING_COLORS: - add_alternating_colors_option(layout=layout, scene=scene, options=options) - - # Using a colormap - else: - add_colormap_options(layout=layout, scene=scene, options=options) - - -#################################################################################################### -# @add_color_options -#################################################################################################### -def add_color_options(layout, - scene, - options): - """Morphology coloring options. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Color parameters - arbors_colors_row = layout.row() - arbors_colors_row.label(text='Morphology Colors:', icon='COLOR') - - # Morphology material - morphology_material_row = layout.row() - morphology_material_row.label(text='Shading:') - morphology_material_row.prop(scene, 'NMV_MorphologyMaterial') - options.shading.morphology_material = scene.NMV_MorphologyMaterial - - # Per-section color coding - color_coding_row = layout.row() - - method = options.morphology.reconstruction_method - - # Each section is rendered independently - if method == nmv.enums.Skeleton.Method.DISCONNECTED_SECTIONS or \ - method == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS or \ - method == nmv.enums.Skeleton.Method.PROGRESSIVE: - color_coding_row.label(text='Color Coding:') - color_coding_row.prop(scene, 'NMV_PerSectionColorCodingBasis') - add_per_section_color_coding_options(layout, scene, options) - - # Each segment is rendered independently - elif method == nmv.enums.Skeleton.Method.DISCONNECTED_SEGMENTS: - color_coding_row.label(text='Color Coding:') - color_coding_row.prop(scene, 'NMV_PerSegmentColorCodingBasis') - add_per_segment_color_coding_options(layout, scene, options) - - # The arbor is rendered in one piece as a set of connected sections in whatever format - elif method == nmv.enums.Skeleton.Method.CONNECTED_SECTIONS or \ - method == nmv.enums.Skeleton.Method.SAMPLES or \ - method == nmv.enums.Skeleton.Method.DENDROGRAM: - add_default_coloring_option(layout, scene, options) diff --git a/nmv/interface/ui/morphology/morphology_panel_ops.py b/nmv/interface/ui/morphology/morphology_panel_ops.py deleted file mode 100644 index 8cd88b8ca..000000000 --- a/nmv/interface/ui/morphology/morphology_panel_ops.py +++ /dev/null @@ -1,442 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# Blender imports -import bpy -from mathutils import Vector - -# Internal imports -import nmv.consts -import nmv.enums -from .morphology_panel_color_ops import * - - -#################################################################################################### -# @set_resampling_options -#################################################################################################### -def set_resampling_options(layout, - scene, - options): - """Set the resampling options in the UI. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Resampling step - resampling_row = layout.row() - resampling_row.label(text='Resampling:') - resampling_row.prop(scene, 'NMV_MorphologyResampling') - options.morphology.resampling_method = scene.NMV_MorphologyResampling - - # If Fixed Step resampling method is selected, add the sampling step - if scene.NMV_MorphologyResampling == nmv.enums.Skeleton.Resampling.FIXED_STEP: - resampling_step_row = layout.row() - resampling_step_row.label(text='Resampling Step:') - resampling_step_row.prop(scene, 'NMV_MorphologyResamplingStep') - options.morphology.resampling_step = scene.NMV_MorphologyResamplingStep - - -#################################################################################################### -# @set_skeleton_options -#################################################################################################### -def set_skeleton_options(layout, - scene, - options): - """Morphology skeleton options. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Morphology skeleton options - skeleton_row = layout.row() - skeleton_row.label(text='Morphology Skeleton:', icon='QUESTION') - - # Build soma options if not dendrogram - if not scene.NMV_MorphologyReconstructionTechnique == nmv.enums.Skeleton.Method.DENDROGRAM: - build_soma_row = layout.row() - build_soma_row.label(text='Soma:') - build_soma_row.prop(scene, 'NMV_BuildSoma') - options.morphology.soma_representation = scene.NMV_BuildSoma - - # The morphology must be loaded to be able to draw these options - if nmv.interface.ui_morphology is not None: - - # Build axon options - if nmv.interface.ui_morphology.has_axons(): - axon_row = layout.row() - axon_row.prop(scene, 'NMV_BuildAxon') - axon_level_row = axon_row.column() - axon_level_row.prop(scene, 'NMV_AxonBranchingLevel') - - if not scene.NMV_BuildAxon: - axon_level_row.enabled = False - else: - axon_level_row.enabled = True - - # Pass options from UI to system - options.morphology.ignore_axons = not scene.NMV_BuildAxon - options.morphology.axon_branch_order = scene.NMV_AxonBranchingLevel - - # Build basal dendrites options - if nmv.interface.ui_morphology.has_basal_dendrites(): - basal_dendrites_row = layout.row() - basal_dendrites_row.prop(scene, 'NMV_BuildBasalDendrites') - basal_dendrites_level_row = basal_dendrites_row.column() - basal_dendrites_level_row.prop(scene, 'NMV_BasalDendritesBranchingLevel') - if not scene.NMV_BuildBasalDendrites: - basal_dendrites_level_row.enabled = False - else: - basal_dendrites_level_row.enabled = True - - # Pass options from UI to system - options.morphology.ignore_basal_dendrites = not scene.NMV_BuildBasalDendrites - options.morphology.basal_dendrites_branch_order = scene.NMV_BasalDendritesBranchingLevel - - # Build apical dendrite option - if nmv.interface.ui_morphology.has_apical_dendrites(): - apical_dendrite_row = layout.row() - apical_dendrite_row.prop(scene, 'NMV_BuildApicalDendrite') - apical_dendrite_level_row = apical_dendrite_row.column() - apical_dendrite_level_row.prop(scene, 'NMV_ApicalDendriteBranchingLevel') - - if not scene.NMV_BuildApicalDendrite: - apical_dendrite_level_row.enabled = False - else: - apical_dendrite_level_row.enabled = True - - # Pass options from UI to system - options.morphology.ignore_apical_dendrites = not scene.NMV_BuildApicalDendrite - options.morphology.apical_dendrite_branch_order = scene.NMV_ApicalDendriteBranchingLevel - - # Only a simple demo UI - else: - - # Axons - axon_row = layout.row() - axon_row.prop(scene, 'NMV_BuildAxon') - axon_level_row = axon_row.column() - axon_level_row.prop(scene, 'NMV_AxonBranchingLevel') - - # Basal dendrites - basal_dendrites_row = layout.row() - basal_dendrites_row.prop(scene, 'NMV_BuildBasalDendrites') - basal_dendrites_level_row = basal_dendrites_row.column() - basal_dendrites_level_row.prop(scene, 'NMV_BasalDendritesBranchingLevel') - - # Apical Dendrites - apical_dendrite_row = layout.row() - apical_dendrite_row.prop(scene, 'NMV_BuildApicalDendrite') - apical_dendrite_level_row = apical_dendrite_row.column() - apical_dendrite_level_row.prop(scene, 'NMV_ApicalDendriteBranchingLevel') - - -#################################################################################################### -# set_reconstruction_options -#################################################################################################### -def set_reconstruction_options(layout, - scene, - options): - """Morphology reconstruction options. - - :param layout: - Panel layout. - :param scene: - Context scene.NMV_ - :param options: - System options. - """ - - # Update the reconstruction button name - bpy.types.Scene.NMV_MorphologyButtonLabel = 'Reconstruct Morphology' - - # Reconstruction options - reconstruction_options_row = layout.row() - reconstruction_options_row.label(text='Reconstruction Options:', icon='OUTLINER_OB_EMPTY') - - # Morphology reconstruction techniques option - morphology_reconstruction_row = layout.row() - morphology_reconstruction_row.prop( - scene, 'NMV_MorphologyReconstructionTechnique', icon='FORCE_CURVE') - options.morphology.reconstruction_method = scene.NMV_MorphologyReconstructionTechnique - - # Connected sections only options - if scene.NMV_MorphologyReconstructionTechnique == nmv.enums.Skeleton.Method.CONNECTED_SECTIONS: - - # Skeleton style - skeleton_style_row = layout.row() - skeleton_style_row.label(text='Skeleton Style:') - skeleton_style_row.prop(scene, 'NMV_ArborsStyle', icon='WPAINT_HLT') - options.morphology.arbor_style = scene.NMV_ArborsStyle - - # Morphology branching - branching_row = layout.row() - branching_row.label(text='Branching:') - branching_row.prop(scene, 'NMV_MorphologyBranching', expand=True) - options.morphology.branching = scene.NMV_MorphologyBranching - - # Connection to somata - arbor_to_soma_connection_row = layout.row() - arbor_to_soma_connection_row.label(text='Arbors to Soma:') - arbor_to_soma_connection_row.prop(scene, 'NMV_SomaConnectionToRoot') - options.morphology.arbors_to_soma_connection = scene.NMV_SomaConnectionToRoot - - # Dendrogram options - elif scene.NMV_MorphologyReconstructionTechnique == nmv.enums.Skeleton.Method.DENDROGRAM: - - # Update the button name - bpy.types.Scene.NMV_MorphologyButtonLabel = 'Reconstruct Dendrogram' - - # Type - dendrogram_type_row = layout.row() - dendrogram_type_row.label(text='Dendrogram Type:') - dendrogram_type_row.prop(scene, 'NMV_DendrogramType', expand=True) - options.morphology.dendrogram_type = scene.NMV_DendrogramType - - else: - options.morphology.arbor_style = nmv.enums.Skeleton.Style.ORIGINAL - options.morphology.branching = nmv.enums.Skeleton.Branching.RADII - options.morphology.arbors_to_soma_connection = nmv.enums.Skeleton.Roots.ALL_DISCONNECTED - - # Sections resampling - resampling_row = layout.row() - resampling_row.label(text='Resampling:') - resampling_row.prop(scene, 'NMV_MorphologyResampling') - options.morphology.resampling_method = scene.NMV_MorphologyResampling - - # If Fixed Step resampling method is selected, add the sampling step - if scene.NMV_MorphologyResampling == nmv.enums.Skeleton.Resampling.FIXED_STEP: - resampling_step_row = layout.row() - resampling_step_row.label(text='Resampling Step:') - resampling_step_row.prop(scene, 'NMV_MorphologyResamplingStep') - options.morphology.resampling_step = scene.NMV_MorphologyResamplingStep - - if not scene.NMV_MorphologyReconstructionTechnique == nmv.enums.Skeleton.Method.DENDROGRAM: - - # Sections diameters option - sections_radii_row = layout.row() - sections_radii_row.label(text='Arbors Radii:') - sections_radii_row.prop(scene, 'NMV_SectionsRadii', icon='SURFACE_NCURVE') - - # Radii as specified in the morphology file - if scene.NMV_SectionsRadii == nmv.enums.Skeleton.Radii.ORIGINAL: - options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.ORIGINAL - options.morphology.scale_sections_radii = False - options.morphology.unify_sections_radii = False - options.morphology.sections_radii_scale = 1.0 - - # Unified diameter - elif scene.NMV_SectionsRadii == nmv.enums.Skeleton.Radii.UNIFIED: - fixed_diameter_row = layout.row() - fixed_diameter_row.label(text='Fixed Radius Value:') - fixed_diameter_row.prop(scene, 'NMV_UnifiedRadiusValue') - options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.UNIFIED - options.morphology.scale_sections_radii = False - options.morphology.unify_sections_radii = True - options.morphology.samples_unified_radii_value = scene.NMV_UnifiedRadiusValue - - # Unified diameter per arbor type - elif scene.NMV_SectionsRadii == nmv.enums.Skeleton.Radii.UNIFIED_PER_ARBOR_TYPE: - options.morphology.arbors_radii = \ - nmv.enums.Skeleton.Radii.UNIFIED_PER_ARBOR_TYPE - options.morphology.scale_sections_radii = False - options.morphology.unify_sections_radii = True - - if nmv.interface.ui_morphology.has_axons(): - axon_radius_row = layout.row() - axon_radius_row.label(text='Axon Radius:') - axon_radius_row.prop(scene, 'NMV_AxonUnifiedRadiusValue') - options.morphology.axon_samples_unified_radii_value = scene.NMV_AxonUnifiedRadiusValue - - if nmv.interface.ui_morphology.has_apical_dendrites(): - apical_dendrite_radius_row = layout.row() - apical_dendrite_radius_row.label(text='Apical Dendrite Radius:') - apical_dendrite_radius_row.prop(scene, 'NMV_ApicalDendriteUnifiedRadiusValue') - options.morphology.apical_dendrite_samples_unified_radii_value = \ - scene.NMV_ApicalDendriteUnifiedRadiusValue - - if nmv.interface.ui_morphology.has_basal_dendrites(): - basal_dendrites_radius_row = layout.row() - basal_dendrites_radius_row.label(text='Basal Dendrites Radius:') - basal_dendrites_radius_row.prop(scene, 'NMV_BasalDendritesUnifiedRadiusValue') - options.morphology.basal_dendrites_samples_unified_radii_value = \ - scene.NMV_BasalDendritesUnifiedRadiusValue - - # Scaled diameter - elif scene.NMV_SectionsRadii == nmv.enums.Skeleton.Radii.SCALED: - scaled_diameter_row = layout.row() - scaled_diameter_row.label(text='Radius Scale Factor:') - scaled_diameter_row.prop(scene, 'NMV_RadiusScaleValue') - options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.SCALED - options.morphology.unify_sections_radii = False - options.morphology.scale_sections_radii = True - options.morphology.sections_radii_scale = scene.NMV_RadiusScaleValue - - # Filtered - elif scene.NMV_SectionsRadii == nmv.enums.Skeleton.Radii.FILTERED: - minimum_row = layout.row() - minimum_row.label(text='Minimum Radius:') - minimum_row.prop(scene, 'NMV_MinimumRadiusThreshold') - maximum_row = layout.row() - maximum_row.label(text='Maximum Radius:') - maximum_row.prop(scene, 'NMV_MaximumRadiusThreshold') - options.morphology.unify_sections_radii = False - options.morphology.scale_sections_radii = False - options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.FILTERED - options.morphology.minimum_threshold_radius = scene.NMV_MinimumRadiusThreshold - options.morphology.maximum_threshold_radius = scene.NMV_MaximumRadiusThreshold - else: - nmv.logger.log('ERROR') - - # Arbor quality option - arbor_quality_row = layout.row() - arbor_quality_row.label(text='Arbor Quality:') - arbor_quality_row.prop(scene, 'NMV_ArborQuality') - options.morphology.bevel_object_sides = scene.NMV_ArborQuality - - -#################################################################################################### -# set_rendering_options -#################################################################################################### -def set_rendering_options(layout, - scene, - options): - """Morphology rendering options. - - :param layout: - Panel layout. - :param scene: - Context scene.NMV_ - :param options: - System options. - """ - - # Quick rendering options - quick_rendering_row = layout.row() - quick_rendering_row.label(text='Quick Rendering Options:', icon='RENDER_STILL') - - # Rendering view - rendering_view_row = layout.row() - rendering_view_row.prop(scene, 'NMV_MorphologyRenderingView', expand=True) - - # Close up view - if scene.NMV_MorphologyRenderingView == nmv.enums.Rendering.View.CLOSE_UP: - - # Rendering close up option - render_close_up_row = layout.row() - render_close_up_row.prop(scene, 'NMV_MorphologyCloseUpDimensions') - - # Frame resolution option - frame_resolution_row = layout.row() - frame_resolution_row.label(text='Frame Resolution:') - frame_resolution_row.prop(scene, 'NMV_MorphologyFrameResolution') - frame_resolution_row.enabled = True - - # Full morphology view - else: - - # Rendering type - rendering_type_row = layout.row() - rendering_type_row.prop(scene, 'NMV_RenderingType', expand=True) - - # Render at a specific resolution - if scene.NMV_RenderingType == nmv.enums.Rendering.Resolution.FIXED: - - # Frame resolution option - frame_resolution_row = layout.row() - frame_resolution_row.label(text='Frame Resolution:') - frame_resolution_row.prop(scene, 'NMV_MorphologyFrameResolution') - frame_resolution_row.enabled = True - - # Otherwise, render to scale - else: - - # Scale factor option - scale_factor_row = layout.row() - scale_factor_row.prop(scene, 'NMV_MorphologyFrameScaleFactor') - scale_factor_row.enabled = True - - # Image extension - image_extension_row = layout.row() - image_extension_row.label(text='Image Format:') - image_extension_row.prop(scene, 'NMV_MorphologyImageFormat') - nmv.interface.ui_options.morphology.image_format = scene.NMV_MorphologyImageFormat - - # Scale bar - scale_bar_row = layout.row() - scale_bar_row.prop(scene, 'NMV_RenderMorphologyScaleBar') - nmv.interface.ui_options.rendering.render_scale_bar = scene.NMV_RenderMorphologyScaleBar - - # Render view buttons - render_view_row = layout.row() - render_view_row.label(text='Render View:', icon='RESTRICT_RENDER_OFF') - render_view_buttons_row = layout.row(align=True) - render_view_buttons_row.operator('nmv.render_morphology_front', icon='AXIS_FRONT') - render_view_buttons_row.operator('nmv.render_morphology_side', icon='AXIS_SIDE') - render_view_buttons_row.operator('nmv.render_morphology_top', icon='AXIS_TOP') - render_view_buttons_row.enabled = True - - # Render animations buttons - render_animation_row = layout.row() - render_animation_row.label(text='Render Animation:', icon='CAMERA_DATA') - render_animations_buttons_row = layout.row(align=True) - render_animations_buttons_row.operator('nmv.render_morphology_360', icon='FORCE_MAGNETIC') - render_animations_buttons_row.operator('nmv.render_morphology_progressive', - icon='FORCE_HARMONIC') - render_animations_buttons_row.enabled = True - - # Progress bar - progress_bar_row = layout.row() - progress_bar_row.prop(scene, 'NMV_MorphologyRenderingProgress') - progress_bar_row.enabled = False - - -#################################################################################################### -# set_export_options -#################################################################################################### -def set_export_options(layout, - scene, - options): - """Morphology export options. - - :param layout: - Panel layout. - :param scene: - Context scene. - :param options: - System options. - """ - - # Saving morphology options - save_morphology_row = layout.row() - save_morphology_row.label(text='Save Morphology As:', icon='MESH_UVSPHERE') - - # Saving morphology buttons - save_morphology_buttons_column = layout.column(align=True) - save_morphology_buttons_column.operator('nmv.save_morphology_blend', icon='OUTLINER_OB_META') - save_morphology_buttons_column.operator('nmv.save_morphology_swc', icon='GROUP_VERTEX') - save_morphology_buttons_column.operator('nmv.save_morphology_segments', icon='GROUP_VERTEX') - save_morphology_buttons_column.enabled = True diff --git a/nmv/interface/ui/morphology/ops_documentation.py b/nmv/interface/ui/morphology/ops_documentation.py new file mode 100644 index 000000000..22fa2c143 --- /dev/null +++ b/nmv/interface/ui/morphology/ops_documentation.py @@ -0,0 +1,40 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @NMV_MorphologyReconstructionDocumentation +#################################################################################################### +class NMV_MorphologyReconstructionDocumentation(bpy.types.Operator): + """Open the online documentation page of the Morphology Reconstruction panel.""" + + # Operator parameters + bl_idname = "nmv.documentation_morphology" + bl_label = "Online User Guide" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + import webbrowser + webbrowser.open( + 'https://github.com/BlueBrain/NeuroMorphoVis/wiki/Morphology-Reconstruction') + return {'FINISHED'} diff --git a/nmv/interface/ui/morphology/ops_exports.py b/nmv/interface/ui/morphology/ops_exports.py new file mode 100644 index 000000000..e30f51ef3 --- /dev/null +++ b/nmv/interface/ui/morphology/ops_exports.py @@ -0,0 +1,125 @@ + +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.interface + + +#################################################################################################### +# @NMV_ExportMorphologySWC +#################################################################################################### +class NMV_ExportMorphologySWC(bpy.types.Operator): + """Export the morphology to an SWC file""" + + # Operator parameters + bl_idname = "nmv.save_morphology_swc" + bl_label = "SWC (.swc)" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Verify the output directory + if not nmv.interface.validate_output_directory(self, context.scene): + return {'FINISHED'} + + # Create the meshes directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.morphologies_directory): + nmv.file.ops.clean_and_create_directory( + nmv.interface.ui_options.io.morphologies_directory) + + # Export the reconstructed morphology as an .blend file + # NOTE: Since we don't have meshes, then the mesh_object argument will be set to None and + # the exported blender file will contain all the morphology objects. + nmv.file.write_morphology_to_swc_file( + nmv.interface.ui_morphology, + nmv.interface.ui_options.io.morphologies_directory) + + return {'FINISHED'} + + +#################################################################################################### +# @NMV_ExportMorphologySegments +#################################################################################################### +class NMV_ExportMorphologySegments(bpy.types.Operator): + """Export the morphology as a list of segments into file""" + + # Operator parameters + bl_idname = "nmv.save_morphology_segments" + bl_label = "Segments (.segments)" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Verify the output directory + if not nmv.interface.validate_output_directory(self, context.scene): + return {'FINISHED'} + + # Create the meshes directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.morphologies_directory): + nmv.file.ops.clean_and_create_directory( + nmv.interface.ui_options.io.morphologies_directory) + + # Export the reconstructed morphology as an .blend file + # NOTE: Since we don't have meshes, then the mesh_object argument will be set to None and + # the exported blender file will contain all the morphology objects. + nmv.file.write_morphology_to_segments_file( + nmv.interface.ui_morphology, + nmv.interface.ui_options.io.morphologies_directory) + + return {'FINISHED'} + + +#################################################################################################### +# @NMV_ExportMorphologyBLEND +#################################################################################################### +class NMV_ExportMorphologyBLEND(bpy.types.Operator): + """Save the reconstructed morphology in a blender file""" + + # Operator parameters + bl_idname = "nmv.save_morphology_blend" + bl_label = "Blender Format (.blend)" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Verify the output directory + if not nmv.interface.validate_output_directory(self, context.scene): + return {'FINISHED'} + + # Create the meshes directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.morphologies_directory): + nmv.file.ops.clean_and_create_directory( + nmv.interface.ui_options.io.morphologies_directory) + + # Export the reconstructed morphology as an .blend file + # NOTE: Since we don't have meshes, then the mesh_object argument will be set to None and + # the exported blender file will contain all the morphology objects. + nmv.file.export_scene_to_blend_file( + output_directory=nmv.interface.ui_options.io.morphologies_directory, + output_file_name=nmv.interface.ui_morphology.label) + + return {'FINISHED'} diff --git a/nmv/interface/ui/morphology/ops_reconstruction.py b/nmv/interface/ui/morphology/ops_reconstruction.py new file mode 100644 index 000000000..340215285 --- /dev/null +++ b/nmv/interface/ui/morphology/ops_reconstruction.py @@ -0,0 +1,124 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.bbox +import nmv.builders +import nmv.consts +import nmv.enums +import nmv.file +import nmv.interface +import nmv.shading +import nmv.scene +import nmv.skeleton +import nmv.utilities +import nmv.rendering +import nmv.geometry + + +#################################################################################################### +# NMV_ReconstructMorphologyOperator +#################################################################################################### +class NMV_ReconstructMorphologyOperator(bpy.types.Operator): + """Reconstruct & Draw the Morphology Skeleton for Visualization""" + + # Operator parameters + bl_idname = "nmv.reconstruct_morphology" + bl_label = bpy.types.Scene.NMV_MorphologyButtonLabel + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Clear the scene + nmv.scene.clear_scene(deep_delete=True) + + # If no morphology file is loaded, load the morphology file + if nmv.interface.ui_morphology is None: + loading_result = nmv.interface.ui.load_morphology(self, context.scene) + if loading_result is None: + self.report({'ERROR'}, 'Please select a morphology file') + return {'FINISHED'} + + # Create a skeleton builder object to build the morphology skeleton + start_time = time.time() + method = nmv.interface.ui_options.morphology.reconstruction_method + if method == nmv.enums.Skeleton.Method.DISCONNECTED_SEGMENTS: + nmv.interface.ui_morphology_builder = nmv.builders.DisconnectedSegmentsBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + + # Draw the morphology as a set of disconnected tubes, where each SECTION is a tube + elif method == nmv.enums.Skeleton.Method.DISCONNECTED_SECTIONS or \ + method == nmv.enums.Skeleton.Method.ARTICULATED_SECTIONS: + nmv.interface.ui_morphology_builder = nmv.builders.DisconnectedSectionsBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + + # Draw the morphology as a set of spheres, where each SPHERE represents a sample + elif method == nmv.enums.Skeleton.Method.SAMPLES: + nmv.interface.ui_morphology_builder = nmv.builders.SamplesBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + + elif method == nmv.enums.Skeleton.Method.CONNECTED_SECTIONS: + nmv.interface.ui_morphology_builder = nmv.builders.ConnectedSectionsBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + + elif method == nmv.enums.Skeleton.Method.PROGRESSIVE: + nmv.interface.ui_morphology_builder = nmv.builders.ProgressiveBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + + elif method == nmv.enums.Skeleton.Method.DENDROGRAM: + nmv.interface.ui_morphology_builder = nmv.builders.DendrogramBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + + # Default: DisconnectedSectionsBuilder + else: + nmv.interface.ui_morphology_builder = nmv.builders.DisconnectedSectionsBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + + # Draw the morphology skeleton and return a list of all the reconstructed objects + nmv.interface.ui_reconstructed_skeleton = \ + nmv.interface.ui_morphology_builder.draw_morphology_skeleton(context=context) + reconstruction_time = time.time() + nmv.interface.ui_morphology_reconstructed = True + + context.scene.NMV_MorphologyReconstructionTime = reconstruction_time - start_time + nmv.logger.statistics('Morphology reconstructed in [%f] seconds' % + context.scene.NMV_MorphologyReconstructionTime) + + # Interpolations + scale = float(context.scene.NMV_MaximumValue) - float(context.scene.NMV_MinimumValue) + delta = scale / float(nmv.consts.Color.COLORMAP_RESOLUTION) + + # Fill the colors list + for color_index in range(nmv.consts.Color.COLORMAP_RESOLUTION): + r0_value = float(context.scene.NMV_MinimumValue) + (color_index * delta) + r1_value = float(context.scene.NMV_MinimumValue) + ((color_index + 1) * delta) + setattr(context.scene, 'NMV_R0_Value%d' % color_index, r0_value) + setattr(context.scene, 'NMV_R1_Value%d' % color_index, r1_value) + + # Deselect everything in the scene to be able to see the morphology + nmv.scene.deselect_all() + + # Confirm operation done + return {'FINISHED'} diff --git a/nmv/interface/ui/morphology/ops_render_360.py b/nmv/interface/ui/morphology/ops_render_360.py new file mode 100644 index 000000000..9b86b5388 --- /dev/null +++ b/nmv/interface/ui/morphology/ops_render_360.py @@ -0,0 +1,187 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.bbox +import nmv.utilities +import nmv.rendering +import nmv.enums +import nmv.skeleton +import nmv.consts +import nmv.builders +import nmv.scene + + +#################################################################################################### +# @NMV_RenderMorphology360 +#################################################################################################### +class NMV_RenderMorphology360(bpy.types.Operator): + """Render a 360 sequence of the reconstructed morphology""" + + # Operator parameters + bl_idname = "nmv.render_morphology_360" + bl_label = "360" + + # Timer parameters + event_timer = None + timer_limits = 0 + start_time = 0 + + # Output data + output_directory = None + + ################################################################################################ + # @modal + ################################################################################################ + def modal(self, context, event): + + # Cancelling event, if using right click or exceeding the time limit of the simulation + if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > 360: + + # Reset the orientation of the mesh + nmv.scene.reset_orientation_of_objects( + scene_objects=nmv.interface.ui_reconstructed_skeleton) + + # Stats. + rendering_time = time.time() + context.scene.NMV_MorphologyRenderingTime = rendering_time - self.start_time + nmv.logger.statistics('Morphology rendered in [%f] seconds' % + context.scene.NMV_MorphologyRenderingTime) + + # Reset the timer limits + self.timer_limits = 0 + + # Refresh the panel and return + self.cancel(context) + return {'FINISHED'} + + # Timer event, where the function is executed here on a per-frame basis + if event.type == 'TIMER': + + # Set the frame name + image_name = '%s/%s' % (self.output_directory, '{0:05d}'.format(self.timer_limits)) + + # Compute the bounding box for a close up view + if context.scene.NMV_MorphologyRenderingView == \ + nmv.enums.Rendering.View.CLOSEUP: + + rendering_bbox = nmv.bbox.compute_unified_extent_bounding_box( + extent=context.scene.NMV_MorphologyCloseUpDimensions) + + # Compute the bounding box for a mid-shot view + elif context.scene.NMV_MorphologyRenderingView == \ + nmv.enums.Rendering.View.MID_SHOT: + + # Compute the bounding box for the available meshes only + rendering_bbox = nmv.bbox.compute_scene_bounding_box_for_curves() + + # Compute the bounding box for the wide-shot view that corresponds to the whole + # morphology + else: + + # Compute the full morphology bounding box + rendering_bbox = nmv.skeleton.compute_full_morphology_bounding_box( + morphology=nmv.interface.ui_morphology) + + # Compute a 360 bounding box to fit the arbors + bounding_box_360 = nmv.bbox.compute_360_bounding_box( + rendering_bbox, nmv.interface.ui_morphology.soma.centroid) + + # Stretch the bounding box by few microns + bounding_box_360.extend_bbox_uniformly(delta=nmv.consts.Image.GAP_DELTA) + + # Render a frame + nmv.rendering.renderer.render_at_angle( + scene_objects=nmv.interface.ui_reconstructed_skeleton, + angle=self.timer_limits, + bounding_box=bounding_box_360, + camera_view=nmv.enums.Camera.View.FRONT, + image_resolution=context.scene.NMV_MorphologyFrameResolution, + image_name=image_name) + + # Update the progress shell + nmv.utilities.show_progress('Rendering', self.timer_limits, 360) + + # Update the progress bar + context.scene.NMV_MorphologyRenderingProgress = int(100 * self.timer_limits / 360.0) + + # Upgrade the timer limits + self.timer_limits += 1 + + # Next frame + return {'PASS_THROUGH'} + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # If this is a dendrogram rendering, handle it in a very specific way. + if nmv.interface.ui_options.morphology.reconstruction_method == \ + nmv.enums.Skeleton.Method.DENDROGRAM: + + # Cannot render a 360 of the dendrogram + self.report({'INFO'}, 'Cannot render a 360 of the dendrogram') + return {'FINISHED'} + + # Timer + self.start_time = time.time() + + # Verify the output directory + if not nmv.interface.validate_output_directory(self, context.scene): + return {'FINISHED'} + + # Create the sequences directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): + nmv.file.ops.clean_and_create_directory( + nmv.interface.ui_options.io.sequences_directory) + + # Create a specific directory for this mesh + self.output_directory = '%s/%s%s' % \ + (nmv.interface.ui_options.io.sequences_directory, + nmv.interface.ui_options.morphology.label, + nmv.consts.Suffix.MORPHOLOGY_360) + nmv.file.ops.clean_and_create_directory(self.output_directory) + + # Use the event timer to update the UI during the soma building + wm = context.window_manager + self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) + wm.modal_handler_add(self) + + # Done + return {'RUNNING_MODAL'} + + ################################################################################################ + # @cancel + ################################################################################################ + def cancel(self, context): + + # Multi-threading + wm = context.window_manager + wm.event_timer_remove(self.event_timer) + + # Report the process termination in the UI + self.report({'INFO'}, 'Rendering Done') + + # Confirm operation done + return {'FINISHED'} diff --git a/nmv/interface/ui/morphology/ops_render_progressive.py b/nmv/interface/ui/morphology/ops_render_progressive.py new file mode 100644 index 000000000..271603161 --- /dev/null +++ b/nmv/interface/ui/morphology/ops_render_progressive.py @@ -0,0 +1,224 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time +import copy + +# Blender imports +import bpy + +# Internal imports +import nmv.bbox +import nmv.utilities +import nmv.rendering +import nmv.enums +import nmv.skeleton +import nmv.consts +import nmv.builders +import nmv.scene + + +#################################################################################################### +# @NMV_RenderMorphologyProgressive +#################################################################################################### +class NMV_RenderMorphologyProgressive(bpy.types.Operator): + """Render a progressive sequence of the reconstruction procedure (time-consuming)""" + + # Operator parameters + bl_idname = "nmv.render_morphology_progressive" + bl_label = "Progressive" + + # Timer parameters + start_time = 0 + event_timer = None + timer_limits = 0 + + # Output data + output_directory = None + + # The bounding box of the morphology + morphology_bounding_box = None + + ################################################################################################ + # @modal + ################################################################################################ + def modal(self, context, event): + """ + Threading and non-blocking handling. + + :param context: Panel context. + :param event: A given event for the panel. + """ + + # Cancelling event, if using right click or exceeding the time limit of the simulation + if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > bpy.context.scene.frame_end: + + # Stats. + rendering_time = time.time() + context.scene.NMV_MorphologyRenderingTime = rendering_time - self.start_time + nmv.logger.statistics('Morphology rendered in [%f] seconds' % + context.scene.NMV_MorphologyRenderingTime) + + # Reset the timer limits + self.timer_limits = 0 + + # Refresh the panel context + self.cancel(context) + + # Done + return {'FINISHED'} + + # Timer event, where the function is executed here on a per-frame basis + if event.type == 'TIMER': + + # Update the frame number + bpy.context.scene.frame_set(self.timer_limits) + + # Set the frame name + image_name = '%s' % '{0:05d}'.format(self.timer_limits) + + # Render a frame + nmv.rendering.renderer.render( + bounding_box=self.morphology_bounding_box, + camera_view=nmv.enums.Camera.View.FRONT, + image_resolution=context.scene.NMV_MorphologyFrameResolution, + image_name=image_name, + image_directory=self.output_directory) + + # Update the progress shell + nmv.utilities.show_progress('Rendering', self.timer_limits, bpy.context.scene.frame_end) + + # Update the progress bar + context.scene.NMV_MorphologyRenderingProgress = \ + int(100 * self.timer_limits / float(bpy.context.scene.frame_end)) + + # Upgrade the timer limits + self.timer_limits += 1 + + # Next frame + return {'PASS_THROUGH'} + + ################################################################################################ + # @compute_morphology_bounding_box_for_progressive_reconstruction + ################################################################################################ + def compute_morphology_bounding_box_for_progressive_reconstruction(self, + context): + """Computes the bounding box of the reconstructed morphology from the progressive builder. + + :param context: + Blender context. + """ + + # Move to the last frame to get the bounding box of all the drawn objects + bpy.context.scene.frame_set(bpy.context.scene.frame_end) + + # Morphology view + view = context.scene.NMV_MorphologyRenderingView + + # Compute the bounding box for a close up view + if view == nmv.enums.Rendering.View.CLOSEUP: + self.morphology_bounding_box = nmv.bbox.compute_unified_extent_bounding_box( + extent=context.scene.NMV_MorphologyCloseUpDimensions) + + # Compute the bounding box for a mid-shot view + elif view == nmv.enums.Rendering.View.MID_SHOT: + self.morphology_bounding_box = copy.deepcopy( + nmv.bbox.compute_scene_bounding_box_for_curves_and_meshes()) + + # The bounding box for the wide-shot view that corresponds to the whole morphology + else: + self.morphology_bounding_box = nmv.skeleton.compute_full_morphology_bounding_box( + morphology=nmv.interface.ui_morphology) + + # Stretch the bounding box by few microns + self.morphology_bounding_box.extend_bbox_uniformly(delta=nmv.consts.Image.GAP_DELTA) + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + """ + Execute the operator. + + :param context: Panel context. + """ + + # If this is a dendrogram rendering, handle it in a very specific way. + if nmv.interface.ui_options.morphology.reconstruction_method == \ + nmv.enums.Skeleton.Method.DENDROGRAM: + # Cannot render a 360 of the dendrogram + self.report({'INFO'}, 'Cannot render a progressive reconstruction of the dendrogram') + return {'FINISHED'} + + # Timer + self.start_time = time.time() + + # Verify the output directory + if not nmv.interface.validate_output_directory(self, context.scene): + return {'FINISHED'} + + # Create the sequences directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): + nmv.file.ops.clean_and_create_directory( + nmv.interface.ui_options.io.sequences_directory) + + # Create a specific directory for this mesh + self.output_directory = '%s/%s%s' % \ + (nmv.interface.ui_options.io.sequences_directory, + nmv.interface.ui_options.morphology.label, + nmv.consts.Suffix.MORPHOLOGY_PROGRESSIVE) + nmv.file.ops.clean_and_create_directory(self.output_directory) + + # Clear the scene + nmv.scene.clear_scene() + + # Reconstruct the morphology using the progressive builder + progressive_builder = nmv.builders.ProgressiveBuilder( + morphology=nmv.interface.ui_morphology, options=nmv.interface.ui_options) + progressive_builder.draw_morphology_skeleton() + + # Compute the bounding box of the morphology directly after the reconstruction + self.compute_morphology_bounding_box_for_progressive_reconstruction(context=context) + + # Use the event timer to update the UI during the soma building + wm = context.window_manager + self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) + wm.modal_handler_add(self) + + # Done + return {'RUNNING_MODAL'} + + ################################################################################################ + # @cancel + ################################################################################################ + def cancel(self, context): + """ + Cancel the panel processing and return to the interaction mode. + + :param context: Panel context. + """ + + # Multi-threading + wm = context.window_manager + wm.event_timer_remove(self.event_timer) + + # Report the process termination in the UI + self.report({'INFO'}, 'Morphology Rendering Done') + + # Confirm operation done + return {'FINISHED'} diff --git a/nmv/interface/ui/morphology/ops_render_view.py b/nmv/interface/ui/morphology/ops_render_view.py new file mode 100644 index 000000000..f5155d08d --- /dev/null +++ b/nmv/interface/ui/morphology/ops_render_view.py @@ -0,0 +1,116 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums + + +#################################################################################################### +# @NMV_RenderMorphologyFront +#################################################################################################### +class NMV_RenderMorphologyFront(bpy.types.Operator): + """Render the front view (XY) of the morphology""" + + # Operator parameters + bl_idname = "nmv.render_morphology_front" + bl_label = "Front" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Render the frame + start_time = time.time() + nmv.interface.render_morphology_relevant_image( + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology, + camera_view=nmv.enums.Camera.View.FRONT, + image_suffix=nmv.consts.Suffix.MORPHOLOGY_FRONT) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_morphology_rendered = True + context.scene.NMV_MorphologyRenderingTime = rendering_time - start_time + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderMorphologySide +#################################################################################################### +class NMV_RenderMorphologySide(bpy.types.Operator): + """Render the side view (YZ) of the morphology""" + + # Operator parameters + bl_idname = "nmv.render_morphology_side" + bl_label = "Side" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Render the frame + start_time = time.time() + nmv.interface.render_morphology_relevant_image( + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology, + camera_view=nmv.enums.Camera.View.SIDE, + image_suffix=nmv.consts.Suffix.MORPHOLOGY_SIDE) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_morphology_rendered = True + context.scene.NMV_MorphologyRenderingTime = rendering_time - start_time + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderMorphologyTop +#################################################################################################### +class NMV_RenderMorphologyTop(bpy.types.Operator): + """Render the top view (XZ) of the morphology""" + + # Operator parameters + bl_idname = "nmv.render_morphology_top" + bl_label = "Top" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Render the frame + start_time = time.time() + nmv.interface.render_morphology_relevant_image( + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology, + camera_view=nmv.enums.Camera.View.TOP, + image_suffix=nmv.consts.Suffix.MORPHOLOGY_TOP) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_morphology_rendered = True + context.scene.NMV_MorphologyRenderingTime = rendering_time - start_time + return {'FINISHED'} diff --git a/nmv/interface/ui/morphology/panel.py b/nmv/interface/ui/morphology/panel.py new file mode 100644 index 000000000..6ff8ec515 --- /dev/null +++ b/nmv/interface/ui/morphology/panel.py @@ -0,0 +1,146 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.interface +import nmv.utilities + +# Layout +from .layout_buttons import * +from .layout_skeleton_props import * +from .layout_reconstruction_props import * +from .layout_color_props import * +from .layout_rendering_props import * + + +#################################################################################################### +# @NMV_MorphologyPanel +#################################################################################################### +class NMV_MorphologyPanel(bpy.types.Panel): + """Morphology Tools Panel""" + + ################################################################################################ + # Panel parameters + ################################################################################################ + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_idname = "OBJECT_PT_NMV_MorphologyToolBox" + bl_label = 'Morphology Toolbox' + bl_category = 'NeuroMorphoVis' + bl_options = {'DEFAULT_CLOSED'} + + ################################################################################################ + # @update_ui_colors + ################################################################################################ + def update_ui_colors(self, context): + + # Get a list of initial colors from the selected colormap + colors = nmv.utilities.create_colormap_from_hex_list( + nmv.enums.ColorMaps.get_hex_color_list(context.scene.NMV_ColorMap), + nmv.consts.Color.COLORMAP_RESOLUTION) + + # Invert the colormap + if context.scene.NMV_InvertColorMap: + colors.reverse() + + # Update the colormap in the UI + for color_index in range(nmv.consts.Color.COLORMAP_RESOLUTION): + setattr(context.scene, 'NMV_Color%d' % color_index, colors[color_index]) + + # A list of all the color maps available in NeuroMorphoVis + # Note that once a new colormap is selected, the corresponding colors will be set in the UI + bpy.types.Scene.NMV_ColorMap = bpy.props.EnumProperty( + items=nmv.enums.ColorMaps.COLOR_MAPS, + name='', + default=nmv.enums.ColorMaps.GNU_PLOT, + update=update_ui_colors) + + bpy.types.Scene.NMV_InvertColorMap = bpy.props.BoolProperty( + name='Invert', + description='Invert the selected colormap', + default=False, + update=update_ui_colors) + + # Create a list of colors from the selected colormap + colors = nmv.utilities.create_colormap_from_hex_list( + nmv.enums.ColorMaps.get_hex_color_list(bpy.types.Scene.NMV_ColorMap), + nmv.consts.Color.COLORMAP_RESOLUTION) + + # Update the UI color elements from the color map list + for index in range(nmv.consts.Color.COLORMAP_RESOLUTION): + setattr(bpy.types.Scene, 'NMV_Color%d' % index, bpy.props.FloatVectorProperty( + name='', subtype='COLOR', default=colors[index], min=0.0, max=1.0, description='')) + + ################################################################################################ + # @draw + ################################################################################################ + def draw(self, context): + + # Verify the presence of a reconstructed morphology in the scene + if len(nmv.interface.ui_reconstructed_skeleton) > 0: + if nmv.scene.verify_objects_list_in_scene(nmv.interface.ui_reconstructed_skeleton): + nmv.interface.ui_morphology_reconstructed = True + else: + nmv.interface.ui_morphology_reconstructed = False + else: + nmv.interface.ui_morphology_reconstructed = False + + draw_documentation_button(layout=self.layout) + self.layout.separator() + + draw_morphology_reconstruction_options( + layout=self.layout, scene=context.scene, + options=nmv.interface.ui_options, morphology=nmv.interface.ui_morphology) + self.layout.separator() + + draw_morphology_skeleton_display_options( + layout=self.layout, scene=context.scene, + options=nmv.interface.ui_options, morphology=nmv.interface.ui_morphology) + self.layout.separator() + + draw_morphology_color_options( + layout=self.layout, scene=context.scene, + options=nmv.interface.ui_options, morphology=nmv.interface.ui_morphology) + self.layout.separator() + + method = nmv.interface.ui_options.morphology.reconstruction_method + if method == nmv.enums.Skeleton.Method.DENDROGRAM: + draw_morphology_reconstruction_button( + layout=self.layout, scene=context.scene, label='Reconstruct Dendrogram', + show_stats=nmv.interface.ui_morphology_reconstructed) + else: + draw_morphology_reconstruction_button( + layout=self.layout, scene=context.scene, label='Reconstruct Morphology', + show_stats=nmv.interface.ui_morphology_reconstructed) + self.layout.separator() + + draw_rendering_options( + panel=self, scene=context.scene, options=nmv.interface.ui_options, + show_stats=nmv.interface.ui_morphology_rendered) + self.layout.separator() + + # Export buttons + draw_morphology_export_options(panel=self) + self.layout.separator() + + # Enable or disable the layout + nmv.interface.enable_or_disable_layout(self.layout) diff --git a/nmv/interface/ui/morphology/morphology_panel_options.py b/nmv/interface/ui/morphology/panel_props.py similarity index 95% rename from nmv/interface/ui/morphology/morphology_panel_options.py rename to nmv/interface/ui/morphology/panel_props.py index 82b3466ef..9cfe07b16 100644 --- a/nmv/interface/ui/morphology/morphology_panel_options.py +++ b/nmv/interface/ui/morphology/panel_props.py @@ -1,5 +1,5 @@ #################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project # Marwan Abdellah # # This file is part of NeuroMorphoVis @@ -18,8 +18,6 @@ # Blender imports import bpy -import mathutils - # Internal imports import nmv.consts import nmv.enums @@ -56,7 +54,7 @@ # Axon branching order # Since the axon is so complicated, we will set its default branching order to 5 -bpy.types.Scene.NMV_AxonBranchingLevel = bpy.props.IntProperty( +bpy.types.Scene.NMV_AxonsBranchingOrder = bpy.props.IntProperty( name='Branching Order', description='Branching order for the axon', default=nmv.consts.Skeleton.AXON_DEFAULT_BRANCHING_ORDER, min=0, max=1000) @@ -68,7 +66,7 @@ default=True) # Basal dendrites branching order -bpy.types.Scene.NMV_BasalDendritesBranchingLevel = bpy.props.IntProperty( +bpy.types.Scene.NMV_BasalDendritesBranchingOrder = bpy.props.IntProperty( name='Branching Order', description='Branching order for the basal dendrites', default=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, min=0, max=1000) @@ -80,7 +78,7 @@ default=True) # Apical dendrite branching order -bpy.types.Scene.NMV_ApicalDendriteBranchingLevel = bpy.props.IntProperty( +bpy.types.Scene.NMV_ApicalDendritesBranchingOrder = bpy.props.IntProperty( name='Branching Order', description='Branching order for the apical dendrite', default=nmv.consts.Skeleton.MAX_BRANCHING_ORDER, min=0, max=1000) @@ -97,6 +95,13 @@ name='', default=nmv.enums.ColorCoding.DEFAULT_SCHEME) +# Connected object color-coding +bpy.types.Scene.NMV_ConnectedObjectColorCodingBasis = bpy.props.EnumProperty( + items=nmv.enums.ColorCoding.CONNECTED_OBJECT_COLOR_CODING_ITEMS, + name='', + default=nmv.enums.ColorCoding.DEFAULT_SCHEME) + + # Per-section color-coding bpy.types.Scene.NMV_PerSectionColorCodingBasis = bpy.props.EnumProperty( items=nmv.enums.ColorCoding.SECTIONS_COLOR_CODING_ITEMS, @@ -106,19 +111,19 @@ # The alternative color used to color every second object in the morphology bpy.types.Scene.NMV_MorphologyColor = bpy.props.FloatVectorProperty( name="Color", - subtype='COLOR', default=nmv.consts.Color.WHITE, min=0.0, max=1.0, + subtype='COLOR', default=nmv.consts.Color.LIGHT_RED, min=0.0, max=1.0, description="The color of the entire morphology surface") # The alternative color used to color every second object in the morphology bpy.types.Scene.NMV_MorphologyColor1 = bpy.props.FloatVectorProperty( name="Color 1", - subtype='COLOR', default=nmv.consts.Color.VERY_WHITE, min=0.0, max=1.0, + subtype='COLOR', default=nmv.consts.Color.LIGHT_RED, min=0.0, max=1.0, description="The first alternating color of the morphology") # The alternative color used to color every second object in the morphology bpy.types.Scene.NMV_MorphologyColor2 = bpy.props.FloatVectorProperty( name="Color 2", - subtype='COLOR', default=nmv.consts.Color.MATT_BLACK, min=0.0, max=1.0, + subtype='COLOR', default=nmv.consts.Color.SKY_BLUE, min=0.0, max=1.0, description="The second alternating color of the morphology") # Soma color @@ -225,9 +230,9 @@ # Arbor quality bpy.types.Scene.NMV_ArborQuality = bpy.props.IntProperty( - name='Sides', - description='Number of vertices of the cross-section of each segment along the arbor', - default=16, min=4, max=128) + name='Quality', + description='The tubular quality of the branches.', + default=3, min=1, max=64) # Section radius bpy.types.Scene.NMV_SectionsRadii = bpy.props.EnumProperty( @@ -378,7 +383,7 @@ (nmv.enums.Rendering.View.MID_SHOT, 'Mid Shot', 'Renders an image of the reconstructed arbors only'), - (nmv.enums.Rendering.View.CLOSE_UP, + (nmv.enums.Rendering.View.CLOSEUP, 'Close Up', 'Renders a close up image the focuses on the soma')], name='View', @@ -421,13 +426,13 @@ # Reconstruction time bpy.types.Scene.NMV_MorphologyReconstructionTime = bpy.props.FloatProperty( - name='Reconstruction (Sec)', + name='Reconstruction Time (Sec)', description='The time it takes to reconstruct the morphology and draw it to the viewport', default=0, min=0, max=1000000) # Rendering time bpy.types.Scene.NMV_MorphologyRenderingTime = bpy.props.FloatProperty( - name='Rendering (Sec)', + name='Rendering Time (Sec)', description='The time it takes to render the morphology into an image', default=0, min=0, max=1000000) diff --git a/nmv/interface/ui/morphology/registration.py b/nmv/interface/ui/morphology/registration.py new file mode 100644 index 000000000..6072c18e5 --- /dev/null +++ b/nmv/interface/ui/morphology/registration.py @@ -0,0 +1,88 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @register_panel +#################################################################################################### +def register_panel(): + """Registers all the classes in this panel""" + + from .panel import NMV_MorphologyPanel + from .ops_documentation import NMV_MorphologyReconstructionDocumentation + from .ops_reconstruction import NMV_ReconstructMorphologyOperator + from .ops_render_view import NMV_RenderMorphologyFront + from .ops_render_view import NMV_RenderMorphologySide + from .ops_render_view import NMV_RenderMorphologyTop + from .ops_render_360 import NMV_RenderMorphology360 + from .ops_render_progressive import NMV_RenderMorphologyProgressive + from .ops_exports import NMV_ExportMorphologySWC + from .ops_exports import NMV_ExportMorphologySegments + from .ops_exports import NMV_ExportMorphologyBLEND + + # Soma reconstruction panel + bpy.utils.register_class(NMV_MorphologyPanel) + + # Buttons + bpy.utils.register_class(NMV_MorphologyReconstructionDocumentation) + bpy.utils.register_class(NMV_ReconstructMorphologyOperator) + bpy.utils.register_class(NMV_RenderMorphologyFront) + bpy.utils.register_class(NMV_RenderMorphologySide) + bpy.utils.register_class(NMV_RenderMorphologyTop) + bpy.utils.register_class(NMV_RenderMorphology360) + bpy.utils.register_class(NMV_RenderMorphologyProgressive) + bpy.utils.register_class(NMV_ExportMorphologySWC) + bpy.utils.register_class(NMV_ExportMorphologySegments) + bpy.utils.register_class(NMV_ExportMorphologyBLEND) + + +#################################################################################################### +# @unregister_panel +#################################################################################################### +def unregister_panel(): + """Un-registers all the classes in this panel""" + + from .panel import NMV_MorphologyPanel + from .ops_documentation import NMV_MorphologyReconstructionDocumentation + from .ops_reconstruction import NMV_ReconstructMorphologyOperator + from .ops_render_view import NMV_RenderMorphologyFront + from .ops_render_view import NMV_RenderMorphologySide + from .ops_render_view import NMV_RenderMorphologyTop + from .ops_render_360 import NMV_RenderMorphology360 + from .ops_render_progressive import NMV_RenderMorphologyProgressive + from .ops_exports import NMV_ExportMorphologySWC + from .ops_exports import NMV_ExportMorphologySegments + from .ops_exports import NMV_ExportMorphologyBLEND + + # Morphology reconstruction panel + bpy.utils.unregister_class(NMV_MorphologyPanel) + + # Buttons + bpy.utils.unregister_class(NMV_MorphologyReconstructionDocumentation) + bpy.utils.unregister_class(NMV_ReconstructMorphologyOperator) + bpy.utils.unregister_class(NMV_RenderMorphologyTop) + bpy.utils.unregister_class(NMV_RenderMorphologySide) + bpy.utils.unregister_class(NMV_RenderMorphologyFront) + bpy.utils.unregister_class(NMV_RenderMorphology360) + bpy.utils.unregister_class(NMV_RenderMorphologyProgressive) + bpy.utils.unregister_class(NMV_ExportMorphologySWC) + bpy.utils.unregister_class(NMV_ExportMorphologySegments) + bpy.utils.unregister_class(NMV_ExportMorphologyBLEND) + diff --git a/nmv/interface/ui/soma/layout_buttons.py b/nmv/interface/ui/soma/layout_buttons.py new file mode 100644 index 000000000..c8a32fa52 --- /dev/null +++ b/nmv/interface/ui/soma/layout_buttons.py @@ -0,0 +1,41 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +#################################################################################################### +# @draw_documentation_button +#################################################################################################### +def draw_documentation_button(layout): + + row = layout.row() + row.operator('nmv.documentation_soma', icon='URL') + + +#################################################################################################### +# @draw_soma_mesh_export_button +#################################################################################################### +def draw_soma_mesh_export_button(panel, scene): + + row = panel.layout.row() + row.label(text='Export Soma Mesh', icon='MESH_UVSPHERE') + + export_format = panel.layout.row() + export_format.prop(scene, 'NMV_ExportedSomaMeshFormat', icon='GROUP_VERTEX') + + # Save button + row = panel.layout.column() + row.operator('nmv.export_soma_mesh', icon='MESH_DATA') \ No newline at end of file diff --git a/nmv/interface/ui/soma/layout_color_props.py b/nmv/interface/ui/soma/layout_color_props.py new file mode 100644 index 000000000..800024f1b --- /dev/null +++ b/nmv/interface/ui/soma/layout_color_props.py @@ -0,0 +1,63 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy +from mathutils import Vector + +# Internal imports +import nmv.consts +import nmv.enums + + +#################################################################################################### +# @draw_colors_header +#################################################################################################### +def draw_colors_header(layout): + + row = layout.row() + row.label(text='Colors & Materials', icon='COLOR') + + +#################################################################################################### +# @draw_soma_color +#################################################################################################### +def draw_soma_color(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SomaBaseColor') + options.shading.soma_color = scene.NMV_SomaBaseColor + + +#################################################################################################### +# @draw_soma_material +#################################################################################################### +def draw_soma_material_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SomaMaterial') + options.shading.soma_material = scene.NMV_SomaMaterial + + +#################################################################################################### +# @draw_soma_color_options +#################################################################################################### +def draw_soma_color_options(panel, scene, options): + + draw_colors_header(layout=panel.layout) + draw_soma_color(layout=panel.layout, scene=scene, options=options) + draw_soma_material_option(layout=panel.layout, scene=scene, options=options) \ No newline at end of file diff --git a/nmv/interface/ui/soma/layout_reconstruction_props.py b/nmv/interface/ui/soma/layout_reconstruction_props.py new file mode 100644 index 000000000..d310b8610 --- /dev/null +++ b/nmv/interface/ui/soma/layout_reconstruction_props.py @@ -0,0 +1,180 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums + + +#################################################################################################### +# @draw_morphology_reconstruction_header +#################################################################################################### +def draw_soma_reconstruction_header(layout): + + row = layout.row() + row.label(text='Reconstruction Options', icon='OUTLINER_OB_EMPTY') + + +#################################################################################################### +# @draw_soma_reconstruction_technique_option +#################################################################################################### +def draw_soma_reconstruction_technique_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SomaReconstructionMethod', icon='FORCE_CURVE') + options.soma.method = scene.NMV_SomaReconstructionMethod + + +#################################################################################################### +# @draw_meta_ball_soma_resolution_option +#################################################################################################### +def draw_meta_ball_soma_resolution_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SomaMetaBallResolution') + options.soma.meta_ball_resolution = scene.NMV_SomaMetaBallResolution + + +#################################################################################################### +# @draw_meta_ball_soma_options +#################################################################################################### +def draw_meta_ball_soma_options(layout, scene, options): + + draw_meta_ball_soma_resolution_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_soma_profile_options +#################################################################################################### +def draw_soma_profile_options(layout, scene, options, morphology): + + row = layout.row() + if len(morphology.soma.profile_points) > 0: + row.prop(scene, 'NMV_SomaProfile') + options.soma.profile = scene.NMV_SomaProfile + else: + row.prop(scene, 'NMV_SomaArborsOnlyProfile') + options.soma.profile = scene.NMV_SomaArborsOnlyProfile + + +#################################################################################################### +# @draw_simulation_steps_option +#################################################################################################### +def draw_simulation_steps_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SimulationSteps') + options.soma.simulation_steps = scene.NMV_SimulationSteps + + +#################################################################################################### +# @draw_soft_body_stiffness_option +#################################################################################################### +def draw_soft_body_stiffness_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_Stiffness') + options.soma.stiffness = scene.NMV_Stiffness + + +#################################################################################################### +# @draw_soma_radius_scale_factor +#################################################################################################### +def draw_soma_radius_scale_factor(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SomaRadiusScaleFactor') + options.soma.radius_scale_factor = scene.NMV_SomaRadiusScaleFactor + + +#################################################################################################### +# @draw_ico_sphere_subdivision_level_option +#################################################################################################### +def draw_ico_sphere_subdivision_level_option(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SubdivisionLevel') + options.soma.subdivision_level = scene.NMV_SubdivisionLevel + + +#################################################################################################### +# @draw_soft_body_soma_options +#################################################################################################### +def draw_soft_body_soma_options(layout, scene, options, morphology): + + draw_soma_profile_options(layout=layout, scene=scene, options=options, morphology=morphology) + draw_simulation_steps_option(layout=layout, scene=scene, options=options) + draw_soft_body_stiffness_option(layout=layout, scene=scene, options=options) + draw_soma_radius_scale_factor(layout=layout, scene=scene, options=options) + draw_ico_sphere_subdivision_level_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_hybrid_soma_options +#################################################################################################### +def draw_hybrid_soma_options(layout, scene, options, morphology): + + draw_soma_profile_options(layout=layout, scene=scene, options=options, morphology=morphology) + draw_simulation_steps_option(layout=layout, scene=scene, options=options) + draw_soft_body_stiffness_option(layout=layout, scene=scene, options=options) + draw_soma_radius_scale_factor(layout=layout, scene=scene, options=options) + draw_ico_sphere_subdivision_level_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_soma_reconstruction_button +#################################################################################################### +def draw_soma_reconstruction_button(layout, scene, options): + + row = layout.row(align=True) + row.operator('nmv.reconstruct_soma', icon='FORCE_LENNARDJONES') + + # Soma simulation progress bar + if options.soma.method == nmv.enums.Soma.Representation.SOFT_BODY: + row = layout.row() + row.prop(scene, 'NMV_SomaSimulationProgress') + row.enabled = False + + if nmv.interface.ui_soma_reconstructed: + row = layout.row() + row.prop(scene, 'NMV_SomaReconstructionTime') + row.enabled = False + + +#################################################################################################### +# @draw_morphology_reconstruction_options +#################################################################################################### +def draw_soma_reconstruction_options(panel, scene, options, morphology): + + draw_soma_reconstruction_header(layout=panel.layout) + draw_soma_reconstruction_technique_option(layout=panel.layout, scene=scene, options=options) + + if options.soma.method == nmv.enums.Soma.Representation.META_BALLS: + draw_meta_ball_soma_options( + layout=panel.layout, scene=scene, options=options) + elif options.soma.method == nmv.enums.Soma.Representation.SOFT_BODY: + draw_soft_body_soma_options( + layout=panel.layout, scene=scene, options=options, morphology=morphology) + elif options.soma.method == nmv.enums.Soma.Representation.HYBRID: + draw_hybrid_soma_options( + layout=panel.layout, scene=scene, options=options, morphology=morphology) + else: + nmv.logger.log('UI_ERROR: draw_soma_reconstruction_options') + diff --git a/nmv/interface/ui/soma/layout_rendering_props.py b/nmv/interface/ui/soma/layout_rendering_props.py new file mode 100644 index 000000000..ebadfda03 --- /dev/null +++ b/nmv/interface/ui/soma/layout_rendering_props.py @@ -0,0 +1,127 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + + +#################################################################################################### +# @draw_render_view_header +#################################################################################################### +def draw_render_view_header(layout): + + row = layout.row() + row.label(text='Render View', icon='RESTRICT_RENDER_OFF') + + +#################################################################################################### +# @draw_render_animation_header +#################################################################################################### +def draw_render_animation_header(layout): + + row = layout.row() + row.label(text='Render Animation', icon='CAMERA_DATA') + + +#################################################################################################### +# @draw_soma_rendering_buttons +#################################################################################################### +def draw_soma_rendering_buttons(panel, scene): + + draw_render_view_header(layout=panel.layout) + + row = panel.layout.row(align=True) + row.operator('nmv.render_soma_front', icon='AXIS_FRONT') + row.operator('nmv.render_soma_side', icon='AXIS_SIDE') + row.operator('nmv.render_soma_top', icon='AXIS_TOP') + + +#################################################################################################### +# @draw_soma_render_animation_buttons +#################################################################################################### +def draw_soma_render_animation_buttons(layout, scene, options): + + row = layout.row(align=True) + row.operator('nmv.render_soma_360', icon='FORCE_MAGNETIC') + + # Progressive rendering is only for the soft body physics + if options.soma.method == nmv.enums.Soma.Representation.SOFT_BODY: + row.operator('nmv.render_soma_progressive', icon='FORCE_HARMONIC') + + +#################################################################################################### +# @draw_soma_animation_rendering_progress +#################################################################################################### +def draw_soma_animation_rendering_progress(layout, scene): + + row = layout.row() + row.prop(scene, 'NMV_SomaRenderingProgress') + row.enabled = False + +#################################################################################################### +# @draw_soma_rendering_time +#################################################################################################### +def draw_soma_rendering_time(layout, scene): + + row = layout.row() + row.prop(scene, 'NMV_SomaRenderingTime') + row.enabled = False + +#################################################################################################### +# draw_soma_frame_rendering_options +#################################################################################################### +def draw_soma_frame_rendering_options(panel, scene, options): + + nmv.interface.ui.common.draw_rendering_header( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_resolution_basis_option( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_resolution_options( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_image_format_option( + layout=panel.layout, scene=scene, options=options) + + nmv.interface.ui.common.draw_scale_bar_option( + layout=panel.layout, scene=scene, options=options) + panel.layout.separator() + + draw_soma_rendering_buttons(panel=panel, scene=scene) + + if nmv.interface.ui_soma_rendered: + draw_soma_rendering_time(layout=panel.layout, scene=scene) + panel.layout.separator() + + +#################################################################################################### +# draw_soma_progressive_rendering_options +#################################################################################################### +def draw_soma_progressive_rendering_options(panel, scene, options): + + draw_render_animation_header(layout=panel.layout) + + draw_soma_render_animation_buttons(layout=panel.layout, scene=scene, options=options) + + draw_soma_animation_rendering_progress(layout=panel.layout, scene=scene) diff --git a/nmv/interface/ui/soma/ops_documentation.py b/nmv/interface/ui/soma/ops_documentation.py new file mode 100644 index 000000000..7452e5f1f --- /dev/null +++ b/nmv/interface/ui/soma/ops_documentation.py @@ -0,0 +1,28 @@ + + + + + + +import bpy + + +#################################################################################################### +# @SomaReconstructionDocumentation +#################################################################################################### +class SomaReconstructionDocumentation(bpy.types.Operator): + """Open the online documentation page of the Soma Reconstruction panel""" + + # Operator parameters + bl_idname = "nmv.documentation_soma" + bl_label = "Online User Guide" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + import webbrowser + webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki/Soma-Reconstruction') + return {'FINISHED'} + diff --git a/nmv/interface/ui/soma/ops_export.py b/nmv/interface/ui/soma/ops_export.py new file mode 100644 index 000000000..54d458358 --- /dev/null +++ b/nmv/interface/ui/soma/ops_export.py @@ -0,0 +1,71 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal modules +import nmv.builders +import nmv.consts +import nmv.enums +import nmv.file +import nmv.interface +import nmv.mesh +import nmv.rendering +import nmv.scene +import nmv.utilities + + +#################################################################################################### +# @NMV_ExportSomaMesh +#################################################################################################### +class NMV_ExportSomaMesh(bpy.types.Operator): + """Exports the reconstructed soma mesh""" + + # Operator parameters + bl_idname = "nmv.export_soma_mesh" + bl_label = "Export Mesh" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # If no morphology is loaded, report it + if nmv.interface.ui_morphology is None: + self.report({'ERROR'}, 'Please select a morphology file') + return {'FINISHED'} + + # Verify the output directory + if not nmv.interface.validate_output_directory(self, context.scene): + return {'FINISHED'} + + # Create the meshes directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.meshes_directory): + nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.meshes_directory) + + # Export + nmv.file.export_mesh_object_to_file(nmv.interface.ui_soma_mesh, + nmv.interface.ui_options.io.meshes_directory, + nmv.interface.ui_morphology.label, + context.scene.NMV_ExportedSomaMeshFormat) + + # Finished + return {'FINISHED'} diff --git a/nmv/interface/ui/soma/ops_reconstruction.py b/nmv/interface/ui/soma/ops_reconstruction.py new file mode 100644 index 000000000..e2f43d1e1 --- /dev/null +++ b/nmv/interface/ui/soma/ops_reconstruction.py @@ -0,0 +1,255 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.utilities +import nmv.scene +import nmv.builders +import nmv.mesh + + +#################################################################################################### +# @NMV_ReconstructSoma +#################################################################################################### +class NMV_ReconstructSoma(bpy.types.Operator): + """Soma reconstruction""" + + # Operator parameters + bl_idname = "nmv.reconstruct_soma" + bl_label = "Reconstruct Soma" + + # Timer parameters + event_timer = None + timer_limits = 0 + + # Builder parameters + soma_builder = None + soma_sphere_object = None + + # Reconstruction time + reconstruction_time = 0 + + ################################################################################################ + # @modal + ################################################################################################ + def modal(self, + context, + event): + """Threading and non-blocking handling. + + :param context: + Panel context. + :param event: + A given event for the panel. + """ + + # Get a reference to the scene + scene = context.scene + + # Cancelling event, if using right click or exceeding the time limit of the simulation + if event.type in {'RIGHTMOUSE', 'ESC'} or \ + self.timer_limits > scene.NMV_SimulationSteps: + + # Get the reconstruction time to update the UI + scene.NMV_SomaReconstructionTime = time.time() - self.reconstruction_time + nmv.logger.info_done('Soma reconstructed in [%f] seconds' % + scene.NMV_MorphologyLoadingTime) + + # Set the reconstruction flag to on + nmv.interface.ui_soma_reconstructed = True + + # Reset the timer limits + self.timer_limits = 0 + + # Refresh the panel context + self.cancel(context) + + # Finished + return {'FINISHED'} + + # Timer event, where the function is executed here on a per-frame basis + if event.type == 'TIMER': + + # Update the frame based on the soft body simulation + bpy.context.scene.frame_set(self.timer_limits) + + # Update the progress shell + nmv.utilities.show_progress( + 'Simulation', self.timer_limits, scene.NMV_SimulationSteps) + + # Update the progress bar + scene.NMV_SomaSimulationProgress = \ + int(100.0 * self.timer_limits / scene.NMV_SimulationSteps) + + # Upgrade the timer limits + self.timer_limits += 1 + + # View all the objects in the scene + nmv.scene.ops.view_all_scene() + + # Deselect everything in the scene to be able to see the morphology + nmv.scene.deselect_all() + + # Next frame + return {'PASS_THROUGH'} + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Clear the scene + nmv.scene.ops.clear_scene() + + # If no morphology file is loaded, load the morphology file + if nmv.interface.ui_morphology is None: + loading_result = nmv.interface.ui.load_morphology(self, context.scene) + if loading_result is None: + self.report({'ERROR'}, 'Please select a morphology file') + return {'FINISHED'} + + # Reconstruction time + self.reconstruction_time = time.time() + + # MetaBall reconstruction + if nmv.interface.ui_options.soma.method == nmv.enums.Soma.Representation.META_BALLS: + + # Create a some builder, and reconstruct the soma in a single step + self.soma_builder = nmv.builders.SomaMetaBuilder( + nmv.interface.ui_morphology, nmv.interface.ui_options) + nmv.interface.ui_soma_mesh = self.soma_builder.reconstruct_soma_mesh() + + # Get the reconstruction time to update the UI + self.reconstruction_time = time.time() - self.reconstruction_time + context.scene.NMV_SomaReconstructionTime = self.reconstruction_time + nmv.logger.info_done('Soma reconstructed in [%f] seconds' % + context.scene.NMV_MorphologyLoadingTime) + + # Set the reconstruction flag to True + nmv.interface.ui_soma_reconstructed = True + + # View all the objects in the scene + nmv.scene.ops.view_all_scene() + + # Finished + return {'FINISHED'} + + # MetaBall reconstruction + elif bpy.context.scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.HYBRID: + + # Create a some builder, and reconstruct the soma in a single step + self.soma_builder = nmv.builders.SomaHybridBuilder( + nmv.interface.ui_morphology, nmv.interface.ui_options) + nmv.interface.ui_soma_mesh = self.soma_builder.reconstruct_soma_mesh() + + # Get the reconstruction time to update the UI + self.reconstruction_time = time.time() - self.reconstruction_time + context.scene.NMV_SomaReconstructionTime = self.reconstruction_time + nmv.logger.info_done('Soma reconstructed in [%f] seconds' % + context.scene.NMV_MorphologyLoadingTime) + + # Set the reconstruction flag to True + nmv.interface.ui_soma_reconstructed = True + + # View all the objects in the scene + nmv.scene.ops.view_all_scene() + + # Finished + return {'FINISHED'} + + # Soft-body reconstruction + else: + + # SoftBody reconstruction + self.soma_builder = nmv.builders.SomaSoftBodyBuilder( + nmv.interface.ui_morphology, nmv.interface.ui_options) + + # Build the basic profile of the soma from the soft body operation, but don't run the + # simulation now. Run the simulation in the '@modal' mode, to avoid freezing the UI + profile_option = nmv.interface.ui_options.soma.profile + if profile_option == nmv.enums.Soma.Profile.ARBORS_ONLY: + self.soma_sphere_object = self.soma_builder.build_soma_soft_body() + elif profile_option == nmv.enums.Soma.Profile.PROFILE_POINTS_ONLY: + self.soma_sphere_object = self.soma_builder.build_soma_based_on_profile_points_only() + elif profile_option == nmv.enums.Soma.Profile.COMBINED: + self.soma_sphere_object = self.soma_builder.build_soma_soft_body( + use_profile_points=True) + else: + self.soma_sphere_object = self.soma_builder.build_soma_soft_body() + + # Use the event timer to update the UI during the soma building + wm = context.window_manager + self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) + wm.modal_handler_add(self) + + # View all the objects in the scene + nmv.scene.ops.view_all_scene() + + # Modal + return {'RUNNING_MODAL'} + + ################################################################################################ + # @cancel + ################################################################################################ + def cancel(self, context): + """Cancel the panel processing and return to the interaction mode. + + :param context: + Panel context. + """ + + # Get a reference to the scene + scene = context.scene + + # Multi-threading + wm = context.window_manager + wm.event_timer_remove(self.event_timer) + + # Build the mesh from the soft body object + self.soma_sphere_object = self.soma_builder.build_soma_mesh_from_soft_body_object( + self.soma_sphere_object) + + # Keep a reference to the mesh object in case we need to save or texture it + nmv.interface.ui_soma_mesh = self.soma_sphere_object + + # Show the progress, Done + nmv.utilities.show_progress( + 'Simulation', self.timer_limits, scene.NMV_SimulationSteps, done=True) + + if nmv.interface.ui_options.soma.profile == nmv.enums.Soma.Profile.PROFILE_POINTS_ONLY: + + # Decimate the mesh using 25% + nmv.logger.info('Decimation') + nmv.mesh.ops.decimate_mesh_object(self.soma_sphere_object, decimation_ratio=0.25) + + # Smooth the mesh again to look nice + nmv.logger.info('Smoothing') + nmv.mesh.ops.smooth_object(self.soma_sphere_object, level=2) + + # Deselect everything in the scene to be able to see the morphology + nmv.scene.deselect_all() + + # Report the process termination in the UI + self.report({'INFO'}, 'Soma Reconstruction Done') diff --git a/nmv/interface/ui/soma/ops_render_360.py b/nmv/interface/ui/soma/ops_render_360.py new file mode 100644 index 000000000..ffafc1f8b --- /dev/null +++ b/nmv/interface/ui/soma/ops_render_360.py @@ -0,0 +1,164 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.bbox +import nmv.consts +import nmv.enums +import nmv.rendering +import nmv.utilities +import nmv.scene + +#################################################################################################### +# @NMV_RenderSoma360 +#################################################################################################### +class NMV_RenderSoma360(bpy.types.Operator): + """Render a 360 movie of the soma reconstruction process""" + + # Operator parameters + bl_idname = "nmv.render_soma_360" + bl_label = "360" + + # Timer parameters + event_timer = None + timer_limits = 0 + + # 360 bounding box + bounding_box_360 = None + + # Output data + output_directory = None + + ################################################################################################ + # @modal + ################################################################################################ + def modal(self, context, event): + + # Cancelling event, if using right click or exceeding the time limit of the simulation + if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > 360: + + # Reset the timer limits + self.timer_limits = 0 + + # Refresh the panel context + self.cancel(context) + + # Finished + return {'FINISHED'} + + # Timer event, where the function is executed here on a per-frame basis + if event.type == 'TIMER': + + # Set the frame name + image_name = '%s/%s' % ( + self.output_directory, '{0:05d}'.format(self.timer_limits)) + + # Get a list of all the meshes in the scene + scene_objects = nmv.scene.get_list_of_meshes_in_scene() + + nmv.rendering.renderer.render_at_angle( + scene_objects=scene_objects, + angle=self.timer_limits, + bounding_box=self.bounding_box_360, + camera_view=nmv.enums.Camera.View.FRONT, + image_resolution=context.scene.NMV_SomaFrameResolution, + image_name=image_name) + + # Update the progress shell + nmv.utilities.show_progress('Rendering', self.timer_limits, 360) + + # Update the progress bar + context.scene.NMV_SomaRenderingProgress = int(100 * self.timer_limits / 360.0) + + # Upgrade the timer limits + self.timer_limits += 1 + + # Next frame + return {'PASS_THROUGH'} + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, + context): + """Execute the operator. + + :param context: + Panel context. + """ + + # Get a reference to the scene + scene = context.scene + + # Verify the output directory + if not nmv.interface.validate_output_directory(self, context.scene): + return {'FINISHED'} + + # Create the sequences directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): + nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.sequences_directory) + + # Compute the bounding box for the available meshes only + rendering_bbox = nmv.bbox.compute_scene_bounding_box_for_meshes() + + # Compute a 360 bounding box to fit the arbors + self.bounding_box_360 = nmv.bbox.compute_360_bounding_box( + rendering_bbox, nmv.interface.ui_morphology.soma.centroid) + + # Stretch the bounding box by few microns + self.bounding_box_360.extend_bbox_uniformly(delta=nmv.consts.Image.GAP_DELTA) + + # Create a specific directory for this soma mesh + self.output_directory = '%s/%s%s' % \ + (nmv.interface.ui_options.io.sequences_directory, + nmv.interface.ui_options.morphology.label, + nmv.consts.Suffix.SOMA_360) + nmv.file.ops.clean_and_create_directory(self.output_directory) + + # Use the event timer to update the UI during the soma building + wm = context.window_manager + self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) + wm.modal_handler_add(self) + + # Modal + return {'RUNNING_MODAL'} + + ################################################################################################ + # @cancel + ################################################################################################ + def cancel(self, context): + """ + Cancel the panel processing and return to the interaction mode. + + :param context: + Panel context. + """ + + # Multi-threading + wm = context.window_manager + wm.event_timer_remove(self.event_timer) + + # Report the process termination in the UI + self.report({'INFO'}, 'Soma Rendering Done') + + # Finished + return {'FINISHED'} + + diff --git a/nmv/interface/ui/soma/ops_render_progressive.py b/nmv/interface/ui/soma/ops_render_progressive.py new file mode 100644 index 000000000..032ce0ad3 --- /dev/null +++ b/nmv/interface/ui/soma/ops_render_progressive.py @@ -0,0 +1,176 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.builders +import nmv.consts +import nmv.enums +import nmv.interface +import nmv.scene +import nmv.utilities + + +#################################################################################################### +# @NMV_RenderSomaProgressive +#################################################################################################### +class NMV_RenderSomaProgressive(bpy.types.Operator): + """Render progressive soma reconstruction sequence""" + + # Operator parameters + bl_idname = "nmv.render_soma_progressive" + bl_label = "Progressive" + + # Timer parameters + event_timer = None + timer_limits = 0 + + # Output data + output_directory = None + + # Morphology parameters + morphology_object = None + + # Soma builder parameters + soma_builder = None + soma_sphere_object = None + + ################################################################################################ + # @modal + ################################################################################################ + def modal(self, context, event): + + # Cancelling event, if using right click or exceeding the time limit of the simulation + if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > context.scene.NMV_SimulationSteps: + + # Reset the timer limits + self.timer_limits = 0 + + # Refresh the panel context + self.cancel(context) + + # Finished + return {'FINISHED'} + + # Timer event, where the function is executed here on a per-frame basis + if event.type == 'TIMER': + + # Update the frame based on the soft body simulation + bpy.context.scene.frame_set(self.timer_limits) + + # Set the frame name + image_name = '%s/%s' % ( + self.output_directory, '{0:05d}'.format(self.timer_limits)) + + # Render a frame + nmv.rendering.SomaRenderer.render_at_angle( + soma_mesh=self.soma_sphere_object, + angle=0.0, + view_extent=scene.NMV_ViewDimensions, + camera_view=nmv.enums.Camera.View.FRONT, + image_resolution=scene.NMV_SomaFrameResolution, + image_name=image_name) + + # Update the progress shell + nmv.utilities.show_progress('Rendering', + self.timer_limits, scene.NMV_SimulationSteps) + + # Update the progress bar + scene.NMV_SomaRenderingProgress = self.timer_limits + + # Upgrade the timer limits + self.timer_limits += 1 + + # Next frame + return {'PASS_THROUGH'} + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + # Verify the output directory + if not nmv.interface.validate_output_directory(self, context.scene): + return {'FINISHED'} + + # Clear the scene + nmv.scene.ops.clear_scene() + + # Create the sequences directory if it does not exist + if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): + nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.sequences_directory) + + # Create a specific directory for this mesh + self.output_directory = '%s/%s%s' % ( + nmv.interface.ui_options.io.sequences_directory, + nmv.interface.ui_options.morphology.label, + nmv.consts.Suffix.SOMA_PROGRESSIVE) + nmv.file.ops.clean_and_create_directory(self.output_directory) + + # Load the morphology file + loading_result = nmv.interface.ui.load_morphology(self, context.scene) + + # If the result is None, report the issue + if loading_result is None: + self.report({'ERROR'}, 'Please select a morphology file') + return {'FINISHED'} + + # Create a some builder object + self.soma_builder = nmv.builders.SomaSoftBodyBuilder( + nmv.interface.ui_morphology, nmv.interface.ui_options) + + # Build the basic profile of the some from the soft body operation, but don't run the + # simulation now. Run the simulation in the '@modal' mode, to avoid freezing the UI + self.soma_sphere_object = self.soma_builder.build_soma_soft_body() + + # Use the event timer to update the UI during the soma building + wm = context.window_manager + self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) + wm.modal_handler_add(self) + + # Modal + return {'RUNNING_MODAL'} + + ################################################################################################ + # @cancel + ################################################################################################ + def cancel(self, context): + + # Multi-threading + wm = context.window_manager + wm.event_timer_remove(self.event_timer) + + # Build the mesh from the soft body object + self.soma_sphere_object = self.soma_builder.build_soma_mesh_from_soft_body_object( + self.soma_sphere_object) + + # Keep a reference to the mesh object in case we need to save or texture it + nmv.interface.ui_soma_mesh = self.soma_sphere_object + + # Show the progress, Done + nmv.utilities.show_progress( + 'Rendering', self.timer_limits, context.scene.NMV_SimulationSteps, done=True) + + # Report the process termination in the UI + self.report({'INFO'}, 'Soma Rendering Done') + + # Delete the camera + # nmv.scene.ops.delete_list_objects([self.soma_sphere_object]) + + diff --git a/nmv/interface/ui/soma/ops_render_view.py b/nmv/interface/ui/soma/ops_render_view.py new file mode 100644 index 000000000..3092c1273 --- /dev/null +++ b/nmv/interface/ui/soma/ops_render_view.py @@ -0,0 +1,113 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums + + +#################################################################################################### +# @NMV_RenderSomaFront +#################################################################################################### +class NMV_RenderSomaFront(bpy.types.Operator): + """Render the Front view of the soma""" + + # Operator parameters + bl_idname = "nmv.render_soma_front" + bl_label = "Front (XY)" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + start_time = time.time() + nmv.interface.render_meshes_relevant_image( + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology, + camera_view=nmv.enums.Camera.View.FRONT, + image_suffix=nmv.consts.Suffix.SOMA_FRONT) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_soma_rendered = True + context.scene.NMV_SomaRenderingTime = rendering_time - start_time + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderSomaSide +#################################################################################################### +class NMV_RenderSomaSide(bpy.types.Operator): + """Render the Side view of the soma""" + + # Operator parameters + bl_idname = "nmv.render_soma_side" + bl_label = "Side (YZ)" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + start_time = time.time() + nmv.interface.render_meshes_relevant_image( + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology, + camera_view=nmv.enums.Camera.View.SIDE, + image_suffix=nmv.consts.Suffix.SOMA_SIDE) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_soma_rendered = True + context.scene.NMV_SomaRenderingTime = rendering_time - start_time + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderSomaTop +#################################################################################################### +class NMV_RenderSomaTop(bpy.types.Operator): + """Render the Top view of the soma""" + + # Operator parameters + bl_idname = "nmv.render_soma_top" + bl_label = "Top (XZ)" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + start_time = time.time() + nmv.interface.render_meshes_relevant_image( + options=nmv.interface.ui_options, + morphology=nmv.interface.ui_morphology, + camera_view=nmv.enums.Camera.View.TOP, + image_suffix=nmv.consts.Suffix.SOMA_TOP) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_soma_rendered = True + context.scene.NMV_SomaRenderingTime = rendering_time - start_time + return {'FINISHED'} diff --git a/nmv/interface/ui/soma/panel.py b/nmv/interface/ui/soma/panel.py new file mode 100644 index 000000000..2ef394ab3 --- /dev/null +++ b/nmv/interface/ui/soma/panel.py @@ -0,0 +1,299 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy +from mathutils import Vector + +# Internal modules +import nmv.builders +import nmv.consts +import nmv.enums +import nmv.file +import nmv.interface +import nmv.mesh +import nmv.rendering +import nmv.scene +import nmv.utilities + + +from .layout_buttons import draw_documentation_button +from .layout_buttons import draw_soma_mesh_export_button +from .layout_color_props import draw_soma_color_options +from .layout_rendering_props import draw_soma_frame_rendering_options +from .layout_rendering_props import draw_soma_progressive_rendering_options +from .layout_reconstruction_props import draw_soma_reconstruction_options +from .layout_reconstruction_props import draw_soma_reconstruction_button + + +#################################################################################################### +# @NMV_SomaPanel +#################################################################################################### +class NMV_SomaPanel(bpy.types.Panel): + """Soma panel""" + + ################################################################################################ + # Panel parameters + ################################################################################################ + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_idname = "OBJECT_PT_NMV_SomaToolBox" + bl_label = 'Soma Toolbox' + bl_category = 'NeuroMorphoVis' + bl_options = {'DEFAULT_CLOSED'} + + ################################################################################################ + # @draw + ################################################################################################ + def draw(self, context): + + options = nmv.interface.ui_options + morphology = nmv.interface.ui_morphology + + # Documentation button + draw_documentation_button(layout=self.layout) + self.layout.separator() + + # Soma reconstruction options + draw_soma_reconstruction_options( + panel=self, scene=context.scene, options=options, morphology=morphology) + self.layout.separator() + + # Color options + draw_soma_color_options( + panel=self, scene=context.scene, options=options) + self.layout.separator() + + # Reconstruction buttons + draw_soma_reconstruction_button( + layout=self.layout, scene=context.scene, options=options) + self.layout.separator() + + # Still-frame rendering options + draw_soma_frame_rendering_options( + panel=self, scene=context.scene, options=options) + self.layout.separator() + + # Progressive rendering options + draw_soma_progressive_rendering_options( + panel=self, scene=context.scene, options=options) + self.layout.separator() + + draw_soma_mesh_export_button(panel=self, scene=context.scene) + + return + + # Get a reference to the scene + scene = context.scene + + # Get a reference to the panel layout + layout = self.layout + + # Documentation button + documentation_button = layout.column() + documentation_button.operator('nmv.documentation_soma', icon='URL') + documentation_button.separator() + + # Get a reference to the soma options + soma_options = nmv.interface.ui_options.soma + + reconstruction_method_row = layout.row() + reconstruction_method_row.prop(scene, 'NMV_SomaReconstructionMethod') + + if scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.META_BALLS: + reconstruction_method_row = layout.row() + reconstruction_method_row.prop(scene, 'NMV_SomaMetaBallResolution') + soma_options.meta_ball_resolution = scene.NMV_SomaMetaBallResolution + + elif scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.SOFT_BODY or \ + scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.HYBRID: + + reconstruction_method_row = layout.row() + reconstruction_method_row.prop(scene, 'NMV_SomaProfile') + soma_options.method = scene.NMV_SomaProfile + + # Soft body options + soft_body_params_row = layout.row() + soft_body_params_row.label(text='Soft Body Parameters:', icon='GROUP_UVS') + + # Simulation steps + simulation_steps_row = layout.row() + simulation_steps_row.prop(scene, 'NMV_SimulationSteps') + soma_options.simulation_steps = scene.NMV_SimulationSteps + + # Soft body stiffness option + stiffness_row = layout.row() + stiffness_row.prop(scene, 'NMV_Stiffness') + soma_options.stiffness = scene.NMV_Stiffness + + # Radius scale factor + radius_scale_factor_row = layout.row() + radius_scale_factor_row.prop(scene, 'NMV_SomaRadiusScaleFactor') + soma_options.radius_scale_factor = scene.NMV_SomaRadiusScaleFactor + + # Ico-sphere subdivision level option + subdivision_level_row = layout.row() + subdivision_level_row.prop(scene, 'NMV_SubdivisionLevel') + soma_options.subdivision_level = scene.NMV_SubdivisionLevel + + else: + pass + + # Color options + colors_row = layout.row() + colors_row.label(text='Colors & Materials:', icon='COLOR') + + # Soma color + soma_base_color_row = layout.row() + soma_base_color_row.prop(scene, 'NMV_SomaBaseColor') + + # Pass options from UI to system + color = scene.NMV_SomaBaseColor + soma_base_color_value = Vector((color.r, color.g, color.b)) + nmv.interface.ui_options.shading.soma_color = soma_base_color_value + + # Soma material option + soma_material_row = layout.row() + soma_material_row.prop(scene, 'NMV_SomaMaterial') + nmv.interface.ui_options.shading.soma_material = scene.NMV_SomaMaterial + + # Soma reconstruction options + soma_reconstruction_row = layout.row() + soma_reconstruction_row.label(text='Quick Reconstruction:', icon='META_DATA') + + # Soma reconstruction button + soma_reconstruction_buttons_row = layout.row(align=True) + soma_reconstruction_buttons_row.operator('nmv.reconstruct_soma', icon='FORCE_LENNARDJONES') + + # Progress + if scene.NMV_SomaReconstructionMethod == \ + nmv.enums.Soma.Representation.SOFT_BODY: + + # Soma simulation progress bar + soma_simulation_progress_row = layout.row() + soma_simulation_progress_row.prop(scene, 'NMV_SomaSimulationProgress') + soma_simulation_progress_row.enabled = False + + # Report the stats + global is_soma_reconstructed + if is_soma_reconstructed: + soma_stats_row = layout.row() + soma_stats_row.label(text='Stats:', icon='RECOVER_LAST') + + reconstruction_time_row = layout.row() + reconstruction_time_row.prop(scene, 'NMV_SomaReconstructionTime') + reconstruction_time_row.enabled = False + + # Soma rendering options + quick_rendering_row = layout.row() + quick_rendering_row.label(text='Quick Rendering Options:', icon='RENDER_STILL') + + # Soma frame resolution option + frame_resolution_row = layout.row() + frame_resolution_row.label(text='Frame Resolution:') + frame_resolution_row.prop(scene, 'NMV_SomaFrameResolution') + + # Soma view dimensions in micron option + view_dimensions_row = layout.row() + view_dimensions_row.label(text='View Dimensions:') + view_dimensions_row.prop(scene, 'NMV_ViewDimensions') + view_dimensions_row.enabled = False + + # Image extension + image_extension_row = layout.row() + image_extension_row.label(text='Image Format:') + image_extension_row.prop(scene, 'NMV_SomaImageFormat') + nmv.interface.ui_options.soma.image_format = scene.NMV_SomaImageFormat + + # Render view buttons + render_view_row = layout.row() + render_view_row.label(text='Render View:', icon='RESTRICT_RENDER_OFF') + render_view_buttons_row = layout.row(align=True) + render_view_buttons_row.operator('nmv.render_soma_front', icon='AXIS_FRONT') + render_view_buttons_row.operator('nmv.render_soma_side', icon='AXIS_SIDE') + render_view_buttons_row.operator('nmv.render_soma_top', icon='AXIS_TOP') + render_view_buttons_row.enabled = False + + # Soma render animation buttons + render_animation_row = layout.row() + render_animation_row.label(text='Render Animation:', icon='CAMERA_DATA') + render_animations_buttons_row = layout.row(align=True) + render_animations_buttons_row.operator('nmv.render_soma_360', icon='FORCE_MAGNETIC') + + # Progressive rendering is only for the soft body physics + if bpy.context.scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.SOFT_BODY: + render_animations_buttons_row.operator('nmv.render_soma_progressive', + icon='FORCE_HARMONIC') + + # Soma rendering progress bar + soma_rendering_progress_row = layout.row() + soma_rendering_progress_row.prop(scene, 'NMV_SomaRenderingProgress') + soma_rendering_progress_row.enabled = False + + # Saving somata parameters + save_soma_mesh_row = layout.row() + save_soma_mesh_row.label(text='Save Soma Mesh As:', icon='MESH_UVSPHERE') + + # Saving somata buttons + save_soma_mesh_buttons_column = layout.column(align=True) + save_soma_mesh_buttons_column.operator('nmv.save_soma_mesh_obj', icon='MESH_DATA') + save_soma_mesh_buttons_column.operator('nmv.save_soma_mesh_ply', icon='GROUP_VERTEX') + save_soma_mesh_buttons_column.operator('nmv.save_soma_mesh_stl', icon='MESH_DATA') + save_soma_mesh_buttons_column.operator('nmv.save_soma_mesh_blend', icon='OUTLINER_OB_META') + + # If the reconstructed soma is not available in the scene, then deactivate these buttons + # NOTE: To activate the rendering and saving buttons in the soma panel, the reconstructed + # soma mesh must exist in the scene, otherwise the rendered image and the saved meshes + # will contain invalid data. To verify whether the soma is reconstructed or not, we search + # for the soma mesh by name and accordingly activate or deactivate the buttons. + + # Ensure that the morphology is loaded to get its label + if nmv.interface.ui_options.morphology.label is not None: + + # Does the soma mesh exist in the scene, then activate the buttons + if nmv.scene.ops.is_object_in_scene_by_name(nmv.consts.Skeleton.SOMA_PREFIX): + save_soma_mesh_buttons_column.enabled = True + view_dimensions_row.enabled = True + frame_resolution_row.enabled = True + render_view_buttons_row.enabled = True + render_animations_buttons_row.enabled = True + + # The soma mesh is not in the scene, then deactivate the buttons + else: + save_soma_mesh_buttons_column.enabled = False + view_dimensions_row.enabled = False + frame_resolution_row.enabled = False + render_view_buttons_row.enabled = False + render_animations_buttons_row.enabled = False + + # No morphology is loaded, then deactivate the buttons + else: + save_soma_mesh_buttons_column.enabled = False + view_dimensions_row.enabled = False + frame_resolution_row.enabled = False + render_view_buttons_row.enabled = False + render_animations_buttons_row.enabled = False + + # Enable or disable the layout + nmv.interface.enable_or_disable_layout(layout) + + + + diff --git a/nmv/interface/ui/soma/soma_panel_options.py b/nmv/interface/ui/soma/panel_props.py similarity index 91% rename from nmv/interface/ui/soma/soma_panel_options.py rename to nmv/interface/ui/soma/panel_props.py index dd1df52e8..ded2a85cb 100644 --- a/nmv/interface/ui/soma/soma_panel_options.py +++ b/nmv/interface/ui/soma/panel_props.py @@ -61,19 +61,27 @@ # Profile bpy.types.Scene.NMV_SomaProfile = bpy.props.EnumProperty( items=[(nmv.enums.Soma.Profile.ARBORS_ONLY, - '3D Profile', + 'Arbor Points', 'Reconstruct the shape of the soma using the initial samples of the arbors only'), (nmv.enums.Soma.Profile.PROFILE_POINTS_ONLY, - '2D Profile', + 'Profile Points', 'Reconstruct the shape of the soma using the reported profile points only. ' 'If the morphology file does not contain any profile points, the initial sphere will ' 'not be deformed at all'), (nmv.enums.Soma.Profile.COMBINED, - 'Mixed', + 'Combined', 'Reconstruct a complex shape for the soma using all available data')], name='Profile', default=nmv.enums.Soma.Profile.ARBORS_ONLY) +# Profile of arbors only if no profile points were available +bpy.types.Scene.NMV_SomaArborsOnlyProfile = bpy.props.EnumProperty( + items=[(nmv.enums.Soma.Profile.ARBORS_ONLY, + 'Arbor Points', + 'Reconstruct the shape of the soma using the initial samples of the arbors only')], + name='Profile', + default=nmv.enums.Soma.Profile.ARBORS_ONLY) + # Reconstruction time bpy.types.Scene.NMV_SomaReconstructionTime = bpy.props.FloatProperty( name='Generation Time (Sec)', @@ -129,7 +137,7 @@ # Soma simulation progress bar bpy.types.Scene.NMV_SomaSimulationProgress = bpy.props.IntProperty( - name='Physics Simulation Progress', + name='Simulation Progress', description='Reconstruction progress', default=0, min=0, max=100, subtype='PERCENTAGE') @@ -162,4 +170,9 @@ bpy.types.Scene.NMV_SomaRenderingTime = bpy.props.FloatProperty( name='Rendering (Sec)', description='The time it takes to render the soma into an image', - default=0, min=0, max=1000000) \ No newline at end of file + default=0, min=0, max=1000000) + +# Exported mesh file formats +bpy.types.Scene.NMV_ExportedSomaMeshFormat = bpy.props.EnumProperty( + items=nmv.enums.Meshing.ExportFormat.FORMATS_ITEMS, + name='Format', default=nmv.enums.Meshing.ExportFormat.OBJ) diff --git a/nmv/interface/ui/soma/registration.py b/nmv/interface/ui/soma/registration.py new file mode 100644 index 000000000..a13b058fa --- /dev/null +++ b/nmv/interface/ui/soma/registration.py @@ -0,0 +1,80 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @register_panel +#################################################################################################### +def register_panel(): + """Registers all the classes in this panel""" + + from .panel import NMV_SomaPanel + from .ops_documentation import SomaReconstructionDocumentation + from .ops_reconstruction import NMV_ReconstructSoma + + from .ops_render_view import NMV_RenderSomaFront + from .ops_render_view import NMV_RenderSomaSide + from .ops_render_view import NMV_RenderSomaTop + from .ops_render_360 import NMV_RenderSoma360 + from .ops_render_progressive import NMV_RenderSomaProgressive + from .ops_export import NMV_ExportSomaMesh + + # Panel + bpy.utils.register_class(NMV_SomaPanel) + + # Buttons + bpy.utils.register_class(SomaReconstructionDocumentation) + bpy.utils.register_class(NMV_ReconstructSoma) + bpy.utils.register_class(NMV_RenderSomaFront) + bpy.utils.register_class(NMV_RenderSomaSide) + bpy.utils.register_class(NMV_RenderSomaTop) + bpy.utils.register_class(NMV_RenderSoma360) + bpy.utils.register_class(NMV_RenderSomaProgressive) + bpy.utils.register_class(NMV_ExportSomaMesh) + + +#################################################################################################### +# @unregister_panel +#################################################################################################### +def unregister_panel(): + """Un-registers all the classes in this panel""" + + from .panel import NMV_SomaPanel + from .ops_documentation import SomaReconstructionDocumentation + from .ops_reconstruction import NMV_ReconstructSoma + from .ops_render_view import NMV_RenderSomaFront + from .ops_render_view import NMV_RenderSomaSide + from .ops_render_view import NMV_RenderSomaTop + from .ops_render_360 import NMV_RenderSoma360 + from .ops_render_progressive import NMV_RenderSomaProgressive + from .ops_export import NMV_ExportSomaMesh + + # Panel + bpy.utils.unregister_class(NMV_SomaPanel) + + # Buttons + bpy.utils.unregister_class(SomaReconstructionDocumentation) + bpy.utils.unregister_class(NMV_ReconstructSoma) + bpy.utils.unregister_class(NMV_RenderSomaFront) + bpy.utils.unregister_class(NMV_RenderSomaSide) + bpy.utils.unregister_class(NMV_RenderSomaTop) + bpy.utils.unregister_class(NMV_RenderSoma360) + bpy.utils.unregister_class(NMV_RenderSomaProgressive) + bpy.utils.unregister_class(NMV_ExportSomaMesh) diff --git a/nmv/interface/ui/soma/soma_panel.py b/nmv/interface/ui/soma/soma_panel.py deleted file mode 100644 index 3069b9d9f..000000000 --- a/nmv/interface/ui/soma/soma_panel.py +++ /dev/null @@ -1,1206 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# System imports -import time - -# Blender imports -import bpy -from mathutils import Vector - -# Internal modules -import nmv.builders -import nmv.consts -import nmv.enums -import nmv.file -import nmv.interface -import nmv.mesh -import nmv.rendering -import nmv.scene -import nmv.utilities -from .soma_panel_options import * - -# A global flag to indicate if the soma is reconstructed or not -is_soma_reconstructed = False - - -#################################################################################################### -# @SomaPanel -#################################################################################################### -class SomaPanel(bpy.types.Panel): - """Soma panel""" - - ################################################################################################ - # Panel parameters - ################################################################################################ - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' if nmv.utilities.is_blender_280() else 'TOOLS' - bl_idname = "OBJECT_PT_NMV_SomaToolBox" - bl_label = 'Soma Toolbox' - bl_category = 'NeuroMorphoVis' - bl_options = {'DEFAULT_CLOSED'} - - ################################################################################################ - # @draw - ################################################################################################ - def draw(self, context): - """Draws the panel - - :param context: - Panel context. - """ - - # Get a reference to the scene - scene = context.scene - - # Get a reference to the panel layout - layout = self.layout - - # Documentation button - documentation_button = layout.column() - documentation_button.operator('nmv.documentation_soma', icon='URL') - documentation_button.separator() - - # Get a reference to the soma options - soma_options = nmv.interface.ui_options.soma - - reconstruction_method_row = layout.row() - reconstruction_method_row.prop(scene, 'NMV_SomaReconstructionMethod') - - if scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.META_BALLS: - reconstruction_method_row = layout.row() - reconstruction_method_row.prop(scene, 'NMV_SomaMetaBallResolution') - soma_options.meta_ball_resolution = scene.NMV_SomaMetaBallResolution - - elif scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.SOFT_BODY or \ - scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.HYBRID: - - reconstruction_method_row = layout.row() - reconstruction_method_row.prop(scene, 'NMV_SomaProfile') - soma_options.method = scene.NMV_SomaProfile - - # Soft body options - soft_body_params_row = layout.row() - soft_body_params_row.label(text='Soft Body Parameters:', icon='GROUP_UVS') - - # Simulation steps - simulation_steps_row = layout.row() - simulation_steps_row.prop(scene, 'NMV_SimulationSteps') - soma_options.simulation_steps = scene.NMV_SimulationSteps - - # Soft body stiffness option - stiffness_row = layout.row() - stiffness_row.prop(scene, 'NMV_Stiffness') - soma_options.stiffness = scene.NMV_Stiffness - - # Radius scale factor - radius_scale_factor_row = layout.row() - radius_scale_factor_row.prop(scene, 'NMV_SomaRadiusScaleFactor') - soma_options.radius_scale_factor = scene.NMV_SomaRadiusScaleFactor - - # Ico-sphere subdivision level option - subdivision_level_row = layout.row() - subdivision_level_row.prop(scene, 'NMV_SubdivisionLevel') - soma_options.subdivision_level = scene.NMV_SubdivisionLevel - - else: - pass - - # Color options - colors_row = layout.row() - colors_row.label(text='Colors & Materials:', icon='COLOR') - - # Soma color - soma_base_color_row = layout.row() - soma_base_color_row.prop(scene, 'NMV_SomaBaseColor') - - # Pass options from UI to system - color = scene.NMV_SomaBaseColor - soma_base_color_value = Vector((color.r, color.g, color.b)) - nmv.interface.ui_options.shading.soma_color = soma_base_color_value - - # Soma material option - soma_material_row = layout.row() - soma_material_row.prop(scene, 'NMV_SomaMaterial') - nmv.interface.ui_options.shading.soma_material = scene.NMV_SomaMaterial - - # Soma reconstruction options - soma_reconstruction_row = layout.row() - soma_reconstruction_row.label(text='Quick Reconstruction:', icon='META_DATA') - - # Soma reconstruction button - soma_reconstruction_buttons_row = layout.row(align=True) - soma_reconstruction_buttons_row.operator('nmv.reconstruct_soma', icon='FORCE_LENNARDJONES') - - # Progress - if scene.NMV_SomaReconstructionMethod == \ - nmv.enums.Soma.Representation.SOFT_BODY: - - # Soma simulation progress bar - soma_simulation_progress_row = layout.row() - soma_simulation_progress_row.prop(scene, 'NMV_SomaSimulationProgress') - soma_simulation_progress_row.enabled = False - - # Report the stats - global is_soma_reconstructed - if is_soma_reconstructed: - soma_stats_row = layout.row() - soma_stats_row.label(text='Stats:', icon='RECOVER_LAST') - - reconstruction_time_row = layout.row() - reconstruction_time_row.prop(scene, 'NMV_SomaReconstructionTime') - reconstruction_time_row.enabled = False - - # Soma rendering options - quick_rendering_row = layout.row() - quick_rendering_row.label(text='Quick Rendering Options:', icon='RENDER_STILL') - - # Soma frame resolution option - frame_resolution_row = layout.row() - frame_resolution_row.label(text='Frame Resolution:') - frame_resolution_row.prop(scene, 'NMV_SomaFrameResolution') - - # Soma view dimensions in micron option - view_dimensions_row = layout.row() - view_dimensions_row.label(text='View Dimensions:') - view_dimensions_row.prop(scene, 'NMV_ViewDimensions') - view_dimensions_row.enabled = False - - # Image extension - image_extension_row = layout.row() - image_extension_row.label(text='Image Format:') - image_extension_row.prop(scene, 'NMV_SomaImageFormat') - nmv.interface.ui_options.soma.image_format = scene.NMV_SomaImageFormat - - # Render view buttons - render_view_row = layout.row() - render_view_row.label(text='Render View:', icon='RESTRICT_RENDER_OFF') - render_view_buttons_row = layout.row(align=True) - render_view_buttons_row.operator('nmv.render_soma_front', icon='AXIS_FRONT') - render_view_buttons_row.operator('nmv.render_soma_side', icon='AXIS_SIDE') - render_view_buttons_row.operator('nmv.render_soma_top', icon='AXIS_TOP') - render_view_buttons_row.enabled = False - - # Soma render animation buttons - render_animation_row = layout.row() - render_animation_row.label(text='Render Animation:', icon='CAMERA_DATA') - render_animations_buttons_row = layout.row(align=True) - render_animations_buttons_row.operator('nmv.render_soma_360', icon='FORCE_MAGNETIC') - - # Progressive rendering is only for the soft body physics - if bpy.context.scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.SOFT_BODY: - render_animations_buttons_row.operator('nmv.render_soma_progressive', - icon='FORCE_HARMONIC') - - # Soma rendering progress bar - soma_rendering_progress_row = layout.row() - soma_rendering_progress_row.prop(scene, 'NMV_SomaRenderingProgress') - soma_rendering_progress_row.enabled = False - - # Saving somata parameters - save_soma_mesh_row = layout.row() - save_soma_mesh_row.label(text='Save Soma Mesh As:', icon='MESH_UVSPHERE') - - # Saving somata buttons - save_soma_mesh_buttons_column = layout.column(align=True) - save_soma_mesh_buttons_column.operator('nmv.save_soma_mesh_obj', icon='MESH_DATA') - save_soma_mesh_buttons_column.operator('nmv.save_soma_mesh_ply', icon='GROUP_VERTEX') - save_soma_mesh_buttons_column.operator('nmv.save_soma_mesh_stl', icon='MESH_DATA') - save_soma_mesh_buttons_column.operator('nmv.save_soma_mesh_blend', icon='OUTLINER_OB_META') - - # If the reconstructed soma is not available in the scene, then deactivate these buttons - # NOTE: To activate the rendering and saving buttons in the soma panel, the reconstructed - # soma mesh must exist in the scene, otherwise the rendered image and the saved meshes - # will contain invalid data. To verify whether the soma is reconstructed or not, we search - # for the soma mesh by name and accordingly activate or deactivate the buttons. - - # Ensure that the morphology is loaded to get its label - if nmv.interface.ui_options.morphology.label is not None: - - # Does the soma mesh exist in the scene, then activate the buttons - if nmv.scene.ops.is_object_in_scene_by_name(nmv.consts.Skeleton.SOMA_PREFIX): - save_soma_mesh_buttons_column.enabled = True - view_dimensions_row.enabled = True - frame_resolution_row.enabled = True - render_view_buttons_row.enabled = True - render_animations_buttons_row.enabled = True - - # The soma mesh is not in the scene, then deactivate the buttons - else: - save_soma_mesh_buttons_column.enabled = False - view_dimensions_row.enabled = False - frame_resolution_row.enabled = False - render_view_buttons_row.enabled = False - render_animations_buttons_row.enabled = False - - # No morphology is loaded, then deactivate the buttons - else: - save_soma_mesh_buttons_column.enabled = False - view_dimensions_row.enabled = False - frame_resolution_row.enabled = False - render_view_buttons_row.enabled = False - render_animations_buttons_row.enabled = False - - # Enable or disable the layout - nmv.interface.enable_or_disable_layout(layout) - - -#################################################################################################### -# @ReconstructSoma -#################################################################################################### -class ReconstructSomaOperator(bpy.types.Operator): - """Soma reconstruction operator""" - - # Operator parameters - bl_idname = "nmv.reconstruct_soma" - bl_label = "Reconstruct Soma" - - # Timer parameters - event_timer = None - timer_limits = 0 - - # Builder parameters - soma_builder = None - soma_sphere_object = None - - # Reconstruction time - reconstruction_time = 0 - - ################################################################################################ - # @modal - ################################################################################################ - def modal(self, - context, - event): - """Threading and non-blocking handling. - - :param context: - Panel context. - :param event: - A given event for the panel. - """ - - # Get a reference to the scene - scene = context.scene - - # Cancelling event, if using right click or exceeding the time limit of the simulation - if event.type in {'RIGHTMOUSE', 'ESC'} or \ - self.timer_limits > scene.NMV_SimulationSteps: - - # Set the reconstruction flag to on - global is_soma_reconstructed - is_soma_reconstructed = True - - # Get the reconstruction time to update the UI - scene.NMV_SomaReconstructionTime = time.time() - self.reconstruction_time - nmv.logger.info_done('Soma reconstructed in [%f] seconds' % - scene.NMV_MorphologyLoadingTime) - - # Reset the timer limits - self.timer_limits = 0 - - # Refresh the panel context - self.cancel(context) - - # Finished - return {'FINISHED'} - - # Timer event, where the function is executed here on a per-frame basis - if event.type == 'TIMER': - - # Update the frame based on the soft body simulation - bpy.context.scene.frame_set(self.timer_limits) - - # Update the progress shell - nmv.utilities.show_progress( - 'Simulation', self.timer_limits, scene.NMV_SimulationSteps) - - # Update the progress bar - scene.NMV_SomaSimulationProgress = \ - int(100.0 * self.timer_limits / scene.NMV_SimulationSteps) - - # Upgrade the timer limits - self.timer_limits += 1 - - # View all the objects in the scene - nmv.scene.ops.view_all_scene() - - # Next frame - return {'PASS_THROUGH'} - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Panel context. - """ - - # Get a reference to the scene - scene = context.scene - - # Reset the scene - nmv.scene.reset_scene() - - # Clear the scene - nmv.scene.ops.clear_scene() - - # Load the morphology file - loading_result = nmv.interface.ui.load_morphology(self, scene) - - # If the result is None, report the issue - if loading_result is None: - self.report({'ERROR'}, 'Please select a morphology file') - return {'FINISHED'} - - # Reconstruction time - self.reconstruction_time = time.time() - - # Set the reconstruction flag to on - global is_soma_reconstructed - - # MetaBall reconstruction - if bpy.context.scene.NMV_SomaReconstructionMethod == \ - nmv.enums.Soma.Representation.META_BALLS: - - # Create a some builder - self.soma_builder = nmv.builders.SomaMetaBuilder( - nmv.interface.ui_morphology, nmv.interface.ui_options) - - # Reconstruct the soma in a single step - nmv.interface.ui_soma_mesh = self.soma_builder.reconstruct_soma_mesh() - - # Get the reconstruction time to update the UI - self.reconstruction_time = time.time() - self.reconstruction_time - scene.NMV_SomaReconstructionTime = self.reconstruction_time - nmv.logger.info_done('Soma reconstructed in [%f] seconds' % - scene.NMV_MorphologyLoadingTime) - - # Set the reconstruction flag to on - is_soma_reconstructed = True - - # View all the objects in the scene - nmv.scene.ops.view_all_scene() - - # Finished - return {'FINISHED'} - - # MetaBall reconstruction - elif bpy.context.scene.NMV_SomaReconstructionMethod == nmv.enums.Soma.Representation.HYBRID: - - # Create a some builder - self.soma_builder = nmv.builders.SomaHybridBuilder( - nmv.interface.ui_morphology, nmv.interface.ui_options) - - # Reconstruct the soma in a single step - nmv.interface.ui_soma_mesh = self.soma_builder.reconstruct_soma_mesh() - - # Get the reconstruction time to update the UI - self.reconstruction_time = time.time() - self.reconstruction_time - scene.NMV_SomaReconstructionTime = self.reconstruction_time - nmv.logger.info_done('Soma reconstructed in [%f] seconds' % - scene.NMV_MorphologyLoadingTime) - - is_soma_reconstructed = True - - # View all the objects in the scene - nmv.scene.ops.view_all_scene() - - # Finished - return {'FINISHED'} - - # Softbody reconstruction - else: - - # SoftBody reconstruction - self.soma_builder = nmv.builders.SomaSoftBodyBuilder( - nmv.interface.ui_morphology, nmv.interface.ui_options) - - # Build the basic profile of the some from the soft body operation, but don't run the - # simulation now. Run the simulation in the '@modal' mode, to avoid freezing the UI - if bpy.context.scene.NMV_SomaProfile == \ - nmv.enums.Soma.Profile.ARBORS_ONLY: - self.soma_sphere_object = self.soma_builder.build_soma_soft_body() - elif bpy.context.scene.NMV_SomaProfile == \ - nmv.enums.Soma.Profile.PROFILE_POINTS_ONLY: - self.soma_sphere_object = \ - self.soma_builder.build_soma_based_on_profile_points_only() - elif bpy.context.scene.NMV_SomaProfile == \ - nmv.enums.Soma.Profile.COMBINED: - self.soma_sphere_object = self.soma_builder.build_soma_soft_body( - use_profile_points=True) - else: - self.soma_sphere_object = self.soma_builder.build_soma_soft_body() - - # Use the event timer to update the UI during the soma building - wm = context.window_manager - self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) - wm.modal_handler_add(self) - - # View all the objects in the scene - nmv.scene.ops.view_all_scene() - - # Modal - return {'RUNNING_MODAL'} - - ################################################################################################ - # @cancel - ################################################################################################ - def cancel(self, context): - """Cancel the panel processing and return to the interaction mode. - - :param context: - Panel context. - """ - - # Get a reference to the scene - scene = context.scene - - # Multi-threading - wm = context.window_manager - wm.event_timer_remove(self.event_timer) - - # Build the mesh from the soft body object - self.soma_sphere_object = self.soma_builder.build_soma_mesh_from_soft_body_object( - self.soma_sphere_object) - - # Keep a reference to the mesh object in case we need to save or texture it - nmv.interface.ui_soma_mesh = self.soma_sphere_object - - # Show the progress, Done - nmv.utilities.show_progress( - 'Simulation', self.timer_limits, scene.NMV_SimulationSteps, done=True) - - if bpy.context.scene.NMV_SomaProfile == \ - nmv.enums.Soma.Profile.PROFILE_POINTS_ONLY: - - # Decimate the mesh using 25% - nmv.logger.info('Decimation') - nmv.mesh.ops.decimate_mesh_object(self.soma_sphere_object, decimation_ratio=0.25) - - # Smooth the mesh again to look nice - nmv.logger.info('Smoothing') - nmv.mesh.ops.smooth_object(self.soma_sphere_object, level=2) - - # Report the process termination in the UI - self.report({'INFO'}, 'Soma Reconstruction Done') - - -#################################################################################################### -# @RenderSomaFront -#################################################################################################### -class RenderSomaFront(bpy.types.Operator): - """Rendering front view of the soma operator""" - - # Operator parameters - bl_idname = "nmv.render_soma_front" - bl_label = "Front" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Execute the operator. - - :param context: - Context. - :return: - 'FINISHED' - """ - - # Get a reference to the scene - scene = context.scene - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the images directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.images_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.images_directory) - - # Render the soma - nmv.rendering.SomaRenderer.render( - view_extent=scene.NMV_ViewDimensions, - camera_view=nmv.enums.Camera.View.FRONT, - image_resolution=scene.NMV_SomaFrameResolution, - image_name='%s%s' % (nmv.interface.ui_options.morphology.label, - nmv.consts.Suffix.SOMA_FRONT), - image_format=nmv.interface.ui_options.soma.image_format, - image_directory=nmv.interface.ui_options.io.images_directory) - - # Report the process termination in the UI - self.report({'INFO'}, 'Rendering Soma Done') - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderSomaSide -#################################################################################################### -class RenderSomaSide(bpy.types.Operator): - """Render side view of the reconstructed soma""" - - # Operator parameters - bl_idname = "nmv.render_soma_side" - bl_label = "Side" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Execute the operator. - - :param context: - Context. - :return: - 'FINISHED' - """ - - # Get a reference to the scene - scene = context.scene - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the images directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.images_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.images_directory) - - # Render the soma - nmv.rendering.SomaRenderer.render( - view_extent=scene.NMV_ViewDimensions, - camera_view=nmv.enums.Camera.View.SIDE, - image_resolution=scene.NMV_SomaFrameResolution, - image_name='%s%s' % (nmv.interface.ui_options.morphology.label, - nmv.consts.Suffix.SOMA_SIDE), - image_format=nmv.interface.ui_options.soma.image_format, - image_directory=nmv.interface.ui_options.io.images_directory) - - # Report the process termination in the UI - self.report({'INFO'}, 'Soma Reconstruction Done') - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderSomaTop -#################################################################################################### -class RenderSomaTop(bpy.types.Operator): - """Render top view of the reconstructed soma""" - - # Operator parameters - bl_idname = "nmv.render_soma_top" - bl_label = "Top" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, context): - """Execute the operator. - - :param context: - Context. - :return: - 'FINISHED' - """ - - # Get a reference to the scene - scene = context.scene - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the images directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.images_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.images_directory) - - # Render the soma - nmv.rendering.SomaRenderer.render( - view_extent=scene.NMV_ViewDimensions, - camera_view=nmv.enums.Camera.View.TOP, - image_resolution=scene.NMV_SomaFrameResolution, - image_name='%s%s' % (nmv.interface.ui_options.morphology.label, - nmv.consts.Suffix.SOMA_TOP), - image_format=nmv.interface.ui_options.soma.image_format, - image_directory=nmv.interface.ui_options.io.images_directory) - - # Report the process termination in the UI - self.report({'INFO'}, 'Soma Reconstruction Done') - - # Confirm operation done - return {'FINISHED'} - - -#################################################################################################### -# @RenderSoma360 -#################################################################################################### -class RenderSoma360(bpy.types.Operator): - """Render 360 movie of the soma reconstruction process""" - - # Operator parameters - bl_idname = "nmv.render_soma_360" - bl_label = "360" - - # Timer parameters - event_timer = None - timer_limits = 0 - - # Output data - output_directory = None - - ################################################################################################ - # @modal - ################################################################################################ - def modal(self, - context, - event): - """ - Threading and non-blocking handling. - - :param context: - Panel context. - :param event: - A given event for the panel. - """ - - # Get a reference to the scene - scene = context.scene - - # Cancelling event, if using right click or exceeding the time limit of the simulation - if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > 360: - - # Reset the timer limits - self.timer_limits = 0 - - # Refresh the panel context - self.cancel(context) - - # Finished - return {'FINISHED'} - - # Timer event, where the function is executed here on a per-frame basis - if event.type == 'TIMER': - - # Set the frame name - image_name = '%s/%s' % ( - self.output_directory, '{0:05d}'.format(self.timer_limits)) - - # Render a frame - nmv.rendering.SomaRenderer.render_at_angle( - soma_mesh=nmv.interface.ui_soma_mesh, - angle=self.timer_limits, - view_extent=scene.NMV_ViewDimensions, - camera_view=nmv.enums.Camera.View.FRONT, - image_resolution=scene.NMV_SomaFrameResolution, - image_name=image_name) - - # Update the progress shell - nmv.utilities.show_progress('Rendering', self.timer_limits, 360) - - # Update the progress bar - scene.NMV_SomaRenderingProgress = int(100 * self.timer_limits / 360.0) - - # Upgrade the timer limits - self.timer_limits += 1 - - # Next frame - return {'PASS_THROUGH'} - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Panel context. - """ - - # Get a reference to the scene - scene = context.scene - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the sequences directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.sequences_directory) - - # Create a specific directory for this soma mesh - self.output_directory = '%s/%s%s' % \ - (nmv.interface.ui_options.io.sequences_directory, - nmv.interface.ui_options.morphology.label, - nmv.consts.Suffix.SOMA_360) - nmv.file.ops.clean_and_create_directory(self.output_directory) - - # Use the event timer to update the UI during the soma building - wm = context.window_manager - self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) - wm.modal_handler_add(self) - - # Modal - return {'RUNNING_MODAL'} - - ################################################################################################ - # @cancel - ################################################################################################ - def cancel(self, context): - """ - Cancel the panel processing and return to the interaction mode. - - :param context: - Panel context. - """ - - # Multi-threading - wm = context.window_manager - wm.event_timer_remove(self.event_timer) - - # Report the process termination in the UI - self.report({'INFO'}, 'Soma Rendering Done') - - # Finished - return {'FINISHED'} - - -#################################################################################################### -# @RenderSomaProgressive -#################################################################################################### -class RenderSomaProgressive(bpy.types.Operator): - """Render progressive soma reconstruction""" - - # Operator parameters - bl_idname = "nmv.render_soma_progressive" - bl_label = "Progressive" - - # Timer parameters - event_timer = None - timer_limits = 0 - - # Output data - output_directory = None - - # Morphology parameters - morphology_object = None - - # Soma builder parameters - soma_builder = None - soma_sphere_object = None - - ################################################################################################ - # @modal - ################################################################################################ - def modal(self, - context, - event): - """ - Threading and non-blocking handling. - - :param context: - Panel context. - :param event: - A given event for the panel. - """ - - # Get a reference to the scene - scene = context.scene - - # Cancelling event, if using right click or exceeding the time limit of the simulation - if event.type in {'RIGHTMOUSE', 'ESC'} or self.timer_limits > scene.NMV_SimulationSteps: - - # Reset the timer limits - self.timer_limits = 0 - - # Refresh the panel context - self.cancel(context) - - # Finished - return {'FINISHED'} - - # Timer event, where the function is executed here on a per-frame basis - if event.type == 'TIMER': - - # Update the frame based on the soft body simulation - bpy.context.scene.frame_set(self.timer_limits) - - # Set the frame name - image_name = '%s/%s' % ( - self.output_directory, '{0:05d}'.format(self.timer_limits)) - - # Render a frame - nmv.rendering.SomaRenderer.render_at_angle( - soma_mesh=self.soma_sphere_object, - angle=0.0, - view_extent=scene.NMV_ViewDimensions, - camera_view=nmv.enums.Camera.View.FRONT, - image_resolution=scene.NMV_SomaFrameResolution, - image_name=image_name) - - # Update the progress shell - nmv.utilities.show_progress('Rendering', - self.timer_limits, scene.NMV_SimulationSteps) - - # Update the progress bar - scene.NMV_SomaRenderingProgress = self.timer_limits - - # Upgrade the timer limits - self.timer_limits += 1 - - # Next frame - return {'PASS_THROUGH'} - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Panel context. - """ - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Clear the scene - nmv.scene.ops.clear_scene() - - # Create the sequences directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.sequences_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.sequences_directory) - - # Create a specific directory for this mesh - self.output_directory = '%s/%s%s' % ( - nmv.interface.ui_options.io.sequences_directory, - nmv.interface.ui_options.morphology.label, - nmv.consts.Suffix.SOMA_PROGRESSIVE) - nmv.file.ops.clean_and_create_directory(self.output_directory) - - # Load the morphology file - loading_result = nmv.interface.ui.load_morphology(self, context.scene) - - # If the result is None, report the issue - if loading_result is None: - self.report({'ERROR'}, 'Please select a morphology file') - return {'FINISHED'} - - # Create a some builder object - self.soma_builder = nmv.builders.SomaSoftBodyBuilder( - nmv.interface.ui_morphology, nmv.interface.ui_options) - - # Build the basic profile of the some from the soft body operation, but don't run the - # simulation now. Run the simulation in the '@modal' mode, to avoid freezing the UI - self.soma_sphere_object = self.soma_builder.build_soma_soft_body() - - # Use the event timer to update the UI during the soma building - wm = context.window_manager - self.event_timer = wm.event_timer_add(time_step=0.01, window=context.window) - wm.modal_handler_add(self) - - # Modal - return {'RUNNING_MODAL'} - - ################################################################################################ - # @cancel - ################################################################################################ - def cancel(self, context): - """Cancel the panel processing and return to the interaction mode. - - :param context: - Panel context. - """ - - # Multi-threading - wm = context.window_manager - wm.event_timer_remove(self.event_timer) - - # Build the mesh from the soft body object - self.soma_sphere_object = self.soma_builder.build_soma_mesh_from_soft_body_object( - self.soma_sphere_object) - - # Keep a reference to the mesh object in case we need to save or texture it - nmv.interface.ui_soma_mesh = self.soma_sphere_object - - # Show the progress, Done - nmv.utilities.show_progress( - 'Rendering', self.timer_limits, context.scene.NMV_SimulationSteps, done=True) - - # Report the process termination in the UI - self.report({'INFO'}, 'Soma Rendering Done') - - # Delete the camera - # nmv.scene.ops.delete_list_objects([self.soma_sphere_object]) - - -#################################################################################################### -# @SaveSomaMeshOBJ -#################################################################################################### -class SaveSomaMeshOBJ(bpy.types.Operator): - """Save the soma in OBJ file""" - - # Operator parameters - bl_idname = "nmv.save_soma_mesh_obj" - bl_label = "Wavefront (.obj)" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """ - Executes the operator. - - :param context: - Operator context. - :return: - {'FINISHED'} - """ - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the meshes directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.meshes_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.meshes_directory) - - # Export the selected soma mesh as an .obj file - nmv.file.export_object_to_obj_file( - mesh_object=nmv.interface.ui_soma_mesh, - output_directory=nmv.interface.ui_options.io.meshes_directory, - file_name='%s%s' % (nmv.interface.ui_morphology.label, nmv.consts.Suffix.SOMA_MESH)) - - # Finished - return {'FINISHED'} - - -#################################################################################################### -# @SaveSomaMeshPLY -#################################################################################################### -class SaveSomaMeshPLY(bpy.types.Operator): - """Save the soma in PLY file""" - - # Operator parameters - bl_idname = "nmv.save_soma_mesh_ply" - bl_label = "Stanford (.ply)" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """ - Executes the operator. - - :param context: - Operator context. - :return: - {'FINISHED'} - """ - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the meshes directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.meshes_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.meshes_directory) - - # Export the selected soma mesh as an .ply file - nmv.file.export_object_to_ply_file( - mesh_object=nmv.interface.ui_soma_mesh, - output_directory=nmv.interface.ui_options.io.meshes_directory, - file_name='%s%s' % (nmv.interface.ui_morphology.label, nmv.consts.Suffix.SOMA_MESH)) - - # Finished - return {'FINISHED'} - - -#################################################################################################### -# @SaveSomaMeshSTL -#################################################################################################### -class SaveSomaMeshSTL(bpy.types.Operator): - """Save the soma in STL file""" - - # Operator parameters - - bl_idname = "nmv.save_soma_mesh_stl" - bl_label = "Stereolithography CAD (.stl)" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """ - Executes the operator. - - :param context: - Operator context. - :return: - {'FINISHED'} - """ - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the meshes directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.meshes_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.meshes_directory) - - # Export the selected soma mesh as an .stl file - nmv.file.export_object_to_stl_file( - mesh_object=nmv.interface.ui_soma_mesh, - output_directory=nmv.interface.ui_options.io.meshes_directory, - file_name='%s%s' % (nmv.interface.ui_morphology.label, nmv.consts.Suffix.SOMA_MESH)) - - # Finished - return {'FINISHED'} - - -#################################################################################################### -# @SaveSomaMeshBlend -#################################################################################################### -class SaveSomaMeshBLEND(bpy.types.Operator): - """Save the soma in a blender file""" - - # Operator parameters - bl_idname = "nmv.save_soma_mesh_blend" - bl_label = "Blender Format (.blend)" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """ - Executes the operator. - - :param context: - Operator context. - :return: - {'FINISHED'} - """ - - # Get a reference to the scene - scene = context.scene - - # Verify the output directory - if not nmv.interface.validate_output_directory(self, context.scene): - return {'FINISHED'} - - # Create the meshes directory if it does not exist - if not nmv.file.ops.path_exists(nmv.interface.ui_options.io.meshes_directory): - nmv.file.ops.clean_and_create_directory(nmv.interface.ui_options.io.meshes_directory) - - # Export the selected soma mesh as a .blend file - nmv.file.export_scene_to_blend_file( - output_directory=nmv.interface.ui_options.io.meshes_directory, - output_file_name='%s%s' % (nmv.interface.ui_morphology.label, - nmv.consts.Suffix.SOMA_MESH)) - - # Finished - return {'FINISHED'} - - -#################################################################################################### -# @SomaReconstructionDocumentation -#################################################################################################### -class SomaReconstructionDocumentation(bpy.types.Operator): - """Open the online documentation page of the Soma Reconstruction panel.""" - - # Operator parameters - bl_idname = "nmv.documentation_soma" - bl_label = "Online User Guide" - - ################################################################################################ - # @execute - ################################################################################################ - def execute(self, - context): - """Execute the operator. - - :param context: - Blender context - :return: - 'FINISHED' - """ - - import webbrowser - webbrowser.open('https://github.com/BlueBrain/NeuroMorphoVis/wiki/Soma-Reconstruction') - return {'FINISHED'} - - -#################################################################################################### -# @register_panel -#################################################################################################### -def register_panel(): - """Registers all the classes in this panel. - """ - - # Soma reconstruction panel - bpy.utils.register_class(SomaPanel) - - # Buttons - bpy.utils.register_class(SomaReconstructionDocumentation) - bpy.utils.register_class(ReconstructSomaOperator) - bpy.utils.register_class(RenderSomaFront) - bpy.utils.register_class(RenderSomaSide) - bpy.utils.register_class(RenderSomaTop) - bpy.utils.register_class(RenderSoma360) - bpy.utils.register_class(RenderSomaProgressive) - bpy.utils.register_class(SaveSomaMeshOBJ) - bpy.utils.register_class(SaveSomaMeshPLY) - bpy.utils.register_class(SaveSomaMeshSTL) - bpy.utils.register_class(SaveSomaMeshBLEND) - - -#################################################################################################### -# @unregister_panel -#################################################################################################### -def unregister_panel(): - """Un-registers all the classes in this panel. - """ - - # Soma reconstruction panel - bpy.utils.unregister_class(SomaPanel) - - # Buttons - bpy.utils.unregister_class(SomaReconstructionDocumentation) - bpy.utils.unregister_class(ReconstructSomaOperator) - bpy.utils.unregister_class(RenderSomaFront) - bpy.utils.unregister_class(RenderSomaSide) - bpy.utils.unregister_class(RenderSomaTop) - bpy.utils.unregister_class(RenderSoma360) - bpy.utils.unregister_class(RenderSomaProgressive) - bpy.utils.unregister_class(SaveSomaMeshOBJ) - bpy.utils.unregister_class(SaveSomaMeshPLY) - bpy.utils.unregister_class(SaveSomaMeshSTL) - bpy.utils.unregister_class(SaveSomaMeshBLEND) diff --git a/nmv/interface/ui/synaptics/__init__.py b/nmv/interface/ui/synaptics/__init__.py new file mode 100644 index 000000000..0bed52d0a --- /dev/null +++ b/nmv/interface/ui/synaptics/__init__.py @@ -0,0 +1,19 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +from .panel_props import * +from .registration import * diff --git a/nmv/interface/ui/synaptics/layout_neuron_props.py b/nmv/interface/ui/synaptics/layout_neuron_props.py new file mode 100644 index 000000000..73908d0c7 --- /dev/null +++ b/nmv/interface/ui/synaptics/layout_neuron_props.py @@ -0,0 +1,174 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + + +#################################################################################################### +# @draw_neuron_options_header +#################################################################################################### +def draw_neuron_options_header(layout): + + row = layout.row() + row.label(text='Neuron Options', icon='OUTLINER_OB_EMPTY') + + +#################################################################################################### +# @draw_neuron_radius_option +#################################################################################################### +def draw_neuron_radius_option(layout, scene, options): + + neuron_radius_row = layout.row() + unify_radius_column = neuron_radius_row.column() + unify_radius_column.prop(scene, 'NMV_SynapticsUnifyRadius') + options.synaptics.unify_branch_radii = scene.NMV_SynapticsUnifyRadius + + unified_radius_column = neuron_radius_row.column() + unified_radius_column.prop(scene, 'NMV_SynapticsUnifiedNeuronRadius') + options.synaptics.unified_radius = scene.NMV_SynapticsUnifiedNeuronRadius + + # Disable the options if all arbors are not selected for display + if not options.synaptics.display_axons and not options.synaptics.display_dendrites: + unify_radius_column.enabled = False + unified_radius_column.enabled = False + + # Disable the column + unified_radius_column.enabled = False if not scene.NMV_SynapticsUnifyRadius else True + + +#################################################################################################### +# @draw_dendrites_options +#################################################################################################### +def draw_dendrites_options(layout, scene, options): + + dendrites_row = layout.row() + add_dendrites_column = dendrites_row.column() + add_dendrites_column.prop(scene, 'NMV_DisplayDendrites') + options.synaptics.display_dendrites = scene.NMV_DisplayDendrites + + dendrites_options_column = dendrites_row.column() + dendrites_options_column.prop(scene, 'NMV_SynapticsDendritesColor') + options.synaptics.dendrites_color = scene.NMV_SynapticsDendritesColor + + dendrites_options_column.enabled = True if scene.NMV_DisplayDendrites else False + + +#################################################################################################### +# @draw_axons_options +#################################################################################################### +def draw_axons_options(layout, scene, options): + + axons_row = layout.row() + add_axons_column = axons_row.column() + add_axons_column.prop(scene, 'NMV_DisplayAxons') + options.synaptics.display_axons = scene.NMV_DisplayAxons + + axons_options_column = axons_row.column() + axons_options_column.prop(scene, 'NMV_SynapticsAxonsColor') + options.synaptics.axons_color = scene.NMV_SynapticsAxonsColor + + axons_options_column.enabled = True if scene.NMV_DisplayAxons else False + + +#################################################################################################### +# @draw_single_neuron_options +#################################################################################################### +def draw_single_neuron_options(layout, scene, options): + + draw_neuron_options_header(layout=layout) + draw_dendrites_options(layout=layout, scene=scene, options=options) + draw_axons_options(layout=layout, scene=scene, options=options) + draw_neuron_radius_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_pre_synaptic_dendrites_options +#################################################################################################### +def draw_pre_synaptic_dendrites_options(layout, scene, options): + + pre_dendrites_row = layout.row() + add_dendrites_column = pre_dendrites_row.column() + add_dendrites_column.prop(scene, 'NMV_DisplayPreSynapticDendrites') + options.synaptics.display_pre_synaptic_dendrites = scene.NMV_DisplayPreSynapticDendrites + + dendrites_options_column = pre_dendrites_row.column() + dendrites_options_column.prop(scene, 'NMV_PreSynapticDendritesColor') + options.synaptics.pre_synaptic_dendrites_color = scene.NMV_PreSynapticDendritesColor + + dendrites_options_column.enabled = True if scene.NMV_DisplayPreSynapticDendrites else False + + +#################################################################################################### +# @draw_pre_synaptic_axons_options +#################################################################################################### +def draw_pre_synaptic_axons_options(layout, scene, options): + + pre_axons_row = layout.row() + add_axons_column = pre_axons_row.column() + add_axons_column.prop(scene, 'NMV_DisplayPreSynapticAxons') + options.synaptics.display_pre_synaptic_axons = scene.NMV_DisplayPreSynapticAxons + + axons_options_column = pre_axons_row.column() + axons_options_column.prop(scene, 'NMV_PreSynapticAxonsColor') + options.synaptics.pre_synaptic_axons_color = scene.NMV_PreSynapticAxonsColor + + axons_options_column.enabled = True if scene.NMV_DisplayPreSynapticAxons else False + + +#################################################################################################### +# @draw_post_synaptic_dendrites_options +#################################################################################################### +def draw_post_synaptic_dendrites_options(layout, scene, options): + + post_dendrites_row = layout.row() + add_dendrites_column = post_dendrites_row.column() + add_dendrites_column.prop(scene, 'NMV_DisplayPostSynapticDendrites') + options.synaptics.display_post_synaptic_dendrites = scene.NMV_DisplayPostSynapticDendrites + + dendrites_options_column = post_dendrites_row.column() + dendrites_options_column.prop(scene, 'NMV_PostSynapticDendritesColor') + options.synaptics.post_synaptic_dendrites_color = scene.NMV_PostSynapticDendritesColor + dendrites_options_column.enabled = True if scene.NMV_DisplayPostSynapticDendrites else False + + +#################################################################################################### +# @draw_post_synaptic_axons_options +#################################################################################################### +def draw_post_synaptic_axons_options(layout, scene, options): + axons_row = layout.row() + add_axons_column = axons_row.column() + add_axons_column.prop(scene, 'NMV_DisplayPostSynapticAxons') + options.synaptics.display_post_synaptic_axons = scene.NMV_DisplayPostSynapticAxons + + axons_options_column = axons_row.column() + axons_options_column.prop(scene, 'NMV_PostSynapticAxonsColor') + options.synaptics.post_synaptic_axons_color = scene.NMV_PostSynapticAxonsColor + + axons_options_column.enabled = True if scene.NMV_DisplayPostSynapticAxons else False + + +#################################################################################################### +# @draw_neuron_pair_options +#################################################################################################### +def draw_neuron_pair_options(layout, scene, options): + + draw_neuron_options_header(layout=layout) + + draw_pre_synaptic_dendrites_options(layout=layout, scene=scene, options=options) + draw_pre_synaptic_axons_options(layout=layout, scene=scene, options=options) + draw_post_synaptic_dendrites_options(layout=layout, scene=scene, options=options) + draw_post_synaptic_axons_options(layout=layout, scene=scene, options=options) + + draw_neuron_radius_option(layout, scene, options) diff --git a/nmv/interface/ui/synaptics/layout_props.py b/nmv/interface/ui/synaptics/layout_props.py new file mode 100644 index 000000000..a7708ec1e --- /dev/null +++ b/nmv/interface/ui/synaptics/layout_props.py @@ -0,0 +1,158 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + +from .layout_synapses_props import * +from .layout_neuron_props import * + + +#################################################################################################### +# @draw_synaptics_reconstruction_button +#################################################################################################### +def draw_synaptics_reconstruction_button(layout, scene): + + row = layout.row() + row.operator('nmv.reconstruct_synaptics') + + if nmv.interface.ui_synaptics_reconstructed: + row = layout.row() + row.prop(scene, 'NMV_SynapticReconstructionTime') + row.enabled = False + + +#################################################################################################### +# @draw_synapses_options_header +#################################################################################################### +def draw_synapses_options_header(layout): + + # Reconstruction options + row = layout.row() + row.label(text='Synapses Options', icon='OUTLINER_OB_EMPTY') + + +#################################################################################################### +# @draw_synaptics_reconstruction_options +#################################################################################################### +def draw_synaptics_reconstruction_options(layout, scene, options): + + # Header + draw_synapses_options_header(layout=layout) + + # Use case + use_case = options.synaptics.use_case + if use_case == nmv.enums.Synaptics.UseCase.AFFERENT: + draw_afferent_options(layout=layout, scene=scene, options=options) + draw_common_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_single_neuron_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.EFFERENT: + draw_efferent_options(layout=layout, scene=scene, options=options) + draw_common_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_single_neuron_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.AFFERENT_AND_EFFERENT: + draw_afferent_and_efferent_options(layout=layout, scene=scene, options=options) + draw_common_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_single_neuron_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.EXCITATORY: + draw_excitatory_options(layout=layout, scene=scene, options=options) + draw_common_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_single_neuron_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.INHIBITORY: + draw_inhibitory_options(layout=layout, scene=scene, options=options) + draw_common_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_single_neuron_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.EXCITATORY_AND_INHIBITORY: + draw_excitatory_and_inhibitory_options(layout=layout, scene=scene, options=options) + draw_common_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_single_neuron_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.SPECIFIC_COLOR_CODED_SET: + draw_specific_color_coded_set_options(layout=layout, scene=scene, options=options) + draw_common_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_single_neuron_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.PATHWAY_PRE_SYNAPTIC: + draw_pre_synaptic_pathway_options(layout=layout, scene=scene, options=options) + draw_synapse_radius_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_neuron_pair_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.PATHWAY_POST_SYNAPTIC: + draw_post_synaptic_pathway_options(layout=layout, scene=scene, options=options) + draw_synapse_radius_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_neuron_pair_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.TARGETS: + draw_synaptic_targets(layout=layout, scene=scene, options=options) + layout.separator() + + draw_single_neuron_options(layout=layout, scene=scene, options=options) + + elif use_case == nmv.enums.Synaptics.UseCase.PROJECTION_TO_CELL: + + draw_synaptics_afferent_projection(layout=layout, scene=scene, options=options) + draw_afferent_options(layout=layout, scene=scene, options=options) + draw_common_options(layout=layout, scene=scene, options=options) + layout.separator() + + draw_single_neuron_options(layout=layout, scene=scene, options=options) + + else: + nmv.logger.log('UI_ERROR: draw_synaptics_reconstruction_options') + layout.separator() + + # Common shading options + draw_shading_options(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_out_of_context_message +#################################################################################################### +def draw_out_of_context_message(layout, scene, options): + + message_row = layout.row() + message_row.label(text='The Synaptics panel can ONLY be used with a circuit!') + diff --git a/nmv/interface/ui/synaptics/layout_rendering_props.py b/nmv/interface/ui/synaptics/layout_rendering_props.py new file mode 100644 index 000000000..996013dc6 --- /dev/null +++ b/nmv/interface/ui/synaptics/layout_rendering_props.py @@ -0,0 +1,78 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + + +#################################################################################################### +# @draw_rendering_buttons +#################################################################################################### +def draw_synaptics_rendering_buttons(panel, scene, options): + + view_row = panel.layout.row() + view_row.label(text='Render View', icon='RESTRICT_RENDER_OFF') + buttons_row = panel.layout.row(align=True) + buttons_row.operator('nmv.render_synaptics_front', icon='AXIS_FRONT') + buttons_row.operator('nmv.render_synaptics_side', icon='AXIS_SIDE') + buttons_row.operator('nmv.render_synaptics_top', icon='AXIS_TOP') + + if nmv.interface.ui_synaptics_rendered: + row = panel.layout.row() + row.prop(scene, 'NMV_SynapticsRenderingTime') + row.enabled = False + + +#################################################################################################### +# @draw_rendering_buttons +#################################################################################################### +def draw_synaptics_rendering_options(panel, scene, options): + + # Rendering header + nmv.interface.ui.draw_rendering_header( + layout=panel.layout, scene=scene, options=options) + + # Rendering view + nmv.interface.ui.draw_synaptics_rendering_view_option( + layout=panel.layout, scene=scene, options=options) + + # Resolution basis + nmv.interface.ui.draw_resolution_basis_option( + layout=panel.layout, scene=scene, options=options) + + # Resolution + nmv.interface.ui.draw_resolution_options( + layout=panel.layout, scene=scene, options=options) + + # Image format + nmv.interface.ui.draw_image_format_option( + layout=panel.layout, scene=scene, options=options) + + # Scale bar + nmv.interface.ui.draw_scale_bar_option( + layout=panel.layout, scene=scene, options=options) + + # Rendering buttons + draw_synaptics_rendering_buttons( + panel=panel, scene=scene, options=options) + diff --git a/nmv/interface/ui/synaptics/layout_synapses_props.py b/nmv/interface/ui/synaptics/layout_synapses_props.py new file mode 100644 index 000000000..2202af38b --- /dev/null +++ b/nmv/interface/ui/synaptics/layout_synapses_props.py @@ -0,0 +1,352 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.bbp +import nmv.scene + + +#################################################################################################### +# @draw_shading_options_header +#################################################################################################### +def draw_shading_options_header(layout): + + row = layout.row() + row.label(text='Shading Options', icon='OUTLINER_OB_EMPTY') + + +#################################################################################################### +# @draw_shading_option +#################################################################################################### +def draw_shading_option(layout, scene, options): + + # Shading options + row = layout.row() + row.label(text='Shading') + row.prop(scene, 'NMV_SynapticsShader') + options.synaptics.shader = scene.NMV_SynapticsShader + + +#################################################################################################### +# @draw_shading_options +#################################################################################################### +def draw_shading_options(layout, scene, options): + + draw_shading_options_header(layout=layout) + draw_shading_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_excitatory_synapses_color_and_count +#################################################################################################### +def draw_excitatory_synapses_color_and_count(layout, scene, options): + + color_row = layout.row() + color_row.prop(scene, 'NMV_ExcitatorySynapsesColor') + options.synaptics.excitatory_synapses_color = scene.NMV_ExcitatorySynapsesColor + synapse_count_column = color_row.column() + synapse_count_column.prop(scene, 'NMV_SynapticsNumberExcitatorySynapses') + synapse_count_column.enabled = False + + +#################################################################################################### +# @draw_inhibitory_synapses_color_and_count +#################################################################################################### +def draw_inhibitory_synapses_color_and_count(layout, scene, options): + + color_row = layout.row() + color_row.prop(scene, 'NMV_InhibitorySynapsesColor') + options.synaptics.inhibitory_synapses_color = scene.NMV_InhibitorySynapsesColor + synapse_count_column = color_row.column() + synapse_count_column.prop(scene, 'NMV_SynapticsNumberInhibitorySynapses') + synapse_count_column.enabled = False + + +#################################################################################################### +# @draw_excitatory_options +#################################################################################################### +def draw_excitatory_options(layout, scene, options): + + draw_excitatory_synapses_color_and_count(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_inhibitory_options +#################################################################################################### +def draw_inhibitory_options(layout, scene, options): + + draw_inhibitory_synapses_color_and_count(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_excitatory_and_inhibitory_options +#################################################################################################### +def draw_excitatory_and_inhibitory_options(layout, scene, options): + + draw_excitatory_synapses_color_and_count(layout=layout, scene=scene, options=options) + draw_inhibitory_synapses_color_and_count(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_synapses_color_option +#################################################################################################### +def draw_synapses_color_option(layout, scene, options): + + color_row = layout.row() + color_row.prop(scene, 'NMV_SynapsesColor') + options.synaptics.synapses_color = scene.NMV_SynapsesColor + + synapse_count_column = color_row.column() + synapse_count_column.prop(scene, 'NMV_SynapticsNumberSharedSynapses') + synapse_count_column.enabled = False + + +#################################################################################################### +# @draw_pre_synaptic_gid +#################################################################################################### +def draw_pre_synaptic_gid(layout, scene, options): + + gid_row = layout.row() + gid_row.prop(scene, 'NMV_PreSynapticGID') + options.synaptics.pre_synaptic_gid = scene.NMV_PreSynapticGID + + +#################################################################################################### +# @draw_post_synaptic_gid +#################################################################################################### +def draw_post_synaptic_gid(layout, scene, options): + + gid_row = layout.row() + gid_row.prop(scene, 'NMV_PostSynapticGID') + options.synaptics.post_synaptic_gid = scene.NMV_PostSynapticGID + + +#################################################################################################### +# @draw_pre_synaptic_pathway_options +#################################################################################################### +def draw_pre_synaptic_pathway_options(layout, scene, options): + + draw_pre_synaptic_gid(layout=layout, scene=scene, options=options) + draw_synapses_color_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_post_synaptic_pathway_options +#################################################################################################### +def draw_post_synaptic_pathway_options(layout, scene, options): + + draw_post_synaptic_gid(layout=layout, scene=scene, options=options) + draw_synapses_color_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_afferent_synapses_color_option +#################################################################################################### +def draw_afferent_synapses_color_option(layout, scene, options): + + color_row = layout.row() + color_row.prop(scene, 'NMV_AfferentSynapsesColor') + options.synaptics.afferent_synapses_color = scene.NMV_AfferentSynapsesColor + synapse_count_column = color_row.column() + synapse_count_column.prop(scene, 'NMV_SynapticsNumberAfferentSynapses') + synapse_count_column.enabled = False + + +#################################################################################################### +# @draw_efferent_synapses_color_option +#################################################################################################### +def draw_efferent_synapses_color_option(layout, scene, options): + + color_row = layout.row() + color_row.prop(scene, 'NMV_EfferentSynapsesColor') + options.synaptics.efferent_synapses_color = scene.NMV_EfferentSynapsesColor + synapse_count_column = color_row.column() + synapse_count_column.prop(scene, 'NMV_SynapticsNumberEfferentSynapses') + synapse_count_column.enabled = False + + +#################################################################################################### +# @draw_mtype_color_palette +#################################################################################################### +def draw_mtype_color_palette(layout, scene, options): + + # A circuit must be loaded + if nmv.consts.Circuit.MTYPES is not None: + options.synaptics.mtypes_colors = list() + + # Add the colormap element to the UI + colors = layout.column() + for i in range(len(nmv.consts.Circuit.MTYPES)): + values = colors.row() + values.prop(scene, 'NMV_MtypeColor_%d' % i) + count_column = values.column() + count_column.prop(scene, 'NMV_Synaptic_MtypeCount_%d' % i) + count_column.enabled = False + + # Get the color value from the panel + options.synaptics.mtypes_colors.append(getattr(scene, 'NMV_MtypeColor_%d' % i)) + + +#################################################################################################### +# @draw_etype_color_palette +#################################################################################################### +def draw_etype_color_palette(layout, scene, options): + + # A circuit must be loaded + if nmv.consts.Circuit.ETYPES is not None: + options.synaptics.etypes_colors = list() + + # Add the colormap element to the UI + colors = layout.column() + for i in range(len(nmv.consts.Circuit.ETYPES)): + values = colors.row() + values.prop(scene, 'NMV_EtypeColor_%d' % i) + count_column = values.column() + count_column.prop(scene, 'NMV_Synaptic_EtypeCount_%d' % i) + count_column.enabled = False + + # Get the color value from the panel + options.synaptics.etypes_colors.append(getattr(scene, 'NMV_EtypeColor_%d' % i)) + + +#################################################################################################### +# @draw_afferent_options +#################################################################################################### +def draw_afferent_options(layout, scene, options): + + color_scheme_row = layout.row() + color_scheme_row.prop(scene, 'NMV_AfferentColorCoding') + options.synaptics.afferent_color_coding = scene.NMV_AfferentColorCoding + + scheme = options.synaptics.afferent_color_coding + if scheme == nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR: + draw_afferent_synapses_color_option(layout=layout, scene=scene, options=options) + elif scheme == nmv.enums.Synaptics.ColorCoding.MTYPE_COLOR_CODED: + draw_mtype_color_palette(layout=layout, scene=scene, options=options) + elif scheme == nmv.enums.Synaptics.ColorCoding.ETYPE_COLOR_CODED: + draw_etype_color_palette(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_efferent_options +#################################################################################################### +def draw_efferent_options(layout, scene, options): + + color_scheme_row = layout.row() + color_scheme_row.prop(scene, 'NMV_EfferentColorCoding') + options.synaptics.efferent_color_coding = scene.NMV_EfferentColorCoding + + scheme = options.synaptics.efferent_color_coding + if scheme == nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR: + draw_efferent_synapses_color_option(layout=layout, scene=scene, options=options) + elif scheme == nmv.enums.Synaptics.ColorCoding.MTYPE_COLOR_CODED: + draw_mtype_color_palette(layout=layout, scene=scene, options=options) + elif scheme == nmv.enums.Synaptics.ColorCoding.ETYPE_COLOR_CODED: + draw_etype_color_palette(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_afferent_and_efferent_options +#################################################################################################### +def draw_afferent_and_efferent_options(layout, scene, options): + + draw_afferent_synapses_color_option(layout=layout, scene=scene, options=options) + draw_efferent_synapses_color_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_specific_color_coded_set_options +#################################################################################################### +def draw_specific_color_coded_set_options(layout, scene, options): + + color_scheme_row = layout.row() + color_scheme_row.prop(scene, 'NMV_SpecificColorCoding') + + +#################################################################################################### +# @draw_synapse_percentage_option +#################################################################################################### +def draw_synapse_percentage_option(layout, scene, options): + + row = layout.row() + row.label(text='Synapses Percentage') + row.prop(scene, 'NMV_SynapsesPercentage') + options.synaptics.percentage = scene.NMV_SynapsesPercentage + + +#################################################################################################### +# @draw_synapse_radius_options +#################################################################################################### +def draw_synapse_radius_options(layout, scene, options): + + row = layout.row() + row.label(text='Synapses Radius') + row.prop(scene, 'NMV_SynapseRadius') + options.synaptics.synapses_radius = scene.NMV_SynapseRadius + + +#################################################################################################### +# @draw_common_options +#################################################################################################### +def draw_common_options(layout, scene, options): + + draw_synapse_radius_options(layout=layout, scene=scene, options=options) + draw_synapse_percentage_option(layout=layout, scene=scene, options=options) + + +#################################################################################################### +# @draw_synaptic_targets +#################################################################################################### +def draw_synaptic_targets(layout, scene, options): + + row = layout.row() + row.prop(scene, 'NMV_SynapticsJsonFile') + options.synaptics.synaptics_json_file = scene.NMV_SynapticsJsonFile + + # If the target is loaded, show the color map + if nmv.interface.ui_synaptics_file_loaded: + + # Get the colors of the customized synapses + options.synaptics.customized_colors = list() + + # Add the colormap element to the UI + colors = layout.column() + for i, group in enumerate(options.synaptics.customized_synaptics_group): + values = colors.row() + values.prop(scene, 'NMV_CustomizedColor%d' % i) + values.enabled = False + count_column = values.column() + count_column.prop(scene, 'NMV_CustomizedCount%d' % i) + count_column.enabled = False + + # Get the color value from the panel + options.synaptics.customized_synaptics_colors.append( + getattr(scene, 'NMV_CustomizedColor%d' % i)) + + +#################################################################################################### +# @draw_synaptics_afferent_projection +#################################################################################################### +def draw_synaptics_afferent_projection(layout, scene, options): + + row = layout.row() + row.label(text='Projection Name') + row.prop(scene, 'NMV_SynapticsProjectionName') + options.synaptics.projection_name = scene.NMV_SynapticsProjectionName + \ No newline at end of file diff --git a/nmv/interface/ui/synaptics/ops_reconstruction.py b/nmv/interface/ui/synaptics/ops_reconstruction.py new file mode 100644 index 000000000..3f8b909fa --- /dev/null +++ b/nmv/interface/ui/synaptics/ops_reconstruction.py @@ -0,0 +1,420 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### +import copy +# System imports +import sys +import os +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.bbp +import nmv.enums +import nmv.interface +import nmv.utilities +import nmv.scene + + +#################################################################################################### +# @NMV_ReconstructSynaptics +#################################################################################################### +class NMV_ReconstructSynaptics(bpy.types.Operator): + """Reconstruct the synaptics scene""" + + # Operator parameters + bl_idname = "nmv.reconstruct_synaptics" + bl_label = "Reconstruct Synaptics" + + ################################################################################################ + # @reconstruct_afferent + ################################################################################################ + def reconstruct_afferent(self, context, circuit, options): + + nmv.scene.clear_scene(deep_delete=True) + synapse_groups = nmv.bbp.visualize_afferent_synapses( + circuit=circuit, gid=options.morphology.gid, options=options, context=context) + nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options) + + # Synapses count + afferent_synapses_count = len(synapse_groups[0].synapses_ids_list) + context.scene.NMV_SynapticsNumberAfferentSynapses = afferent_synapses_count + + ################################################################################################ + # @reconstruct_efferent + ################################################################################################ + def reconstruct_efferent(self, context, circuit, options): + + nmv.scene.clear_scene(deep_delete=True) + synapse_groups = nmv.bbp.visualize_efferent_synapses( + circuit=circuit, gid=options.morphology.gid, options=options, context=context) + nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options) + + # Synapses count + efferent_synapses_count = len(synapse_groups[0].synapses_ids_list) + context.scene.NMV_SynapticsNumberEfferentSynapses = efferent_synapses_count + + ################################################################################################ + # @reconstruct_afferent_and_efferent + ################################################################################################ + def reconstruct_afferent_and_efferent(self, context, circuit, options): + + nmv.scene.clear_scene(deep_delete=True) + synapse_groups = nmv.bbp.visualize_afferent_and_efferent_synapses( + circuit=circuit, gid=options.morphology.gid, options=options, + visualize_afferent=True, visualize_efferent=True) + nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options) + + # Synapses count + afferent_synapses_count = len(synapse_groups[0].synapses_ids_list) + context.scene.NMV_SynapticsNumberAfferentSynapses = afferent_synapses_count + efferent_synapses_count = len(synapse_groups[1].synapses_ids_list) + context.scene.NMV_SynapticsNumberEfferentSynapses = efferent_synapses_count + + ################################################################################################ + # @reconstruct_excitatory + ################################################################################################ + def reconstruct_excitatory(self, context, circuit, options): + + nmv.scene.clear_scene(deep_delete=True) + synapse_groups = nmv.bbp.visualize_excitatory_and_inhibitory_synapses( + circuit=circuit, gid=options.morphology.gid, options=options, + visualize_excitatory=True, visualize_inhibitory=False) + nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options) + + # Synapses count + excitatory_synapses_count = len(synapse_groups[0].synapses_ids_list) + context.scene.NMV_SynapticsNumberExcitatorySynapses = excitatory_synapses_count + + ################################################################################################ + # @reconstruct_inhibitory + ################################################################################################ + def reconstruct_inhibitory(self, context, circuit, options): + + nmv.scene.clear_scene(deep_delete=True) + synapse_groups = nmv.bbp.visualize_excitatory_and_inhibitory_synapses( + circuit=circuit, gid=nmv.interface.ui_options.morphology.gid, + visualize_excitatory=False, visualize_inhibitory=True, options=options) + nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options) + + # Synapses count + inhibitory_synapses_count = len(synapse_groups[0].synapses_ids_list) + context.scene.NMV_SynapticsNumberInhibitorySynapses = inhibitory_synapses_count + + ################################################################################################ + # @reconstruct_excitatory_and_inhibitory + ################################################################################################ + def reconstruct_excitatory_and_inhibitory(self, context, circuit, options): + + nmv.scene.clear_scene(deep_delete=True) + synapse_groups = nmv.bbp.visualize_excitatory_and_inhibitory_synapses( + circuit=circuit, gid=nmv.interface.ui_options.morphology.gid, + visualize_excitatory=True, visualize_inhibitory=True, options=options) + nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options) + + # Synapses count + excitatory_synapses_count = len(synapse_groups[0].synapses_ids_list) + context.scene.NMV_SynapticsNumberExcitatorySynapses = excitatory_synapses_count + inhibitory_synapses_count = len(synapse_groups[1].synapses_ids_list) + context.scene.NMV_SynapticsNumberInhibitorySynapses = inhibitory_synapses_count + + ################################################################################################ + # @reconstruct_pathway_pre_synaptic + ################################################################################################ + def reconstruct_pathway_pre_synaptic(self, context, circuit, options): + + # Ensure that the given pre-synaptic GID is an integer + try: + int(options.synaptics.pre_synaptic_gid) + except ValueError: + self.report({'ERROR'}, 'Please enter a valid GID as an integer') + return {'FINISHED'} + + # Ensure that the pre-synaptic and post-synaptic GIDs are not the same + if int(options.synaptics.pre_synaptic_gid) == int(options.morphology.gid): + self.report({'ERROR'}, + 'Please enter a valid pre-synaptic GID, that is different from the ' + 'post-synaptic one') + return {'FINISHED'} + + # Initially, try to get a list of synapses shared between the two cells + shared_synapses_ids = circuit.get_shared_synapses_ids_between_two_neurons( + pre_gid=options.synaptics.pre_synaptic_gid, + post_gid=options.morphology.gid) + + # If that list is Zero, then report the error and exit + if len(shared_synapses_ids) == 0: + self.report({'ERROR'}, 'No shared synapses between the given neurons [%s - %s]' + % (str(options.synaptics.pre_synaptic_gid), str(options.morphology.gid))) + return {'FINISHED'} + + nmv.scene.clear_scene(deep_delete=True) + + # Create the post-synaptic neuron AT ORIGIN - THIS IS THE FOCUS + post_synaptic_neuron_mesh = nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options, + which_neuron=nmv.enums.Synaptics.WhichNeuron.POST_SYNAPTIC) + + # Create the pre-synaptic neuron AT ORIGIN + pre_synaptic_neuron_mesh = nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.synaptics.pre_synaptic_gid, options=options, + which_neuron=nmv.enums.Synaptics.WhichNeuron.PRE_SYNAPTIC) + + # Get the transformations of the pre- and post-synaptic neurons + pre_synaptic_transformation = circuit.get_neuron_transformation_matrix( + gid=options.synaptics.pre_synaptic_gid) + post_synaptic_transformation = circuit.get_neuron_transformation_matrix( + gid=options.morphology.gid) + + # Since the focus is given to the post-synaptic neuron; it is the originally loaded one, + # transform the pre-synaptic neuron w.r.t to the post synaptic one + if pre_synaptic_neuron_mesh is not None: + pre_synaptic_neuron_mesh.matrix_world = pre_synaptic_transformation + pre_synaptic_neuron_mesh.matrix_world = \ + post_synaptic_transformation.inverted() @ pre_synaptic_neuron_mesh.matrix_world + + # Transform the synapses to be loaded on the post-synaptic neuron + nmv.bbp.visualize_shared_synapses_between_two_neurons( + circuit=circuit, + pre_gid=options.synaptics.pre_synaptic_gid, + post_gid=options.morphology.gid, + options=options, + inverse_transformation=post_synaptic_transformation.inverted()) + + # Synapses count + context.scene.NMV_SynapticsNumberSharedSynapses = len(shared_synapses_ids) + + ################################################################################################ + # @reconstruct_pathway_post_synaptic + ################################################################################################ + def reconstruct_pathway_post_synaptic(self, context, circuit, options): + + # Ensure that the given post-synaptic GID is an integer + try: + int(options.synaptics.post_synaptic_gid) + except ValueError: + self.report({'ERROR'}, 'Please enter a valid GID as an integer') + return {'FINISHED'} + + # Ensure that the pre-synaptic and post-synaptic GIDs are not the same + if int(options.synaptics.post_synaptic_gid) == int(options.morphology.gid): + self.report({'ERROR'}, 'Please enter a valid post-synaptic GID, that is different ' + 'from the pre-synaptic one') + return {'FINISHED'} + + # Initially, try to get a list of synapses shared between the two cells + shared_synapses_ids = circuit.get_shared_synapses_ids_between_two_neurons( + pre_gid=options.morphology.gid, + post_gid=options.synaptics.post_synaptic_gid) + + # If that list is Zero, then report the error and exit + if len(shared_synapses_ids) == 0: + self.report({'ERROR'}, 'No shared synapses between the given neurons [%s - %s]' + % (str(options.morphology.gid), str(options.synaptics.post_synaptic_gid))) + return {'FINISHED'} + + nmv.scene.clear_scene(deep_delete=True) + + # Create the pre-synaptic neuron AT ORIGIN - THIS IS THE FOCUS + pre_synaptic_neuron_mesh = nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options, + which_neuron=nmv.enums.Synaptics.WhichNeuron.PRE_SYNAPTIC) + + # Create the post-synaptic neuron AT ORIGIN + post_synaptic_neuron_mesh = nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.synaptics.post_synaptic_gid, options=options, + which_neuron=nmv.enums.Synaptics.WhichNeuron.POST_SYNAPTIC) + + # Get the transformations of the pre- and post-synaptic neurons + pre_synaptic_transformation = circuit.get_neuron_transformation_matrix( + gid=options.morphology.gid) + post_synaptic_transformation = circuit.get_neuron_transformation_matrix( + gid=options.synaptics.post_synaptic_gid) + + # Since the focus is given to the pre-synaptic neuron; it is the originally loaded one, + # transform the post-synaptic neuron w.r.t to the pre synaptic one + if post_synaptic_neuron_mesh is not None: + post_synaptic_neuron_mesh.matrix_world = post_synaptic_transformation + post_synaptic_neuron_mesh.matrix_world = \ + pre_synaptic_transformation.inverted() @ post_synaptic_neuron_mesh.matrix_world + + nmv.bbp.visualize_shared_synapses_between_two_neurons( + circuit=circuit, + pre_gid=nmv.interface.ui_options.morphology.gid, + post_gid=options.synaptics.post_synaptic_gid, + options=options, + inverse_transformation=pre_synaptic_transformation.inverted()) + + # Synapses count + context.scene.NMV_SynapticsNumberSharedSynapses = len(shared_synapses_ids) + + ################################################################################################ + # @reconstruct_afferent_synapses_for_projection + ################################################################################################ + def reconstruct_afferent_synapses_for_projection(self, context, circuit, options): + + nmv.scene.clear_scene(deep_delete=True) + synapse_groups = nmv.bbp.visualize_afferent_synapses_for_projection( + circuit=circuit, gid=options.morphology.gid, options=options, context=context) + nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options) + + # Synapses count + afferent_synapses_count = len(synapse_groups[0].synapses_ids_list) + context.scene.NMV_SynapticsNumberAfferentSynapses = afferent_synapses_count + + ################################################################################################ + # @reconstruct_projection + ################################################################################################ + def reconstruct_targets(self, context, circuit, options): + + # If the given synaptics file is not valid, handle the error + if not os.path.isfile(options.synaptics.synaptics_json_file): + self.report({'ERROR'}, 'The given synaptics file does not exist.') + return {'FINISHED'} + + # Try to load the synaptics file + try: + options.synaptics.customized_synaptics_group = \ + nmv.bbp.get_synapse_groups_from_color_coded_json_file( + synapse_json_file=options.synaptics.synaptics_json_file) + except IOError: + self.report({'ERROR'}, 'Cannot load the given synaptics file. FORMAT ERROR!') + return {'FINISHED'} + + # If the number of synapse groups is less than 1 report the issue + if len(options.synaptics.customized_synaptics_group) < 1: + self.report({'ERROR'}, 'The file has no groups') + return {'FINISHED'} + + # If we reach that point, the file has been successfully loaded + nmv.interface.ui_synaptics_file_loaded = True + + # Create the info (colors and stats.) of the drawn synapses + for i, group in enumerate(options.synaptics.customized_synaptics_group): + setattr(bpy.types.Scene, 'NMV_CustomizedColor%d' % i, + bpy.props.FloatVectorProperty( + name=group.name, subtype='COLOR', default=group.color, min=0.0, max=1.0, + description='The color of the synapses of the %s group' % group.name)) + + synapse_count = len(group.synapses_ids_list) + setattr(bpy.types.Scene, 'NMV_CustomizedCount%d' % i, + bpy.props.IntProperty( + name="Count", default=synapse_count, min=synapse_count, max=synapse_count, + description="The number of the synapses of the %s group" % group.name,)) + + # Visualize the synapses and the neuron + nmv.scene.clear_scene(deep_delete=True) + nmv.bbp.visualize_synapse_groups( + circuit=circuit, synapse_groups=options.synaptics.customized_synaptics_group, + gid=options.morphology.gid, options=options) + nmv.bbp.visualize_circuit_neuron_for_synaptics( + circuit=circuit, gid=options.morphology.gid, options=options) + + ################################################################################################ + # @reconstruct_synaptics + ################################################################################################ + def reconstruct_synaptics(self, context, circuit, options): + + # Afferent synapses only (on dendrites) + if options.synaptics.use_case == nmv.enums.Synaptics.UseCase.AFFERENT: + self.reconstruct_afferent(context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + # Efferent synapses (on axon) + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.EFFERENT: + self.reconstruct_efferent(context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + # Afferent and efferent synapses + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.AFFERENT_AND_EFFERENT: + self.reconstruct_afferent_and_efferent( + context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + # Excitatory synapses only + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.EXCITATORY: + self.reconstruct_excitatory(context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + # Inhibitory synapses only + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.INHIBITORY: + self.reconstruct_inhibitory(context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + # Excitatory and inhibitory synapses + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.EXCITATORY_AND_INHIBITORY: + self.reconstruct_excitatory_and_inhibitory( + context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + # Pre-synaptic pathways + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.PATHWAY_PRE_SYNAPTIC: + self.reconstruct_pathway_pre_synaptic( + context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + # Post-synaptic pathways + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.PATHWAY_POST_SYNAPTIC: + self.reconstruct_pathway_post_synaptic( + context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + # Projection + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.PROJECTION_TO_CELL: + self.reconstruct_afferent_synapses_for_projection( + context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + # Targets + elif options.synaptics.use_case == nmv.enums.Synaptics.UseCase.TARGETS: + self.reconstruct_targets( + context=context, circuit=circuit, options=options) + nmv.interface.ui_synaptics_reconstructed = True + + else: + self.report({'ERROR'}, 'Please select a valid option') + nmv.logger.log('UI_ERROR: NMV_ReconstructSynaptics::reconstruct_synaptics') + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + start_time = time.time() + self.reconstruct_synaptics( + context=context, circuit=nmv.interface.ui_circuit, options=nmv.interface.ui_options) + end_time = time.time() + context.scene.NMV_SynapticReconstructionTime = end_time - start_time + + # Resets the time-line to the first frame to preserve the structure of the point-cloud + # that forms the synapses + nmv.utilities.reset_time_line_to_first_frame() + + # Deselect all the objects to be able to see the reconstructed data + nmv.scene.deselect_all() + + # Done + return {'FINISHED'} + diff --git a/nmv/interface/ui/synaptics/ops_rendering.py b/nmv/interface/ui/synaptics/ops_rendering.py new file mode 100644 index 000000000..3e470e42b --- /dev/null +++ b/nmv/interface/ui/synaptics/ops_rendering.py @@ -0,0 +1,108 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import time + +# Blender imports +import bpy + +# Internal imports +import nmv.enums +import nmv.interface + + +#################################################################################################### +# @NMV_RenderSynapticsFront +#################################################################################################### +class NMV_RenderSynapticsFront(bpy.types.Operator): + """Render front view of the reconstructed scene""" + + # Operator parameters + bl_idname = "nmv.render_synaptics_front" + bl_label = "Front" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + start_time = time.time() + context.scene.NMV_SynapticsRenderingTime = nmv.interface.ui.render_synaptics_image( + self, scene=context.scene, options=nmv.interface.ui_options, + view=nmv.enums.Camera.View.FRONT) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_synaptics_rendered = True + context.scene.NMV_SynapticsRenderingTime = rendering_time - start_time + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderSynapticsSide +#################################################################################################### +class NMV_RenderSynapticsSide(bpy.types.Operator): + """Render side view of the reconstructed mesh""" + + # Operator parameters + bl_idname = "nmv.render_synaptics_side" + bl_label = "Side" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + start_time = time.time() + context.scene.NMV_SynapticsRenderingTime = nmv.interface.ui.render_synaptics_image( + self, scene=context.scene, options=nmv.interface.ui_options, + view=nmv.enums.Camera.View.SIDE) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_synaptics_rendered = True + context.scene.NMV_SynapticsRenderingTime = rendering_time - start_time + return {'FINISHED'} + + +#################################################################################################### +# @NMV_RenderSynapticsTop +#################################################################################################### +class NMV_RenderSynapticsTop(bpy.types.Operator): + """Render top view of the reconstructed mesh""" + + # Operator parameters + bl_idname = "nmv.render_synaptics_top" + bl_label = "Top" + + ################################################################################################ + # @execute + ################################################################################################ + def execute(self, context): + + start_time = time.time() + context.scene.NMV_SynapticsRenderingTime = nmv.interface.ui.render_synaptics_image( + self, scene=context.scene, options=nmv.interface.ui_options, + view=nmv.enums.Camera.View.TOP) + rendering_time = time.time() + + # Update the UI + nmv.interface.ui_synaptics_rendered = True + context.scene.NMV_SynapticsRenderingTime = rendering_time - start_time + return {'FINISHED'} + diff --git a/nmv/interface/ui/synaptics/panel.py b/nmv/interface/ui/synaptics/panel.py new file mode 100644 index 000000000..3303fb9cc --- /dev/null +++ b/nmv/interface/ui/synaptics/panel.py @@ -0,0 +1,91 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys + +# Blender imports +import bpy + +# Internal imports +import nmv.bbp +import nmv.enums +import nmv.interface +import nmv.utilities +import nmv.scene + +from .layout_props import * +from .layout_rendering_props import * + + +#################################################################################################### +# @NMV_SynapticsPanel +#################################################################################################### +class NMV_SynapticsPanel(bpy.types.Panel): + """NMV Synaptics panel""" + + ################################################################################################ + # Panel parameters + ################################################################################################ + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_idname = "OBJECT_PT_NMV_Synaptics" + bl_label = 'Synaptics Toolbox' + bl_category = 'NeuroMorphoVis' + bl_options = {'DEFAULT_CLOSED'} + + ################################################################################################ + # @draw + ################################################################################################ + def draw(self, context): + + # Get a reference to the options + options = nmv.interface.ui_options + + # If a circuit is loaded, enable this panel, otherwise disable it + if nmv.interface.ui_circuit is not None: + + # Select a use case + use_case_row = self.layout.row() + use_case_row.prop(context.scene, 'NMV_SynapticsUseCase') + options.synaptics.use_case = context.scene.NMV_SynapticsUseCase + self.layout.separator() + + # Display the options accordingly, based on the use case selection + if context.scene.NMV_SynapticsUseCase != nmv.enums.Synaptics.UseCase.NOT_SELECTED: + + # Draw the reconstruction options + draw_synaptics_reconstruction_options( + layout=self.layout, scene=context.scene, options=options) + self.layout.separator() + + # Draw the reconstruction button + draw_synaptics_reconstruction_button(layout=self.layout, scene=context.scene) + self.layout.separator() + + # Draw the rendering operations + if nmv.interface.ui_synaptics_reconstructed: + draw_synaptics_rendering_options( + panel=self, scene=context.scene, options=options) + + # Otherwise, draw the out of context message and disable the panel + else: + draw_out_of_context_message(layout=self.layout, scene=context.scene, options=options) + self.layout.enabled = False if nmv.interface.ui_circuit is None else True + + + diff --git a/nmv/interface/ui/synaptics/panel_props.py b/nmv/interface/ui/synaptics/panel_props.py new file mode 100644 index 000000000..7ccab27f1 --- /dev/null +++ b/nmv/interface/ui/synaptics/panel_props.py @@ -0,0 +1,352 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + +# Internal imports +import nmv.consts +import nmv.enums +import nmv.utilities + + +# Synaptics use case +bpy.types.Scene.NMV_SynapticsUseCase = bpy.props.EnumProperty( + items=[(nmv.enums.Synaptics.UseCase.AFFERENT, + 'Afferent Synapses', + 'Visualize the afferent synapses only'), + + (nmv.enums.Synaptics.UseCase.EFFERENT, + 'Efferent Synapses', + 'Visualize the efferent synapses only'), + + (nmv.enums.Synaptics.UseCase.AFFERENT_AND_EFFERENT, + 'Afferent and Efferent Synapses', + 'Visualize the afferent and efferent synapses combined'), + + (nmv.enums.Synaptics.UseCase.EXCITATORY, + 'Excitatory Synapses', + 'Visualize the excitatory synapses only'), + + (nmv.enums.Synaptics.UseCase.INHIBITORY, + 'Inhibitory Synapses', + 'Visualize the inhibitory synapses only'), + + (nmv.enums.Synaptics.UseCase.EXCITATORY_AND_INHIBITORY, + 'Excitatory and Inhibitory Synapses', + 'Visualize the inhibitory synapses combined'), + + (nmv.enums.Synaptics.UseCase.PATHWAY_PRE_SYNAPTIC, + 'Shared Synapses with a Pre-synaptic Neuron', + 'Visualize the shared synapses with a pre-synaptic neuron'), + + (nmv.enums.Synaptics.UseCase.PATHWAY_POST_SYNAPTIC, + 'Shared Synapses with a Post-synaptic Neuron', + 'Visualize the shared synapses with a post-synaptic neuron'), + + (nmv.enums.Synaptics.UseCase.TARGETS, + 'Customized Synapse List', + 'Visualize a customized color-coded lists of synapses valid only for the input neuron'), + + (nmv.enums.Synaptics.UseCase.PROJECTION_TO_CELL, + 'Projection to Neuron', + 'Visualize a the afferent synapses from a given projection'), + + (nmv.enums.Synaptics.UseCase.NOT_SELECTED, + 'Please select a Use Case', + 'Select a specific use case or configuration to visualize a specific set of synapse')], + name='Use Case', + default=nmv.enums.Synaptics.UseCase.NOT_SELECTED) + +# Color-coding schemes for the afferent synapses use case +bpy.types.Scene.NMV_AfferentColorCoding = bpy.props.EnumProperty( + items=[(nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR, + 'Unified Color', + 'Color all the synapses with a single color'), + + (nmv.enums.Synaptics.ColorCoding.MTYPE_COLOR_CODED, + 'Pre-synaptic Morphological Type', + 'Color code the synapses based on the morphological type (or m-type) of the connecting' + 'pre-synaptic cell.'), + + (nmv.enums.Synaptics.ColorCoding.ETYPE_COLOR_CODED, + 'Pre-synaptic Electrical Type', + 'Color code the synapses based on the electrical type (or e-type) of the connecting' + 'pre-synaptic cell.')], + + name='Color Scheme', + default=nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR) + +# Color-coding schemes for the efferent synapses use case +bpy.types.Scene.NMV_EfferentColorCoding = bpy.props.EnumProperty( + items=[(nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR, + 'Unified Color', + 'Color all the synapses with a single color'), + + (nmv.enums.Synaptics.ColorCoding.MTYPE_COLOR_CODED, + 'Post-synaptic Morphological Type', + 'Color code the synapses based on the morphological type (or m-type) of the connecting' + 'post-synaptic cell.'), + + (nmv.enums.Synaptics.ColorCoding.ETYPE_COLOR_CODED, + 'Post-synaptic Electrical Type', + 'Color code the synapses based on the electrical type (or e-type) of the connecting' + 'post-synaptic cell.')], + + name='Color Scheme', + default=nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR) + +# Excitatory synapses color +bpy.types.Scene.NMV_ExcitatorySynapsesColor = bpy.props.FloatVectorProperty( + name='Excitatory Synapses', + subtype='COLOR', default=nmv.enums.Color.EXCITATORY_SYNAPSES, min=0.0, max=1.0, + description='The color of the excitatory synapses') + +# Inhibitory synapses color +bpy.types.Scene.NMV_InhibitorySynapsesColor = bpy.props.FloatVectorProperty( + name='Inhibitory Synapses', + subtype='COLOR', default=nmv.enums.Color.INHIBITORY_SYNAPSES, min=0.0, max=1.0, + description='The color of the inhibitory synapses') + +# Afferent synapses color +bpy.types.Scene.NMV_AfferentSynapsesColor = bpy.props.FloatVectorProperty( + name='Afferent Synapses', + subtype='COLOR', default=nmv.enums.Color.AFFERENT_SYNAPSES, min=0.0, max=1.0, + description='The color of the afferent synapses') + +# Efferent synapses color +bpy.types.Scene.NMV_EfferentSynapsesColor = bpy.props.FloatVectorProperty( + name='Efferent Synapses', + subtype='COLOR', default=nmv.enums.Color.EFFERENT_SYNAPSES, min=0.0, max=1.0, + description='The color of the efferent synapses') + +# A single color that is used to color all the synapses +bpy.types.Scene.NMV_SynapsesColor = bpy.props.FloatVectorProperty( + name='Synapses Color', + subtype='COLOR', default=nmv.enums.Color.SYNAPSES, min=0.0, max=1.0, + description='A unifying color used to color all the shown synapses') + +# The percentage of the synapses loaded in the scene +bpy.types.Scene.NMV_SynapsesPercentage = bpy.props.FloatProperty( + name='Percentage', + default=100.0, min=0, max=100, + description='The percentage of the synapses loaded in the scene. Applicable values [0-100%]') + +# The unified radius of all the synapses +bpy.types.Scene.NMV_SynapseRadius = bpy.props.FloatProperty( + name='Radius', + description='The unified radius of all the synapses in μm. Applicable values [2-5]', + default=2.0, min=0, max=5) + +# Pre-synaptic neuron GID, for visualizing shared synapses +bpy.types.Scene.NMV_PreSynapticGID = bpy.props.StringProperty( + name='Pre-Synaptic GID', + description="The GID of a pre-synaptic cell that shares synapses with this cell.", + default=nmv.consts.Strings.PRE_GID, maxlen=1024) + +# Post-synaptic neuron GID, for visualizing shared synapses +bpy.types.Scene.NMV_PostSynapticGID = bpy.props.StringProperty( + name='Post-Synaptic GID', + description="The GID of a post-synaptic cell that shares synapses with this cell.", + default=nmv.consts.Strings.POST_GID, maxlen=1024) + +# Single neuron properties ######################################################################### +# Display the dendrites +bpy.types.Scene.NMV_DisplayDendrites = bpy.props.BoolProperty( + name='Dendrites', + description='', + default=True) + +# Display the axons +bpy.types.Scene.NMV_DisplayAxons = bpy.props.BoolProperty( + name='Axons', + description='', + default=True) + +# Dendrites color +bpy.types.Scene.NMV_SynapticsDendritesColor = bpy.props.FloatVectorProperty( + name='', + subtype='COLOR', default=nmv.enums.Color.BASAL_DENDRITES, min=0.0, max=1.0, + description='') + +# Axons color +bpy.types.Scene.NMV_SynapticsAxonsColor = bpy.props.FloatVectorProperty( + name='', + subtype='COLOR', default=nmv.enums.Color.AXONS, min=0.0, max=1.0, + description='') + +# Neuron pair properties ########################################################################### +bpy.types.Scene.NMV_DisplayPreSynapticDendrites = bpy.props.BoolProperty( + name='Pre-Synaptic Dendrites', + description='', + default=True) + +bpy.types.Scene.NMV_DisplayPreSynapticAxons = bpy.props.BoolProperty( + name='Pre-Synaptic Axons', + description='', + default=True) + +bpy.types.Scene.NMV_DisplayPostSynapticDendrites = bpy.props.BoolProperty( + name='Post-Synaptic Dendrites', + description='', + default=True) + +bpy.types.Scene.NMV_DisplayPostSynapticAxons = bpy.props.BoolProperty( + name='Post-Synaptic Axons', + description='', + default=True) + +bpy.types.Scene.NMV_PreSynapticDendritesColor = bpy.props.FloatVectorProperty( + name='', + subtype='COLOR', default=nmv.enums.Color.BASAL_DENDRITES, min=0.0, max=1.0, + description='') + +bpy.types.Scene.NMV_PreSynapticAxonsColor = bpy.props.FloatVectorProperty( + name='', + subtype='COLOR', default=nmv.enums.Color.AXONS, min=0.0, max=1.0, + description='') + +bpy.types.Scene.NMV_PostSynapticDendritesColor = bpy.props.FloatVectorProperty( + name='', + subtype='COLOR', default=nmv.enums.Color.BASAL_DENDRITES, min=0.0, max=1.0, + description='') + +bpy.types.Scene.NMV_PostSynapticAxonsColor = bpy.props.FloatVectorProperty( + name='', + subtype='COLOR', default=nmv.enums.Color.AXONS, min=0.0, max=1.0, + description='') + +# Shared neuron parameters ######################################################################### +bpy.types.Scene.NMV_SynapticsUnifyRadius = bpy.props.BoolProperty( + name='Unify Branches Radii', + description='', + default=True) + +# The unified radius of all the synapses +bpy.types.Scene.NMV_SynapticsUnifiedNeuronRadius = bpy.props.FloatProperty( + name='Neuron Radius', + description='The unified radius of all the branches of the neuron. Applicable values [0.1-5]', + default=0.5, min=0.1, max=5) + +# Number of synapses ############################################################################### +bpy.types.Scene.NMV_SynapticsNumberAfferentSynapses = bpy.props.IntProperty( + name="Count", + description="The number of afferent synapses found", + default=0, min=0, max=1000000) + +bpy.types.Scene.NMV_SynapticsNumberEfferentSynapses = bpy.props.IntProperty( + name="Count", + description="The number of efferent synapses found", + default=0, min=0, max=1000000) + +bpy.types.Scene.NMV_SynapticsNumberExcitatorySynapses = bpy.props.IntProperty( + name="Count", + description="The number of excitatory synapses found", + default=0, min=0, max=1000000) + +bpy.types.Scene.NMV_SynapticsNumberInhibitorySynapses = bpy.props.IntProperty( + name="Count", + description="The number of inhibitory synapses found", + default=0, min=0, max=1000000) + +bpy.types.Scene.NMV_SynapticsNumberSharedSynapses = bpy.props.IntProperty( + name="Count", + description="The number of shared synapses between the two neurons", + default=0, min=0, max=1000000) + +# Performance ###################################################################################### +bpy.types.Scene.NMV_SynapticReconstructionTime = bpy.props.FloatProperty( + name="Time (Sec)", + description="The time it takes to reconstruct the synaptome", + default=0, min=0, max=1000000) + +# Rendering options ################################################################################ +# Rendering resolution +bpy.types.Scene.NMV_SynapticsRenderingResolution = bpy.props.EnumProperty( + items=[(nmv.enums.Rendering.Resolution.FIXED, + 'Fixed', + 'Renders an image at a specific resolution'), + (nmv.enums.Rendering.Resolution.TO_SCALE, + 'To Scale', + 'Renders an image at a multiple factor of the exact scale in (um)')], + name='Type', + default=nmv.enums.Rendering.Resolution.FIXED) + +# Rendering view +bpy.types.Scene.NMV_SynapticsRenderingView = bpy.props.EnumProperty( + items=[(nmv.enums.Rendering.View.WIDE_SHOT, + 'Wide Shot', + 'Renders an image of the full view'), + (nmv.enums.Rendering.View.CLOSEUP, + 'Close Up', + 'Renders a close up image the focuses on the soma of the chosen neuron')], + name='View', default=nmv.enums.Rendering.View.WIDE_SHOT) + +# Render the corresponding scale bar on the resulting image +bpy.types.Scene.NMV_SynapticsScaleBar = bpy.props.BoolProperty( + name='Add Scale Bar', + description='Render the scale bar on the resulting image', + default=False) + +# Image format +bpy.types.Scene.NMV_SynapticsImageFormat = bpy.props.EnumProperty( + items=nmv.enums.Image.Extension.IMAGE_EXTENSION_ITEMS, + name='', + default=nmv.enums.Image.Extension.PNG) + +# Image resolution +bpy.types.Scene.NMV_SynapticsFrameResolution = bpy.props.IntProperty( + name='Resolution', + description='The resolution of the image generated from rendering the mesh', + default=nmv.consts.Image.DEFAULT_RESOLUTION, min=128, max=1024 * 10) + +# Frame scale factor 'for rendering to scale option ' +bpy.types.Scene.NMV_SynapticsFrameScaleFactor = bpy.props.FloatProperty( + name="Scale", default=1.0, min=1.0, max=100.0, + description="The scale factor for rendering a mesh to scale") + +# Rendering closeup size +bpy.types.Scene.NMV_SynapticsCloseUpSize = bpy.props.FloatProperty( + name='Size', + description='The size of the view that will be rendered in microns', + default=20, min=5, max=100) + +# Rendering time +bpy.types.Scene.NMV_SynapticsRenderingTime = bpy.props.FloatProperty( + name='Rendering (Sec)', + description='The time it takes to render the synaptics scene into an image', + default=0, min=0, max=1000000) + +# Synaptics json file +bpy.types.Scene.NMV_SynapticsJsonFile = bpy.props.StringProperty( + name="Synaptics File", + description="Select a specific synaptics json file that contains a color-coded list " + "of synapses", + default=nmv.consts.Strings.SELECT_FILE, maxlen=2048, subtype='FILE_PATH') + +# Shader applied to the synaptics elements +bpy.types.Scene.NMV_SynapticsShader = bpy.props.EnumProperty( + items=nmv.enums.Shader.MATERIAL_ITEMS, + name='', + default=nmv.enums.Shader.LAMBERT_WARD) + +# Synaptics projection name +bpy.types.Scene.NMV_SynapticsProjectionName = bpy.props.StringProperty( + name="Projection Name", + description="Enter the name of the projection.", + default='Enter Projection Name', maxlen=2048 +) \ No newline at end of file diff --git a/nmv/interface/ui/synaptics/registration.py b/nmv/interface/ui/synaptics/registration.py new file mode 100644 index 000000000..47aba0170 --- /dev/null +++ b/nmv/interface/ui/synaptics/registration.py @@ -0,0 +1,64 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @register_panel +#################################################################################################### +def register_panel(): + """Registers all the classes in this panel""" + + from .panel import NMV_SynapticsPanel + from .ops_reconstruction import NMV_ReconstructSynaptics + from .ops_rendering import NMV_RenderSynapticsFront + from .ops_rendering import NMV_RenderSynapticsSide + from .ops_rendering import NMV_RenderSynapticsTop + + # Panel + bpy.utils.register_class(NMV_SynapticsPanel) + + # Button(s) + bpy.utils.register_class(NMV_ReconstructSynaptics) + bpy.utils.register_class(NMV_RenderSynapticsFront) + bpy.utils.register_class(NMV_RenderSynapticsSide) + bpy.utils.register_class(NMV_RenderSynapticsTop) + + +#################################################################################################### +# @unregister_panel +#################################################################################################### +def unregister_panel(): + """Un-registers all the classes in this panel""" + + from .panel import NMV_SynapticsPanel + from .ops_reconstruction import NMV_ReconstructSynaptics + from .ops_rendering import NMV_RenderSynapticsFront + from .ops_rendering import NMV_RenderSynapticsSide + from .ops_rendering import NMV_RenderSynapticsTop + + # Panel + bpy.utils.unregister_class(NMV_SynapticsPanel) + + # Button(s) + bpy.utils.unregister_class(NMV_ReconstructSynaptics) + bpy.utils.unregister_class(NMV_RenderSynapticsFront) + bpy.utils.unregister_class(NMV_RenderSynapticsSide) + bpy.utils.unregister_class(NMV_RenderSynapticsTop) + diff --git a/nmv/options/synaptics_options.py b/nmv/options/synaptics_options.py new file mode 100644 index 000000000..a712e537c --- /dev/null +++ b/nmv/options/synaptics_options.py @@ -0,0 +1,105 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.consts +import nmv.enums + + +#################################################################################################### +# @SynapticsOptions +#################################################################################################### +class SynapticsOptions: + """Synaptics options""" + + ################################################################################################ + # @__init__ + ################################################################################################ + def __init__(self): + """Constructor""" + + # Synapse visualization use case + self.use_case = None + + # In case + self.synaptics_json_file = None + + # Reconstruction method + self.synapses_radius = nmv.consts.Synaptics.SYNAPSES_RADIUS + + # Colors for excitatory and inhibitory synapses + self.excitatory_synapses_color = nmv.enums.Color.EXCITATORY_SYNAPSES + self.inhibitory_synapses_color = nmv.enums.Color.INHIBITORY_SYNAPSES + + # Color coding schemes of afferent and efferent synapses + self.afferent_color_coding = nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR + self.efferent_color_coding = nmv.enums.Synaptics.ColorCoding.SINGLE_COLOR + + # Afferent and efferent colors + self.afferent_synapses_color = nmv.enums.Color.AFFERENT_SYNAPSES + self.efferent_synapses_color = nmv.enums.Color.EFFERENT_SYNAPSES + + # If the single color option is used, assign its value to this parameter + self.synapses_color = nmv.enums.Color.SYNAPSES + + # The percentage of the synapses loaded on the neuron + self.percentage = nmv.consts.Synaptics.SYNAPSES_PERCENTAGE + + # The GIDs of pre- and post-synaptic neurons that are only used to visualize shared synapses + self.pre_synaptic_gid = None + self.post_synaptic_gid = None + + # Parameters for a single neuron and NOT a PAIR + self.display_dendrites = True + self.display_axons = True + self.dendrites_color = nmv.enums.Color.BASAL_DENDRITES + self.axons_color = nmv.enums.Color.AXONS + + # A list of the color map of the pre- or post-synaptic mtypes and etypes synapses + # NOTE: This list is initialized once a neuron is loaded from the circuit + self.mtypes_colors = list() + self.etypes_colors = list() + + # Parameters for a PAIR, NOT a SINGLE neuron + self.display_pre_synaptic_dendrites = True + self.display_pre_synaptic_axons = True + self.pre_synaptic_dendrites_color = nmv.enums.Color.BASAL_DENDRITES + self.pre_synaptic_axons_color = nmv.enums.Color.AXONS + self.display_post_synaptic_dendrites = True + self.display_post_synaptic_axons = True + self.post_synaptic_dendrites_color = nmv.enums.Color.BASAL_DENDRITES + self.post_synaptic_axons_color = nmv.enums.Color.AXONS + + # Neuron radius + self.unify_branch_radii = True + self.unified_radius = 1.0 + + self.shader = nmv.enums.Shader.LAMBERT_WARD + + self.customized_synaptics_group = None + self.customized_synaptics_colors = list() + + self.projection_name = None + + + + + + + + + diff --git a/nmv/skeleton/ops/skeleton_radius_alternation_ops.py b/nmv/skeleton/ops/skeleton_radius_alternation_ops.py new file mode 100644 index 000000000..72e735a32 --- /dev/null +++ b/nmv/skeleton/ops/skeleton_radius_alternation_ops.py @@ -0,0 +1,82 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Internal imports +import nmv.enums +import nmv.skeleton + + +#################################################################################################### +# @update_arbors_radii +#################################################################################################### +def update_arbors_radii(morphology, + morphology_options): + """Update the radii of the arbors of a given morphology skeleton. + + :param morphology: + A given morphology skeleton. + :param morphology_options: + Morphology options. + """ + + nmv.logger.info('Updating radii') + + # Selected option + option = morphology_options.arbors_radii + + # Filtered + if option == nmv.enums.Skeleton.Radii.FILTERED: + nmv.skeleton.ops.apply_operation_to_morphology( + *[morphology, nmv.skeleton.ops.set_section_radii_between_given_range, + morphology_options.minimum_threshold_radius, + morphology_options.maximum_threshold_radius]) + + elif option == nmv.enums.Skeleton.Radii.UNIFIED: + nmv.skeleton.ops.apply_operation_to_morphology( + *[morphology, nmv.skeleton.ops.unify_section_radii, + morphology_options.samples_unified_radii_value]) + + elif option == nmv.enums.Skeleton.Radii.UNIFIED_PER_ARBOR_TYPE: + nmv.skeleton.ops.apply_operation_to_morphology( + *[morphology, nmv.skeleton.ops.unify_section_radii_based_on_type, + morphology_options.axon_samples_unified_radii_value, + morphology_options.apical_dendrite_samples_unified_radii_value, + morphology_options.basal_dendrites_samples_unified_radii_value]) + + elif option == nmv.enums.Skeleton.Radii.SCALED: + nmv.skeleton.ops.apply_operation_to_morphology( + *[morphology, nmv.skeleton.ops.scale_section_radii, + morphology_options.sections_radii_scale]) + + +#################################################################################################### +# @set_smallest_sample_radius_to_value +#################################################################################################### +def set_smallest_sample_radius_to_value(morphology, + smallest_radius=0.1): + """Sets the radius of the smallest sample to a given value. This function is mainly used for + the meshing builder to avoid meshing artifacts. + + :param morphology: + A given morphology skeleton. + :param smallest_radius: + The value of the smallest radius of the samples in the morphology. + """ + + nmv.logger.info('Verifying samples radii') + nmv.skeleton.ops.apply_operation_to_morphology( + *[morphology, nmv.skeleton.ops.verify_smallest_radius_to_value, smallest_radius]) diff --git a/nmv/utilities/collections.py b/nmv/utilities/collections.py new file mode 100644 index 000000000..fea099f3d --- /dev/null +++ b/nmv/utilities/collections.py @@ -0,0 +1,132 @@ +#################################################################################################### +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender imports +import bpy + + +#################################################################################################### +# @create_new_collection +#################################################################################################### +def create_new_collection(name='Collection'): + """Creates a new collection in the scene to group the objects into it. + + :param name: + Collection name. + :return: + A reference to the created collection + """ + + # Create the collection + collection = bpy.data.collections.new(name=name) + + # Link it to the scene to appear in the UI + bpy.context.scene.collection.children.link(collection) + + # Return a reference to it + return collection + + +#################################################################################################### +# @delete_collection +#################################################################################################### +def delete_collection(collection, + remove_contents=True): + """Deletes a given collection and deletes its contents. + + :param collection: + A reference to the collection that needs to be deleted. + :param remove_contents: + If this flag is set, all the content of the collection will be removed. + """ + + # Remove the contents + if remove_contents: + for i_object in collection.objects: + bpy.data.objects.remove(i_object, do_unlink=True) + + # Delete the collection + bpy.data.collections.remove(collection) + + +#################################################################################################### +# @delete_collection_by_name +#################################################################################################### +def delete_collection_by_name(name): + """Deletes a collection identified by its name. + + :param name: + Collection name. + """ + + # Get the collection + try: + collection = bpy.data.collections.get(name) + except IndexError: + return + + # Delete the collection + delete_collection(collection) + + +#################################################################################################### +# @move_objects_to_collection +#################################################################################################### +def move_objects_to_collection(collection, + objects_list): + """Moves a given list of objects into a given collection. + + :param collection: + A reference to the collection. + :param objects_list: + A list of objects to be moved to the given collection. + """ + + for i_object in objects_list: + + # Unlink from the collections + for i_collection in i_object.users_collection: + i_collection.objects.unlink(i_object) + + # Link to the current collection + if i_object.name not in collection.objects: + collection.objects.link(i_object) + + +#################################################################################################### +# @create_collection_with_objects +#################################################################################################### +def create_collection_with_objects(name, + objects_list): + """Creates a new collection and moves a list of objects into it. + + :param name: + Collection name. + :param objects_list: + A list of objects to be moved to the collection after its creation. + :return: + A reference to the created collection + """ + + # Create the collection + collection = create_new_collection(name=name) + + # Move the objects into this collection + move_objects_to_collection(collection=collection, objects_list=objects_list) + + # Return a reference to this collection + return collection diff --git a/nmv/utilities/version.py b/nmv/utilities/version.py index 813cb3b64..2b426fe7b 100644 --- a/nmv/utilities/version.py +++ b/nmv/utilities/version.py @@ -32,7 +32,6 @@ def get_nmv_version(): # Load the version from the version file version_file_path = '%s/../../__init__.py' % os.path.dirname(os.path.realpath(__file__)) version_file = open(version_file_path, 'r') - version_string = '' for line in version_file: if '"version":' in line: string = line.split('\"version\": (')[1].split(')')[0].split(', ') diff --git a/scripts/astrocyte-collage/astrocytes-collage.py b/scripts/astrocyte-collage/astrocytes-collage.py deleted file mode 100644 index 1ab7afb79..000000000 --- a/scripts/astrocyte-collage/astrocytes-collage.py +++ /dev/null @@ -1,212 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -import sys, os -import argparse - -import bpy -import nmv.file -import nmv.rendering -import nmv.scene -import nmv.mesh - - -#################################################################################################### -# @parse_command_line_arguments -#################################################################################################### -def parse_command_line_arguments(): - """ - Parses the command line arguments. - NOTE: We do not define a destination to facilitate printing to a string and doing another - iteration of parsing for blender. - - :return: A structure with all the system options. - """ - - # Create an argument parser, and then add the options one by one - parser = argparse.ArgumentParser() - - # Output directory - arg_help = 'Output directory' - parser.add_argument('--output-directory', - action='store', default=None, - help=arg_help) - - # Morphology directory - arg_help = 'Morphology (.blend) directory containing multiple files' - parser.add_argument('--meshes-directory', - action='store', default=None, - help=arg_help) - - # Distance between the morphologies - arg_help = 'Distance between the mtype groups' - parser.add_argument('--distance', - action='store', type=float, default=100, - help=arg_help) - - # Frame name - arg_help = 'The name of the output image' - parser.add_argument('--frame-name', - action='store', default='image', - help=arg_help) - - # Base resolution of the frame - arg_help = 'Resolution scale factor of the frame' - parser.add_argument('--resolution-scale-factor', - action='store', type=int, default=5, - help=arg_help) - - # Parse the arguments, and return a list of them - return parser.parse_args() - - -#################################################################################################### -# @load_morphologies -#################################################################################################### -def load_meshes(meshes_directory): - - # List all the files in the directory (.blend) - mesh_files = nmv.file.ops.get_files_in_directory(directory=meshes_directory, - file_extension='blend') - - # Sort - mesh_files.sort() - - # A list of all the meshes - meshes = [] - - for mesh_file in mesh_files: - - # Load the mesh - scene_objects = nmv.file.import_object_from_blend_file(meshes_directory, mesh_file) - - # Get the mesh from all the objects in the scene - for scene_object in scene_objects: - if scene_object.type == 'MESH': - - # Adjust it - nmv.scene.select_object(scene_object) - nmv.scene.set_active_object(scene_object) - bpy.context.object.data.use_auto_texspace = False - bpy.context.object.data.texspace_size[0] = 5 - bpy.context.object.data.texspace_size[1] = 5 - bpy.context.object.data.texspace_size[2] = 5 - nmv.scene.deselect_object(scene_object) - - meshes.append(scene_object) - else: - nmv.scene.ops.delete_list_objects([scene_object]) - - return meshes - - -#################################################################################################### -# @how_many_items_in_list -#################################################################################################### -def how_many_items_in_list(mtype_list, layer_sub_string): - """ - Find how many mtypes in the list. - - :param mtype_list: - :param layer_sub_string: - :return: - """ - return [s for s in mtype_list if layer_sub_string in s] - - -#################################################################################################### -# @Run the main function if invoked from the command line. -#################################################################################################### -if __name__ == "__main__": - - # Ignore blender extra arguments required to launch blender given to the command line interface - args = sys.argv - sys.argv = args[args.index("--") + 1:] - - # Parse the command line arguments, filter them and report the errors - args = parse_command_line_arguments() - - # Column dimensions - # - # - # - # - # - # - - layer_1_height = 164.94915873 - layer_2_height = 148.87602025 - layer_3_height = 352.92508322 - layer_4_height = 189.57183895 - layer_5_height = 525.05585701 - layer_6_height = 700.37845971 - - # Assuming that layer 6 starts at y_cords = 0 - layer_6_y_center = layer_6_height / 2.0 - layer_5_y_center = layer_6_y_center + (layer_6_height / 2.0) + (layer_5_height / 2.0) - layer_4_y_center = layer_5_y_center + (layer_5_height / 2.0) + (layer_4_height / 2.0) - layer_3_y_center = layer_4_y_center + (layer_4_height / 2.0) + (layer_3_height / 2.0) - layer_2_y_center = layer_3_y_center + (layer_3_height / 2.0) + (layer_2_height / 2.0) - layer_1_y_center = layer_2_y_center + (layer_2_height / 2.0) + (layer_1_height / 2.0) - - # Clear the default scene - nmv.scene.ops.clear_scene() - - # Get all the mtypes from the directories - mtypes = os.listdir(args.meshes_directory) - - # Sort the mtypes - mtypes.sort() - - # Load the meshes from the blend files - meshes = load_meshes(args.meshes_directory) - - # Get the number of meshes - n_meshes = len(meshes) - - if n_meshes == 0: - print('Zero meshes loaded!') - exit(0) - - # Shift the cells - for i, mesh in enumerate(meshes): - - # Compute the translation factor - x_translation = (i * args.distance) - - # Deselect all the cells in the scene - nmv.scene.ops.deselect_all() - - # Get the location of the mesh - mesh_location = nmv.scene.ops.get_object_location(mesh) - - # Update the x-coordinate - mesh_location[0] = x_translation - - # Relocate the mesh - nmv.scene.ops.set_object_location(mesh, mesh_location) - - # Export the scene to keep a reference for it later - nmv.file.export_scene_to_blend_file(args.output_directory, args.frame_name) - - # Render the scene - #rendering_path = '%s/%s' % (args.output_directory, args.frame_name) - ##camera = nmv.rendering.Camera() - #camera.render_scene_to_scale( - # scale_factor=args.resolution_scale_factor,image_name = rendering_path, - # keep_camera_in_scene=True) - diff --git a/scripts/astrocyte-collage/astrocytes-morphology-collage.py b/scripts/astrocyte-collage/astrocytes-morphology-collage.py deleted file mode 100644 index c6e65d8b3..000000000 --- a/scripts/astrocyte-collage/astrocytes-morphology-collage.py +++ /dev/null @@ -1,213 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -import sys, os -import argparse - -import bpy -import nmv.file -import nmv.rendering -import nmv.scene -import nmv.mesh - - -#################################################################################################### -# @parse_command_line_arguments -#################################################################################################### -def parse_command_line_arguments(): - """ - Parses the command line arguments. - NOTE: We do not define a destination to facilitate printing to a string and doing another - iteration of parsing for blender. - - :return: A structure with all the system options. - """ - - # Create an argument parser, and then add the options one by one - parser = argparse.ArgumentParser() - - # Output directory - arg_help = 'Output directory' - parser.add_argument('--output-directory', - action='store', default=None, - help=arg_help) - - # Morphology directory - arg_help = 'Morphology (.blend) directory containing multiple files' - parser.add_argument('--meshes-directory', - action='store', default=None, - help=arg_help) - - # Distance between the morphologies - arg_help = 'Distance between the mtype groups' - parser.add_argument('--distance', - action='store', type=float, default=100, - help=arg_help) - - # Frame name - arg_help = 'The name of the output image' - parser.add_argument('--frame-name', - action='store', default='image', - help=arg_help) - - # Base resolution of the frame - arg_help = 'Resolution scale factor of the frame' - parser.add_argument('--resolution-scale-factor', - action='store', type=int, default=5, - help=arg_help) - - # Parse the arguments, and return a list of them - return parser.parse_args() - - -#################################################################################################### -# @load_morphologies -#################################################################################################### -def load_meshes(meshes_directory): - - # List all the files in the directory (.blend) - mesh_files = nmv.file.ops.get_files_in_directory(directory=meshes_directory, - file_extension='blend') - - # Sort - mesh_files.sort() - - # A list of all the meshes - meshes = [] - - for mesh_file in mesh_files: - - # Load the mesh - scene_objects = nmv.file.import_object_from_blend_file(meshes_directory, mesh_file) - - # Get the mesh from all the objects in the scene - for scene_object in scene_objects: - if scene_object.type == 'CURVE' or scene_object.type == 'MESH': - - # Adjust it - nmv.scene.select_object(scene_object) - nmv.scene.set_active_object(scene_object) - bpy.context.object.data.use_auto_texspace = False - bpy.context.object.data.texspace_size[0] = 5 - bpy.context.object.data.texspace_size[1] = 5 - bpy.context.object.data.texspace_size[2] = 5 - nmv.scene.deselect_object(scene_object) - - meshes.append(scene_object) - else: - nmv.scene.ops.delete_list_objects([scene_object]) - - return meshes - - -#################################################################################################### -# @how_many_items_in_list -#################################################################################################### -def how_many_items_in_list(mtype_list, layer_sub_string): - """ - Find how many mtypes in the list. - - :param mtype_list: - :param layer_sub_string: - :return: - """ - return [s for s in mtype_list if layer_sub_string in s] - - -#################################################################################################### -# @ Run the main function if invoked from the command line. -#################################################################################################### -if __name__ == "__main__": - - # Ignore blender extra arguments required to launch blender given to the command line interface - args = sys.argv - sys.argv = args[args.index("--") + 1:] - - # Parse the command line arguments, filter them and report the errors - args = parse_command_line_arguments() - - # Column dimensions - # - # - # - # - # - # - - layer_1_height = 164.94915873 - layer_2_height = 148.87602025 - layer_3_height = 352.92508322 - layer_4_height = 189.57183895 - layer_5_height = 525.05585701 - layer_6_height = 700.37845971 - - # Assuming that layer 6 starts at y_cords = 0 - layer_6_y_center = layer_6_height / 2.0 - layer_5_y_center = layer_6_y_center + (layer_6_height / 2.0) + (layer_5_height / 2.0) - layer_4_y_center = layer_5_y_center + (layer_5_height / 2.0) + (layer_4_height / 2.0) - layer_3_y_center = layer_4_y_center + (layer_4_height / 2.0) + (layer_3_height / 2.0) - layer_2_y_center = layer_3_y_center + (layer_3_height / 2.0) + (layer_2_height / 2.0) - layer_1_y_center = layer_2_y_center + (layer_2_height / 2.0) + (layer_1_height / 2.0) - - # Clear the default scene - nmv.scene.ops.clear_scene() - - # Get all the mtypes from the directories - mtypes = os.listdir(args.meshes_directory) - - # Sort the mtypes - mtypes.sort() - - # Load the meshes from the blend files - meshes = load_meshes(args.meshes_directory) - - # Get the number of meshes - n_meshes = len(meshes) - - if n_meshes == 0: - print('Zero meshes loaded!') - exit(0) - - ''' - # Shift the cells - for i, mesh in enumerate(meshes): - - # Compute the translation factor - x_translation = (i * args.distance) - - # Deselect all the cells in the scene - nmv.scene.ops.deselect_all() - - # Get the location of the mesh - mesh_location = nmv.scene.ops.get_object_location(mesh) - - # Update the x-coordinate - mesh_location[0] = x_translation - - # Relocate the mesh - nmv.scene.ops.set_object_location(mesh, mesh_location) - ''' - # Export the scene to keep a reference for it later - nmv.file.export_scene_to_blend_file(args.output_directory, args.frame_name) - - # Render the scene - #rendering_path = '%s/%s' % (args.output_directory, args.frame_name) - ##camera = nmv.rendering.Camera() - #camera.render_scene_to_scale( - # scale_factor=args.resolution_scale_factor,image_name = rendering_path, - # keep_camera_in_scene=True) - diff --git a/scripts/synaptics/auxiliary/compositor.py b/scripts/synaptics-refactor/auxiliary/compositor.py similarity index 100% rename from scripts/synaptics/auxiliary/compositor.py rename to scripts/synaptics-refactor/auxiliary/compositor.py diff --git a/scripts/synaptics/auxiliary/copy-synaptomes.py b/scripts/synaptics-refactor/auxiliary/copy-synaptomes.py similarity index 100% rename from scripts/synaptics/auxiliary/copy-synaptomes.py rename to scripts/synaptics-refactor/auxiliary/copy-synaptomes.py diff --git a/scripts/synaptics/auxiliary/create_portal_structure.py b/scripts/synaptics-refactor/auxiliary/create_portal_structure.py similarity index 100% rename from scripts/synaptics/auxiliary/create_portal_structure.py rename to scripts/synaptics-refactor/auxiliary/create_portal_structure.py diff --git a/scripts/synaptics/auxiliary/create_portal_structure_wo_directories.py b/scripts/synaptics-refactor/auxiliary/create_portal_structure_wo_directories.py similarity index 100% rename from scripts/synaptics/auxiliary/create_portal_structure_wo_directories.py rename to scripts/synaptics-refactor/auxiliary/create_portal_structure_wo_directories.py diff --git a/scripts/synaptics/core/circuit_data.py b/scripts/synaptics-refactor/core/circuit_data.py similarity index 100% rename from scripts/synaptics/core/circuit_data.py rename to scripts/synaptics-refactor/core/circuit_data.py diff --git a/scripts/synaptics/core/color_map.py b/scripts/synaptics-refactor/core/color_map.py similarity index 100% rename from scripts/synaptics/core/color_map.py rename to scripts/synaptics-refactor/core/color_map.py diff --git a/scripts/synaptics/core/font.ttf b/scripts/synaptics-refactor/core/font.ttf similarity index 100% rename from scripts/synaptics/core/font.ttf rename to scripts/synaptics-refactor/core/font.ttf diff --git a/scripts/synaptics-refactor/core/neuron_data.py b/scripts/synaptics-refactor/core/neuron_data.py new file mode 100644 index 000000000..0aea80a8d --- /dev/null +++ b/scripts/synaptics-refactor/core/neuron_data.py @@ -0,0 +1,105 @@ +#################################################################################################### +# Copyright (c) 2020 - 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import random +import os +import sys + +# Internal imports +import_paths = ['core'] +for import_path in import_paths: + sys.path.append(('%s/%s' % (os.path.dirname(os.path.realpath(__file__)), import_path))) + +import circuit_data + +# BBP imports +import bluepy +from bluepy import Circuit + +# Blender +from mathutils import Vector, Matrix + +# Internal imports +import nmv.bbox +import nmv.builders +import nmv.consts +import nmv.enums +import nmv.geometry +import nmv.options +import nmv.mesh +import nmv.rendering +import nmv.scene +import nmv.shading +import nmv.utilities + + +#################################################################################################### +# @create_neuron_mesh +#################################################################################################### +def create_neuron_mesh(circuit, + gid, + color, + material_type=nmv.enums.Shader.LAMBERT_WARD): + """Creates the mesh of the neuron. + + :param circuit: + BBP circuit. + :param gid: + Neuron GID. + :param neuron_material: + The color of the neuron in a RGB Vector((R, G, B)) format + :return: + A reference to the created neuron mesh. + """ + + # Get the path of the morphology from the circuit + morphology_path = circuit.morph.get_filepath(int(gid)) + + # Read the morphology and get its NMV object, and ensure that it is centered at the origin + morphology = nmv.file.read_morphology_with_morphio( + morphology_file_path=morphology_path, + morphology_format=nmv.file.get_morphology_file_format(morphology_file_path=morphology_path), + center_at_origin=True) + + # Adjust the label to be set according to the GID not the morphology label + morphology.label = str(gid) + + # Create default NMV options with fixed radius value for the visualization + nmv_options = nmv.options.NeuroMorphoVisOptions() + nmv_options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.UNIFIED + nmv_options.morphology.samples_unified_radii_value = 1.0 + nmv_options.morphology.axon_branch_order = 1e5 + nmv_options.shading.mesh_material = material_type + nmv_options.mesh.soma_type = nmv.enums.Soma.Representation.META_BALLS + + # Create a meta balls meshing builder + mesh_builder = nmv.builders.PiecewiseBuilder(morphology=morphology, options=nmv_options) + + # Create the neuron mesh + neuron_mesh = mesh_builder.reconstruct_proxy_mesh() + + # Smooth the mesh to make it look nice + # nmv.mesh.smooth_object(mesh_object=neuron_mesh, level=1) + + # Add the material top the reconstructed mesh + neuron_material = nmv.shading.create_material( + name='neuron_%s' % str(gid), color=color, material_type=material_type) + nmv.shading.set_material_to_object(mesh_object=neuron_mesh, material_reference=neuron_material) + + # Return a reference to the neuron mesh + return neuron_mesh diff --git a/scripts/synaptics/core/parsing.py b/scripts/synaptics-refactor/core/parsing.py similarity index 99% rename from scripts/synaptics/core/parsing.py rename to scripts/synaptics-refactor/core/parsing.py index fa0d747db..5844d4b92 100644 --- a/scripts/synaptics/core/parsing.py +++ b/scripts/synaptics-refactor/core/parsing.py @@ -1,5 +1,5 @@ #################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project +# Copyright (c) 2016 - 2023, EPFL / Blue Brain Project # Marwan Abdellah # # This file is part of NeuroMorphoVis diff --git a/scripts/synaptics/core/rendering.py b/scripts/synaptics-refactor/core/rendering.py similarity index 100% rename from scripts/synaptics/core/rendering.py rename to scripts/synaptics-refactor/core/rendering.py diff --git a/scripts/synaptics/core/slurm.py b/scripts/synaptics-refactor/core/slurm.py similarity index 100% rename from scripts/synaptics/core/slurm.py rename to scripts/synaptics-refactor/core/slurm.py diff --git a/scripts/synaptics/core/synaptic_pathways.py b/scripts/synaptics-refactor/core/synaptic_pathways.py similarity index 99% rename from scripts/synaptics/core/synaptic_pathways.py rename to scripts/synaptics-refactor/core/synaptic_pathways.py index 43f45b63e..543488d12 100644 --- a/scripts/synaptics/core/synaptic_pathways.py +++ b/scripts/synaptics-refactor/core/synaptic_pathways.py @@ -88,9 +88,10 @@ def create_neuron_meshes_with_piecewise_builder(circuit, # Use the H5 morphology loader to load this file # Don't center the morphology, as it is assumed to be cleared and reviewed by the team - print(morphology_path) morphology = nmv.file.read_morphology_with_morphio( - morphology_file_path=morphology_path, center_at_origin=True) + morphology_file_path=morphology_path, + morphology_format=nmv.file.get_morphology_file_format(morphology_file_path=morphology_path), + center_at_origin=True) # h5_reader = nmv.file.H5Reader(h5_file=h5_morphology_path) # morphology = h5_reader.read_file() diff --git a/scripts/synaptics/core/synaptome.py b/scripts/synaptics-refactor/core/synaptome.py similarity index 91% rename from scripts/synaptics/core/synaptome.py rename to scripts/synaptics-refactor/core/synaptome.py index 746b5971e..ee31fb3ee 100644 --- a/scripts/synaptics/core/synaptome.py +++ b/scripts/synaptics-refactor/core/synaptome.py @@ -47,6 +47,60 @@ import nmv.shading import nmv.utilities +import numpy +from tqdm import tqdm + + + + + +#################################################################################################### +# @create_color_coded_synapses_mesh +#################################################################################################### +def create_color_coded_synapses_mesh(circuit, + gid, + color_coded_synapses_list, + synapse_size, + inverted_transformation): + + for group in color_coded_synapses_list: + + # The key is the color code in HEX + key = group[0] + + # The list of IDs + synapse_ids = group[1] + + # The post-synaptic position + positions = circuit.connectome.synapse_positions( + numpy.array(synapse_ids), 'post', 'center').values.tolist() + + # Update the positions taking into consideration the transformation + for i in range(len(positions)): + position = Vector((positions[i][0], positions[i][1], positions[i][2])) + position = inverted_transformation @ position + positions[i] = position + + return create_synapse_group_mesh_using_spheres(positions=positions, synapse_size=4, group_name=key) + + exit(0) + + + + # Get all the IDs of the afferent, i.e. post-synaptic, synapses + afferent_synapses_ids = circuit.connectome.afferent_synapses(gid) + print(type(afferent_synapses_ids)) + # Get their positions + post_synaptic_positions = circuit.connectome.synapse_positions( + afferent_synapses_ids, 'post', 'center').values.tolist() + + + + + + print(afferent_synapses_ids) + exit(0) + #################################################################################################### # @create_mtype_based_synapses_mesh diff --git a/scripts/synaptics/create-synaptic-pathway.py b/scripts/synaptics-refactor/create-synaptic-pathway.py similarity index 100% rename from scripts/synaptics/create-synaptic-pathway.py rename to scripts/synaptics-refactor/create-synaptic-pathway.py diff --git a/scripts/synaptics/create-synaptic-pathways-cluster.py b/scripts/synaptics-refactor/create-synaptic-pathways-cluster.py similarity index 100% rename from scripts/synaptics/create-synaptic-pathways-cluster.py rename to scripts/synaptics-refactor/create-synaptic-pathways-cluster.py diff --git a/scripts/synaptics/create-synaptic-pathways-cluster.sh b/scripts/synaptics-refactor/create-synaptic-pathways-cluster.sh similarity index 99% rename from scripts/synaptics/create-synaptic-pathways-cluster.sh rename to scripts/synaptics-refactor/create-synaptic-pathways-cluster.sh index b137a682d..8497de088 100755 --- a/scripts/synaptics/create-synaptic-pathways-cluster.sh +++ b/scripts/synaptics-refactor/create-synaptic-pathways-cluster.sh @@ -56,7 +56,7 @@ SYNAPSE_COLOR='255_255_0' SYNAPSE_SIZE='8.0' # Base image resolution -IMAGE_RESOLUTION='4000' +IMAGE_RESOLUTION='6000' # The background image the frames will get blended to BACKGROUND_IMAGE='/gpfs/bbp.cscs.ch/project/proj83/visualization-SSCXDIS-178/synaptomes-data/backgrounds/background_1900x1080.png' diff --git a/scripts/synaptics/create-synaptic-pathways.py b/scripts/synaptics-refactor/create-synaptic-pathways.py similarity index 100% rename from scripts/synaptics/create-synaptic-pathways.py rename to scripts/synaptics-refactor/create-synaptic-pathways.py diff --git a/scripts/synaptics/create-synaptic-pathways.sh b/scripts/synaptics-refactor/create-synaptic-pathways.sh similarity index 100% rename from scripts/synaptics/create-synaptic-pathways.sh rename to scripts/synaptics-refactor/create-synaptic-pathways.sh diff --git a/scripts/synaptics/create-synaptic-projection.py b/scripts/synaptics-refactor/create-synaptic-projection.py similarity index 96% rename from scripts/synaptics/create-synaptic-projection.py rename to scripts/synaptics-refactor/create-synaptic-projection.py index 03484fb01..d0a6abf4c 100644 --- a/scripts/synaptics/create-synaptic-projection.py +++ b/scripts/synaptics-refactor/create-synaptic-projection.py @@ -41,6 +41,7 @@ import nmv.scene import nmv.shading import nmv.utilities +import nmv.interface # BBP imports from bluepy import Synapse, Circuit @@ -133,7 +134,7 @@ def create_neuron_mesh(circuit, # Use the H5 morphology loader to load this file # Don't center the morphology, as it is assumed to be cleared and reviewed by the team - h5_reader = nmv.file.H5Reader(h5_file=h5_morphology_path, center_morphology=False) + h5_reader = nmv.file.H5Reader(h5_file=h5_morphology_path, center_at_origin=True) morphology = h5_reader.read_file() # Adjust the label to be set according to the GID not the morphology label @@ -142,8 +143,8 @@ def create_neuron_mesh(circuit, # Create default NMV options nmv_options = nmv.options.NeuroMorphoVisOptions() nmv_options.io.statistics_directory = output_directory - # nmv_options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.UNIFIED - # nmv_options.morphology.samples_unified_radii_value = 1.0 + nmv_options.morphology.arbors_radii = nmv.enums.Skeleton.Radii.UNIFIED + nmv_options.morphology.samples_unified_radii_value = 1.0 nmv_options.mesh.neuron_objects_connection = nmv.enums.Meshing.ObjectsConnection.CONNECTED nmv_options.shading.mesh_material = nmv.enums.shading_enums.Shader.FLAT # shader nmv_options.mesh.soma_type = nmv.enums.Soma.Representation.META_BALLS @@ -318,17 +319,23 @@ def create_projection_synapses_mesh(circuit, # Compute the mesh bounding box bounding_box = nmv.bbox.compute_scene_bounding_box_for_meshes() - # Export the scene into a blender file - prefix = 'projection_%s_%s_%dp' % (args.projection, str(gid), args.synapse_percentage) - nmv.logger.info('Exporting %s' % prefix) - nmv.file.export_scene_to_blend_file(output_directory=args.output_directory, - output_file_name=prefix) + scale_bar = nmv.interface.draw_scale_bar( + bounding_box=bounding_box, + material_type=shader, + view=nmv.enums.Camera.View.FRONT) # Render the image + prefix = 'projection_%s_%s_%dp' % (args.projection, str(gid), args.synapse_percentage) nmv.logger.info('Rendering %s' % prefix) nmv.rendering.render( camera_view=nmv.enums.Camera.View.FRONT, bounding_box=bounding_box, image_resolution=args.image_resolution, image_name=prefix, - image_directory=args.output_directory) \ No newline at end of file + image_directory=args.output_directory, + keep_camera_in_scene=True) + + # Export the scene into a blender file + nmv.logger.info('Exporting %s' % prefix) + nmv.file.export_scene_to_blend_file(output_directory=args.output_directory, + output_file_name=prefix) diff --git a/scripts/synaptics/create-synaptic-projection.sh b/scripts/synaptics-refactor/create-synaptic-projection.sh old mode 100644 new mode 100755 similarity index 90% rename from scripts/synaptics/create-synaptic-projection.sh rename to scripts/synaptics-refactor/create-synaptic-projection.sh index b979c18df..339480328 --- a/scripts/synaptics/create-synaptic-projection.sh +++ b/scripts/synaptics-refactor/create-synaptic-projection.sh @@ -20,26 +20,25 @@ BLENDER=$PWD/../../../../../../blender # Circuit config -CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj112/circuits/CA1/20200820/CircuitConfig' +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj112/circuits/CA1/20211110-BioM/BlueConfig' # Post synaptic neurons GIDs, a list separate by '_' -GIDS='1681_408231' +GIDS='2080_14739_128625' +GIDS='1689_2232_1957_2080_14739_128625' # Projection PROJECTION='SC' # The output directory where the scene and images will be generated -OUTPUT_DIRECTORY='/hdd1/projects-data/2021.05.05-synapse-projections/trial-4' +OUTPUT_DIRECTORY='/abdellah2/projects-data/2023.03.24-synaptic-projections' # Neuron color -NEURON_COLOR='0.05_0.2_1' -NEURON_COLOR='0.0_1.0_0.0' - +NEURON_COLOR='1_1.0_1' +# NEURON_COLOR='0.0_1.0_0.0' # Neuron color -SYNAPSE_COLOR='1.0_0.8_0.06' -SYNAPSE_COLOR='1.0_0.0_0.0' - +SYNAPSE_COLOR='1.0_0.3_0.06' +# SYNAPSE_COLOR='1.0_0.0_0.0' # Synapse percentage SYNAPSE_PERCENTAGE='10' diff --git a/scripts/synaptics/create-synaptome.py b/scripts/synaptics-refactor/create-synaptome.py similarity index 100% rename from scripts/synaptics/create-synaptome.py rename to scripts/synaptics-refactor/create-synaptome.py diff --git a/scripts/synaptics/create-synaptome.sh b/scripts/synaptics-refactor/create-synaptome.sh similarity index 93% rename from scripts/synaptics/create-synaptome.sh rename to scripts/synaptics-refactor/create-synaptome.sh index 603e370dd..6d7833d06 100755 --- a/scripts/synaptics/create-synaptome.sh +++ b/scripts/synaptics-refactor/create-synaptome.sh @@ -46,13 +46,13 @@ SYNAPSE_PERCENTAGE='100' SYNAPSE_SIZE='2.0' # Close-up view size -CLOSE_UP_SIZE='30' +CLOSEUP_SIZE='30' # Base full view resolution FULL_VIEW_RESOLUTION='512' # Base close-up resolution -CLOSE_UP_RESOLUTION='512' +CLOSEUP_RESOLUTION='512' # The background image the frames will get blended to BACKGROUND_IMAGE='/gpfs/bbp.cscs.ch/project/proj83/visualization-SSCXDIS-178/synaptomes-data/backgrounds/background_1900x1080.png' @@ -81,10 +81,10 @@ DISPLAY=:0 $BLENDER -b --verbose 0 --python create-synaptome.py -- --color-map=$COLOR_MAP_FILE \ --neuron-color=$NEURON_COLOR \ --full-view-resolution=$FULL_VIEW_RESOLUTION \ - --close-up-resolution=$CLOSE_UP_RESOLUTION \ + --close-up-resolution=$CLOSEUP_RESOLUTION \ --synapse-percentage=$SYNAPSE_PERCENTAGE \ --synapse-size=$SYNAPSE_SIZE \ - --close-up-size=$CLOSE_UP_SIZE \ + --close-up-size=$CLOSEUP_SIZE \ --background-image=$BACKGROUND_IMAGE \ $BOOL_ARGS diff --git a/scripts/synaptics/create-synaptomes-cluster-volta.sh b/scripts/synaptics-refactor/create-synaptomes-cluster-volta.sh similarity index 94% rename from scripts/synaptics/create-synaptomes-cluster-volta.sh rename to scripts/synaptics-refactor/create-synaptomes-cluster-volta.sh index cafad0893..c9d794316 100755 --- a/scripts/synaptics/create-synaptomes-cluster-volta.sh +++ b/scripts/synaptics-refactor/create-synaptomes-cluster-volta.sh @@ -47,13 +47,13 @@ SYNAPSE_PERCENTAGE='100' SYNAPSE_SIZE='2.0' # Close-up view size -CLOSE_UP_SIZE='50' +CLOSEUP_SIZE='50' # Base full view resolution FULL_VIEW_RESOLUTION='2000' # Base close-up resolution -CLOSE_UP_RESOLUTION='1000' +CLOSEUP_RESOLUTION='1000' # The background image the frames will get blended to BACKGROUND_IMAGE='/gpfs/bbp.cscs.ch/project/proj83/visualization-SSCXDIS-178/synaptomes-data/backgrounds/background_1900x1080.png' @@ -98,9 +98,9 @@ DISPLAY=:5 $PWD/../../../../../python/bin/python3.7m create-synaptomes.py --color-map=$COLOR_MAP_FILE \ --neuron-color=$NEURON_COLOR \ --full-view-resolution=$FULL_VIEW_RESOLUTION \ - --close-up-resolution=$CLOSE_UP_RESOLUTION \ + --close-up-resolution=$CLOSEUP_RESOLUTION \ --synapse-percentage=$SYNAPSE_PERCENTAGE \ --synapse-size=$SYNAPSE_SIZE \ - --close-up-size=$CLOSE_UP_SIZE \ + --close-up-size=$CLOSEUP_SIZE \ --background-image=$BACKGROUND_IMAGE \ $BOOL_ARGS; diff --git a/scripts/synaptics/create-synaptomes-cluster.py b/scripts/synaptics-refactor/create-synaptomes-cluster.py similarity index 100% rename from scripts/synaptics/create-synaptomes-cluster.py rename to scripts/synaptics-refactor/create-synaptomes-cluster.py diff --git a/scripts/synaptics/create-synaptomes-cluster.sh b/scripts/synaptics-refactor/create-synaptomes-cluster.sh similarity index 95% rename from scripts/synaptics/create-synaptomes-cluster.sh rename to scripts/synaptics-refactor/create-synaptomes-cluster.sh index 97d32b3b0..57909792a 100755 --- a/scripts/synaptics/create-synaptomes-cluster.sh +++ b/scripts/synaptics-refactor/create-synaptomes-cluster.sh @@ -66,13 +66,13 @@ SYNAPSE_PERCENTAGE='50' SYNAPSE_SIZE='2.0' # Close-up view size -CLOSE_UP_SIZE='30' +CLOSEUP_SIZE='30' # Base full view resolution FULL_VIEW_RESOLUTION='2000' # Base close-up resolution -CLOSE_UP_RESOLUTION='2000' +CLOSEUP_RESOLUTION='2000' # Render 360 movies RENDER_MOVIES='yes' @@ -104,10 +104,10 @@ $BLENDER -b --verbose 0 --python create-synaptomes-cluster.py -- --color-map=$COLOR_MAP_FILE \ --neuron-color=$NEURON_COLOR \ --full-view-resolution=$FULL_VIEW_RESOLUTION \ - --close-up-resolution=$CLOSE_UP_RESOLUTION \ + --close-up-resolution=$CLOSEUP_RESOLUTION \ --synapse-percentage=$SYNAPSE_PERCENTAGE \ --synapse-size=$SYNAPSE_SIZE \ - --close-up-size=$CLOSE_UP_SIZE \ + --close-up-size=$CLOSEUP_SIZE \ --background-image=$BACKGROUND_IMAGE \ --number-jobs-per-core=$NUMBER_OF_JOBS_PER_CORE \ --user-name=$USER_NAME \ diff --git a/scripts/synaptics/create-synaptomes.py b/scripts/synaptics-refactor/create-synaptomes.py similarity index 100% rename from scripts/synaptics/create-synaptomes.py rename to scripts/synaptics-refactor/create-synaptomes.py diff --git a/scripts/synaptics/create-synaptomes.sh b/scripts/synaptics-refactor/create-synaptomes.sh similarity index 95% rename from scripts/synaptics/create-synaptomes.sh rename to scripts/synaptics-refactor/create-synaptomes.sh index 93d937207..4f6ac5124 100755 --- a/scripts/synaptics/create-synaptomes.sh +++ b/scripts/synaptics-refactor/create-synaptomes.sh @@ -53,13 +53,13 @@ SYNAPSE_PERCENTAGE='100' SYNAPSE_SIZE='2.0' # Close-up view size -CLOSE_UP_SIZE='50' +CLOSEUP_SIZE='50' # Base full view resolution FULL_VIEW_RESOLUTION='2000' # Base close-up resolution -CLOSE_UP_RESOLUTION='1000' +CLOSEUP_RESOLUTION='1000' # The background image the frames will get blended to BACKGROUND_IMAGE='/gpfs/bbp.cscs.ch/project/proj83/visualization-SSCXDIS-178/synaptomes-data/backgrounds/background_1900x1080.png' @@ -97,10 +97,10 @@ $PWD/../../../../../python/bin/python3.10 create-synaptomes.py --color-map=$COLOR_MAP_FILE \ --neuron-color=$NEURON_COLOR \ --full-view-resolution=$FULL_VIEW_RESOLUTION \ - --close-up-resolution=$CLOSE_UP_RESOLUTION \ + --close-up-resolution=$CLOSEUP_RESOLUTION \ --synapse-percentage=$SYNAPSE_PERCENTAGE \ --synapse-size=$SYNAPSE_SIZE \ - --close-up-size=$CLOSE_UP_SIZE \ + --close-up-size=$CLOSEUP_SIZE \ --background-image=$BACKGROUND_IMAGE \ $BOOL_ARGS; diff --git a/scripts/synaptics/data/ColorMap b/scripts/synaptics-refactor/data/ColorMap similarity index 100% rename from scripts/synaptics/data/ColorMap rename to scripts/synaptics-refactor/data/ColorMap diff --git a/scripts/synaptics-refactor/visualize_exc_inh_synapses_on_neuron.py b/scripts/synaptics-refactor/visualize_exc_inh_synapses_on_neuron.py new file mode 100644 index 000000000..f91feb25f --- /dev/null +++ b/scripts/synaptics-refactor/visualize_exc_inh_synapses_on_neuron.py @@ -0,0 +1,217 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys +import os +import argparse +import random + +# Internal imports +import_paths = ['core'] +for import_path in import_paths: + sys.path.append(('%s/%s' % (os.path.dirname(os.path.realpath(__file__)), import_path))) + +import circuit_data +import color_map +import neuron_data +import synaptome + +# Internal imports +import nmv.bbox +import nmv.builders +import nmv.consts +import nmv.enums +import nmv.geometry +import nmv.options +import nmv.mesh +import nmv.rendering +import nmv.scene +import nmv.shading +import nmv.utilities +import nmv.bbp + +# BBP imports +from bluepy import Synapse, Circuit + +# Blender imports +from mathutils import Vector + + +# Just set the shader beforehand and create a dummy material +shader = nmv.enums.Shader.LAMBERT_WARD + + +#################################################################################################### +# @parse_command_line_arguments +#################################################################################################### +def parse_command_line_arguments(arguments=None): + """Parses the input arguments. + + :param arguments: + Command line arguments. + :return: + Arguments list. + """ + + # Add all the options + description = 'This add-on uses NeuroMorphoVis to create a Blender file and list of images ' \ + 'to visualize a custom list of synapses on the dendrites of a post-synaptic ' \ + 'neuron in a BBP circuit. ' + parser = argparse.ArgumentParser(description=description) + + arg_help = 'BBP circuit configuration file' + parser.add_argument('--circuit-config', + action='store', dest='circuit_config', help=arg_help) + + arg_help = 'The GID of the post-synaptic neuron' + parser.add_argument('--gid', + action='store', dest='gid', type=int, help=arg_help) + + arg_help = 'A JSON file containing the IDs of the synapses and their corresponding colors in ' \ + 'the following format: ' \ + '{"R_G_B": [6563274686, 6563274687, ..], "R_G_B": [6563277480, 6563277481, .. ]}' + parser.add_argument('--synapses-file', + action='store', dest='synapses_file', help=arg_help) + + arg_help = 'Output directory, where the final artifacts will be generated' + parser.add_argument('--output-directory', + action='store', dest='output_directory', help=arg_help) + + arg_help = 'Neuron color in R_G_B format, for example: 255_231_192' + parser.add_argument('--neuron-color', + action='store', dest='neuron_color', help=arg_help) + + arg_help = 'Synapse size (in um)' + parser.add_argument('--synapse-size', + action='store', dest='synapse_size', type=float, help=arg_help) + + arg_help = 'The percentage of synapses to be drawn in the rendering' + parser.add_argument('--synapse-percentage', + action='store', dest='synapse_percentage', type=float, help=arg_help) + + arg_help = 'Base image resolution' + parser.add_argument('--image-resolution', + action='store', default=2048, type=int, dest='image_resolution', + help=arg_help) + + # Parse the arguments + return parser.parse_args() + + +#################################################################################################### +# @parse_arguments +#################################################################################################### +def parse_arguments(): + """ Parse the command line arguments and return a clean list after the '--'. + :return: + The list of arguments. + """ + + system_args = sys.argv + sys.argv = system_args[system_args.index("--") + 0:] + return parse_command_line_arguments() + + +#################################################################################################### +# @get_color_coded_synapse_list +#################################################################################################### +def get_color_coded_synapse_list(synapse_json_file): + + # The returning synapse list + synapse_list = list() + + # Load the data from the JSON file + try: + f = open(synapse_json_file) + except FileNotFoundError: + print("The file %s is NOT found!" % synapse_json_file) + exit(0) + + # Load all the data from the file + import json + data = json.load(f) + + # Get all the keys and the corresponding arrays + keys = data.keys() + + # Create the synapses list + for key in keys: + group = list() + for i in data[key]: + group.append(int(i)) + synapse_list.append([key, group]) + + # Close the file + f.close() + + # Return the synapse list + return synapse_list + + +#################################################################################################### +# @ Main +#################################################################################################### +if __name__ == "__main__": + + # Parse the command line arguments + args = parse_arguments() + + nmv.logger.header('Synaptome generation') + + # Clear the scene + nmv.logger.info('Cleaning Scene') + nmv.scene.clear_scene() + + material_type = nmv.enums.Shader.LAMBERT_WARD + + # Read the circuit + nmv.logger.info('Loading circuit') + circuit = Circuit(args.circuit_config) + + # Create the neuron mesh + nmv.logger.info('Creating the neuron mesh') + neuron_mesh = neuron_data.create_neuron_mesh( + circuit=circuit, gid=args.gid, + color=nmv.utilities.confirm_rgb_color_from_color_string(args.neuron_color), + material_type=material_type) + + # Create the synapses mesh + nmv.logger.info('Creating the synapse mesh') + color_coded_synapses_dict = nmv.bbp.get_excitatory_and_inhibitory_synapses_color_coded_dict( + circuit=circuit, gid=int(args.gid)) + + transformation = nmv.bbp.get_neuron_transformation_matrix(circuit=circuit, gid=int(args.gid)) + synapses_mesh = nmv.bbp.create_color_coded_synapses_mesh( + circuit=circuit, color_coded_synapses_dict=color_coded_synapses_dict, + synapse_size=args.synapse_size, + inverted_transformation=transformation.inverted(), + material_type=material_type) + + # Render the image + nmv.logger.info('Rendering image') + nmv.rendering.render( + camera_view=nmv.enums.Camera.View.FRONT, + bounding_box=nmv.bbox.compute_scene_bounding_box_for_meshes(), + image_resolution=args.image_resolution, + image_name=str(args.gid), + image_directory=args.output_directory) + + # Export the scene into a blender file for interactive visualization + nmv.logger.info('Exporting the scene') + nmv.file.export_scene_to_blend_file(output_directory=args.output_directory, + output_file_name=str(args.gid)) diff --git a/scripts/synaptics-refactor/visualize_exc_inh_synapses_on_neuron.sh b/scripts/synaptics-refactor/visualize_exc_inh_synapses_on_neuron.sh new file mode 100755 index 000000000..f7b1cc8e2 --- /dev/null +++ b/scripts/synaptics-refactor/visualize_exc_inh_synapses_on_neuron.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../blender + +# Circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the post synaptic neuron +NEURON_GID='3774248' + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/home/abdellah/Desktop/clean-scripts/output' + +# Color-map file +SYNAPSES_JSON_FILE='/home/abdellah/Desktop/clean-scripts/output/3774248.synapses' + +# Neuron color +NEURON_COLOR='255_255_255' + +# Synapse size +SYNAPSE_SIZE='2.0' + +# Base image resolution +IMAGE_RESOLUTION='5000' + + +##################################################################################################### +BOOL_ARGS='' +if [ "$SHOW_EXC_INH" == "yes" ]; + then BOOL_ARGS+=' --show-exc-inh '; fi +if [ "$RENDER_MOVIES" == "yes" ]; + then BOOL_ARGS+=' --render-movies '; fi +if [ "$RENDER_FRAMES" == "yes" ]; + then BOOL_ARGS+=' --render-frames '; fi + +#################################################################################################### +$BLENDER -b --verbose 0 --python visualize_exc_inh_synapses_on_neuron.py -- \ + --circuit-config=$CIRCUIT_CONFIG \ + --gid=$NEURON_GID \ + --output-directory=$OUTPUT_DIRECTORY \ + --synapses-file=$SYNAPSES_JSON_FILE \ + --neuron-color=$NEURON_COLOR \ + --synapse-size=$SYNAPSE_SIZE \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS + diff --git a/scripts/synaptics-refactor/visualize_synapses_on_post_synaptic_neuron.py b/scripts/synaptics-refactor/visualize_synapses_on_post_synaptic_neuron.py new file mode 100644 index 000000000..e11f32bda --- /dev/null +++ b/scripts/synaptics-refactor/visualize_synapses_on_post_synaptic_neuron.py @@ -0,0 +1,155 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys +import os +import argparse +import random + +# Internal imports +import_paths = ['core'] +for import_path in import_paths: + sys.path.append(('%s/%s' % (os.path.dirname(os.path.realpath(__file__)), import_path))) + +import neuron_data + +# BBP imports +from bluepy import Synapse, Circuit + +# Internal imports +import nmv.bbox +import nmv.bbp +import nmv.enums +import nmv.rendering +import nmv.scene +import nmv.utilities + + +#################################################################################################### +# @parse_command_line_arguments +#################################################################################################### +def parse_command_line_arguments(): + """Parses the input arguments. + + :return: + Arguments list. + """ + + # Add all the options + description = 'This add-on uses NeuroMorphoVis to create a Blender file and list of images ' \ + 'to visualize a custom list of synapses on the dendrites of a post-synaptic ' \ + 'neuron in a BBP circuit. ' + parser = argparse.ArgumentParser(description=description) + + arg_help = 'BBP circuit configuration file' + parser.add_argument('--circuit-config', + action='store', dest='circuit_config', help=arg_help) + + arg_help = 'The GID of the post-synaptic neuron' + parser.add_argument('--gid', + action='store', dest='gid', type=int, help=arg_help) + + arg_help = 'A JSON file containing the IDs of the synapses and their corresponding colors in ' \ + 'the following format: ' \ + '{"R_G_B": [6563274686, 6563274687, ..], "R_G_B": [6563277480, 6563277481, .. ]}' + parser.add_argument('--synapses-file', + action='store', dest='synapses_file', help=arg_help) + + arg_help = 'Output directory, where the final artifacts will be generated' + parser.add_argument('--output-directory', + action='store', dest='output_directory', help=arg_help) + + arg_help = 'Neuron color in R_G_B format, for example: 255_231_192' + parser.add_argument('--neuron-color', + action='store', dest='neuron_color', help=arg_help) + + arg_help = 'Synapse radius (in um)' + parser.add_argument('--synapse-radius', + action='store', dest='synapse_radius', type=float, help=arg_help) + + arg_help = 'The percentage of synapses to be drawn in the rendering' + parser.add_argument('--synapse-percentage', + action='store', dest='synapse_percentage', type=float, help=arg_help) + + arg_help = 'Base image resolution' + parser.add_argument('--image-resolution', + action='store', default=2048, type=int, dest='image_resolution', + help=arg_help) + + # Parse the arguments + return parser.parse_args() + + +#################################################################################################### +# @ Main +#################################################################################################### +if __name__ == "__main__": + + # Parse the command line arguments + system_args = sys.argv + sys.argv = system_args[system_args.index("--") + 0:] + args = parse_command_line_arguments() + + # Clear the scene + nmv.logger.info('Cleaning Scene') + nmv.scene.clear_scene() + + material_type = nmv.enums.Shader.LAMBERT_WARD + + # Read the circuit + nmv.logger.info('Loading circuit') + circuit = Circuit(args.circuit_config) + + # Create the neuron mesh + nmv.logger.info('Creating the neuron mesh') + neuron_mesh = nmv.bbp.create_symbolic_neuron_mesh_in_circuit( + circuit=circuit, gid=args.gid, + color=nmv.utilities.confirm_rgb_color_from_color_string(args.neuron_color), + material_type=material_type) + + # Create the synapses mesh + nmv.logger.info('Creating the synapse mesh') + + #synapse_groups = nmv.bbp.create_color_coded_synapse_groups_by_pre_mtype(circuit, args.gid) + #synapse_groups = nmv.bbp.create_color_coded_synapse_groups_by_post_mtype(circuit, args.gid) + + synapse_groups = nmv.bbp.create_color_coded_synapse_groups_by_pre_etype(circuit, args.gid) + + print(len(synapse_groups)) + + #color_coded_synapses_dict = nmv.bbp.get_color_coded_synapse_dict(args.synapses_file) + transformation = nmv.bbp.get_neuron_transformation_matrix(circuit=circuit, gid=int(args.gid)) + synapses_mesh = nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=args.synapse_radius, + inverted_transformation=transformation.inverted(), + material_type=material_type) + + # Render the image + nmv.logger.info('Rendering image') + nmv.rendering.render( + camera_view=nmv.enums.Camera.View.FRONT, + bounding_box=nmv.bbox.compute_scene_bounding_box_for_meshes(), + image_resolution=args.image_resolution, + image_name=str(args.gid), + image_directory=args.output_directory) + + # Export the scene into a blender file for interactive visualization + nmv.logger.info('Exporting the scene') + nmv.file.export_scene_to_blend_file(output_directory=args.output_directory, + output_file_name=str(args.gid)) diff --git a/scripts/synaptics-refactor/visualize_synapses_on_post_synaptic_neuron.sh b/scripts/synaptics-refactor/visualize_synapses_on_post_synaptic_neuron.sh new file mode 100755 index 000000000..285f2c89c --- /dev/null +++ b/scripts/synaptics-refactor/visualize_synapses_on_post_synaptic_neuron.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../blender + +# Circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the post synaptic neuron +NEURON_GID='3774248' + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/home/abdellah/Desktop/clean-scripts/output' + +# Color-map file +SYNAPSES_JSON_FILE='/home/abdellah/Desktop/clean-scripts/output/3774248.synapses' + +# Neuron color +NEURON_COLOR='255_255_255' + +# Synapse size +SYNAPSE_SIZE='1.0' + +# Base image resolution +IMAGE_RESOLUTION='5000' + + +##################################################################################################### +BOOL_ARGS='' +if [ "$SHOW_EXC_INH" == "yes" ]; + then BOOL_ARGS+=' --show-exc-inh '; fi +if [ "$RENDER_MOVIES" == "yes" ]; + then BOOL_ARGS+=' --render-movies '; fi +if [ "$RENDER_FRAMES" == "yes" ]; + then BOOL_ARGS+=' --render-frames '; fi + +#################################################################################################### +$BLENDER -b --verbose 0 --python visualize_synapses_on_post_synaptic_neuron.py -- \ + --circuit-config=$CIRCUIT_CONFIG \ + --gid=$NEURON_GID \ + --output-directory=$OUTPUT_DIRECTORY \ + --synapses-file=$SYNAPSES_JSON_FILE \ + --neuron-color=$NEURON_COLOR \ + --synapse-radius=$SYNAPSE_SIZE \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS + diff --git a/scripts/synaptics/etype/post-etypes.colors b/scripts/synaptics/etype/post-etypes.colors new file mode 100644 index 000000000..8053e6329 --- /dev/null +++ b/scripts/synaptics/etype/post-etypes.colors @@ -0,0 +1,13 @@ +{ +"bAC": "#414c41", +"bIR": "#fc311e", +"bNAC": "#3bcddb", +"bSTUT": "#1bb82b", +"cACint": "#27e626", +"cADpyr": "#3e6f05", +"cIR": "#ba18ec", +"cNAC": "#30e953", +"cSTUT": "#1a919c", +"dNAC": "#efe6c6", +"dSTUT": "#303a5f" +} diff --git a/scripts/synaptics/etype/pre-etypes.colors b/scripts/synaptics/etype/pre-etypes.colors new file mode 100644 index 000000000..2575efff1 --- /dev/null +++ b/scripts/synaptics/etype/pre-etypes.colors @@ -0,0 +1,13 @@ +{ +"bAC": "#3431e4", +"bIR": "#b9f700", +"bNAC": "#383733", +"bSTUT": "#2ff7af", +"cACint": "#108b8b", +"cADpyr": "#abb228", +"cIR": "#b1bccb", +"cNAC": "#4d683e", +"cSTUT": "#40cd73", +"dNAC": "#cb59d3", +"dSTUT": "#323c07" +} diff --git a/scripts/synaptics/etype/visualize_synapses_color_coded_by_post_synaptic_etypes.sh b/scripts/synaptics/etype/visualize_synapses_color_coded_by_post_synaptic_etypes.sh new file mode 100755 index 000000000..cd2adb603 --- /dev/null +++ b/scripts/synaptics/etype/visualize_synapses_color_coded_by_post_synaptic_etypes.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../../blender + +# BBP circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the post synaptic neuron +NEURON_GID='3774248' + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-2' + +# Color-map of the synapses based on their etype +SYNAPSES_COLOR_MAP='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-3/post-etypes.colors' + +# If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for all the branches +UNIFY_BRANCHES_RADII='yes' + +# A constant value for the radius of the neuron branches. This value will be ignore if +# UNIFY_BRANCHES_RADII is set to yes +UNIFIED_NEURON_RADIUS='1.0' + +# The color of the neuron +NEURON_COLOR='#fffff0' + +# The fixed radius of the synapses +SYNAPSE_RADIUS='2.0' + +# The percentage of the displayed synapses (from 0.1% - 100%) +SYNAPSE_PERCENTAGE='100' + +# Base resolution of the rendered image +IMAGE_RESOLUTION='5000' + +# Save the rendering into a Blender file such that we can visualize the scene later interactively +SAVE_TO_BLEND_FILE='yes' + +##################################################################################################### +BOOL_ARGS='' +if [ "$SAVE_TO_BLEND_FILE" == "yes" ]; + then BOOL_ARGS+=' --save-blend-file '; fi +if [ "$UNIFY_BRANCHES_RADII" == "yes" ]; + then BOOL_ARGS+=' --unify-branches-radii '; fi + +#################################################################################################### +$BLENDER -b --verbose 0 --python visualize_synapses_color_coded_by_x_synaptic_etypes.py -- \ + --circuit-config=$CIRCUIT_CONFIG \ + --gid=$NEURON_GID \ + --output-directory=$OUTPUT_DIRECTORY \ + --neuron-color=$NEURON_COLOR \ + --pre-or-post='post' \ + --unified-branches-radius=$UNIFIED_NEURON_RADIUS \ + --synapses-color-map=$SYNAPSES_COLOR_MAP \ + --synapse-radius=$SYNAPSE_RADIUS \ + --synapse-percentage=$SYNAPSE_PERCENTAGE \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS diff --git a/scripts/synaptics/etype/visualize_synapses_color_coded_by_pre_synaptic_etypes.sh b/scripts/synaptics/etype/visualize_synapses_color_coded_by_pre_synaptic_etypes.sh new file mode 100755 index 000000000..13906d1d8 --- /dev/null +++ b/scripts/synaptics/etype/visualize_synapses_color_coded_by_pre_synaptic_etypes.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../../blender + +# BBP circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the post synaptic neuron +NEURON_GID='3774248' + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-2' + +# Color-map of the synapses based on their etype +SYNAPSES_COLOR_MAP='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-3/pre-etypes.colors' + +# If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for all the branches +UNIFY_BRANCHES_RADII='yes' + +# A constant value for the radius of the neuron branches. This value will be ignore if +# UNIFY_BRANCHES_RADII is set to yes +UNIFIED_NEURON_RADIUS='1.0' + +# Axon branching order, in certain cases, it is nice to hide the axon to focus on the synapses +AXON_BRANCHING_ORDER=1 + +# The color of the neuron +NEURON_COLOR='#fffff0' + +# The fixed radius of the synapses +SYNAPSE_RADIUS='2.0' + +# The percentage of the displayed synapses (from 0.1% - 100%) +SYNAPSE_PERCENTAGE='100' + +# Base resolution of the rendered image +IMAGE_RESOLUTION='5000' + +# Save the rendering into a Blender file such that we can visualize the scene later interactively +SAVE_TO_BLEND_FILE='yes' + +##################################################################################################### +BOOL_ARGS='' +if [ "$SAVE_TO_BLEND_FILE" == "yes" ]; + then BOOL_ARGS+=' --save-blend-file '; fi +if [ "$UNIFY_BRANCHES_RADII" == "yes" ]; + then BOOL_ARGS+=' --unify-branches-radii '; fi + +#################################################################################################### +$BLENDER -b --verbose 0 --python visualize_synapses_color_coded_by_x_synaptic_etypes.py -- \ + --circuit-config=$CIRCUIT_CONFIG \ + --gid=$NEURON_GID \ + --output-directory=$OUTPUT_DIRECTORY \ + --neuron-color=$NEURON_COLOR \ + --pre-or-post='pre' \ + --axon-branching-order=$AXON_BRANCHING_ORDER \ + --unified-branches-radius=$UNIFIED_NEURON_RADIUS \ + --synapses-color-map=$SYNAPSES_COLOR_MAP \ + --synapse-radius=$SYNAPSE_RADIUS \ + --synapse-percentage=$SYNAPSE_PERCENTAGE \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS diff --git a/scripts/synaptics/etype/visualize_synapses_color_coded_by_x_synaptic_etypes.py b/scripts/synaptics/etype/visualize_synapses_color_coded_by_x_synaptic_etypes.py new file mode 100644 index 000000000..2e3e0164e --- /dev/null +++ b/scripts/synaptics/etype/visualize_synapses_color_coded_by_x_synaptic_etypes.py @@ -0,0 +1,182 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys +import argparse + +# BBP imports +from bluepy import Circuit + +# Internal imports +import nmv.bbox +import nmv.bbp +import nmv.enums +import nmv.rendering +import nmv.scene +import nmv.utilities + + +#################################################################################################### +# @parse_command_line_arguments +#################################################################################################### +def parse_command_line_arguments(): + """Parses the input arguments. + + :return: + Arguments list. + """ + + # Add all the options + description = 'This add-on uses NeuroMorphoVis to create a Blender file and list of images ' \ + 'to visualize a custom list of synapses on the dendrites of a post-synaptic ' \ + 'neuron in a BBP circuit. ' + parser = argparse.ArgumentParser(description=description) + + arg_help = 'BBP circuit configuration file' + parser.add_argument('--circuit-config', + action='store', dest='circuit_config', help=arg_help) + + arg_help = 'The GID of the post-synaptic neuron' + parser.add_argument('--gid', + action='store', dest='gid', type=int, help=arg_help) + + arg_help = 'A JSON file containing the color-map of the synapses based on the mtypes of the ' \ + 'pre-synaptic neuron in the following format: ' \ + '{MTYPE_0: HEX_COLOR_0, MTYPE_1: HEX_COLOR_1, ...}' + parser.add_argument('--synapses-color-map', + action='store', dest='synapses_color_map', help=arg_help) + + arg_help = 'Output directory, where the final artifacts will be generated' + parser.add_argument('--output-directory', + action='store', dest='output_directory', help=arg_help) + + arg_help = 'Neuron color in R_G_B format, for example: 255_231_192' + parser.add_argument('--neuron-color', + action='store', dest='neuron_color', help=arg_help) + + arg_help = 'Display the synapses based on the etypes of the pre- or the post-synaptic neurons' + parser.add_argument('--pre-or-post', + action='store', dest='pre_or_post', help=arg_help) + + arg_help = 'If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for ' \ + 'all the branches' + parser.add_argument('--unify-branches-radii', + dest='unify_branches_radii', action='store_true', default=False, + help=arg_help) + + arg_help = 'The value of the branches unified radius (in um)' + parser.add_argument('--unified-branches-radius', + action='store', dest='unified_branches_radius', type=float, help=arg_help) + + arg_help = 'The branching order of the axon. By default, it is 1 to highlight the synapses' + parser.add_argument('--axon-branching-order', + action='store', dest='axon_branching_order', type=int, default=10000, + help=arg_help) + + arg_help = 'Synapse radius (in um)' + parser.add_argument('--synapse-radius', + action='store', dest='synapse_radius', type=float, help=arg_help) + + arg_help = 'The percentage of synapses to be drawn in the rendering' + parser.add_argument('--synapse-percentage', + action='store', dest='synapse_percentage', type=float, help=arg_help) + + arg_help = 'Base image resolution' + parser.add_argument('--image-resolution', + action='store', default=2048, type=int, dest='image_resolution', + help=arg_help) + + arg_help = 'Save the scene into a 3D Blender file for interactive visualization later' + parser.add_argument('--save-blend-file', + dest='save_blend_file', action='store_true', default=False, help=arg_help) + + # Parse the arguments + return parser.parse_args() + + +#################################################################################################### +# @ Main +#################################################################################################### +if __name__ == "__main__": + + # Parse the command line arguments + system_args = sys.argv + sys.argv = system_args[system_args.index("--") + 0:] + args = parse_command_line_arguments() + + # Clear the scene + nmv.logger.info('Cleaning Scene') + nmv.scene.clear_scene() + + material_type = nmv.enums.Shader.LAMBERT_WARD + + # Read the circuit + nmv.logger.info('Loading circuit') + circuit = Circuit(args.circuit_config) + + # Creating the synapse group from the mesh + nmv.logger.info('Loading synapses from the synapses file ') + if args.pre_or_post == 'pre': + synapse_groups = nmv.bbp.create_color_coded_synapse_groups_by_pre_etype( + circuit=circuit, post_gid=int(args.gid), mtype_color_dict= + nmv.bbp.get_color_coded_synapse_dict(args.synapses_color_map)) + else: + synapse_groups = nmv.bbp.create_color_coded_synapse_groups_by_post_etype( + circuit=circuit, pre_gid=int(args.gid), mtype_color_dict= + nmv.bbp.get_color_coded_synapse_dict(args.synapses_color_map)) + + # Create the neuron mesh + nmv.logger.info('Creating the neuron mesh') + neuron_color = nmv.utilities.confirm_rgb_color_from_color_string(args.neuron_color) + neuron_mesh = nmv.bbp.create_symbolic_neuron_mesh_in_circuit( + circuit=circuit, gid=args.gid, + unified_radius=args.unify_branches_radii, + branch_radius=args.unified_branches_radius, + soma_color=neuron_color, + basal_dendrites_color=neuron_color, + apical_dendrites_color=neuron_color, + axons_color=neuron_color, + material_type=material_type, + axon_branching_order=args.axon_branching_order) + + # Create the synapses mesh + nmv.logger.info('Creating the synapse mesh') + transformation = nmv.bbp.get_neuron_transformation_matrix(circuit=circuit, gid=int(args.gid)) + synapses_mesh = nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=args.synapse_radius, + synapses_percentage=args.synapse_percentage, + inverted_transformation=transformation.inverted(), + material_type=material_type) + + # Render the image + nmv.logger.info('Rendering image') + nmv.rendering.render( + camera_view=nmv.enums.Camera.View.FRONT, + bounding_box=nmv.bbox.compute_scene_bounding_box_for_meshes(), + image_resolution=args.image_resolution, + image_name='%s-%s' % (str(args.gid), args.pre_or_post), + image_directory=args.output_directory) + + # Export the scene into a blender file for interactive visualization + if args.save_blend_file: + nmv.logger.info('Saving into a .BLEND file [%s/%s-%s.blend]' % + (args.output_directory, str(args.gid), args.pre_or_post)) + nmv.file.export_scene_to_blend_file( + output_directory=args.output_directory, + output_file_name='%s-%s' % (str(args.gid), args.pre_or_post)) diff --git a/scripts/synaptics/exc_inh/visualize_exc_inh_synapses_on_neuron.py b/scripts/synaptics/exc_inh/visualize_exc_inh_synapses_on_neuron.py new file mode 100644 index 000000000..dcf681406 --- /dev/null +++ b/scripts/synaptics/exc_inh/visualize_exc_inh_synapses_on_neuron.py @@ -0,0 +1,170 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys +import argparse + +# Internal imports +import nmv.bbox +import nmv.enums +import nmv.rendering +import nmv.scene +import nmv.utilities +import nmv.bbp + +# BBP imports +from bluepy import Circuit + + +#################################################################################################### +# @parse_command_line_arguments +#################################################################################################### +def parse_command_line_arguments(arguments=None): + """Parses the input arguments. + + :param arguments: + Command line arguments. + :return: + Arguments list. + """ + + # Add all the options + description = 'This add-on uses NeuroMorphoVis to create a Blender file and list of images ' \ + 'to visualize the excitatory and inhibitory synapses on a neuron in a BBP ' \ + 'circuit. ' + parser = argparse.ArgumentParser(description=description) + + arg_help = 'BBP circuit configuration file' + parser.add_argument('--circuit-config', + action='store', dest='circuit_config', help=arg_help) + + arg_help = 'The GID of the neuron' + parser.add_argument('--gid', + action='store', dest='gid', type=int, help=arg_help) + + arg_help = 'Output directory, where the final artifacts will be generated' + parser.add_argument('--output-directory', + action='store', dest='output_directory', help=arg_help) + + arg_help = 'Neuron color, either in RGB (R_G_B) or in hex (#RRGGBB) formats' + parser.add_argument('--neuron-color', + action='store', dest='neuron_color', help=arg_help) + + arg_help = 'If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for ' \ + 'all the branches' + parser.add_argument('--unify-branches-radii', + dest='unify_branches_radii', action='store_true', default=False, + help=arg_help) + + arg_help = 'The value of the branches unified radius (in um)' + parser.add_argument('--unified-branches-radius', + action='store', dest='unified_branches_radius', type=float, help=arg_help) + + arg_help = 'Excitatory synapses color, either in RGB (R_G_B) or in hex (#RRGGBB) formats' + parser.add_argument('--exc-synapses-color', + action='store', dest='exc_synapses_color', help=arg_help) + + arg_help = 'Inhibitory synapses color, either in RGB (R_G_B) or in hex (#RRGGBB) formats' + parser.add_argument('--inh-synapses-color', + action='store', dest='inh_synapses_color', help=arg_help) + + arg_help = 'Synapse radius (in um)' + parser.add_argument('--synapse-radius', + action='store', dest='synapse_radius', type=float, help=arg_help) + + arg_help = 'The percentage of synapses to be drawn in the rendering' + parser.add_argument('--synapse-percentage', + action='store', dest='synapse_percentage', type=float, help=arg_help) + + arg_help = 'Base image resolution' + parser.add_argument('--image-resolution', + action='store', default=2048, type=int, dest='image_resolution', + help=arg_help) + + arg_help = 'Save the scene into a 3D Blender file for interactive visualization later' + parser.add_argument('--save-blend-file', + dest='save_blend_file', action='store_true', default=False, help=arg_help) + + # Parse the arguments + return parser.parse_args() + + +#################################################################################################### +# @ Main +#################################################################################################### +if __name__ == "__main__": + + # Parse the command line arguments + system_args = sys.argv + sys.argv = system_args[system_args.index("--") + 0:] + args = parse_command_line_arguments() + + # Clear the scene + nmv.logger.info('Cleaning Scene') + nmv.scene.clear_scene() + + material_type = nmv.enums.Shader.LAMBERT_WARD + + # Read the circuit + nmv.logger.info('Loading circuit') + circuit = Circuit(args.circuit_config) + + # Creating the synapse group from the mesh + nmv.logger.info('Loading synapses from the circuit ') + synapse_groups = nmv.bbp.get_excitatory_and_inhibitory_synapse_groups( + circuit=circuit, gid=int(args.gid), + exc_color=args.exc_synapses_color, inh_color=args.inh_synapses_color) + + # Create the neuron mesh + nmv.logger.info('Creating the neuron mesh') + neuron_color = nmv.utilities.confirm_rgb_color_from_color_string(args.neuron_color) + neuron_mesh = nmv.bbp.create_symbolic_neuron_mesh_in_circuit( + circuit=circuit, gid=args.gid, + unified_radius=args.unify_branches_radii, + branch_radius=args.unified_branches_radius, + soma_color=neuron_color, + basal_dendrites_color=neuron_color, + apical_dendrites_color=neuron_color, + axons_color=neuron_color, + material_type=material_type) + + # Create the synapses mesh + nmv.logger.info('Creating the synapse mesh') + transformation = nmv.bbp.get_neuron_transformation_matrix(circuit=circuit, gid=int(args.gid)) + synapses_mesh = nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=args.synapse_radius, + synapses_percentage=args.synapse_percentage, + inverted_transformation=transformation.inverted(), + material_type=material_type) + + # Render the image + nmv.logger.info('Rendering image') + nmv.rendering.render( + camera_view=nmv.enums.Camera.View.FRONT, + bounding_box=nmv.bbox.compute_scene_bounding_box_for_meshes(), + image_resolution=args.image_resolution, + image_name=str(args.gid), + image_directory=args.output_directory) + + # Export the scene into a blender file for interactive visualization + if args.save_blend_file: + nmv.logger.info('Saving into a .BLEND file [%s/%s.blend]' % + (args.output_directory, str(args.gid))) + nmv.file.export_scene_to_blend_file(output_directory=args.output_directory, + output_file_name=str(args.gid)) diff --git a/scripts/synaptics/exc_inh/visualize_exc_inh_synapses_on_neuron.sh b/scripts/synaptics/exc_inh/visualize_exc_inh_synapses_on_neuron.sh new file mode 100755 index 000000000..081cde057 --- /dev/null +++ b/scripts/synaptics/exc_inh/visualize_exc_inh_synapses_on_neuron.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../../blender + +# Circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the neuron +NEURON_GID='3774248' + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-1' + +# The color of the neuron +NEURON_COLOR='#fffff0' + +# The color of the excitatory synapses +EXC_SYNAPSES_COLOR='#ff0000 ' + +# The color of the inhibitory synapses +INH_SYNAPSES_COLOR='#0000ff' + +# If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for all the branches +UNIFY_BRANCHES_RADII='yes' + +# A constant value for the radius of the neuron branches. This value will be ignore if +# UNIFY_BRANCHES_RADII is set to yes +UNIFIED_NEURON_RADIUS='1.0' + +# Synapse size +SYNAPSE_RADIUS='2.0' + +# The percentage of the displayed synapses (from 0.1% - 100%) +SYNAPSE_PERCENTAGE='100' + +# Base image resolution +IMAGE_RESOLUTION='5000' + +# Save the rendering into a Blender file such that we can visualize the scene later interactively +SAVE_TO_BLEND_FILE='yes' + +##################################################################################################### +BOOL_ARGS='' +if [ "$SAVE_TO_BLEND_FILE" == "yes" ]; + then BOOL_ARGS+=' --save-blend-file '; fi +if [ "$UNIFY_BRANCHES_RADII" == "yes" ]; + then BOOL_ARGS+=' --unify-branches-radii '; fi + +#################################################################################################### +$BLENDER -b --verbose 0 --python visualize_exc_inh_synapses_on_neuron.py -- \ + --circuit-config=$CIRCUIT_CONFIG \ + --gid=$NEURON_GID \ + --output-directory=$OUTPUT_DIRECTORY \ + --neuron-color=$NEURON_COLOR \ + --unified-branches-radius=$UNIFIED_NEURON_RADIUS \ + --exc-synapses-color=$EXC_SYNAPSES_COLOR \ + --inh-synapses-color=$INH_SYNAPSES_COLOR \ + --synapse-radius=$SYNAPSE_RADIUS \ + --synapse-percentage=$SYNAPSE_PERCENTAGE \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS + diff --git a/scripts/synaptics/mtype/post-mtypes.colors b/scripts/synaptics/mtype/post-mtypes.colors new file mode 100644 index 000000000..372bb89df --- /dev/null +++ b/scripts/synaptics/mtype/post-mtypes.colors @@ -0,0 +1,62 @@ +{ +"L1_DAC": "#9c0dde", +"L1_HAC": "#77285b", +"L1_LAC": "#774cf7", +"L1_NGC-DA": "#395864", +"L1_NGC-SA": "#13b628", +"L1_SAC": "#cd31dc", +"L23_BP": "#d4f853", +"L23_BTC": "#45f29c", +"L23_CHC": "#dd6eae", +"L23_DBC": "#f204be", +"L23_LBC": "#1e4571", +"L23_MC": "#502901", +"L23_NBC": "#6c96a8", +"L23_NGC": "#1049c5", +"L23_SBC": "#48802b", +"L2_IPC": "#68f8bd", +"L2_TPC:A": "#398a1d", +"L2_TPC:B": "#6b7f6f", +"L3_TPC:A": "#24d594", +"L3_TPC:C": "#db40f8", +"L4_BP": "#141e41", +"L4_BTC": "#566d32", +"L4_CHC": "#bbc5d0", +"L4_DBC": "#ea1e46", +"L4_LBC": "#3983dc", +"L4_MC": "#07e324", +"L4_NBC": "#d6fb63", +"L4_NGC": "#30195b", +"L4_SBC": "#541e27", +"L4_SSC": "#dabf61", +"L4_TPC": "#ccad5a", +"L4_UPC": "#0e769c", +"L5_BP": "#ad76ad", +"L5_BTC": "#7047e7", +"L5_CHC": "#6af79f", +"L5_DBC": "#034ee8", +"L5_LBC": "#0785af", +"L5_MC": "#72901e", +"L5_NBC": "#8f5d3a", +"L5_NGC": "#a3a144", +"L5_SBC": "#ae8da4", +"L5_TPC:A": "#bb98d7", +"L5_TPC:B": "#0a8a06", +"L5_TPC:C": "#976ecd", +"L5_UPC": "#2c6f36", +"L6_BP": "#e91a64", +"L6_BPC": "#489a6f", +"L6_BTC": "#6f1071", +"L6_CHC": "#d6a20f", +"L6_DBC": "#0f9640", +"L6_HPC": "#fd7af5", +"L6_IPC": "#d4b890", +"L6_LBC": "#ade2c8", +"L6_MC": "#34753b", +"L6_NBC": "#363851", +"L6_NGC": "#edad96", +"L6_SBC": "#09d36d", +"L6_TPC:A": "#60685c", +"L6_TPC:C": "#aa2d55", +"L6_UPC": "#78b9a0" +} diff --git a/scripts/synaptics/mtype/pre-mtypes.colors b/scripts/synaptics/mtype/pre-mtypes.colors new file mode 100644 index 000000000..e56a427a9 --- /dev/null +++ b/scripts/synaptics/mtype/pre-mtypes.colors @@ -0,0 +1,62 @@ +{ +"L1_DAC":"#5d149a", +"L1_HAC":"#958a4f", +"L1_LAC":"#4f7f7c", +"L1_NGC-DA":"#8b7d34", +"L1_NGC-SA":"#4f00c3", +"L1_SAC":"#4c65de", +"L23_BP":"#bf887e", +"L23_BTC":"#a63408", +"L23_CHC":"#7d3a48", +"L23_DBC":"#41e1b3", +"L23_LBC":"#322594", +"L23_MC":"#ad2f4b", +"L23_NBC":"#553124", +"L23_NGC":"#aa4f84", +"L23_SBC":"#1e264f", +"L2_IPC":"#d36ede", +"L2_TPC:A":"#ba5e4e", +"L2_TPC:B":"#f3d7b0", +"L3_TPC:A":"#4cc8da", +"L3_TPC:C":"#ae08f3", +"L4_BP":"#fd6222", +"L4_BTC":"#4b4698", +"L4_CHC":"#e92666", +"L4_DBC":"#110bbc", +"L4_LBC":"#e56afb", +"L4_MC":"#bf84dd", +"L4_NBC":"#a85817", +"L4_NGC":"#f2417e", +"L4_SBC":"#a41124", +"L4_SSC":"#704664", +"L4_TPC":"#6dc191", +"L4_UPC":"#1f30af", +"L5_BP":"#9c5bec", +"L5_BTC":"#67b88f", +"L5_CHC":"#86eb51", +"L5_DBC":"#e9e0c9", +"L5_LBC":"#7840cb", +"L5_MC":"#d01829", +"L5_NBC":"#f20951", +"L5_NGC":"#a8a185", +"L5_SBC":"#8275e0", +"L5_TPC:A":"#588942", +"L5_TPC:B":"#bd6bd3", +"L5_TPC:C":"#cfada8", +"L5_UPC":"#249197", +"L6_BP":"#ed6a17", +"L6_BPC":"#b7a84a", +"L6_BTC":"#4bb19b", +"L6_CHC":"#a2e118", +"L6_DBC":"#a39799", +"L6_HPC":"#90502f", +"L6_IPC":"#d86b7d", +"L6_LBC":"#c6f990", +"L6_MC":"#7e4ccc", +"L6_NBC":"#ca87ad", +"L6_NGC":"#508f3b", +"L6_SBC":"#7c5820", +"L6_TPC:A":"#9d0896", +"L6_TPC:C":"#0b2e97", +"L6_UPC":"#21e3ee" +} diff --git a/scripts/synaptics/mtype/visualize_synapses_color_coded_by_post_synaptic_mtypes.sh b/scripts/synaptics/mtype/visualize_synapses_color_coded_by_post_synaptic_mtypes.sh new file mode 100755 index 000000000..f09ec2d04 --- /dev/null +++ b/scripts/synaptics/mtype/visualize_synapses_color_coded_by_post_synaptic_mtypes.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../../blender + +# BBP circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the post synaptic neuron +NEURON_GID='3774248' + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-2' + +# Color-map of the synapses based on their mtype +SYNAPSES_COLOR_MAP='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-2/pre-mtypes.colors' + +# If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for all the branches +UNIFY_BRANCHES_RADII='yes' + +# A constant value for the radius of the neuron branches. This value will be ignore if +# UNIFY_BRANCHES_RADII is set to yes +UNIFIED_NEURON_RADIUS='1.0' + +# The color of the neuron +NEURON_COLOR='#fffff0' + +# The fixed radius of the synapses +SYNAPSE_RADIUS='2.0' + +# The percentage of the displayed synapses (from 0.1% - 100%) +SYNAPSE_PERCENTAGE='100' + +# Base resolution of the rendered image +IMAGE_RESOLUTION='5000' + +# Save the rendering into a Blender file such that we can visualize the scene later interactively +SAVE_TO_BLEND_FILE='yes' + +##################################################################################################### +BOOL_ARGS='' +if [ "$SAVE_TO_BLEND_FILE" == "yes" ]; + then BOOL_ARGS+=' --save-blend-file '; fi +if [ "$UNIFY_BRANCHES_RADII" == "yes" ]; + then BOOL_ARGS+=' --unify-branches-radii '; fi + +#################################################################################################### +$BLENDER -b --verbose 0 --python visualize_synapses_color_coded_by_x_synaptic_mtypes.py -- \ + --circuit-config=$CIRCUIT_CONFIG \ + --gid=$NEURON_GID \ + --output-directory=$OUTPUT_DIRECTORY \ + --neuron-color=$NEURON_COLOR \ + --pre-or-post='post' \ + --unified-branches-radius=$UNIFIED_NEURON_RADIUS \ + --synapses-color-map=$SYNAPSES_COLOR_MAP \ + --synapse-radius=$SYNAPSE_RADIUS \ + --synapse-percentage=$SYNAPSE_PERCENTAGE \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS diff --git a/scripts/synaptics/mtype/visualize_synapses_color_coded_by_pre_synaptic_mtypes.sh b/scripts/synaptics/mtype/visualize_synapses_color_coded_by_pre_synaptic_mtypes.sh new file mode 100755 index 000000000..2cb8f2246 --- /dev/null +++ b/scripts/synaptics/mtype/visualize_synapses_color_coded_by_pre_synaptic_mtypes.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../../blender + +# BBP circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the post synaptic neuron +NEURON_GID='3793945' + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-4' + +# Color-map of the synapses based on their mtype +SYNAPSES_COLOR_MAP='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-2/pre-mtypes.colors' + +# If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for all the branches +UNIFY_BRANCHES_RADII='yes' + +# A constant value for the radius of the neuron branches. This value will be ignore if +# UNIFY_BRANCHES_RADII is set to yes +UNIFIED_NEURON_RADIUS='1.0' + +# The color of the neuron +NEURON_COLOR='#fffff0' + +# Axon branching order, in certain cases, it is nice to hide the axon to focus on the synapses +AXON_BRANCHING_ORDER=1 + +# The fixed radius of the synapses +SYNAPSE_RADIUS='2.0' + +# The percentage of the displayed synapses (from 0.1% - 100%) +SYNAPSE_PERCENTAGE='100' + +# Base resolution of the rendered image +IMAGE_RESOLUTION='5000' + +# Save the rendering into a Blender file such that we can visualize the scene later interactively +SAVE_TO_BLEND_FILE='yes' + +##################################################################################################### +BOOL_ARGS='' +if [ "$SAVE_TO_BLEND_FILE" == "yes" ]; + then BOOL_ARGS+=' --save-blend-file '; fi +if [ "$UNIFY_BRANCHES_RADII" == "yes" ]; + then BOOL_ARGS+=' --unify-branches-radii '; fi + +#################################################################################################### +$BLENDER -b --verbose 0 --python visualize_synapses_color_coded_by_x_synaptic_mtypes.py -- \ + --circuit-config=$CIRCUIT_CONFIG \ + --gid=$NEURON_GID \ + --output-directory=$OUTPUT_DIRECTORY \ + --neuron-color=$NEURON_COLOR \ + --pre-or-post='pre' \ + --unified-branches-radius=$UNIFIED_NEURON_RADIUS \ + --axon-branching-order=$AXON_BRANCHING_ORDER \ + --synapses-color-map=$SYNAPSES_COLOR_MAP \ + --synapse-radius=$SYNAPSE_RADIUS \ + --synapse-percentage=$SYNAPSE_PERCENTAGE \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS diff --git a/scripts/synaptics/mtype/visualize_synapses_color_coded_by_x_synaptic_mtypes.py b/scripts/synaptics/mtype/visualize_synapses_color_coded_by_x_synaptic_mtypes.py new file mode 100644 index 000000000..b77e547e8 --- /dev/null +++ b/scripts/synaptics/mtype/visualize_synapses_color_coded_by_x_synaptic_mtypes.py @@ -0,0 +1,182 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys +import argparse + +# BBP imports +from bluepy import Circuit + +# Internal imports +import nmv.bbox +import nmv.bbp +import nmv.enums +import nmv.rendering +import nmv.scene +import nmv.utilities + + +#################################################################################################### +# @parse_command_line_arguments +#################################################################################################### +def parse_command_line_arguments(): + """Parses the input arguments. + + :return: + Arguments list. + """ + + # Add all the options + description = 'This add-on uses NeuroMorphoVis to create a Blender file and list of images ' \ + 'to visualize a custom list of synapses on the dendrites of a post-synaptic ' \ + 'neuron in a BBP circuit. ' + parser = argparse.ArgumentParser(description=description) + + arg_help = 'BBP circuit configuration file' + parser.add_argument('--circuit-config', + action='store', dest='circuit_config', help=arg_help) + + arg_help = 'The GID of the post-synaptic neuron' + parser.add_argument('--gid', + action='store', dest='gid', type=int, help=arg_help) + + arg_help = 'A JSON file containing the color-map of the synapses based on the mtypes of the ' \ + 'pre-synaptic neuron in the following format: ' \ + '{MTYPE_0: HEX_COLOR_0, MTYPE_1: HEX_COLOR_1, ...}' + parser.add_argument('--synapses-color-map', + action='store', dest='synapses_color_map', help=arg_help) + + arg_help = 'Output directory, where the final artifacts will be generated' + parser.add_argument('--output-directory', + action='store', dest='output_directory', help=arg_help) + + arg_help = 'Neuron color in R_G_B format, for example: 255_231_192' + parser.add_argument('--neuron-color', + action='store', dest='neuron_color', help=arg_help) + + arg_help = 'Display the synapses based on the mtypes of the pre- or the post-synaptic neurons' + parser.add_argument('--pre-or-post', + action='store', dest='pre_or_post', help=arg_help) + + arg_help = 'If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for ' \ + 'all the branches' + parser.add_argument('--unify-branches-radii', + dest='unify_branches_radii', action='store_true', default=False, + help=arg_help) + + arg_help = 'The value of the branches unified radius (in um)' + parser.add_argument('--unified-branches-radius', + action='store', dest='unified_branches_radius', type=float, help=arg_help) + + arg_help = 'The branching order of the axon. By default, it is 1 to highlight the synapses' + parser.add_argument('--axon-branching-order', + action='store', dest='axon_branching_order', type=int, default=10000, + help=arg_help) + + arg_help = 'Synapse radius (in um)' + parser.add_argument('--synapse-radius', + action='store', dest='synapse_radius', type=float, help=arg_help) + + arg_help = 'The percentage of synapses to be drawn in the rendering' + parser.add_argument('--synapse-percentage', + action='store', dest='synapse_percentage', type=float, help=arg_help) + + arg_help = 'Base image resolution' + parser.add_argument('--image-resolution', + action='store', default=2048, type=int, dest='image_resolution', + help=arg_help) + + arg_help = 'Save the scene into a 3D Blender file for interactive visualization later' + parser.add_argument('--save-blend-file', + dest='save_blend_file', action='store_true', default=False, help=arg_help) + + # Parse the arguments + return parser.parse_args() + + +#################################################################################################### +# @ Main +#################################################################################################### +if __name__ == "__main__": + + # Parse the command line arguments + system_args = sys.argv + sys.argv = system_args[system_args.index("--") + 0:] + args = parse_command_line_arguments() + + # Clear the scene + nmv.logger.info('Cleaning Scene') + nmv.scene.clear_scene() + + material_type = nmv.enums.Shader.LAMBERT_WARD + + # Read the circuit + nmv.logger.info('Loading circuit') + circuit = Circuit(args.circuit_config) + + # Creating the synapse group from the mesh + nmv.logger.info('Loading synapses from the synapses file ') + if args.pre_or_post == 'pre': + synapse_groups = nmv.bbp.create_color_coded_synapse_groups_by_pre_mtype( + circuit=circuit, post_gid=int(args.gid), mtype_color_dict= + nmv.bbp.get_color_coded_synapse_dict(args.synapses_color_map)) + else: + synapse_groups = nmv.bbp.create_color_coded_synapse_groups_by_post_mtype( + circuit=circuit, pre_gid=int(args.gid), mtype_color_dict= + nmv.bbp.get_color_coded_synapse_dict(args.synapses_color_map)) + + # Create the neuron mesh + nmv.logger.info('Creating the neuron mesh') + neuron_color = nmv.utilities.confirm_rgb_color_from_color_string(args.neuron_color) + neuron_mesh = nmv.bbp.create_symbolic_neuron_mesh_in_circuit( + circuit=circuit, gid=args.gid, + unified_radius=args.unify_branches_radii, + branch_radius=args.unified_branches_radius, + soma_color=neuron_color, + basal_dendrites_color=neuron_color, + apical_dendrites_color=neuron_color, + axons_color=neuron_color, + material_type=material_type, + axon_branching_order=args.axon_branching_order) + + # Create the synapses mesh + nmv.logger.info('Creating the synapse mesh') + transformation = nmv.bbp.get_neuron_transformation_matrix(circuit=circuit, gid=int(args.gid)) + nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=args.synapse_radius, + synapses_percentage=args.synapse_percentage, + inverted_transformation=transformation.inverted(), + material_type=material_type) + + # Render the image + nmv.logger.info('Rendering image') + nmv.rendering.render( + camera_view=nmv.enums.Camera.View.FRONT, + bounding_box=nmv.bbox.compute_scene_bounding_box_for_meshes(), + image_resolution=args.image_resolution, + image_name='%s-%s' % (str(args.gid), args.pre_or_post), + image_directory=args.output_directory) + + # Export the scene into a blender file for interactive visualization + if args.save_blend_file: + nmv.logger.info('Saving into a .BLEND file [%s/%s-%s.blend]' % + (args.output_directory, str(args.gid), args.pre_or_post)) + nmv.file.export_scene_to_blend_file( + output_directory=args.output_directory, + output_file_name='%s-%s' % (str(args.gid), args.pre_or_post)) diff --git a/scripts/synaptics/post_synaptic/visualize_synapses_on_post_synaptic_neuron.py b/scripts/synaptics/post_synaptic/visualize_synapses_on_post_synaptic_neuron.py new file mode 100644 index 000000000..b3f0c0e08 --- /dev/null +++ b/scripts/synaptics/post_synaptic/visualize_synapses_on_post_synaptic_neuron.py @@ -0,0 +1,169 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys +import argparse + +# BBP imports +from bluepy import Circuit + +# Internal imports +import nmv.bbox +import nmv.bbp +import nmv.enums +import nmv.rendering +import nmv.scene +import nmv.utilities + + +#################################################################################################### +# @parse_command_line_arguments +#################################################################################################### +def parse_command_line_arguments(): + """Parses the input arguments. + + :return: + Arguments list. + """ + + # Add all the options + description = 'This add-on uses NeuroMorphoVis to create a Blender file and list of images ' \ + 'to visualize a custom list of synapses on the dendrites of a post-synaptic ' \ + 'neuron in a BBP circuit. ' + parser = argparse.ArgumentParser(description=description) + + arg_help = 'BBP circuit configuration file' + parser.add_argument('--circuit-config', + action='store', dest='circuit_config', help=arg_help) + + arg_help = 'The GID of the post-synaptic neuron' + parser.add_argument('--gid', + action='store', dest='gid', type=int, help=arg_help) + + arg_help = 'A JSON file containing the IDs of the synapses and their corresponding colors in ' \ + 'the following format: ' \ + '{"R_G_B": [6563274686, 6563274687, ..], "R_G_B": [6563277480, 6563277481, .. ]}' + parser.add_argument('--synapses-file', + action='store', dest='synapses_file', help=arg_help) + + arg_help = 'Output directory, where the final artifacts will be generated' + parser.add_argument('--output-directory', + action='store', dest='output_directory', help=arg_help) + + arg_help = 'Neuron color in R_G_B format, for example: 255_231_192' + parser.add_argument('--neuron-color', + action='store', dest='neuron_color', help=arg_help) + + arg_help = 'If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for ' \ + 'all the branches' + parser.add_argument('--unify-branches-radii', + dest='unify_branches_radii', action='store_true', default=False, + help=arg_help) + + arg_help = 'The value of the branches unified radius (in um)' + parser.add_argument('--unified-branches-radius', + action='store', dest='unified_branches_radius', type=float, help=arg_help) + + arg_help = 'The branching order of the axon. By default, it is 1 to highlight the synapses' + parser.add_argument('--axon-branching-order', + action='store', dest='axon_branching_order', type=int, help=arg_help) + + arg_help = 'Synapse radius (in um)' + parser.add_argument('--synapse-radius', + action='store', dest='synapse_radius', type=float, help=arg_help) + + arg_help = 'The percentage of synapses to be drawn in the rendering' + parser.add_argument('--synapse-percentage', + action='store', dest='synapse_percentage', type=float, help=arg_help) + + arg_help = 'Base image resolution' + parser.add_argument('--image-resolution', + action='store', default=2048, type=int, dest='image_resolution', + help=arg_help) + + arg_help = 'Save the scene into a 3D Blender file for interactive visualization later' + parser.add_argument('--save-blend-file', + dest='save_blend_file', action='store_true', default=False, help=arg_help) + + # Parse the arguments + return parser.parse_args() + + +#################################################################################################### +# @ Main +#################################################################################################### +if __name__ == "__main__": + + # Parse the command line arguments + system_args = sys.argv + sys.argv = system_args[system_args.index("--") + 0:] + args = parse_command_line_arguments() + + # Clear the scene + nmv.logger.info('Cleaning Scene') + nmv.scene.clear_scene() + + material_type = nmv.enums.Shader.LAMBERT_WARD + + # Read the circuit + nmv.logger.info('Loading circuit') + circuit = Circuit(args.circuit_config) + + # Creating the synapse group from the mesh + nmv.logger.info('Loading synapses from the synapses file ') + synapse_groups = nmv.bbp.get_synapse_groups_from_color_coded_json_file(args.synapses_file) + + # Create the neuron mesh + nmv.logger.info('Creating the neuron mesh') + neuron_color = nmv.utilities.confirm_rgb_color_from_color_string(args.neuron_color) + neuron_mesh = nmv.bbp.create_symbolic_neuron_mesh_in_circuit( + circuit=circuit, gid=args.gid, + unified_radius=args.unify_branches_radii, + branch_radius=args.unified_branches_radius, + soma_color=neuron_color, + basal_dendrites_color=neuron_color, + apical_dendrites_color=neuron_color, + axons_color=neuron_color, + material_type=material_type, + axon_branching_order=args.axon_branching_order) + + # Create the synapses mesh + nmv.logger.info('Creating the synapse mesh') + transformation = nmv.bbp.get_neuron_transformation_matrix(circuit=circuit, gid=int(args.gid)) + synapses_mesh = nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=args.synapse_radius, + synapses_percentage=args.synapse_percentage, + inverted_transformation=transformation.inverted(), + material_type=material_type) + + # Render the image + nmv.logger.info('Rendering image') + nmv.rendering.render( + camera_view=nmv.enums.Camera.View.FRONT, + bounding_box=nmv.bbox.compute_scene_bounding_box_for_meshes(), + image_resolution=args.image_resolution, + image_name=str(args.gid), + image_directory=args.output_directory) + + # Export the scene into a blender file for interactive visualization + if args.save_blend_file: + nmv.logger.info('Saving into a .BLEND file [%s/%s.blend]' % + (args.output_directory, str(args.gid))) + nmv.file.export_scene_to_blend_file(output_directory=args.output_directory, + output_file_name=str(args.gid)) diff --git a/scripts/synaptics/post_synaptic/visualize_synapses_on_post_synaptic_neuron.sh b/scripts/synaptics/post_synaptic/visualize_synapses_on_post_synaptic_neuron.sh new file mode 100755 index 000000000..252b29d88 --- /dev/null +++ b/scripts/synaptics/post_synaptic/visualize_synapses_on_post_synaptic_neuron.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../../blender + +# BBP circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the post synaptic neuron +NEURON_GID='3774248' + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-1' + +# Color-coded synapses file +SYNAPSES_JSON_FILE='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-1/3774248.synapses' + +# If this variable is set to yes, we will use the UNIFIED_NEURON_RADIUS value for all the branches +UNIFY_BRANCHES_RADII='yes' + +# A constant value for the radius of the neuron branches. This value will be ignore if +# UNIFY_BRANCHES_RADII is set to yes +UNIFIED_NEURON_RADIUS='1.0' + +# The color of the neuron +NEURON_COLOR='#ff2e48' + +# Axon branching order, in certain cases, it is nice to hide the axon to focus on the synapses +AXON_BRANCHING_ORDER=1 + +# The fixed radius of the synapses +SYNAPSE_RADIUS='2.0' + +# The percentage of the displayed synapses (from 0.1% - 100%) +SYNAPSE_PERCENTAGE='100' + +# Base resolution of the rendered image +IMAGE_RESOLUTION='5000' + +# Save the rendering into a Blender file such that we can visualize the scene later interactively +SAVE_TO_BLEND_FILE='yes' + +##################################################################################################### +BOOL_ARGS='' +if [ "$SAVE_TO_BLEND_FILE" == "yes" ]; + then BOOL_ARGS+=' --save-blend-file '; fi +if [ "$UNIFY_BRANCHES_RADII" == "yes" ]; + then BOOL_ARGS+=' --unify-branches-radii '; fi + +#################################################################################################### +$BLENDER -b --verbose 0 --python visualize_synapses_on_post_synaptic_neuron.py -- \ + --circuit-config=$CIRCUIT_CONFIG \ + --gid=$NEURON_GID \ + --output-directory=$OUTPUT_DIRECTORY \ + --neuron-color=$NEURON_COLOR \ + --unified-branches-radius=$UNIFIED_NEURON_RADIUS \ + --axon-branching-order=$AXON_BRANCHING_ORDER \ + --synapses-file=$SYNAPSES_JSON_FILE \ + --synapse-radius=$SYNAPSE_RADIUS \ + --synapse-percentage=$SYNAPSE_PERCENTAGE \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS diff --git a/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathway.py b/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathway.py new file mode 100644 index 000000000..5e1f1f31a --- /dev/null +++ b/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathway.py @@ -0,0 +1,246 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys +import argparse + +# BBP imports +from bluepy import Circuit + +# Internal imports +import nmv.bbox +import nmv.bbp +import nmv.enums +import nmv.rendering +import nmv.scene +import nmv.utilities + + +#################################################################################################### +# @parse_command_line_arguments +#################################################################################################### +def parse_command_line_arguments(): + """Parses the input arguments. + + :return: + Arguments list. + """ + + # Add all the options + description = 'This add-on uses NeuroMorphoVis to create a Blender file and list of images ' \ + 'to visualize a custom list of synapses on the dendrites of a post-synaptic ' \ + 'neuron in a BBP circuit. ' + parser = argparse.ArgumentParser(description=description) + + arg_help = 'Output directory, where the final artifacts will be generated' + parser.add_argument('--output-directory', + action='store', dest='output_directory', help=arg_help) + + arg_help = 'BBP circuit configuration file' + parser.add_argument('--circuit-config', + action='store', dest='circuit_config', help=arg_help) + + arg_help = 'The GID of the pre-synaptic neuron' + parser.add_argument('--pre-synaptic-neuron-gid', + action='store', dest='pre_gid', type=int, help=arg_help) + + arg_help = 'The GID of the post-synaptic neuron' + parser.add_argument('--post-synaptic-neuron-gid', + action='store', dest='post_gid', type=int, help=arg_help) + + arg_help = 'The type of the radii of the neuronal branches' + parser.add_argument('--branches-radii-type', + action='store', dest='branches_radii_type', help=arg_help) + + arg_help = 'The value of the unified radius of the branches (in um)' + parser.add_argument('--unified-branches-radius', + action='store', dest='unified_branches_radius', type=float, help=arg_help) + + arg_help = 'A scale factor for the radii' + parser.add_argument('--branches-radius-scale', + action='store', dest='branches_radius_scale', type=float, help=arg_help) + + arg_help = 'The color of the dendrites of the pre-synaptic neuron.' + parser.add_argument('--pre-synaptic-dendrites-color', + action='store', dest='pre_synaptic_dendrites_color', help=arg_help) + + arg_help = 'The color of the axons of the pre-synaptic neuron.' + parser.add_argument('--pre-synaptic-axons-color', + action='store', dest='pre_synaptic_axons_color', help=arg_help) + + arg_help = 'The color of the dendrites of the post-synaptic neuron.' + parser.add_argument('--post-synaptic-dendrites-color', + action='store', dest='post_synaptic_dendrites_color', help=arg_help) + + arg_help = 'The color of the axons of the post-synaptic neuron.' + parser.add_argument('--post-synaptic-axons-color', + action='store', dest='post_synaptic_axons_color', help=arg_help) + + arg_help = 'The color of the synapses.' + parser.add_argument('--synapses-color', + action='store', dest='synapses_color', help=arg_help) + + arg_help = 'Synapse radius (in um)' + parser.add_argument('--synapse-radius', + action='store', dest='synapse_radius', type=float, help=arg_help) + + arg_help = 'Base image resolution' + parser.add_argument('--image-resolution', + action='store', default=2048, type=int, dest='image_resolution', + help=arg_help) + + arg_help = 'Save the scene into a 3D Blender file for interactive visualization later' + parser.add_argument('--save-blend-file', + dest='save_blend_file', action='store_true', default=False, help=arg_help) + + # Parse the arguments + return parser.parse_args() + + +#################################################################################################### +# @get_branch_radius_type +#################################################################################################### +def get_branch_radius_type(in_type): + """Gets the type of the radii of the branches of the neuron. + + :param in_type: + The input string that defines the type from the interface. + :return: + The corresponding enumerator. + """ + + if 'original' in in_type.lower(): + return nmv.enums.Skeleton.Radii.ORIGINAL + elif 'scaled' in in_type.lower(): + return nmv.enums.Skeleton.Radii.SCALED + elif 'unified' in in_type.lower(): + return nmv.enums.Skeleton.Radii.UNIFIED + else: + return nmv.enums.Skeleton.Radii.ORIGINAL + + +#################################################################################################### +# @ Main +#################################################################################################### +if __name__ == "__main__": + + # Parse the command line arguments + system_args = sys.argv + sys.argv = system_args[system_args.index("--") + 0:] + args = parse_command_line_arguments() + + # Clear the scene + nmv.logger.info('Cleaning Scene') + nmv.scene.clear_scene(deep_delete=True) + + material_type = nmv.enums.Shader.FLAT + + # Read the circuit + nmv.logger.info('Loading circuit') + bluepy_circuit = Circuit(args.circuit_config) + + # Make a NMV circuit + circuit = nmv.bbp.BBPCircuit(circuit_config=args.circuit_config) + + # Get the radii of the branches + branches_radii_type = get_branch_radius_type(in_type=args.branches_radii_type) + + # Create the mesh of the pre-synaptic neuron at the global coordinates + nmv.logger.info('Creating the pre-synaptic neuron mesh') + pre_synaptic_dendrites_color = nmv.utilities.confirm_rgb_color_from_color_string( + args.pre_synaptic_dendrites_color) + pre_synaptic_axons_color = nmv.utilities.confirm_rgb_color_from_color_string( + args.pre_synaptic_axons_color) + pre_synaptic_neuron_mesh = nmv.bbp.nmv.bbp.create_neuron_mesh_in_circuit( + circuit=circuit, gid=args.pre_gid, + branch_radius_type=branches_radii_type, + branch_radius=args.unified_branches_radius, + branch_radius_scale_factor=args.branches_radius_scale, + soma_color=pre_synaptic_dendrites_color, + basal_dendrites_color=pre_synaptic_dendrites_color, + apical_dendrites_color=pre_synaptic_dendrites_color, + axons_color=pre_synaptic_axons_color, + material_type=material_type) + + nmv.logger.info('Creating the post-synaptic neuron mesh') + post_synaptic_dendrites_color = nmv.utilities.confirm_rgb_color_from_color_string( + args.post_synaptic_dendrites_color) + post_synaptic_axons_color = nmv.utilities.confirm_rgb_color_from_color_string( + args.post_synaptic_axons_color) + post_synaptic_neuron_mesh = nmv.bbp.create_neuron_mesh_in_circuit( + circuit=circuit, gid=args.post_gid, + branch_radius_type=branches_radii_type, + branch_radius=args.unified_branches_radius, + branch_radius_scale_factor=args.branches_radius_scale, + axon_branching_order=1, + soma_color=post_synaptic_dendrites_color, + basal_dendrites_color=post_synaptic_dendrites_color, + apical_dendrites_color=post_synaptic_dendrites_color, + axons_color=post_synaptic_axons_color, + material_type=material_type) + + # Get the transformations of the pre- and post-synaptic neurons + pre_synaptic_transformation = circuit.get_neuron_transformation_matrix( + gid=args.pre_gid) + post_synaptic_transformation = circuit.get_neuron_transformation_matrix( + gid=args.post_gid) + + # Since the focus is given to the pre-synaptic neuron; it is the originally loaded one, + # transform the post-synaptic neuron w.r.t to the pre synaptic one + if post_synaptic_neuron_mesh is not None: + post_synaptic_neuron_mesh.matrix_world = post_synaptic_transformation + post_synaptic_neuron_mesh.matrix_world = \ + pre_synaptic_transformation.inverted() @ post_synaptic_neuron_mesh.matrix_world + + # Create the synapses mesh + nmv.logger.info('Creating the synapse mesh') + # Initially, try to get a list of synapses shared between the two cells + shared_synapses_ids = circuit.get_shared_synapses_ids_between_two_neurons( + pre_gid=args.pre_gid, post_gid=args.post_gid) + + # Create the shared group + synapse_groups = list() + synapse_groups.append( + nmv.bbp.get_shared_synapses_group_between_two_neurons( + circuit=circuit, pre_gid=args.pre_gid, post_gid=args.post_gid, + color=args.synapses_color)) + + nmv.logger.info('Adding synapses to the scene') + nmv.bbp.create_color_coded_synapses_particle_system( + circuit=circuit, synapse_groups=synapse_groups, + synapse_radius=args.synapse_radius, + synapses_percentage=100, + inverted_transformation=pre_synaptic_transformation.inverted(), + material_type=material_type) + + # Render the image + nmv.logger.info('Rendering image') + nmv.rendering.render( + camera_view=nmv.enums.Camera.View.FRONT, + bounding_box=nmv.bbox.compute_scene_bounding_box_for_meshes(), + image_resolution=args.image_resolution, + image_name='synaptic_pathways_%s-%s' % (str(args.pre_gid), args.post_gid), + image_directory=args.output_directory) + + # Export the scene into a blender file for interactive visualization + if args.save_blend_file: + nmv.logger.info('Saving into a .BLEND file [%s/%s-%s.blend]' % + (args.output_directory, str(args.pre_gid), args.post_gid)) + nmv.file.export_scene_to_blend_file( + output_directory=args.output_directory, + output_file_name='%s-%s' % (str(args.pre_gid), args.post_gid)) diff --git a/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathway.sh b/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathway.sh new file mode 100755 index 000000000..53bc2d621 --- /dev/null +++ b/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathway.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../../blender + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-4' +OUTPUT_DIRECTORY='/abdellah2/scratch/spines' + +# BBP circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig_h5' +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the pre- and post-synaptic neurons +PRE_SYNAPTIC_NEURON_GID='38749' +POST_SYNAPTIC_NEURON_GID='3642408' + +# Options: 'original', 'unified', or 'scaled' +BRANCHES_RADII_TYPE='original' + +# A constant value for the radius of the neuron branches. This value will be ignore if +# UNIFY_BRANCHES_RADII is set to yes +UNIFIED_NEURON_RADIUS='1.0' + +# Scale factor for the radii +RADII_SCALE_FACTOR='3.0' + +# The colors of the pre- and post-synaptic neurons +PRE_SYNAPTIC_DENDRITES_COLOR='#E02F61' # 194, 59, 212 +PRE_SYNAPTIC_AXONS_COLOR='#EB0038' # 59, 118, 212 +POST_SYNAPTIC_DENDRITES_COLOR='#68A8E0' # 194, 59, 212 +POST_SYNAPTIC_AXONS_COLOR='#68A8E0' # 212, 59, 79 + +# The color of the shaed synapses +SYNAPSES_COLOR='#2D2C7F' + +# A given fixed radius for the synapses, it is represented as a symbolic sphere +SYNAPSE_RADIUS='3.0' + +# Base resolution of the rendered image +IMAGE_RESOLUTION='5000' + +# Save the rendering into a Blender file such that we can visualize the scene later interactively +SAVE_TO_BLEND_FILE='yes' + +##################################################################################################### +BOOL_ARGS='' +if [ "$SAVE_TO_BLEND_FILE" == "yes" ]; + then BOOL_ARGS+=' --save-blend-file '; fi + +#################################################################################################### +$BLENDER -b --verbose 0 --python visualize_synaptic_pathway.py -- \ + --output-directory=$OUTPUT_DIRECTORY \ + --circuit-config=$CIRCUIT_CONFIG \ + --pre-synaptic-neuron-gid=$PRE_SYNAPTIC_NEURON_GID \ + --post-synaptic-neuron-gid=$POST_SYNAPTIC_NEURON_GID \ + --pre-synaptic-dendrites-color=$PRE_SYNAPTIC_DENDRITES_COLOR \ + --pre-synaptic-axons-color=$PRE_SYNAPTIC_AXONS_COLOR \ + --post-synaptic-dendrites-color=$POST_SYNAPTIC_DENDRITES_COLOR \ + --post-synaptic-axons-color=$POST_SYNAPTIC_AXONS_COLOR \ + --branches-radii-type=$BRANCHES_RADII_TYPE \ + --unified-branches-radius=$UNIFIED_NEURON_RADIUS \ + --branches-radius-scale=$RADII_SCALE_FACTOR \ + --synapses-color=$SYNAPSES_COLOR \ + --synapse-radius=$SYNAPSE_RADIUS \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS diff --git a/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathways.py b/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathways.py new file mode 100644 index 000000000..05e6f5963 --- /dev/null +++ b/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathways.py @@ -0,0 +1,215 @@ +#################################################################################################### +# Copyright (c) 2023, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# System imports +import sys +import argparse +import subprocess + + +#################################################################################################### +# @parse_command_line_arguments +#################################################################################################### +def parse_command_line_arguments(): + """Parses the input arguments. + + :return: + Arguments list. + """ + + # Add all the options + description = 'This add-on uses NeuroMorphoVis to create a Blender file and list of images ' \ + 'to visualize a custom list of synapses on the dendrites of a post-synaptic ' \ + 'neuron in a BBP circuit. ' + parser = argparse.ArgumentParser(description=description) + + arg_help = 'The path to blender' + parser.add_argument('--blender', + action='store', dest='blender', help=arg_help) + + arg_help = 'Output directory, where the final artifacts will be generated' + parser.add_argument('--output-directory', + action='store', dest='output_directory', help=arg_help) + + arg_help = 'BBP circuit configuration file' + parser.add_argument('--circuit-config', + action='store', dest='circuit_config', help=arg_help) + + arg_help = 'The list of the pre- and post-synaptic pairs.' + parser.add_argument('--pairs-list', + action='store', dest='pairs_list', help=arg_help) + + arg_help = 'The type of the radii of the neuronal branches' + parser.add_argument('--branches-radii-type', + action='store', dest='branches_radii_type', help=arg_help) + + arg_help = 'The value of the unified radius of the branches (in um)' + parser.add_argument('--unified-branches-radius', + action='store', dest='unified_branches_radius', type=float, help=arg_help) + + arg_help = 'A scale factor for the radii' + parser.add_argument('--branches-radius-scale', + action='store', dest='branches_radius_scale', type=float, help=arg_help) + + arg_help = 'The color of the dendrites of the pre-synaptic neuron.' + parser.add_argument('--pre-synaptic-dendrites-color', + action='store', dest='pre_synaptic_dendrites_color', help=arg_help) + + arg_help = 'The color of the axons of the pre-synaptic neuron.' + parser.add_argument('--pre-synaptic-axons-color', + action='store', dest='pre_synaptic_axons_color', help=arg_help) + + arg_help = 'The color of the dendrites of the post-synaptic neuron.' + parser.add_argument('--post-synaptic-dendrites-color', + action='store', dest='post_synaptic_dendrites_color', help=arg_help) + + arg_help = 'The color of the axons of the post-synaptic neuron.' + parser.add_argument('--post-synaptic-axons-color', + action='store', dest='post_synaptic_axons_color', help=arg_help) + + arg_help = 'The color of the synapses.' + parser.add_argument('--synapses-color', + action='store', dest='synapses_color', help=arg_help) + + arg_help = 'Synapse radius (in um)' + parser.add_argument('--synapse-radius', + action='store', dest='synapse_radius', type=float, help=arg_help) + + arg_help = 'Base image resolution' + parser.add_argument('--image-resolution', + action='store', default=2048, type=int, dest='image_resolution', + help=arg_help) + + arg_help = 'Save the scene into a 3D Blender file for interactive visualization later' + parser.add_argument('--save-blend-file', + dest='save_blend_file', action='store_true', default=False, help=arg_help) + + # Parse the arguments + return parser.parse_args() + + +#################################################################################################### +# @parse_pair_file +#################################################################################################### +def parse_pair_file(file_path): + """Parses the input pair file and returns the pre- and post-synaptic pair. + + :param file_path: + The input synaptic pair file. + :return: + A list of all the pairs. + """ + + # Open the file + f = open(file_path, 'r') + + # Get the list + pairs = list() + for line in f: + data = line.split('-') + pre_gid = data[0].replace('a', '').replace('\n', '') + post_gid = data[1].replace('a', '').replace('\n', '') + pairs.append([pre_gid, post_gid]) + + # Close the file + f.close() + + # Return the list + return pairs + + +#################################################################################################### +# @construct_command +#################################################################################################### +def construct_command(args, + pair): + """Constructs the command that will be executed on a per-pair-basis. + + :param args: + System args. + :param pair: + Neuron pair list, pre and post. + :return: + The execution command. + """ + + command = '%s -b --verbose 0 ' % args.blender + command += '--python visualize_synaptic_pathway.py -- ' + command += '--output-directory %s ' % args.output_directory + command += '--circuit %s ' % args.circuit_config + command += '--pre-synaptic-neuron-gid %s ' % pair[0] + command += '--post-synaptic-neuron-gid %s ' % pair[1] + command += '--pre-synaptic-dendrites-color \'%s\' ' % args.pre_synaptic_dendrites_color + command += '--pre-synaptic-axons-color \'%s\' ' % args.pre_synaptic_axons_color + command += '--post-synaptic-dendrites-color \'%s\' ' % args.post_synaptic_dendrites_color + command += '--post-synaptic-axons-color \'%s\' ' % args.post_synaptic_axons_color + command += '--branches-radii-type %s ' % args.branches_radii_type + command += '--unified-branches-radius %s ' % args.unified_branches_radius + command += '--branches-radius-scale %s ' % args.branches_radius_scale + command += '--synapses-color \'%s\' ' % args.synapses_color + command += '--synapse-radius %s ' % args.synapse_radius + command += '--image-resolution %s ' % args.image_resolution + + # Bool options + if args.save_blend_file: + command += '--save-blend-file ' + + # Return the resulting command + return command + + +#################################################################################################### +# @execute_shell_command +#################################################################################################### +def execute_shell_command(shell_command): + """Executes the shell command. + + :param shell_command: + A given shell command to execute. + """ + print(shell_command) + subprocess.call(shell_command, shell=True) + + +#################################################################################################### +# @main +#################################################################################################### +def main(): + """The main function.""" + + # Parse the command line arguments + args = parse_command_line_arguments() + + # Get the pairs + pairs = parse_pair_file(file_path=args.pairs_list) + + # Run the command for the pairs + commands = list() + for pair in pairs: + commands.append(construct_command(args=args, pair=pair)) + + # Execute the commands + for command in commands: + execute_shell_command(shell_command=command) + + +#################################################################################################### +# @ Main +#################################################################################################### +if __name__ == "__main__": + + main() diff --git a/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathways.sh b/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathways.sh new file mode 100755 index 000000000..ed235044d --- /dev/null +++ b/scripts/synaptics/synaptic_pathway/visualize_synaptic_pathways.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +#################################################################################################### +# Copyright (c) 2020, EPFL / Blue Brain Project +# Marwan Abdellah +# +# This file is part of NeuroMorphoVis +# +# This program is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, version 3 of the License. +# +# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program. +# If not, see . +#################################################################################################### + +# Blender executable, adjust it to match the executable located on macOS or Linux +BLENDER=$PWD/../../../../../../../blender + +# The output directory where the scene and images will be generated +OUTPUT_DIRECTORY='/gpfs/bbp.cscs.ch/project/proj3/projects-data/synaptics/ex-4' +OUTPUT_DIRECTORY='/abdellah2/scratch/spines' +OUTPUT_DIRECTORY='/gpfs/bbp.cscs.ch/project/proj3/projects-data/2023.05.02-synaptic-pairs/trial-3/set-3' + +# BBP circuit config +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig_h5' +CIRCUIT_CONFIG='/gpfs/bbp.cscs.ch/project/proj83/circuits/Bio_M/20200805/CircuitConfig' + +# The GID of the pre- and post-synaptic neurons +PAIRS_LIST_FILE='/gpfs/bbp.cscs.ch/project/proj3/projects-data/2023.05.02-synaptic-pairs/data/3.pairs' + +# Options: 'original', 'unified', or 'scaled' +BRANCHES_RADII_TYPE='scaled' + +# A constant value for the radius of the neuron branches. This value will be ignore if +# UNIFY_BRANCHES_RADII is set to yes +UNIFIED_NEURON_RADIUS='1.0' + +# Scale factor for the radii +RADII_SCALE_FACTOR='3.0' + +# The colors of the pre- and post-synaptic neurons +PRE_SYNAPTIC_DENDRITES_COLOR='#000000' # '#68A8E0' #'#E02F61' # 194, 59, 212 +PRE_SYNAPTIC_AXONS_COLOR='#EB0038' # 59, 118, 212 +POST_SYNAPTIC_DENDRITES_COLOR='#68A8E0' # 194, 59, 212 +POST_SYNAPTIC_AXONS_COLOR='#68A8E0' # 212, 59, 79 + +# The color of the shaed synapses +SYNAPSES_COLOR='#2D2C7F' + +# A given fixed radius for the synapses, it is represented as a symbolic sphere +SYNAPSE_RADIUS='5.0' + +# Base resolution of the rendered image +IMAGE_RESOLUTION='5000' + +# Save the rendering into a Blender file such that we can visualize the scene later interactively +SAVE_TO_BLEND_FILE='yes' + +##################################################################################################### +BOOL_ARGS='' +if [ "$SAVE_TO_BLEND_FILE" == "yes" ]; + then BOOL_ARGS+=' --save-blend-file '; fi + +#################################################################################################### +python3 visualize_synaptic_pathways.py \ + --blender=$BLENDER \ + --output-directory=$OUTPUT_DIRECTORY \ + --circuit-config=$CIRCUIT_CONFIG \ + --pairs-list=$PAIRS_LIST_FILE \ + --pre-synaptic-dendrites-color=$PRE_SYNAPTIC_DENDRITES_COLOR \ + --pre-synaptic-axons-color=$PRE_SYNAPTIC_AXONS_COLOR \ + --post-synaptic-dendrites-color=$POST_SYNAPTIC_DENDRITES_COLOR \ + --post-synaptic-axons-color=$POST_SYNAPTIC_AXONS_COLOR \ + --branches-radii-type=$BRANCHES_RADII_TYPE \ + --unified-branches-radius=$UNIFIED_NEURON_RADIUS \ + --branches-radius-scale=$RADII_SCALE_FACTOR \ + --branches-radius-scale=$RADII_SCALE_FACTOR \ + --synapses-color=$SYNAPSES_COLOR \ + --synapse-radius=$SYNAPSE_RADIUS \ + --image-resolution=$IMAGE_RESOLUTION \ + $BOOL_ARGS diff --git a/scripts/vasculature/main.py b/scripts/vasculature/main.py deleted file mode 100755 index ff44a6ec6..000000000 --- a/scripts/vasculature/main.py +++ /dev/null @@ -1,68 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# System imports -import os, sys, ntpath, h5py - -# Blender imports -import bpy -from mathutils import Vector - -# NeuroMorphoVis imports -import nmv.mesh -import nmv.scene - -# Internal imports -project_space = ntpath.basename(os.path.dirname(os.path.realpath(__file__))) -project_path = os.path.dirname(os.path.realpath(__file__)).replace(project_space, '') -sys.path.append(project_path) - -import vasculature_loader -import vasculature_skeletonizer - - -#################################################################################################### -# @reconstruct_vasculature -#################################################################################################### -def reconstruct_vasculature(): - - # Clear the scene - nmv.scene.ops.clear_scene() - - # Vasculature path - vasculature_morphology = '/data/morphologies/vasculature/vasculature-datas-set-2.h5' - #vasculature_morphology = '/computer/data/vasculature.h5' - #vasculature_morphology = '/data/vasculature/vasculature.h5' - - # Load the morphology - loader = vasculature_loader.VasculatureLoader(vasculature_morphology) - - # Skeletonize the morphology - skeletonizer = vasculature_skeletonizer.VasculatureSkeletonizer( - points_list=loader.points_list, segments_list=loader.segments_list, - sections_list=loader.sections_list, connections_list=loader.connections_list) - skeletonizer.skeletonize() - - # Get the roots - print('Roots: %d' % len(skeletonizer.roots)) - - # Data - print('STATUS: Skeleton reconstruction Done !') - - -reconstruct_vasculature() - diff --git a/scripts/vasculature/vasculature.blend b/scripts/vasculature/vasculature.blend deleted file mode 100644 index 17516ab59..000000000 Binary files a/scripts/vasculature/vasculature.blend and /dev/null differ diff --git a/scripts/vasculature/vasculature_loader.py b/scripts/vasculature/vasculature_loader.py deleted file mode 100644 index 26ef57eec..000000000 --- a/scripts/vasculature/vasculature_loader.py +++ /dev/null @@ -1,83 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# System imports -import h5py - -# Blender imports -import bpy -from mathutils import Vector - - -#################################################################################################### -# VasculatureLoader -#################################################################################################### -class VasculatureLoader: - """ A simple loader to load the vasculature data from h5 files. """ - - ################################################################################################ - # @__init__ - ################################################################################################ - def __init__(self, - dataset): - """Constructor - - :param dataset: - A path to the data set to load. - """ - - # Section index - self.dataset = dataset - - # A list of all the points in the data set - self.points_list = list() - - # A list of all the segments in the data set - self.segments_list = list() - - # A list of all the sections in the data set - self.sections_list = list() - - # A list of all the connections in the data set - self.connections_list = list() - - # Load the data set directly - self.load_dataset_from_file() - - ################################################################################################ - # @load_dataset_from_file - ################################################################################################ - def load_dataset_from_file(self): - """Loads the dataset from the file. - """ - - print('STATUS: Loading dataset') - - # Read the h5 file using the python module into a data array - data = h5py.File(self.dataset, 'r') - - # A list of all the samples in the data set - self.points_list = data['points'].value - - # A list of all the edges or 'segments' in the data set - self.segments_list = data['edges'].value - - # A list of all the sections (called structures) in the data set - self.sections_list = data['chains']['structure'].value - - # A list of all the connections between the different sections in the data set - self.connections_list = data['chains']['connectivity'].value diff --git a/scripts/vasculature/vasculature_section.py b/scripts/vasculature/vasculature_section.py deleted file mode 100644 index 0f59cead8..000000000 --- a/scripts/vasculature/vasculature_section.py +++ /dev/null @@ -1,124 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - - -#################################################################################################### -# VasculatureSection -#################################################################################################### -class VasculatureSection: - """ A morphological section represents a series of morphological samples of vasculature. """ - - ################################################################################################ - # @__init__ - ################################################################################################ - def __init__(self, - index, - samples_list): - """Constructor - - :param index: - Section index. - :param samples_list: - A list of samples that compose this section. - """ - - # Section index - self.index = index - - # Segments samples (points along the section) - self.samples_list = samples_list - - # A reference to the section parents, if exist - self.parents = list() - - # A list of the children, if exist - self.children = list() - - # Section name - self.name = 'section_' + str(index) - - ################################################################################################ - # @update_children - ################################################################################################ - def update_children(self, child_section): - """Updates the children sections list. - - :param child_section: - The child section that is supposed to be emanating from this section. - """ - - # Append the child to the list - # TODO: Verify if a child already exists with the same index or not! - self.children.append(child_section) - - ################################################################################################ - # @update_parents - ################################################################################################ - def update_parents(self, parent_section): - """Updates the parents section list. - - :param parent_section: - The parent section with which this section is emanating from. - """ - - # Append the section to the list - self.parents.append(parent_section) - - ################################################################################################ - # @is_root - ################################################################################################ - def is_root(self): - """Check if the section is root or not. - - :return: - True if the section is root, and False otherwise. - """ - - if len(self.parents) == 0: - return True - - return False - - ################################################################################################ - # @has_children - ################################################################################################ - def has_children(self): - """Check if the section has children sections or not. - - :return: - True or False. - """ - - if len(self.children) > 0: - return True - - return False - - ################################################################################################ - # @has_parents - ################################################################################################ - def has_parents(self): - """Check if the section has a parent sections or not. - - :return: - True of False. - """ - - if len(self.parents) > 0: - return True - - return False diff --git a/scripts/vasculature/vasculature_skeletonizer.py b/scripts/vasculature/vasculature_skeletonizer.py deleted file mode 100644 index d191ed638..000000000 --- a/scripts/vasculature/vasculature_skeletonizer.py +++ /dev/null @@ -1,248 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# Blender import -from mathutils import Vector - -# Import vasculature scripts -import vasculature_sample -import vasculature_section - - -#################################################################################################### -# VasculatureSection -#################################################################################################### -class VasculatureSkeletonizer: - """Vasculature skeletonization class.""" - - ################################################################################################ - # @__init__ - ################################################################################################ - def __init__(self, - points_list, - segments_list, - sections_list, - connections_list): - """Constructor - - :param points_list: - A list of all the points in the morphology skeleton. - :param segments_list: - A list of all the edges in the morphology skeleton. - :param sections_list: - A list of all the sections in the morphology skeleton. - :param connections_list: - A list of all the connections in the morphology skeleton. - """ - - # A list of all the points in the morphology - self.morphology_points_list = points_list - - # A list of all the segments in the morphology - self.morphology_segments_list = segments_list - - # A list of all the sections in the morphology - self.morphology_sections_list = sections_list - - # A list of all the connections in the morphology - self.morphology_connections_list = connections_list - - # A list of all the sections in the vasculature morphology - self.sections_list = list() - - # A list of all the auxiliary sections that were added from the connectivity data - self.auxiliary_sections_list = list() - - # A list of all the root sections into the skeleton that can give us access to the rest - # of the sections - self.roots = list() - - ################################################################################################ - # @get_sample_on_segment - ################################################################################################ - def get_sample_on_segment(self, - sample_index): - """Returns an object of VasculatureSample using its index from the morphology samples list. - - :param sample_index: - The index of the sample. - :return: - Returns an object of VasculatureSample using its index from the morphology samples list. - """ - - # The coordinates of the first sample - x = self.morphology_points_list[sample_index][0] - y = self.morphology_points_list[sample_index][1] - z = self.morphology_points_list[sample_index][2] - - # The cartesian coordinates of the sample - point = Vector((x, y, z)) - - # The radius of the first sample - radius = self.morphology_points_list[sample_index][3] - - # Construct a new sample and return a reference to it - return vasculature_sample.VasculatureSample(sample_index, point, radius) - - ################################################################################################ - # @get_samples_on_section - ################################################################################################ - def get_samples_on_section(self, - initial_segment_index, - final_segment_index): - """Returns a list of all the samples belonging to a section that is composed of a set of - connected segments. - - :param initial_segment_index: - The index of the initial segment along the section. - :param final_segment_index: - The index of the final segment along the section. - :return: - Returns a list of all the samples that belong to the section. - """ - - # A list of all the samples along the section - section_samples_list = list() - - # Traverse the section, segment by segment - for i_segment in range(initial_segment_index, final_segment_index): - - # Get the first sample along the segment - sample = self.get_sample_on_segment(self.morphology_segments_list[i_segment][0]) - - # Add the sample to the samples list - section_samples_list.append(sample) - - # Return a reference to the samples list along the section - return section_samples_list - - ################################################################################################ - # @build_sections_list - ################################################################################################ - def build_sections_list(self): - """Builds a list of all the sections in the morphology to be able to skeletonize it - """ - - print('STATUS: Build sections list') - - # For each section in the morphology sections list - for i_section in range(len(self.morphology_sections_list) - 1): - - # The index of the first segment (or edge) along the section - initial_segment_index = self.morphology_sections_list[i_section] - - # The index of the last segment (or edge) along the section - final_segment_index = self.morphology_sections_list[i_section + 1] - - # Get a list that has all the samples of the section - section_samples_list = self.get_samples_on_section(initial_segment_index, - final_segment_index) - # Construct the section - section = vasculature_section.VasculatureSection(i_section, section_samples_list) - - # Add the section to the final list - self.sections_list.append(section) - - ################################################################################################ - # @construct_auxiliary_section - ################################################################################################ - def construct_auxiliary_section(self, - parent_section, - child_section): - """Construct an auxiliary section that connects the parent to the child, and add it to - the auxiliary sections list. - - :param parent_section: - A reference to the parent section. - :param child_section: - A reference to the child section. - """ - - # A list of samples - section_samples_list = list() - - # Add the parent section samples - section_samples_list.extend(parent_section.samples_list) - - # Add the child section samples - section_samples_list.extend(child_section.samples_list) - - # Construct the section - section = vasculature_section.VasculatureSection(-1, section_samples_list) - section.name = 'section_' + str(parent_section.index) + '_' + str(child_section.index) - - # Add the section to the auxiliary sections list - self.auxiliary_sections_list.append(section) - - ################################################################################################ - # @build_skeleton_trees - ################################################################################################ - def build_skeleton_trees(self): - """Use the connectivity data to build the trees of the different structures in the data. - """ - - print('STATUS: Build skeletons trees') - - # For each connection in the data - for i_connection in range(len(self.morphology_connections_list)): - - # Get the index of the parent - parent_index = self.morphology_connections_list[i_connection][0] - - # Get the index of the child - child_index = self.morphology_connections_list[i_connection][1] - - # Update the children list - # self.sections_list[parent_index].update_children(self.sections_list[child_index]) - - # Update the parent from None to a specific parent, otherwise it is a root - # self.sections_list[child_index].update_parents(self.sections_list[parent_index]) - - # Get the parent section - parent_section = self.sections_list[parent_index] - - # Get the child section - child_section = self.sections_list[child_index] - - # Construct the connectivity section from the parent and child - self.construct_auxiliary_section(parent_section, child_section) - - # Updating the roots - """ - for section in self.sections_list: - - # If the section has no parent - if not section.has_parents(): - - # Add him to the root list - self.roots.append(section) - """ - - ################################################################################################ - # @skeletonize - ################################################################################################ - def skeletonize(self): - """Skeletonizes the vasculature morphology. - """ - - # Build the sections list - self.build_sections_list() - - # Build the trees - self.build_skeleton_trees() - - diff --git a/scripts/vasculature/vasculature_sketcher.py b/scripts/vasculature/vasculature_sketcher.py deleted file mode 100644 index d7bef9a6a..000000000 --- a/scripts/vasculature/vasculature_sketcher.py +++ /dev/null @@ -1,130 +0,0 @@ -#################################################################################################### -# Copyright (c) 2016 - 2020, EPFL / Blue Brain Project -# Marwan Abdellah -# -# This file is part of NeuroMorphoVis -# -# This program is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, version 3 of the License. -# -# This Blender-based tool is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. -# If not, see . -#################################################################################################### - -# Blender imports -from mathutils import Vector - -# NeuroMorphoVis imports -import nmv.geometry -import nmv.mesh -import nmv.file -import nmv.scene - -# Import vasculature scripts -import vasculature_sample -import vasculature_section - - -#################################################################################################### -# VasculatureSketcher -#################################################################################################### -class VasculatureSketcher: - """Vasculature sketching class.""" - - ################################################################################################ - # @__init__ - ################################################################################################ - def __init__(self, - bevel_object): - """Constructor. - - :param bevel_object: - Input bevel object. - """ - - self.bevel_object = bevel_object - - ################################################################################################ - # @sketch_section - ################################################################################################ - def sketch_section(self, - section): - """Sketches the section as a tube - - :param section: - A given vasculature section. - :return: - A reference to the section polyline. - """ - - # Construct the poly-line data - poly_line_data = list() - - # Append the samples - for sample in section.samples_list: - poly_line_data.append([(sample.point[0], sample.point[1], sample.point[2], 1), - sample.radius]) - - bevel_object = nmv.mesh.create_bezier_circle(radius=1.0, vertices=16, name='bevel') - - # Draw a polyline - section_polyline = nmv.geometry.ops.draw_poly_line(poly_line_data, - bevel_object=bevel_object, - name=section.name, - caps=True) - return section_polyline - - ################################################################################################ - # @sketch_section - ################################################################################################ - def draw_and_save_section(self, - section, - output_directory): - """Draw and save the section. - - :param section: - Input section. - :param output_directory: - Output directory - """ - - # Construct the section polyline - section_polyline = self.sketch_section(section) - - # Convert the section polyline into a mesh - section_mesh = nmv.scene.ops.convert_object_to_mesh(section_polyline) - - # Save the section mesh into file - nmv.file.export_object_to_ply_file(section_mesh, output_directory, section.name) - - ################################################################################################ - # @sketch_section - ################################################################################################ - def draw_and_save_sections(self, - sections_list, - output_directory, - portion, - max_portions): - """Draws and saves the section as a ply file. - - :param sections_list: - Input list of sections to be drawn. - :param output_directory: - Output directory. - """ - - # For each section - for i in range(0, 100000): - - # Indication - print('%d' % (i)) - - # Clear the scene - # nmv.scene.clear_scene() - - # Draw and save the section - self.draw_and_save_section(sections_list[i], output_directory) diff --git a/setup.py b/setup.py index b37cd8d49..00766225d 100755 --- a/setup.py +++ b/setup.py @@ -19,12 +19,36 @@ # System imports import os import sys +import platform import subprocess import argparse import shutil import shlex +#################################################################################################### +# @get_pip_wheels +#################################################################################################### +def get_pip_wheels(): + """Returns a lit of the required wheels that should be installed to be able to run NMV. + + :return: + A list of lists, where each item in the list is composed of two strings. The first string + is the name of the pip package, and the second string is the recommended version. + If the second version if empty, the latest version will be installed. + :rtype: + """ + + return [['numpy', ''], + ['h5py', ''], + ['matplotlib', ''], + ['seaborn', ''], + ['pandas', ''], + ['Pillow', ''], + ['webbrowser', ''], + ['morphio', '']] + + #################################################################################################### # @parse_command_line_arguments #################################################################################################### @@ -38,15 +62,15 @@ def parse_command_line_arguments(arguments=None): """ # add all the options - description = 'Installing NeuroMorphoVis from scratch. Simple, easy and awesome! ' \ + description = 'Installing NeuroMorphoVis simple, easy and awesome! ' \ 'This script is valid for *nix-based operating systems including macOSX and ' \ - 'Linux distributions. For windows, you can download a zip package from the ' \ + 'Linux distributions. For windows, you can download a .zip package from the ' \ 'release page. \n' \ - 'NOTE: git, wget or curl must be installed to run this script.' + 'NOTE: python, git, wget or curl must be installed to run this script.' parser = argparse.ArgumentParser(description=description) - arg_help = 'Blender version. 2.79, 2.80, or 2.81, 2.82, 2.90 and 3.1. ' \ - 'By default it is 3.1. It is recommended to avoid 2.79.' + arg_help = 'Blender version. 2.79, 2.80, 3.1 and 3.5; default (3.5)' \ + 'It is recommended to avoid all preceding versions as much as possible.' parser.add_argument('--blender-version', action='store', dest='blender_version', default='3.1', help=arg_help) @@ -142,28 +166,16 @@ def install_for_linux(directory, blender_version, verbose=False): python_version = '3.7' package_name = 'blender-2.80-linux-glibc217-x86_64' extension = 'tar.bz2' - elif blender_version == '2.81': - python_version = '3.7' - package_name = 'blender-2.81-linux-glibc217-x86_64' - extension = 'tar.bz2' - elif blender_version == '2.82': - python_version = '3.7' - package_name = 'blender-2.82a-linux64' - extension = 'tar.xz' - elif blender_version == '2.83': - python_version = '3.7' - package_name = 'blender-2.83.9-linux64' - extension = 'tar.xz' - elif blender_version == '2.90': - python_version = '3.7' - package_name = 'blender-2.90.1-linux64' - extension = 'tar.xz' elif blender_version == '3.1': python_version = '3.10' package_name = 'blender-3.1.0-linux-x64' extension = 'tar.xz' + elif blender_version == '3.5': + python_version = '3.10' + package_name = 'blender-3.5.0-linux-x64' + extension = 'tar.xz' else: - print('ERROR: Wrong Blender version [%s]' % blender_version) + print('ERROR: Unsupported Blender version [%s]' % blender_version) exit(0) # Blender url @@ -183,23 +195,20 @@ def install_for_linux(directory, blender_version, verbose=False): run_command(shell_command, verbose) # Moving to blender - blender_directory = '%s/blender-neuromorphovis' % directory + blender_directory = '%s/blender-bbp' % directory shell_command = 'mv %s/%s %s' % (directory, package_name, blender_directory) if os.path.exists(blender_directory): os.rmdir(blender_directory) run_command(shell_command, verbose) # Clone NeuroMorphoVis into the 'addons' directory - addons_directory = '%s/blender-neuromorphovis/%s/scripts/addons/' % (directory, blender_version) + addons_directory = '%s/blender-bbp/%s/scripts/addons/' % (directory, blender_version) neuromorphovis_url = 'https://github.com/BlueBrain/NeuroMorphoVis.git' shell_command = 'git clone %s %s/neuromorphovis' % (neuromorphovis_url, addons_directory) run_command(shell_command, verbose) - # Installing dependencies - pip_wheels = ['numpy', 'matplotlib', 'seaborn', 'pandas', 'Pillow', 'webbrowser', 'morphio'] - # Removing the site-packages directory - blender_python_wheels = '%s/blender-neuromorphovis/%s/python/lib/python%s/site-packages/' % \ + blender_python_wheels = '%s/blender-bbp/%s/python/lib/python%s/site-packages/' % \ (directory, blender_version, python_version) shell_command = 'rm -rf %s/numpy' % blender_python_wheels run_command(shell_command, verbose) @@ -207,10 +216,10 @@ def install_for_linux(directory, blender_version, verbose=False): # Blender python blender_python_prefix = '%s/%s/python/bin/' % (blender_directory, blender_version) - if blender_version == '3.1': - blender_python = '%s/python%s' % (blender_python_prefix, python_version) - else: + if float(blender_version) < 3.1: blender_python = '%s/python%sm' % (blender_python_prefix, python_version) + else: + blender_python = '%s/python%s' % (blender_python_prefix, python_version) # Pip installation get_pip_script_url = 'https://bootstrap.pypa.io/get-pip.py' @@ -228,9 +237,12 @@ def install_for_linux(directory, blender_version, verbose=False): # Pip executable pip_executable = '%s/pip' % blender_python_prefix + # Installing dependencies + pip_wheels = get_pip_wheels() + # packages for i, wheel in enumerate(pip_wheels): - shell_command = '%s install --ignore-installed %s' % (pip_executable, wheel) + shell_command = '%s install --ignore-installed %s' % (pip_executable, wheel[0]) print('INSTALL: %s' % shell_command) run_command(shell_command, verbose) @@ -260,7 +272,7 @@ def install_for_linux(directory, blender_version, verbose=False): #################################################################################################### # @install_for_mac #################################################################################################### -def install_for_mac(directory, blender_version, verbose=False): +def install_for_mac(directory, blender_version, arch, verbose=False): """Install NeuroMorphoVis on macOSX operating system. :param directory: @@ -281,21 +293,18 @@ def install_for_mac(directory, blender_version, verbose=False): elif blender_version == '2.80': python_version = '3.7' package_name = 'blender-2.80rc3-macOS.dmg' - elif blender_version == '2.81': - python_version = '3.7' - package_name = 'blender-2.81-macOS.dmg' - elif blender_version == '2.82': - python_version = '3.7' - package_name = 'blender-2.82a-macOS.dmg' - elif blender_version == '2.83': - python_version = '3.7' - package_name = 'blender-2.83.1-macOS.dmg' - elif blender_version == '2.90': - python_version = '3.7' - package_name = 'blender-2.90.1-macOS.dmg' elif blender_version == '3.1': python_version = '3.10' package_name = 'blender-3.1.0-macos-x64.dmg' + elif blender_version == '3.5': + python_version = '3.10' + if 'x86' in arch: + package_name = 'blender-3.5.0-macos-x64.dmg' + elif 'arm' in arch: + package_name = 'blender-3.5.0-macos-arm64.dmg' + else: + print('ERROR: Unsupported architecture [%s]' % arch) + exit(0) else: print('ERROR: Wrong Blender version [%s]' % blender_version) exit(0) @@ -339,10 +348,10 @@ def install_for_mac(directory, blender_version, verbose=False): # Blender python blender_python_prefix = '%s/Contents/Resources/%s/python/bin/' % (blender_app_directory, blender_version) - if blender_version == '3.1': - blender_python = '%s/python%s' % (blender_python_prefix, python_version) - else: + if float(blender_version) < 3.1: blender_python = '%s/python%sm' % (blender_python_prefix, python_version) + else: + blender_python = '%s/python%s' % (blender_python_prefix, python_version) # Pip installation log_process('Installing Dependencies') @@ -368,11 +377,10 @@ def install_for_mac(directory, blender_version, verbose=False): run_command(shell_command, verbose) # Installing dependencies - pip_wheels = ['numpy', 'matplotlib', 'seaborn', 'pandas', 'Pillow', 'webbrowser', 'morphio'] - + pip_wheels = get_pip_wheels() for wheel in pip_wheels: log_detail('Installing: %s' % wheel) - shell_command = '%s install --ignore-installed %s' % (pip_executable, wheel) + shell_command = '%s install --ignore-installed %s' % (pip_executable, wheel[0]) run_command(shell_command, verbose) # h5py specific version @@ -414,24 +422,22 @@ def install_neuromorphovis(directory, blender_version, verbose=False): Verbose. """ + # Get the platform details + platform_uname = platform.uname() + # Header - log_header('Installing Blender for %s' % sys.platform) + log_header('Installing Blender for %s' % platform_uname.system) log_process('Installation Directory: %s' % directory) - # Linux - if sys.platform == "linux" or sys.platform == "linux2": + # Verify the OS + if "linux" in platform_uname.system.lower(): install_for_linux(directory, blender_version, verbose) - - # OS X - elif sys.platform == "darwin": - install_for_mac(directory, blender_version, verbose) - - # Windows + elif "darwin" in platform_uname.system.lower(): + install_for_mac(directory, blender_version, platform_uname.processor, verbose) elif sys.platform == "win32": print('This script is only valid for *nix-based operating systems. ' 'For windows, you can download a zip package from the release page.') exit(0) - else: print('ERROR: Unrecognized operating system %s' % sys.platform) exit(0) @@ -457,29 +463,24 @@ def install_neuromorphovis(directory, blender_version, verbose=False): log_header('Blender 2.79') elif args.blender_version == '2.80': log_header('Blender 2.80') - elif args.blender_version == '2.81': - log_header('Blender 2.81') - elif args.blender_version == '2.82': - log_header('Blender 2.82') - elif args.blender_version == '2.83': - log_header('Blender 2.83') - elif args.blender_version == '2.90': - log_header('Blender 2.90') elif args.blender_version == '3.1': log_header('Blender 3.1') + elif args.blender_version == '3.5': + log_header('Blender 3.5') else: - log_header('NeuroMorphoVis is ONLY available for Blender versions ' - '2.79, 2.80, 2.81, 2.82, 2.83, 2.90, 3.1. Recommended version: 3.1') + log_header('NeuroMorphoVis is ONLY available for the following Blender versions ' + '2.79, 2.80, 3.1 and 3.5. Recommended version: 3.5') exit(0) # Installation directory - installation_directory = '%s/bluebrain-blender-%s' % (args.install_prefix, args.blender_version) + installation_directory = '%s/bbp-blender-%s' % (args.install_prefix, args.blender_version) # Verify the installation directory + if not os.path.exists(args.install_prefix): + os.mkdir(args.install_prefix) if not os.path.exists(installation_directory): os.mkdir(installation_directory) else: - pass shutil.rmtree(installation_directory) os.mkdir(installation_directory)