def compute_and_visualize_tracts(trekker, position, affine, affine_vtk, n_tracts_max): """ Compute tractograms using the Trekker library. :param trekker: Trekker library instance :type trekker: Trekker.T :param position: 3 double coordinates (x, y, z) in list or array :type position: list :param affine: 4 x 4 numpy double array :type affine: numpy.ndarray :param affine_vtk: vtkMatrix4x4 isntance with affine transformation matrix :type affine_vtk: vtkMatrix4x4 :param n_tracts_max: maximum number of tracts to compute :type n_tracts_max: int """ # root = vtk.vtkMultiBlockDataSet() # Juuso's # seed = np.array([[-8.49, -8.39, 2.5]]) # Baran M1 # seed = np.array([[27.53, -77.37, 46.42]]) seed_trk = img_utils.convert_world_to_voxel(position, affine) bundle = vtkMultiBlockDataSet() n_branches, n_tracts, count_loop = 0, 0, 0 n_threads = 2 * const.N_CPU - 1 while n_tracts < n_tracts_max: n_param = 1 + (count_loop % 10) # rescale the alpha value that defines the opacity of the branch # the n interval is [1, 10] and the new interval is [51, 255] # the new interval is defined to have no 0 opacity (minimum is 51, i.e., 20%) alpha = (n_param - 1) * (255 - 51) / (10 - 1) + 51 trekker.minFODamp(n_param * 0.01) # print("seed example: {}".format(seed_trk)) trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0)) # print("trk list len: ", len(trekker.run())) trk_list = trekker.run() n_tracts += len(trk_list) if len(trk_list): branch = compute_tracts(trk_list, n_tract=0, alpha=alpha) bundle.SetBlock(n_branches, branch) n_branches += 1 count_loop += 1 if (count_loop == 20) and (n_tracts == 0): break Publisher.sendMessage('Remove tracts') if n_tracts: Publisher.sendMessage('Update tracts', root=bundle, affine_vtk=affine_vtk, coord_offset=position, coord_offset_w=seed_trk[0].tolist())
def run(self): trekker, affine, offset, n_tracts_total, seed_radius, n_threads = self.inp # as a single block, computes all the maximum number of tracts at once, not optimal for navigation n_threads = n_tracts_total p_old = np.array([[0., 0., 0.]]) root = vtk.vtkMultiBlockDataSet() # Compute the tracts # print('ComputeTractsThread: event {}'.format(self.event.is_set())) while not self.event.is_set(): try: coord, m_img, m_img_flip = self.coord_queue.get_nowait() # translate the coordinate along the normal vector of the object/coil coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] # coord_offset = np.array([[27.53, -77.37, 46.42]]) dist = abs(np.linalg.norm(p_old - np.asarray(coord_offset))) p_old = coord_offset.copy() seed_trk = img_utils.convert_world_to_voxel( coord_offset, affine) # Juuso's # seed_trk = np.array([[-8.49, -8.39, 2.5]]) # Baran M1 # seed_trk = np.array([[27.53, -77.37, 46.42]]) # print("Seed: {}".format(seed)) # set the seeds for trekker, one seed is repeated n_threads times # trekker has internal multiprocessing approach done in C. Here the number of available threads is give, # but in case a large number of tracts is requested, it will compute all in parallel automatically # for a more fluent navigation, better to compute the maximum number the computer handles trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0)) # run the trekker, this is the slowest line of code, be careful to just use once! trk_list = trekker.run() if trk_list: # if the seed is outside the defined radius, restart the bundle computation if dist >= seed_radius: root = tracts_computation(trk_list, root, 0) self.visualization_queue.put_nowait((coord, m_img, root)) self.coord_queue.task_done() time.sleep(self.sle) except queue.Empty: pass except queue.Full: self.coord_queue.task_done()
def compute_tracts(trekker, position, affine, affine_vtk, n_tracts): """ Compute tractograms using the Trekker library. :param trekker: Trekker library instance :type trekker: Trekker.T :param position: 3 double coordinates (x, y, z) in list or array :type position: list :param affine: 4 x 4 numpy double array :type affine: numpy.ndarray :param affine_vtk: vtkMatrix4x4 isntance with affine transformation matrix :type affine_vtk: vtkMatrix4x4 :param n_tracts: number of tracts to compute :type n_tracts: int """ # during neuronavigation, root needs to be initialized outside the while loop so the new tracts # can be appended to the root block set root = vtk.vtkMultiBlockDataSet() # Juuso's # seed = np.array([[-8.49, -8.39, 2.5]]) # Baran M1 # seed = np.array([[27.53, -77.37, 46.42]]) seed_trk = img_utils.convert_world_to_voxel(position, affine) # print("seed example: {}".format(seed_trk)) trekker.seed_coordinates(np.repeat(seed_trk, n_tracts, axis=0)) # print("trk list len: ", len(trekker.run())) trk_list = trekker.run() if trk_list: root = tracts_computation(trk_list, root, 0) Publisher.sendMessage('Remove tracts') Publisher.sendMessage('Update tracts', flag=True, root=root, affine_vtk=affine_vtk) else: Publisher.sendMessage('Remove tracts')
def run(self): trekker, affine, offset, n_tracts_total, seed_radius, n_threads = self.inp # n_threads = n_tracts_total p_old = np.array([[0., 0., 0.]]) n_tracts = 0 # Compute the tracts # print('ComputeTractsThread: event {}'.format(self.event.is_set())) while not self.event.is_set(): try: # print("Computing tracts") # get from the queue the coordinates, coregistration transformation matrix, and flipped matrix m_img_flip = self.coord_tracts_queue.get_nowait() # coord, m_img, m_img_flip = self.coord_queue.get_nowait() # print('ComputeTractsThread: get {}'.format(count)) # 20200402: in this new refactored version the m_img comes different than the position # the new version m_img is already flixped in y, which means that Y is negative # if only the Y is negative maybe no need for the flip_x funtcion at all in the navigation # but check all coord_queue before why now the m_img comes different than position # 20200403: indeed flip_x is just a -1 multiplication to the Y coordinate, remove function flip_x # m_img_flip = m_img.copy() # m_img_flip[1, -1] = -m_img_flip[1, -1] # translate the coordinate along the normal vector of the object/coil coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] # coord_offset = np.array([[27.53, -77.37, 46.42]]) dist = abs(np.linalg.norm(p_old - np.asarray(coord_offset))) p_old = coord_offset.copy() # print("p_new_shape", coord_offset.shape) # print("m_img_flip_shape", m_img_flip.shape) seed_trk = img_utils.convert_world_to_voxel( coord_offset, affine) # Juuso's # seed_trk = np.array([[-8.49, -8.39, 2.5]]) # Baran M1 # seed_trk = np.array([[27.53, -77.37, 46.42]]) # print("Seed: {}".format(seed)) # set the seeds for trekker, one seed is repeated n_threads times # trekker has internal multiprocessing approach done in C. Here the number of available threads is give, # but in case a large number of tracts is requested, it will compute all in parallel automatically # for a more fluent navigation, better to compute the maximum number the computer handles trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0)) # run the trekker, this is the slowest line of code, be careful to just use once! trk_list = trekker.run() if trk_list: # print("dist: {}".format(dist)) if dist >= seed_radius: # when moving the coil further than the seed_radius restart the bundle computation bundle = vtk.vtkMultiBlockDataSet() n_branches = 0 branch = tracts_computation_branch(trk_list) bundle.SetBlock(n_branches, branch) n_branches += 1 n_tracts = branch.GetNumberOfBlocks() # TODO: maybe keep computing even if reaches the maximum elif dist < seed_radius and n_tracts < n_tracts_total: # compute tracts blocks and add to bungle until reaches the maximum number of tracts branch = tracts_computation_branch(trk_list) if bundle: bundle.SetBlock(n_branches, branch) n_tracts += branch.GetNumberOfBlocks() n_branches += 1 else: bundle = None # rethink if this should be inside the if condition, it may lock the thread if no tracts are found # use no wait to ensure maximum speed and avoid visualizing old tracts in the queue, this might # be more evident in slow computer or for heavier tract computations, it is better slow update # than visualizing old data # self.visualization_queue.put_nowait([coord, m_img, bundle]) self.tracts_queue.put_nowait( (bundle, self.affine_vtk, coord_offset)) # print('ComputeTractsThread: put {}'.format(count)) self.coord_tracts_queue.task_done() # self.coord_queue.task_done() # print('ComputeTractsThread: done {}'.format(count)) # sleep required to prevent user interface from being unresponsive time.sleep(self.sle) # if no coordinates pass except queue.Empty: # print("Empty queue in tractography") pass # if queue is full mark as done (may not be needed in this new "nowait" method) except queue.Full: # self.coord_queue.task_done() self.coord_tracts_queue.task_done()
def run(self): # trekker, affine, offset, n_tracts_total, seed_radius, n_threads = self.inp trekker, affine, offset, n_tracts_total, seed_radius, n_threads, act_data, affine_vtk, img_shift = self.inp # n_threads = n_tracts_total p_old = np.array([[0., 0., 0.]]) p_old_pre = np.array([[0., 0., 0.]]) coord_offset = None n_tracts = 0 n_branches = 0 bundle = None sph_sampling = True dist_radius = 1.5 xyz = img_utils.random_sample_sphere(radius=seed_radius, size=100) coord_list_sph = np.hstack([xyz, np.ones([xyz.shape[0], 1])]).T m_seed = np.identity(4) # Compute the tracts # print('ComputeTractsThread: event {}'.format(self.event.is_set())) while not self.event.is_set(): try: # print("Computing tracts") # get from the queue the coordinates, coregistration transformation matrix, and flipped matrix m_img_flip = self.coord_tracts_queue.get_nowait() # coord, m_img, m_img_flip = self.coord_queue.get_nowait() # print('ComputeTractsThread: get {}'.format(count)) # TODO: Remove this is not needed # 20200402: in this new refactored version the m_img comes different than the position # the new version m_img is already flixped in y, which means that Y is negative # if only the Y is negative maybe no need for the flip_x funtcion at all in the navigation # but check all coord_queue before why now the m_img comes different than position # 20200403: indeed flip_x is just a -1 multiplication to the Y coordinate, remove function flip_x # m_img_flip = m_img.copy() # m_img_flip[1, -1] = -m_img_flip[1, -1] # DEBUG: Uncomment the m_img_flip below so that distance is fixed and tracts keep computing # m_img_flip[:3, -1] = (5., 10., 12.) dist = abs( np.linalg.norm(p_old_pre - np.asarray(m_img_flip[:3, -1]))) p_old_pre = m_img_flip[:3, -1].copy() # Uncertanity visualization -- # each tract branch is computed with one set of parameters ajusted from 1 to 10 n_param = 1 + (n_branches % 10) # rescale the alpha value that defines the opacity of the branch # the n interval is [1, 10] and the new interval is [51, 255] # the new interval is defined to have no 0 opacity (minimum is 51, i.e., 20%) alpha = (n_param - 1) * (255 - 51) / (10 - 1) + 51 trekker.minFODamp(n_param * 0.01) trekker.dataSupportExponent(n_param * 0.1) # --- # When moving the coil further than the seed_radius restart the bundle computation # Currently, it stops to compute tracts when the maximum number of tracts is reached maybe keep # computing even if reaches the maximum if dist >= dist_radius: # Anatomic constrained seed computation --- # The original seed location is replaced by the gray-white matter interface that is closest to # the coil center try: #TODO: Create a dialog error to say when the ACT data is not loaded and prevent # the interface from freezing. Give the user a chance to load it (maybe in task_navigator) coord_list_w_tr = m_img_flip @ self.coord_list_w coord_offset = grid_offset(act_data, coord_list_w_tr, img_shift) except IndexError: # This error might be caused by the coordinate exceeding the image array dimensions. # Needs further verification. coord_offset = None # --- # Translate the coordinate along the normal vector of the object/coil --- if coord_offset is None: # apply the coil transformation matrix coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] # --- # convert the world coordinates to the voxel space for using as a seed in Trekker # seed_trk.shape == [1, 3] seed_trk = img_utils.convert_world_to_voxel( coord_offset, affine) # print("Desired: {}".format(seed_trk.shape)) # DEBUG: uncomment the seed_trk below # Juuso's # seed_trk = np.array([[-8.49, -8.39, 2.5]]) # Baran M1 # seed_trk = np.array([[27.53, -77.37, 46.42]]) # print("Given: {}".format(seed_trk.shape)) # print("Seed: {}".format(seed)) # Spherical sampling of seed coordinates --- if sph_sampling: # CHECK: We use ACT only for the origin seed, but not for all the other coordinates. # Check how this can be solved. Applying ACT to all coordinates is maybe too much. # Maybe it doesn't matter because the ACT is just to help finding the closest location to # the TMS coil center. Also, note that the spherical sampling is applied only when the coil # location changes, all further iterations used the fixed n_threads samples to compute the # remaining tracts. # samples = np.random.choice(self.sph_idx, size=n_threads, p=self.pdf) # m_seed[:-1, -1] = seed_trk # sph_seed = m_seed @ self.coord_list_sph # seed_trk_r = sph_seed[samples, :] samples = np.random.choice(coord_list_sph.shape[1], size=n_threads) m_seed[:-1, -1] = seed_trk seed_trk_r = m_seed @ coord_list_sph[:, samples] seed_trk_r = seed_trk_r[:-1, :].T else: # set the seeds for trekker, one seed is repeated n_threads times # shape (24, 3) seed_trk_r = np.repeat(seed_trk, n_threads, axis=0) # --- # Trekker has internal multiprocessing approach done in C. Here the number of available threads is # given, but in case a large number of tracts is requested, it will compute all in parallel # automatically for a more fluent navigation, better to compute the maximum number the computer # handles trekker.seed_coordinates(seed_trk_r) # trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0)) # run the trekker, this is the slowest line of code, be careful to just use once! trk_list = trekker.run() # check if any tract was found, otherwise doesn't count if trk_list: bundle = vtk.vtkMultiBlockDataSet() branch = tracts_computation_branch(trk_list, alpha) bundle.SetBlock(n_branches, branch) n_branches = 1 n_tracts = branch.GetNumberOfBlocks() else: bundle = None n_branches = 0 n_tracts = 0 elif dist < dist_radius and n_tracts < n_tracts_total: trk_list = trekker.run() if trk_list: # compute tract blocks and add to bundle until reaches the maximum number of tracts # the alpha changes depending on the parameter set branch = tracts_computation_branch(trk_list, alpha) if bundle: bundle.SetBlock(n_branches, branch) n_tracts += branch.GetNumberOfBlocks() n_branches += 1 else: bundle = vtk.vtkMultiBlockDataSet() bundle.SetBlock(n_branches, branch) n_branches = 1 n_tracts = branch.GetNumberOfBlocks() # else: # bundle = None # else: # bundle = None # rethink if this should be inside the if condition, it may lock the thread if no tracts are found # use no wait to ensure maximum speed and avoid visualizing old tracts in the queue, this might # be more evident in slow computer or for heavier tract computations, it is better slow update # than visualizing old data # self.visualization_queue.put_nowait([coord, m_img, bundle]) self.tracts_queue.put_nowait( (bundle, affine_vtk, coord_offset)) # print('ComputeTractsThread: put {}'.format(count)) self.coord_tracts_queue.task_done() # self.coord_queue.task_done() # print('ComputeTractsThread: done {}'.format(count)) # sleep required to prevent user interface from being unresponsive time.sleep(self.sle) # if no coordinates pass except queue.Empty: # print("Empty queue in tractography") pass # if queue is full mark as done (may not be needed in this new "nowait" method) except queue.Full: # self.coord_queue.task_done() self.coord_tracts_queue.task_done()