From 1e79d00ff4aedd9be32df3d1d3939e22e22de7ce Mon Sep 17 00:00:00 2001 From: Philipp Schlegel Date: Tue, 26 Apr 2016 14:29:21 +0200 Subject: [PATCH] improved export/import of connectors - improved speed of connector import/export - using mesh color when exporting to svg now affects order of export > same colors are grouped - fixed several bugs --- CATMAIDImport.py | 351 ++++++++++++++++++++++++----------------------- 1 file changed, 178 insertions(+), 173 deletions(-) diff --git a/CATMAIDImport.py b/CATMAIDImport.py index 3c16141..3e9b156 100644 --- a/CATMAIDImport.py +++ b/CATMAIDImport.py @@ -19,6 +19,13 @@ ### CATMAID to Blender Import Script - Version History: +### V5.21 26/04/2015: + - greatly improved speed when importing/exporting connectors by pooling requests to server + - use mesh color will now also affect the order of export such that same colors are grouped + - added perspective brain outline to connector to svg export + - fixed a bug when importing connector spheres + - fixed a bug that ignored project_id when requesting skeleton data + ### V5.2 19/04/2015: - combined all threaded skeleton data retrieval into one function and added a timeout variable @@ -279,7 +286,7 @@ bl_info = { "name": "CATMAIDImport", "author": "Philipp Schlegel", - "version": (5, 1, 0), + "version": (5, 2, 1), "blender": (2, 7, 7), "location": "Properties > Scene > CATMAID Import", "description": "Imports Neuron from CATMAID server, Analysis tools, Export to SVG", @@ -626,7 +633,7 @@ def get_3D_skeleton ( skids, remote_instance, connector_flag = 1, tag_flag = 1): #print('Retrieving %i skeletons' % len(skids)) for i, skeleton_id in enumerate(skids): #Create URL for retrieving example skeleton from server - remote_compact_skeleton_url = remote_instance.get_compact_skeleton_url( 1 , skeleton_id, connector_flag, tag_flag ) + remote_compact_skeleton_url = remote_instance.get_compact_skeleton_url( project_id , skeleton_id, connector_flag, tag_flag ) #Retrieve node_data for example skeleton skeleton_data = remote_instance.fetch( remote_compact_skeleton_url ) @@ -636,7 +643,7 @@ def get_3D_skeleton ( skids, remote_instance, connector_flag = 1, tag_flag = 1): #print('Skeleton #%s retrieved [%i of %i]' % ( str(skeleton_id) , i+1 , len(skids) ) ) #if only a single skids is provided else: - remote_compact_skeleton_url = remote_instance.get_compact_skeleton_url( 1 , skids, connector_flag, tag_flag ) + remote_compact_skeleton_url = remote_instance.get_compact_skeleton_url( project_id , skids, connector_flag, tag_flag ) sk_data = remote_instance.fetch( remote_compact_skeleton_url ) return (sk_data) @@ -1493,7 +1500,7 @@ def poll(cls, context): if connected: return True else: - return False + return False def retrieveSkeletonData(skid_list,neuron_names=[],time_out=20): threads = {} @@ -1501,13 +1508,14 @@ def retrieveSkeletonData(skid_list,neuron_names=[],time_out=20): skdata = {} errors = None - print('Creating threads to retrieve data:') + print('Creating threads to retrieve skeleton data:') for i,skid in enumerate(skid_list): if neuron_names: if checkIfNeuronExists(skid,neuron_names[skid]): #print(neuron_names[skid],'already exists - skipping.') continue - t = retrieveSkidThreaded(skid) + remote_compact_skeleton_url = remote_instance.get_compact_skeleton_url( project_id ,skid, 1 , 1 ) + t = retrieveUrlThreaded ( remote_compact_skeleton_url ) t.start() threads[skid] = t print('\r Threads: '+str(len(threads)),end='') @@ -1536,11 +1544,11 @@ def retrieveSkeletonData(skid_list,neuron_names=[],time_out=20): return skdata, errors - -class retrieveSkidThreaded(threading.Thread): - def __init__(self,skid): +class retrieveUrlThreaded(threading.Thread): + def __init__(self,url,post_data=None): try: - self.skid = skid + self.url = url + self.post_data = post_data threading.Thread.__init__(self) self.connector_flag = 1 self.tag_flag = 1 @@ -1549,19 +1557,21 @@ def __init__(self,skid): def run(self): """ - Retrieve 3D skeletons for a single skeleton_ids + Retrieve data from single url """ - #print(self.skids) - remote_compact_skeleton_url = remote_instance.get_compact_skeleton_url( 1 , self.skid, self.connector_flag, self.tag_flag ) - self.sk_data = remote_instance.fetch( remote_compact_skeleton_url ) + #print(self.skids) + if self.post_data: + self.data = remote_instance.fetch( self.url, self.post_data ) + else: + self.data = remote_instance.fetch( self.url ) return def join(self): try: threading.Thread.join(self) - return self.sk_data + return self.data except: - print('!ERROR joining thread for',self.skid) + print('!ERROR joining thread for',self.url) return None def checkIfNeuronExists(skid,neuron_name): @@ -1865,41 +1875,69 @@ def execute(self, context): to_search = bpy.context.selected_objects filtered_ob_list = [] + filtered_skids = [] for ob in to_search: if ob.name.startswith('#'): + skid = re.search('#(.*?) -',ob.name).group(1) filtered_ob_list.append(ob) + filtered_skids.append(skid) start = time.clock() print("Retrieving connector data for %i neurons" % len(filtered_ob_list)) - skdata, errors = retrieveSkeletonData(filtered_ob_list, time_out = context.user_preferences.addons['CATMAIDImport'].preferences.time_out) + skdata, errors = retrieveSkeletonData( filtered_skids, time_out = context.user_preferences.addons['CATMAIDImport'].preferences.time_out ) + cndata, neuron_names = self.get_all_connectors( skdata ) for i,neuron in enumerate(filtered_ob_list): print('Creating Connectors for Neuron %i [of %i]' % ( i, len(filtered_ob_list) ) ) skid = re.search('#(.*?) -',neuron.name).group(1) - self.get_connectors(skid,skdata[skid], neuron.active_material.diffuse_color[0:3]) + self.get_connectors(skid,skdata[skid], cndata, neuron_names ,neuron.active_material.diffuse_color[0:3]) bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP',iterations = 1) if errors is None: self.report({'INFO'},'Import successfull. Look in layer %i' % self.layer) else: - self.report({'ERROR'}, errors) - + self.report({'ERROR'}, errors) - return {'FINISHED'} - - - def get_connectors(self, active_skeleton, node_data ,mesh_color = None): - """ - ### Retrieve compact json data and filter connector ids - node_data = [] - print('Retrieving connector data for skid %s...' % active_skeleton) - node_data = get_3D_skeleton ( active_skeleton, remote_instance, connector_flag = 1, tag_flag = 0 ) + return {'FINISHED'} - if node_data: - print('Success!') - """ + def get_all_connectors(self, skdata): + connector_id_list = [] + connector_postdata = {} + + for skid in skdata: + for c in skdata[skid][1]: + if self.get_outputs is True and c[2] == 0: + connector_id_list.append(c[1]) + if self.get_inputs is True and c[2] == 1: + connector_id_list.append(c[1]) + + for i, c in enumerate( list( set( connector_id_list ) ) ): + connector_tag = 'connector_ids[%i]' % i + connector_postdata[connector_tag] = c + remote_connector_url = remote_instance.get_connectors_url( project_id ) + + temp_data = remote_instance.fetch( remote_connector_url , connector_postdata ) + + skids_to_check = [] + cn_data = {} + for c in temp_data: + cn_data[ c[0] ] = c[1] + + if c[1]['presynaptic_to'] != None: + skids_to_check.append(c[1]['presynaptic_to']) + + for target_skid in c[1]['postsynaptic_to']: + if target_skid != None: + skids_to_check.append(target_skid) + + neuron_names = get_neuronnames( list ( set( skids_to_check + list(skdata) ) ) ) + + return cn_data, neuron_names + + + def get_connectors(self, active_skeleton, node_data, cndata, neuron_names ,mesh_color = None): connector_ids = [] i_pre = 0 i_post = 0 @@ -1907,68 +1945,43 @@ def get_connectors(self, active_skeleton, node_data ,mesh_color = None): connector_pre_postdata = {} connector_post_coords = {} connector_pre_coords = {} + + connector_data_pre = [] + connector_data_post = [] print('Extracting coordinates..') ### Get coordinates, divide into pre-/postsynapses and bring them into Blender space: switch y and z, divide by 10.000/10.000/-10.000 - for connection in node_data[1]: - + for connection in node_data[1]: if connection[2] == 1 and self.get_inputs is True: connector_pre_coords[connection[1]] = {} connector_pre_coords[connection[1]]['id'] = connection[1] connector_pre_coords[connection[1]]['coords'] = (connection[3]/self.conversion_factor,connection[5]/self.conversion_factor,connection[4]/-self.conversion_factor) - connector_tag = 'connector_ids[%i]' % i_pre - connector_pre_postdata[connector_tag] = connection[1] + #connector_tag = 'connector_ids[%i]' % i_pre + #connector_pre_postdata[connector_tag] = connection[1] + + #i_pre += 1 - i_pre += 1 + connector_data_pre.append ( [connection[1] , cndata[ connection[ 1 ] ] ] ) if connection[2] == 0 and self.get_outputs is True: connector_post_coords[connection[1]] = {} connector_post_coords[connection[1]]['id'] = connection[1] connector_post_coords[connection[1]]['coords'] = (connection[3]/self.conversion_factor,connection[5]/self.conversion_factor,connection[4]/-self.conversion_factor) - connector_ids.append(connection[1]) - connector_tag = 'connector_ids[%i]' % i_post + #connector_ids.append(connection[1]) + #connector_tag = 'connector_ids[%i]' % i_post ### Add connector_id of this synapse to postdata - connector_post_postdata[connector_tag] = connection[1] + #connector_post_postdata[connector_tag] = connection[1] - i_post += 1 - - print('%s Down- / %s Upstream connectors for skid %s found' % (len(connector_post_coords), len(connector_pre_coords), active_skeleton)) - - remote_connector_url = remote_instance.get_connectors_url( project_id ) + #i_post += 1 - if self.get_outputs is True and len(connector_post_postdata) > 0: - print( "Retrieving Postsynaptic Targets..." ) - ### Get connector data for all presynapses to determine number of postsynaptic targets and filter - connector_data_post = remote_instance.fetch( remote_connector_url , connector_post_postdata ) - else: - connector_data_post = [] - - if self.get_inputs is True and len(connector_pre_postdata) > 0: - print( "Retrieving Presynaptic Targets..." ) - ### Get connector data for all presynapses to filter later - connector_data_pre = remote_instance.fetch( remote_connector_url , connector_pre_postdata ) - else: - connector_data_pre = [] - - skids_to_check = [] - - for connector in connector_data_post+connector_data_pre: - if connector[1]['presynaptic_to'] != None: - skids_to_check.append(connector[1]['presynaptic_to']) - - for target_skid in connector[1]['postsynaptic_to']: - if target_skid != None: - skids_to_check.append(target_skid) - - skids_to_check = set(skids_to_check) + connector_data_post.append ( [connection[1] , cndata[ connection[ 1 ] ] ] ) - neuron_names = get_neuronnames(skids_to_check) + print('%s Down- / %s Upstream connectors for skid %s found' % (len(connector_post_coords), len(connector_pre_coords), active_skeleton)) - if connector_data_post or connector_data_pre: - print("Connector data successfully retrieved") + if connector_data_post or connector_data_pre: number_of_targets = {} neurons_included = [] @@ -2026,6 +2039,9 @@ def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def availableObjects(self, context): + """ + Polls for available density objects for export to svg (skeletons as well as connectors) + """ available_objects = [] for obj in bpy.data.objects: name = obj.name @@ -2046,7 +2062,7 @@ class ConnectorsToSVG(Operator, ExportHelper): items = [('Active','Active','Active'),('Selected','Selected','Selected'),('All','All','All')], description = "Choose for which neurons to export connectors.") random_colors = BoolProperty(name="Use Random Colors", default = False) - mesh_colors = BoolProperty(name="Use Mesh Colors", default = False, + use_mesh_colors = BoolProperty(name="Use Mesh Colors", default = False, description = "Neurons are exported with their Blender material diffuse color") #gray_colors = BoolProperty(name="Use Gray Colors", default = False) merge = BoolProperty(name="Merge into One", default = True, @@ -2066,7 +2082,7 @@ class ConnectorsToSVG(Operator, ExportHelper): default = 0.25, description = "Maximum allowed distance between Connector and a Node") export_inputs = BoolProperty(name="Export Inputs", default = True) - export_outputs = BoolProperty(name="Export Outputs", default = False) + export_outputs = BoolProperty(name="Export Outputs", default = True) scale_outputs = BoolProperty(name="Scale Presynapses", default = False, description = "Size of Presynapses based on number of postsynaptically connected neurons") basic_radius = FloatProperty(name="Base Radius", default = 0.5) @@ -2146,7 +2162,7 @@ def execute(self, context): skids_to_export.append(active_skid) neurons_to_export.append(bpy.context.active_object) - if self.mesh_colors: + if self.use_mesh_colors: self.mesh_color[active_skeleton] = bpy.context.active_object.active_material.diffuse_color elif self.which_neurons == 'Selected': @@ -2155,7 +2171,7 @@ def execute(self, context): skid = re.search('#(.*?) -',neuron.name).group(1) skids_to_export.append(skid) neurons_to_export.append(neuron) - if self.mesh_colors: + if self.use_mesh_colors: self.mesh_color[skid] = neuron.active_material.diffuse_color elif self.which_neurons == 'All': @@ -2164,17 +2180,18 @@ def execute(self, context): skid = re.search('#(.*?) -',neuron.name).group(1) skids_to_export.append(skid) neurons_to_export.append(neuron) - if self.mesh_colors: + if self.use_mesh_colors: self.mesh_color[skid] = neuron.active_material.diffuse_color print("Retrieving connector data for %i neurons" % len(skids_to_export)) skdata,errors = retrieveSkeletonData(skids_to_export, time_out = context.user_preferences.addons['CATMAIDImport'].preferences.time_out) + cndata = self.get_all_connectors( skdata ) if errors is not None: self.report({'ERROR'},errors) for skid in skids_to_export: - connector_data[skid] = self.get_connectors(skid,skdata[skid]) + connector_data[skid] = self.get_connectors(skid,skdata[skid],cndata) if self.color_by_connections: #If outputs are exported then count only upstream connections (upstream sources of these outputs) @@ -2186,62 +2203,54 @@ def execute(self, context): neurons_svg_string = self.create_svg_for_neuron(neurons_to_export) else: neurons_svg_string = {} - - self.export_to_svg(connector_data, neurons_svg_string) - - """ - if bpy.context.active_object is None and self.all_neurons is False: - print ('No Object Active') - elif bpy.context.active_object is not None and '#' not in bpy.context.active_object.name and self.all_neurons is False: - print ('Active Object not a Neuron') - elif self.all_neurons is False: - active_skeleton = re.search('#(.*?) -',bpy.context.active_object.name).group(1) - connector_data[active_skeleton] = self.get_connectors(active_skeleton) - - if self.color_by_connections: - #If outputs are exported then count only upstream connections (upstream sources of these outputs) - #If inputs are exported then count only downstream connections (downstream targets of these inputs) - #-> just use them invertedly for use_inputs/outputs when calling get_connectivity - self.connections_for_color = self.get_connectivity([active_skeleton],self.export_outputs,self.export_inputs) - - if self.mesh_colors: - self.mesh_color[active_skeleton] = bpy.context.active_object.active_material.diffuse_color - if self.export_neuron is True: - neurons_svg_string = self.create_svg_for_neuron([bpy.context.active_object]) - else: - neurons_svg_string = {} - - self.export_to_svg(connector_data,neurons_svg_string) - elif self.all_neurons is True: - - for neuron in bpy.data.objects: - if neuron.name.startswith('#'): - skid = re.search('#(.*?) -',neuron.name).group(1) - connector_data[skid] = self.get_connectors(skid) - neurons_to_export.append(neuron) - skids_to_export.append(skid) - if self.mesh_colors: - self.mesh_color[skid] = neuron.active_material.diffuse_color - - if self.color_by_connections: - #If outputs are exported then count only upstream connections (upstream sources of these outputs) - #If inputs are exported then count only downstream connections (downstream targets of these inputs) - #-> just use them invertedly for use_inputs/outputs when calling get_connectivity - self.connections_for_color = self.get_connectivity(skids_to_export,self.export_outputs,self.export_inputs) - - if self.export_neuron is True: - neurons_svg_string = self.create_svg_for_neuron(neurons_to_export) - else: - neurons_svg_string = {} - - self.export_to_svg(connector_data, neurons_svg_string) - """ + #Sort skids_to_export by color + if self.use_mesh_colors: + color_strings = { skid:str(color) for (skid,color) in self.mesh_color.items() } + skids_to_export = list( sorted( self.mesh_color, key = color_strings.__getitem__ ) ) + + self.export_to_svg( skids_to_export, connector_data, neurons_svg_string) return {'FINISHED'} + + + def get_all_connectors(self, skdata): + connector_id_list = [] + connector_postdata = {} + + for skid in skdata: + for c in skdata[skid][1]: + if self.export_outputs is True and c[2] == 0: + connector_id_list.append(c[1]) + if self.export_inputs is True and c[2] == 1: + connector_id_list.append(c[1]) + + for i, c in enumerate( list( set( connector_id_list ) ) ): + connector_tag = 'connector_ids[%i]' % i + connector_postdata[connector_tag] = c + + remote_connector_url = remote_instance.get_connectors_url( project_id ) + + temp_data = remote_instance.fetch( remote_connector_url , connector_postdata ) + + skids_to_check = [] + cn_data = {} + for c in temp_data: + cn_data[ c[0] ] = c[1] + + if c[1]['presynaptic_to'] != None: + skids_to_check.append(c[1]['presynaptic_to']) + + for target_skid in c[1]['postsynaptic_to']: + if target_skid != None: + skids_to_check.append(target_skid) + + self.check_ancestry ( list ( set( skids_to_check + list(skdata) ) ) ) + + return cn_data - def get_connectors(self, active_skeleton,node_data): + def get_connectors(self, active_skeleton, node_data, cndata): connector_ids = [] if self.filter_connectors: @@ -2261,6 +2270,9 @@ def get_connectors(self, active_skeleton,node_data): connector_post_coords = {} connector_pre_coords = {} nodes_list = {} + + connector_data_post = [] + connector_data_pre = [] print('Extracting coordinates..') @@ -2285,46 +2297,21 @@ def get_connectors(self, active_skeleton,node_data): #Format: connector_pre_coord[target_treenode_id][upstream_connector_id] = coords of target treenode connector_pre_coords[connection[0]][connection[1]] = {} - connector_pre_coords[connection[0]][connection[1]]['coords'] = nodes_list[connection[0]] #these are treenode coords, NOT connector coords - - """ - connector_pre_coords[connection[0]] = {} - connector_pre_coords[connection[0]]['connector_id'] = connection[1] - connector_pre_coords[connection[0]]['coords'] = nodes_list[connection[0]] - """ + connector_pre_coords[connection[0]][connection[1]]['coords'] = nodes_list[connection[0]] #these are treenode coords, NOT connector coords - ### This format is necessary for CATMAID url postdata: - ### Dictonary: 'connector_ids[x]' : connectorid - connector_tag = 'connector_ids[%i]' % i - connector_postdata_presynapses[connector_tag] = connection[1] + connector_data_pre.append( [ connection[1] , cndata[ connection[1] ] ] ) if connection[2] == 0 and self.export_outputs is True: connector_post_coords[connection[1]] = {} connector_post_coords[connection[1]]['id'] = connection[1] - connector_post_coords[connection[1]]['coords'] = (connection[3]/self.conversion_factor,connection[5]/self.conversion_factor,connection[4]/-self.conversion_factor) + connector_post_coords[connection[1]]['coords'] = (connection[3]/self.conversion_factor,connection[5]/self.conversion_factor,connection[4]/-self.conversion_factor) - connector_ids.append(connection[1]) - connector_tag = 'connector_ids[%i]' % i - connector_postdata_postsynapses[connector_tag] = connection[1] + connector_data_post.append( [ connection[1] , cndata[ connection[1] ] ] ) i += 1 print('%s Down- / %s Upstream connectors for skid %s found' % (len(connector_post_coords), len(connector_pre_coords), active_skeleton)) remote_connector_url = remote_instance.get_connectors_url( project_id ) - - if self.export_outputs is True: - print( "Retrieving Target Connectors..." ) - connector_data_post = remote_instance.fetch( remote_connector_url , connector_postdata_postsynapses ) - #print(connector_data_post) - else: - connector_data_post = [] - - if self.export_inputs is True: - print( "Retrieving Source Connectors..." ) - connector_data_pre = remote_instance.fetch( remote_connector_url , connector_postdata_presynapses ) - #print(connector_data_pre) - else: - connector_data_pre = [] if connector_data_pre or connector_data_post: print("Connectors successfully retrieved") @@ -2541,7 +2528,7 @@ def get_connectors(self, active_skeleton,node_data): return {'FINISHED'} - def export_to_svg(self, connector_data, neurons_svg_string): + def export_to_svg(self, skids_to_export, connector_data, neurons_svg_string): print('%i Neurons in Connector data found' % len(connector_data)) svg_header = '\n' @@ -2587,10 +2574,14 @@ def export_to_svg(self, connector_data, neurons_svg_string): brain_shape_top_string = '\n \n \n ' brain_shape_front_string = ' \n \n \n ' brain_shape_lateral_string = ' \n \n ' + brain_shape_dorsal_perspective_05_string = ' \n \n \n \n \n ' + brain_shape_dorsal_perspective_09_string = ' \n \n \n \n' ring_gland_top = ' \n \n ' ring_gland_front = ' \n \n ' ring_gland_lateral = ' \n \n ' + ring_gland_dorsal_perspective_05 = ' \n \n ' + ring_gland_dorsal_perspective_09 = ' \n \n \n' arrows_defs = ' \n \n \n \n ' @@ -2788,7 +2779,7 @@ def export_to_svg(self, connector_data, neurons_svg_string): print('Max connections for color_by_connection:', max_connection) ### Creating SVG starts here - for neuron in connector_data: + for neuron in skids_to_export: connectors_weight = connector_data[neuron][0] connectors_pre = connector_data[neuron][1] connectors_post = connector_data[neuron][2] @@ -2804,7 +2795,7 @@ def export_to_svg(self, connector_data, neurons_svg_string): if self.export_inputs is True: inputs_color = colormap[0] colormap.pop(0) - elif self.mesh_colors is True: + elif self.use_mesh_colors is True: inputs_color = (int(self.mesh_color[neuron][0] * 255), int(self.mesh_color[neuron][1] * 255), int(self.mesh_color[neuron][2] * 255)) @@ -3186,7 +3177,25 @@ def export_to_svg(self, connector_data, neurons_svg_string): if self.export_outputs is True: line_to_write += '' - f.write(line_to_write + '\n') + f.write(line_to_write + '\n') + + ### Add perspective brain shape + if self.merge is False or first_neuron is True: + if 'Perspective-Dorsal' in self.views_to_export and self.export_brain_outlines is True: + if round(self.x_persp_offset,2) == 0.5: + if self.export_brain_outlines is True: + f.write('\n' + brain_shape_dorsal_perspective_05_string + '\n') + + if self.export_ring_gland is True: + f.write('\n' + ring_gland_dorsal_perspective_05 + '\n') + + elif round(self.x_persp_offset,2) == 0.9: + if self.export_brain_outlines is True: + f.write('\n' + brain_shape_dorsal_perspective_09_string + '\n') + + if self.export_ring_gland is True: + f.write('\n' + ring_gland_dorsal_perspective_09 + '\n') + line_to_write = '' f.write(line_to_write + '\n \n \n') @@ -3837,16 +3846,12 @@ def check_ancestry(self, neurons_to_check): skids_to_check.append(neuron) elif neuron not in self.neuron_names: print('ERROR: Invalid Neuron Name found: %s' % neuron ) - self.report({'ERROR'},'Error(s) occurred: see console') + self.report({'ERROR'},'Error(s) occurred: see console') - #print('Checking Skeleton IDs:') - #print(skids_to_check) - skids = [] - names = [] - new_names = get_neuronnames(skids_to_check) - - for entry in new_names: - self.neuron_names[int(entry)] = new_names[entry] + if skids_to_check: + new_names = get_neuronnames(skids_to_check) + for entry in new_names: + self.neuron_names[int(entry)] = new_names[entry] def get_connectivity(self,neurons,use_upstream=True,use_downstream=True): """Counts connections of neurons to/from filter set by self.color_by_connections """ @@ -4979,7 +4984,7 @@ class ExportAllToSVG(Operator, ExportHelper): which_neurons = EnumProperty(name = "For which Neuron(s)?", items = [('Active','Active','Active'),('Selected','Selected','Selected'),('All','All','All')], description = "Choose which neurons to export.") - merge = BoolProperty(name="Merge into One", default = False, + merge = BoolProperty(name="Merge into One", default = True, description = "If exporting more than one neuron, render them all on top of each other, not in separate panels.") random_colors = BoolProperty(name="Use Random Colors", default = False,