Package napari_blender
Expand source code
__version__ = "0.0.1"
from ._reader import napari_get_reader, reader_function
from ._widget import (
transparant_widget, gradient_widget, tracked_widget,
timelapse_widget, video_loader_widget, microscopy_widget
)
from ._writer import write_multiple, write_single_image
# Import from scripts
from scripts.boolean_ops import (
create_difference, create_intersection, create_overlap
)
from scripts.compare import (
Transparant, Gradient, Tracked, Timelapse, Microscopy
)
from scripts.enums import MODES, SAMPLES, ROTATION
from scripts.interface import (
set_values, visualize, render, display, print_globals
)
from scripts.lineage import (
track_ancestor, track_child, get_track
)
from scripts.material import (
hsv_to_rgb, rgb_to_hsv, set_material, create_material,
create_child_material, color_nuclei, clamp_hue, set_hue,
find_material, MaterialNotFoundException
)
from scripts.metrics import (
metrics_dictionary, calculate_are, iou, frequency,
compare_labeled_volumes, calculate_jaccard, confusion_matrix
)
from scripts.mode import Mode
from scripts.object_helper import (
remesh_objects, get_all_coords, set_interpolation,
clean_duplicate_objs, import_object, set_origin_nuclei,
scale_object, rename_nuclei, set_rotation, set_parent,
duplicate_object, find_collection, find_object,
ObjectNotFoundException, CollectionNotFoundException
)
from scripts.parse import (
clean_data, downscale_image_by_sampling, read_trackmate,
extract_mesh, refine_mesh, export_to_obj, create_obj,
create_contour, create_contour_obj, create_contour_data
)
from scripts.rename import calculate_distance, min_cost, rename_obj
from scripts.render import RenderProgress
from scripts.scene_helper import (
change_scene_obj, register, recalculate_text, unregister,
change_text, clamp_indicator, set_indicator, set_gradient_color,
set_legend_color, hide_legend_items
)
from scripts.shapekey import hide_in_render, apply_shape_key, set_shape_keys
from scripts.trackmate import trackmate_peak_import, filter_spots
# Get all names in the current module's namespace
all_names = dir()
# Filter out any private attributes (those starting with underscore)
public_names = [name for name in all_names if not name.startswith('_')]
# Sort the names
public_names.sort()
__all__ = tuple(public_names)
Functions
def apply_shape_key(obj, target, index)
-
Set shape key to morph one object into another.
Parameters
obj
:bpy.types.Object
- The object to morph.
target
:bpy.types.Object
- The object to morph into.
index
:int
- The index of the current frame.
Expand source code
def apply_shape_key(obj, target, index): """ Set shape key to morph one object into another. Parameters ---------- obj : bpy.types.Object The object to morph. target : bpy.types.Object The object to morph into. index : int The index of the current frame. """ # Ensure the target object has the same number of vertices as the object # if len(obj.data.vertices) != len(target.data.vertices): # print(f'{len(obj.data.vertices)} != {len(target.data.vertices)}') # print(f"Error: Objects ({obj.name} & {target.name}) have different number of vertices.") # return obj.shape_key_add(from_mix=False) obj.shape_key_add(name='Morph') # Get the coordinates of vertices for both objects obj_vertices = np.array([vert.co for vert in obj.data.vertices]) target_vertices = np.array([vert.co for vert in target.data.vertices]) # Calculate the cost matrix (Euclidean distance between vertices) cost_matrix = np.linalg.norm(obj_vertices[:, np.newaxis] - target_vertices, axis=2) # Use Hungarian algorithm to find the minimum cost vertex mapping row_ind, col_ind = linear_sum_assignment(cost_matrix) # Apply the shape key by mapping vertices from the target to the object for i, j in zip(row_ind, col_ind): w = target.data.vertices[j] obj.data.shape_keys.key_blocks['Morph'].data[i].co = w.co set_shape_keys(obj, target, index)
def calculate_are(truth, pred)
-
Calculates adapted Rand error, precision, and recall.
Parameters
truth
:numpy.ndarray
- Ground truth labels.
pred
:numpy.ndarray
- Predicted labels.
Returns
tuple
- Adapted Rand error, precision, and recall.
Expand source code
def calculate_are(truth, pred): """ Calculates adapted Rand error, precision, and recall. Parameters ---------- truth : numpy.ndarray Ground truth labels. pred : numpy.ndarray Predicted labels. Returns ------- tuple Adapted Rand error, precision, and recall. """ return skimage.metrics.adapted_rand_error(truth, pred)
def calculate_distance(coords1, coords2)
-
Helper function that calculates the Euclidean distance between two 3D coordinates.
Parameters
coords1
:tuple
- First set of coordinates (x, y, z).
coords2
:tuple
- Second set of coordinates (x, y, z).
Returns
float
- The Euclidean distance between the two sets of coordinates.
Expand source code
def calculate_distance(coords1, coords2): """ Helper function that calculates the Euclidean distance between two 3D coordinates. Parameters ---------- coords1 : tuple First set of coordinates (x, y, z). coords2 : tuple Second set of coordinates (x, y, z). Returns ------- float The Euclidean distance between the two sets of coordinates. """ return math.sqrt(sum((c1 - c2)**2 for c1, c2 in zip(coords1, coords2)))
def calculate_jaccard(truth, prediction)
-
Calculates the Jaccard Index (JI) score between ground truth and predicted masks.
Parameters
truth
:numpy.ndarray
- Ground truth labels.
prediction
:numpy.ndarray
- Predicted labels.
Returns
float
- Jaccard Index (JI) score.
Expand source code
def calculate_jaccard(truth, prediction): """ Calculates the Jaccard Index (JI) score between ground truth and predicted masks. Parameters ---------- truth : numpy.ndarray Ground truth labels. prediction : numpy.ndarray Predicted labels. Returns ------- float Jaccard Index (JI) score. """ ji_values = compare_labeled_volumes(truth, prediction) try: final_values = [t[2] for t in ji_values] ji = sum(final_values) / len(final_values) if len(final_values) > 0 else 0 return ji except TypeError: print('Could not calculate Jaccard index')
def change_scene_obj(dicts)
-
Helper function that applies changes to the scene based on a dictionary generated by different visualization modes. This arranges the scene to be ready for that mode.
Parameters
dicts
:dict
- Dictionary containing multiple dictionaries, indicating the required starting conditions for the visualization mode.
Expand source code
def change_scene_obj(dicts): """ Helper function that applies changes to the scene based on a dictionary generated by different visualization modes. This arranges the scene to be ready for that mode. Parameters ---------- dicts : dict Dictionary containing multiple dictionaries, indicating the required starting conditions for the visualization mode. """ # Get sub-dictionaries that indicate respective imports texts = dicts['texts'] materials = dicts['materials'] hide_obj = dicts['hide_obj'] # Change all text objects indicated for text_name, text in texts.items(): change_text(text, text_name) # Generate all indicated materials for count, (mat_name, values) in enumerate(materials.items()): # Check if material contains the correct number of parameters if len(values) == 5: create_material(mat_name, values[:4], values[-1]) # Set this material to be correctly displayed in the color legend set_legend_color(mat_name, count+1) else: print('Material color object does not contain the correct amount of values.:\n(Hue,Saturation,Value,Alpha,Material Blend Mode)') # After creating materials, hide unused legend items from render. hide_legend_items(len(materials)) # (Un)Hide all objects indicated for obj_name, hide in hide_obj.items(): try: # First check if name is collection, before checking if it is an object coll = find_collection(obj_name) coll.hide_render = hide except CollectionNotFoundException: try: obj = find_object(obj_name) obj.hide_render = hide except ObjectNotFoundException: print(f'Changing hide render setting to {hide} failed')
def change_text(text, text_name)
-
Changes the text of a specified text object in the scene.
Parameters
text
:str
- The new text to set.
text_name
:str
- The name of the text object to modify.
Expand source code
def change_text(text, text_name): """ Changes the text of a specified text object in the scene. Parameters ---------- text : str The new text to set. text_name : str The name of the text object to modify. """ # Find the text object by name try: text_obj = find_object(text_name) text_obj.data.body = text except ObjectNotFoundException: print('Changing text suspended')
def clamp_hue(ji)
-
Clamp the hue value to a specific range.
Parameters
ji
:float
- The hue value to clamp [0..1].
Returns
float
orNone
- Clamped hue value.
Expand source code
def clamp_hue(ji): """ Clamp the hue value to a specific range. Parameters ---------- ji : float The hue value to clamp [0..1]. Returns ------- float or None Clamped hue value. """ if ji is not None and 0 <= ji <= 1: new_value = ji * 0.3 return new_value else: print(f"Jaccard index should be within the range [0, 1] but was {ji}") return None
def clamp_indicator(metric)
-
Clamp the indicator value.
Parameters
metric
:float
- The metric value to clamp.
Returns
float
- Clamped metric value.
Expand source code
def clamp_indicator(metric): """ Clamp the indicator value. Parameters ---------- metric : float The metric value to clamp. Returns ------- float Clamped metric value. """ # Check if value is within the range [0, 1] if metric is not None and 0 <= metric <= 1: # Rescale the value to the range [0.335, 0]. # This is the hue that represents a dark green color # that will show a prediction to be perfect new_value = -0.34 + (0.36 + 0.34) * metric return new_value else: print(f"Metric should be within the range [0, 1] but was {metric}") return 0
def clean_data(data, frame=None)
-
Cleans and preprocesses image data.
This function accepts image data as a NumPy array and performs preprocessing steps such as selecting a specific frame, converting to grayscale, and scaling pixel values.
Parameters
data
:numpy.ndarray
- The input image data as a NumPy array.
frame
:int
, optional- The frame index to select from the image data.
Returns
numpy.ndarray
- Preprocessed image data.
Expand source code
def clean_data(data, frame=None): """ Cleans and preprocesses image data. This function accepts image data as a NumPy array and performs preprocessing steps such as selecting a specific frame, converting to grayscale, and scaling pixel values. Parameters ---------- data : numpy.ndarray The input image data as a NumPy array. frame : int, optional The frame index to select from the image data. Returns ------- numpy.ndarray Preprocessed image data. """ if frame is not None: try: if data.ndim == 3: # If data has 3 dimensions, return as is result = data elif data.ndim == 4: # If data has 4 dimensions, select specific frame result = data[frame, :, :, :] elif data.ndim == 5: # If data has 5 dimensions, convert to grayscale result = np.dot(data[frame,:,:,:,:3], [0.299, 0.587, 0.114]) else: raise ValueError("Data array must have either 3, 4 or 5 dimensions") # Check if result data type is uint8 if result.dtype != np.uint8: # Scale pixel values to uint8 range [0, 255] min_val = np.min(result) max_val = np.max(result) scaled_image = ((result - min_val) * 255 / (max_val - min_val)).astype(np.uint8) return scaled_image return data # Return preprocessed data except IndexError: print(f'Could not read data, image stack does not contain index {frame}') return data # Return original data if no frame specified
def clean_duplicate_objs()
-
Clean up duplicate objects in the scene.
Expand source code
def clean_duplicate_objs(): """ Clean up duplicate objects in the scene. """ # Get all objects in the scene all_objects = D.objects # Iterate through objects and delete those with a dot in their name, # since objects with names that exist in the scene are named using "name".001, # this cleans up already imported objects. for obj in all_objects: if ("." in obj.name or "temp" in obj.name) and obj.type == "MESH": D.objects.remove(obj, do_unlink=True)
def color_nuclei(parent, i)
-
Color nuclei based on a given metric value.
Parameters
parent
:bpy.types.Object
- The parent object.
i
:int
- Index for the metric value.
Expand source code
def color_nuclei(parent, i): """ Color nuclei based on a given metric value. Parameters ---------- parent : bpy.types.Object The parent object. i : int Index for the metric value. """ ji_vals = s.METRICS[i]['ji_vals'] children_objs = parent.children for child_obj in children_objs: group_id = child_obj.get("Group_ID") if group_id is not None: try: group_id = int(group_id) except ValueError: print(f"Warning: Invalid group_id {group_id} for object {child_obj.name}") continue if 'gt' in parent.name: matching_tuples = [t for t in ji_vals if t[0] == group_id] if not matching_tuples or matching_tuples[0][1] == 0: set_material(child_obj, 'Not Predicted') print(f'{group_id} not predicted') else: D.objects.remove(child_obj, do_unlink=True) if 'pred' in parent.name: matching_tuples = [t for t in ji_vals if t[1] == group_id] if matching_tuples: set_hue(child_obj, matching_tuples[0][2]) else: set_material(child_obj, 'False Prediction') else: print(f"Warning: Group_ID not found for object {child_obj.name}")
def compare_labeled_volumes(truth, prediction)
-
Compares labelled volumes by performing a Jaccard Index (JI) calculation over all of the labelled regions in the provided volumes.
Parameters
truth
:numpy.ndarray
- Ground truth labels.
prediction
:numpy.ndarray
- Predicted labels.
Returns
list
oftuple
- A list of tuples containing truth label, prediction label, and Jaccard Index (JI).
Expand source code
def compare_labeled_volumes(truth, prediction): """ Compares labelled volumes by performing a Jaccard Index (JI) calculation over all of the labelled regions in the provided volumes. Parameters ---------- truth : numpy.ndarray Ground truth labels. prediction : numpy.ndarray Predicted labels. Returns ------- list of tuple A list of tuples containing truth label, prediction label, and Jaccard Index (JI). """ tlabels = np.unique(truth)[1:] ji = [] try: for label in tlabels: x, y, z = np.where(truth == label) values = prediction[x, y, z] freq, lbl = frequency(values) if lbl[0] == 0: freq[0] = 0 mx = freq.max() mlbl = lbl[np.where(freq == mx)[0][0]] xp, _, _ = np.where(prediction == mlbl) ji.append((label, mlbl, 2 * mx / (len(x) + len(xp)))) return ji except ValueError: print(f'There was a mismatch in the expected amount of labels in the data (3), and the labeled data provided ({tlabels.shape})') return
def confusion_matrix(truth, prediction)
-
Compute the confusion matrix between ground truth and predicted labels.
Parameters
truth
:numpy.ndarray
- Ground truth labels.
prediction
:numpy.ndarray
- Predicted labels.
Returns
tuple
- Confusion matrix as a 2D numpy array and corresponding labels.
Expand source code
def confusion_matrix(truth, prediction): """ Compute the confusion matrix between ground truth and predicted labels. Parameters ---------- truth : numpy.ndarray Ground truth labels. prediction : numpy.ndarray Predicted labels. Returns ------- tuple Confusion matrix as a 2D numpy array and corresponding labels. """ tlabels = np.unique(truth) plabels = np.unique(prediction) num_classes = max(tlabels.max(), plabels.max()) + 1 conf_matrix = np.zeros((num_classes, num_classes), dtype=np.int) labels = np.arange(num_classes) return conf_matrix, labels
def create_child_material(obj, shift)
-
Create a material based on another objects color by shifting the hue.
Parameters
obj
:bpy.types.Object
- The object whose material to adapt.
shift
:float
- Amount to change the hue value with.
Returns
str
- The name of the changed material.
Expand source code
def create_child_material(obj, shift): """ Create a material based on another objects color by shifting the hue. Parameters ---------- obj : bpy.types.Object The object whose material to adapt. shift : float Amount to change the hue value with. Returns ------- str The name of the changed material. """ if obj.material_slots: mat = obj.material_slots[obj.active_material_index].material new_material = mat.copy() new_material.name = mat.name + f"_{shift}" principled = new_material.node_tree.nodes["Principled BSDF"] h, s, v = rgb_to_hsv(principled.inputs["Base Color"].default_value[:3]) r, g, b = hsv_to_rgb((h+shift, s, v)) principled.inputs["Base Color"].default_value = (r, g, b, principled.inputs["Base Color"].default_value[-1]) return new_material.name else: print("No materials assigned to the object.")
def create_contour(data, thresh)
-
Method to get the contours of a single slice (2D numpy array) of data.
Parameters
data
:numpy.ndarray
- Numpy array representation of a single z slice of data.
thresh
:int
- Threshold with which to threshold this slice.
Returns
numpy.ndarray
- Binary Numpy array containing the location of the contours (edges of nuclei).
Expand source code
def create_contour(data, thresh): """ Method to get the contours of a single slice (2D numpy array) of data. Parameters ---------- data : numpy.ndarray Numpy array representation of a single z slice of data. thresh : int Threshold with which to threshold this slice. Returns ------- numpy.ndarray Binary Numpy array containing the location of the contours (edges of nuclei). """ # Threshold the data and make it binary _, thresh = cv.threshold(data, thresh, 255, 0) # Find contours (corresponds with nuclei) on this binary data, cv.RETR_EXTERNAL # is used to return the non-overlapping items per slice. contours, _ = cv.findContours(thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) contour_image = np.zeros_like(data) # Sanitize slices by only allowing contours that are >= 15 pixels in area. This may lose # a very small edge of the nucleus but this is counteracted by how the 3D object is created, # namely using marching cubes. for contour in contours: area = cv.contourArea(contour) if area >= 15: cv.drawContours(contour_image, [contour], -1, (255), 1) return contour_image
def create_contour_data(data)
-
Method to yield the numpy array representation of only the contours found in the convoluted original data.
Parameters
data
:numpy.ndarray
- Numpy array representation of the convoluted original data.
Returns
numpy.ndarray
- Cleaned numpy array data containing the rough contours of nuclei.
Expand source code
def create_contour_data(data): """ Method to yield the numpy array representation of only the contours found in the convoluted original data. Parameters ---------- data : numpy.ndarray Numpy array representation of the convoluted original data. Returns ------- numpy.ndarray Cleaned numpy array data containing the rough contours of nuclei. """ # # Sanitize data without changing topology by erosion and dilation, this step # # elimates the heavy amount of background noise # eroded = ndi.grey_erosion(data, size=(3,3,3)) # dilated = ndi.grey_dilation(eroded, size=(3,3,3)) # Threshold image using industry standard (Otsu thresholding), with threshold # taken from the middle z slice. This is done since the middle z-slice will # contain part of the organoid, where the 1st slice will be only noise middle_slice = int(data.shape[2]/2) otsu = threshold_otsu(data[:,:,middle_slice]) binary = data > otsu # opened = ndi.binary_opening(binary) data = data * binary # Take the gradient of this image and expand it to a numpy containing the gradient # of each pixel in each direction. A nucleus is a connected object so all of the x,y,z # frames will have a heavy gradient. gradient = np.gradient(data) magnitudes = (np.sqrt(sum(np.square(g) for g in gradient))).astype(np.uint8) # Stack contours for each z slice and export to object. contours = np.zeros(magnitudes.shape) for z in range(magnitudes.shape[2]): contours[:,:,z] = create_contour(magnitudes[:,:,z], otsu) return contours
def create_contour_obj(data, fname)
-
" Function to completely create a 3D contour object from data.
Parameters
data
:numpy.ndarray
- Numpy array representation of the convoluted original data.
fname
:str
- Filename to export 3D object to.
Expand source code
def create_contour_obj(data, fname): """" Function to completely create a 3D contour object from data. Parameters ---------- data : numpy.ndarray Numpy array representation of the convoluted original data. fname : str Filename to export 3D object to. """ contours = create_contour_data(data) create_obj(contours, fname)
def create_difference(obj1, obj2)
-
Create a boolean difference between two objects.
Parameters
obj1
:bpy.types.Object
- The first object to use for operation.
obj2
:bpy.types.Object
- The second object to use for operation.
Expand source code
def create_difference(obj1, obj2): """Create a boolean difference between two objects. Parameters ---------- obj1 : bpy.types.Object The first object to use for operation. obj2 : bpy.types.Object The second object to use for operation. """ obj1.select_set(True) C.view_layer.objects.active = obj1 # Create new difference modifier and apply it between obj1 and obj2 bool_mod = C.active_object.modifiers.new(name="Boolean", type='BOOLEAN') bool_mod.object = obj2 bool_mod.solver = 'FAST' bool_mod.operation = 'DIFFERENCE' O.object.modifier_apply(modifier=bool_mod.name)
def create_intersection(obj, inner)
-
Create a boolean intersection between two objects.
Parameters
obj
:bpy.types.Object
- The object for intersection.
inner
:bpy.types.Object
- The inner object.
Expand source code
def create_intersection(obj, inner): """Create a boolean intersection between two objects. Parameters ---------- obj : bpy.types.Object The object for intersection. inner : bpy.types.Object The inner object. """ inner.select_set(True) C.view_layer.objects.active = inner # Create new intersection modifier and apply it to the obj and intersection object # (inner) is the result of the initial intersection between the two objs bool_mod = C.active_object.modifiers.new(name="Boolean", type='BOOLEAN') bool_mod.object = obj bool_mod.solver = 'FAST' bool_mod.operation = 'INTERSECT' O.object.modifier_apply(modifier=bool_mod.name)
def create_material(mat_name, color, blend)
-
Create a material with the given name and color.
Parameters
mat_name
:str
- Name of the material.
color
:tuple
- HSV color tuple (hue, saturation, value, alpha).
blend
:str
- Blend mode for the material.
Expand source code
def create_material(mat_name, color, blend): """ Create a material with the given name and color. Parameters ---------- mat_name : str Name of the material. color : tuple HSV color tuple (hue, saturation, value, alpha). blend : str Blend mode for the material. """ if mat_name in D.materials: print(f'Material {mat_name} already exists in this scene') else: new_material = D.materials.new(name=mat_name) new_material.use_nodes = True tree = new_material.node_tree nodes = tree.nodes links = tree.links for node in nodes: nodes.remove(node) principled = nodes.new(type='ShaderNodeBsdfPrincipled') material_output = nodes.new(type='ShaderNodeOutputMaterial') links.new(principled.outputs['BSDF'], material_output.inputs['Surface']) r, g, b = hsv_to_rgb(color[:3]) principled.inputs['Base Color'].default_value = (r, g, b, 1.0) principled.inputs["Alpha"].default_value = color[3] new_material.blend_method = blend new_material.shadow_method = 'NONE' if blend == 'BLEND': new_material.use_backface_culling = True
def create_obj(data, fname)
-
Single method to complete reading a prediction from labeled data and to export it to a 3D object
Parameters
data
:numpy.ndarray
- Labeled data from .tif file.
fname
:str
- Filename to export 3D object to.
Expand source code
def create_obj(data, fname): """ Single method to complete reading a prediction from labeled data and to export it to a 3D object Parameters ---------- data : numpy.ndarray Labeled data from .tif file. fname : str Filename to export 3D object to. """ vals = np.unique(data)[1:] # Exclude 0 from unique values vert_count = 0 for i, val in enumerate(vals): last_filter = (data == val).astype(int) filt = data * last_filter verts, faces = extract_mesh(filt) mesh = refine_mesh(verts,faces) export_to_obj(mesh.vertices, mesh.faces, i+1, vert_count, fname) vert_count += len(verts)
def create_overlap(gt, pred)
-
Handle the entire boolean operations to get a final product of an intersection object, and the two differences (false negative & false positive).
Parameters
gt
:bpy.types.Object
- The ground truth object.
pred
:bpy.types.Object
- The predicted object.
Expand source code
def create_overlap(gt, pred): """Handle the entire boolean operations to get a final product of an intersection object, and the two differences (false negative & false positive). Parameters ---------- gt : bpy.types.Object The ground truth object. pred : bpy.types.Object The predicted object. """ # Get the number of the current frame operated on and duplicate objs for temporary # working objects num = gt.name.split('_')[-1] inner = duplicate_object(gt, f'inner_{num}') temp = duplicate_object(pred, f'temp_{num}') # Perform operations needed for end product: starting with creating the true positives # by making intersection between prediction and ground-truth (inner at this stage is # the duplicate of ground-truth). After this, create difference of both objs with the # true positives and clean-up temporary working objects from scene. create_intersection(pred, inner) create_difference(pred, gt) create_difference(gt, temp) clean_duplicate_objs()
def display(viewer)
-
Display the rendered video in the Napari viewer.
Parameters:
viewer : Napari viewer instance Napari viewer instance.
Expand source code
def display(viewer): """ Display the rendered video in the Napari viewer. Parameters: ----------- viewer : Napari viewer instance Napari viewer instance. """ path = str(Path(SRC_VID / FILENAME)) vr = VideoReaderNP(path) ratio = 1.5 * DATA1.shape[2] / vr.shape[2] # Add the image to the existing viewer viewer.add_image(vr, name='Loaded Video', scale=(1.0, ratio, ratio))
def downscale_image_by_sampling(image, factor)
-
Downscales the input image by sampling every nth pixel in each dimension.
Parameters
image
:numpy.ndarray
- Input image as a NumPy array.
factor
:int
- Factor by which to downscale (e.g., 2 for half, 4 for quarter).
Returns
numpy.ndarray
- Downscaled image as a NumPy array.
Expand source code
def downscale_image_by_sampling(image, factor): """ Downscales the input image by sampling every nth pixel in each dimension. Parameters ---------- image : numpy.ndarray Input image as a NumPy array. factor : int Factor by which to downscale (e.g., 2 for half, 4 for quarter). Returns ------- numpy.ndarray Downscaled image as a NumPy array. """ if image.ndim == 4: # Multichannel image (e.g., RGB) return image[:, ::factor, ::factor, :] else: # Grayscale image return image[:, ::factor, ::factor] # def create_volume(data): # import open3d as o3d # pcd = o3d.geometry.PointCloud() # pcd.points = o3d.utility.Vector3dVector(data.astype(np.float64)) # o3d.io.write_point_cloud("sync.ply", pcd) # # vertices = data # # print(data.shape) # # vertices = np.array([tuple(e) for e in vertices], dtype=[('x', '<f4'), ('y', '<f4'), ('z', '<f4')]) # # el = PlyElement.describe(vertices, 'vertex',{'some_property': 'f8'},{'some_property': 'u4'}) # # # text = True: save in ascii format # # PlyData([el], text=True).write("test.ply")
def duplicate_object(obj, name)
-
Duplicate the object and rename the duplicated object.
Parameters
obj
:bpy.types.Object
- Object to duplicate.
name
:str
- Name for the duplicated object.
Returns
bpy.types.Object
- The duplicated object.
Expand source code
def duplicate_object(obj, name): """ Duplicate the object and rename the duplicated object. Parameters ---------- obj : bpy.types.Object Object to duplicate. name : str Name for the duplicated object. Returns ------- bpy.types.Object The duplicated object. """ obj.select_set(True) C.view_layer.objects.active = obj # Duplicate the selected object O.object.duplicate() # Get the newly duplicated object duplicated_obj = C.active_object # Rename the duplicated object duplicated_obj.name = name duplicated_obj.data.name = name return duplicated_obj
def export_to_obj(vertices, faces, group, vert_count, fname)
-
Exports mesh data to an OBJ file.
Parameters
vertices
:numpy.ndarray
- Vertices of the submesh.
faces
:numpy.ndarray
- Faces of the submesh.
group
:int
- Group number for the submesh that we want to create now.
vert_count
:int
- Vertex count for the entire object until now.
fname
:str
- Name of the OBJ file to export.
Expand source code
def export_to_obj(vertices, faces, group, vert_count, fname): """ Exports mesh data to an OBJ file. Parameters ---------- vertices : numpy.ndarray Vertices of the submesh. faces : numpy.ndarray Faces of the submesh. group : int Group number for the submesh that we want to create now. vert_count : int Vertex count for the entire object until now. fname : str Name of the OBJ file to export. """ if not os.path.exists(os.path.dirname(fname)): os.makedirs(os.path.dirname(fname)) with open(fname, 'w' if not os.path.exists(fname) else 'a') as f: # Write group statement f.write(f"g {group}\n") # Write vertices for vertex in vertices: f.write(f"v {vertex[0]} {vertex[1]} {vertex[2]}\n") # Write faces for face in faces: f.write(f"f {' '.join([str(i + vert_count + 1) for i in face])}\n")
def extract_mesh(data)
-
Extracts mesh data from a segmented .tif file.
Parameters
path
:str
- Path to the segmented .tif file.
Returns
tuple
- Tuple containing vertices and faces of the extracted mesh.S
Expand source code
def extract_mesh(data): """ Extracts mesh data from a segmented .tif file. Parameters ---------- path : str Path to the segmented .tif file. Returns ------- tuple Tuple containing vertices and faces of the extracted mesh.S """ # Marching cubes algorithm to extract surfaces verts, faces, _, _ = measure.marching_cubes(data) return verts, faces
def filter_spots(spots, name, value, isabove)
-
Filter spots based on a certain condition.
Parameters: Parameters
spots
:pandas.DataFrame
- DataFrame containing spots data.
name
:str
- Feature name.
value
:float
- Threshold value.
isabove
:bool
- If True, filter spots above the threshold, otherwise below.
Returns
pandas.DataFrame
- Filtered DataFrame.
Expand source code
def filter_spots(spots, name, value, isabove): """ Filter spots based on a certain condition. Parameters: Parameters ---------- spots : pandas.DataFrame DataFrame containing spots data. name : str Feature name. value : float Threshold value. isabove : bool If True, filter spots above the threshold, otherwise below. Returns ------- pandas.DataFrame Filtered DataFrame. """ if isabove: spots = spots[spots[name] > value] else: spots = spots[spots[name] < value] return spots
def find_collection(name)
-
Function to find a collection in the scene, or raise an error if this collection is not found.
Parameters
name
:str
- Name of the desired collection.
Expand source code
def find_collection(name): """ Function to find a collection in the scene, or raise an error if this collection is not found. Parameters ---------- name : str Name of the desired collection. """ coll = D.collections.get(name) if coll is not None: return coll else: print(f'Collection {name} was not found') raise CollectionNotFoundException()
def find_material(mat_name)
-
Find a material by its name.
Parameters
mat_name
:str
- Name of the material.
Returns
bpy.types.Material
- The material, if present, otherwise raise error.
Expand source code
def find_material(mat_name): """ Find a material by its name. Parameters ---------- mat_name : str Name of the material. Returns ------- bpy.types.Material The material, if present, otherwise raise error. """ mat = D.materials.get(mat_name) if mat is not None: return mat else: print(f'Material {mat_name} not found') raise MaterialNotFoundException()
def find_object(name)
-
" Function to find an object in the scene, or raise an error if this object is not found.
Parameters
name
:str
- Name of the desired object.
Expand source code
def find_object(name): """" Function to find an object in the scene, or raise an error if this object is not found. Parameters ---------- name : str Name of the desired object. """ obj = D.objects.get(name) if obj is not None: return obj else: print(f'Object {name} was not found') raise ObjectNotFoundException()
def frequency(values)
-
Returns unique values in the array and their frequency of occurrence.
Parameters
values
:numpy.ndarray
- 1D array of values.
Returns
tuple
- A tuple containing frequencies and corresponding labels.
Expand source code
def frequency(values): """ Returns unique values in the array and their frequency of occurrence. Parameters ---------- values : numpy.ndarray 1D array of values. Returns ------- tuple A tuple containing frequencies and corresponding labels. """ lbls = np.unique(values) freq = np.array([np.sum(values == l) for l in lbls]) return freq, lbls
def get_all_coords(nuclei)
-
Function to get coordinates for all objects in a frame. This code facilitates the minimum cost Hungarian algorithm implementation.
Parameters
nuclei
:list
- List of objects in the scene/frame.
Returns
list
- A list of coordinates for all objects in the specified collection.
Expand source code
def get_all_coords(nuclei): """ Function to get coordinates for all objects in a frame. This code facilitates the minimum cost Hungarian algorithm implementation. Parameters ---------- nuclei : list List of objects in the scene/frame. Returns ------- list A list of coordinates for all objects in the specified collection. """ all_coords = [] for obj in nuclei: coords = (obj.location.x, obj.location.y, obj.location.z) # Format to set amount of decimals to minimize rounding errors in comparison formatted_coords = tuple(round(coord, 2) for coord in coords) all_coords.append(formatted_coords) return all_coords
def get_track(df, obj_id)
-
Finds the track ID of a given ID in the DataFrame.
Parameters
df
:pandas.DataFrame
- The DataFrame containing the track data.
obj_id
:str
- The ID for which to find the child.
Returns
str
orNone
- The track ID if found, otherwise None.
Expand source code
def get_track(df, obj_id): """ Finds the track ID of a given ID in the DataFrame. Parameters ---------- df : pandas.DataFrame The DataFrame containing the track data. obj_id : str The ID for which to find the child. Returns ------- str or None The track ID if found, otherwise None. """ # First check if the original object has been found if 'Not_Found' in obj_id: print('Not found nucleus can not have a track') return None # Get the row corresponding to the desired id row = df.loc[df['id'] == int(obj_id)] if not row.empty: track = row['track'].iat[0] return track else: print(f"ID {obj_id} not found in the DataFrame.") return None
def hide_in_render(obj, count, length)
-
Hide an object in renders for a specific range of frames.
Parameters
obj
:bpy.types.Object
- The object to hide in renders.
count
:int
- The frame count to determine when to hide and show the object.
length
:int
- The length of time the object should be rendered.
Expand source code
def hide_in_render(obj, count, length): """ Hide an object in renders for a specific range of frames. Parameters ---------- obj : bpy.types.Object The object to hide in renders. count : int The frame count to determine when to hide and show the object. length : int The length of time the object should be rendered. """ # Show object and set keyframe obj.hide_render = False obj.keyframe_insert(data_path='hide_render', frame=length * count) # Hide object and set keyframe after specified time obj.hide_render = True obj.keyframe_insert(data_path='hide_render', frame=length + length * count) # Hide all child objects initially to be morphed by ancestors if count > 0: # Set keyframes for hide_render property obj.hide_render = True obj.keyframe_insert(data_path='hide_render', frame=0)
def hide_legend_items(count)
-
Function that is called to hide unused legend items from render.
Parameters
count
:int
- Number of legend items that will be used.
Expand source code
def hide_legend_items(count): """ Function that is called to hide unused legend items from render. Parameters ---------- count : int Number of legend items that will be used. """ # Run loop that removes used legend items from the {1,2,3} domain. for i in range(1+count,4): try: mat_obj = find_object(f'Mat_{i}') mat_obj.hide_render = True mat_text = find_object(f'Mat_{i}_name') mat_text.hide_render = True except ObjectNotFoundException: pass
def hsv_to_rgb(color)
-
Convert HSV color representation to RGB.
Parameters
color
:tuple
- A tuple containing hue, saturation, and value components.
Returns
tuple
- RGB color tuple.
Expand source code
def hsv_to_rgb(color): """ Convert HSV color representation to RGB. Parameters ---------- color : tuple A tuple containing hue, saturation, and value components. Returns ------- tuple RGB color tuple. """ h, s, v = color return colorsys.hsv_to_rgb(h, s, v)
def import_object(name, data, use_groups, index)
-
Create an object in the scene.
Parameters
name
:str
- Name of the object.
data
:numpy.ndarray
- Data to create the object from.
use_groups
:bool
- Whether to use split groups, creates a single or multiple objects.
index
:int
- Index of the current frame.
Returns
list
- A list of objects created in the scene.
Expand source code
def import_object(name, data, use_groups, index): """ Create an object in the scene. Parameters ---------- name : str Name of the object. data : numpy.ndarray Data to create the object from. use_groups : bool Whether to use split groups, creates a single or multiple objects. index : int Index of the current frame. Returns ------- list A list of objects created in the scene. """ try: stdout = io.StringIO() with redirect_stdout(stdout): obj = find_object(name) return obj except ObjectNotFoundException: dim_name = name + '.obj' obj_src = s.SRC_3D / dim_name # First check if object is already made as 3D object in default directory, # if so, import without additional computations. if obj_src.is_file(): print(f'Note: Object {str(obj_src)} already exists as 3D object, importing this.\nIf other 3D object is desired, delete existing one, or change its name.') stdout = io.StringIO() with redirect_stdout(stdout): O.wm.obj_import(filepath=str(obj_src), use_split_groups= use_groups) # If there is not yet a 3D object of this object, create it from the source data. else: try: data = clean_data(data,index) if s.CREATE_GROUND_TRUTH: create_contour_obj(data, obj_src) else: create_obj(data,obj_src) stdout = io.StringIO() with redirect_stdout(stdout): O.wm.obj_import(filepath=str(obj_src), use_split_groups= use_groups) except IndexError: print('Index error, no object created') return imported_object = C.selected_objects[0] num = name.rsplit('_', 1)[-1] # Rename the object if s.PRED_NAME is not None and s.PRED_NAME in name and not use_groups: imported_object.name = f'pred_{num}' elif s.GT_NAME in name and not use_groups: imported_object.name = f'gt_{num}' return [obj for obj in C.selected_objects if obj.type == 'MESH']
def iou(mask1, mask2)
-
Calculates Intersection over Union (IoU) score.
Parameters
mask1
:numpy.ndarray
- First binary mask.
mask2
:numpy.ndarray
- Second binary mask.
Returns
float
- Intersection over Union (IoU) score.
Expand source code
def iou(mask1, mask2): """ Calculates Intersection over Union (IoU) score. Parameters ---------- mask1 : numpy.ndarray First binary mask. mask2 : numpy.ndarray Second binary mask. Returns ------- float Intersection over Union (IoU) score. """ mask1 = mask1[:, :, :, 0] if mask1.shape[-1] == 3 else mask1 mask2 = mask2[:, :, :, 0] if mask2.shape[-1] == 3 else mask2 if mask1.shape != mask2.shape: raise ValueError("Input masks must have the same size.") intersection = np.logical_and(mask1, mask2) union = np.logical_or(mask1, mask2) iou_val = np.sum(intersection) / np.sum(union) return iou_val
def metrics_dictionary(img1, img2)
-
Calculates various metrics based on two images.
Parameters
img1
:numpy.ndarray
- First input image in numpy array format.
img2
:numpy.ndarray
- Second input image in numpy array format.
Returns
dict
- Dictionary containing calculated metrics.
Expand source code
def metrics_dictionary(img1, img2): """ Calculates various metrics based on two images. Parameters ---------- img1 : numpy.ndarray First input image in numpy array format. img2 : numpy.ndarray Second input image in numpy array format. Returns ------- dict Dictionary containing calculated metrics. """ metrics = {} truth, count_truth = label(img1) pred, count_pred = label(img2) are, prec, rec = calculate_are(truth, pred) f1 = 1 - are metrics['ji'] = calculate_jaccard(truth, pred) metrics['ji_vals'] = compare_labeled_volumes(truth, pred) metrics['nuclei_truth'] = count_truth metrics['nuclei_pred'] = count_pred metrics['iou'] = iou(truth, pred) metrics['precision'] = prec metrics['recall'] = rec metrics['f1'] = f1 return metrics
def min_cost(df, list_points)
-
Helper function to calculate a matrix of all distances for coordinates of nuclei in Blender to coordinates found in the tracking file. Minimum cost assignment is done via the Hungarian algorithm and returned.
Parameters
df
:pandas.DataFrame
- DataFrame containing the coordinates of nuclei in Blender.
list_points
:list
- List of coordinates found in the tracking file.
Returns
dict
- A dictionary mapping coordinates to their corresponding IDs.
Expand source code
def min_cost(df, list_points): """ Helper function to calculate a matrix of all distances for coordinates of nuclei in Blender to coordinates found in the tracking file. Minimum cost assignment is done via the Hungarian algorithm and returned. Parameters ---------- df : pandas.DataFrame DataFrame containing the coordinates of nuclei in Blender. list_points : list List of coordinates found in the tracking file. Returns ------- dict A dictionary mapping coordinates to their corresponding IDs. """ # Calculate distance matrix distance_matrix = np.array([[calculate_distance(df.loc[i, ['x', 'y', 'z']], point) for point in list_points] for i in df.index]) # Apply Hungarian algorithm _, col_ind = linear_sum_assignment(distance_matrix) # Retrieve results optimal_assignment = {list_points[j]: df.loc[i, 'id'] for i, j in enumerate(col_ind, start=df.index.min())} return optimal_assignment
def napari_get_reader(path)
-
A basic implementation of a Reader contribution.
Parameters
path
:str
orlist
ofstr
- Path to file, or list of paths.
Returns
function
orNone
- If the path is a recognized format, return a function that accepts the same path or list of paths, and returns a list of layer data tuples.
Expand source code
def napari_get_reader(path): """A basic implementation of a Reader contribution. Parameters ---------- path : str or list of str Path to file, or list of paths. Returns ------- function or None If the path is a recognized format, return a function that accepts the same path or list of paths, and returns a list of layer data tuples. """ if isinstance(path, list): # reader plugins may be handed single path, or a list of paths. # if it is a list, it is assumed to be an image stack... # so we are only going to look at the first file. path = path[0] # if we know we cannot read the file, we immediately return None. if not path.endswith(".npy"): return None # otherwise we return the *function* that can read ``path``. return reader_function
def print_globals()
-
Print global variables for debugging purposes.
Expand source code
def print_globals(): """ Print global variables for debugging purposes. """ global_vars = globals() for k, v in global_vars.items(): print(f"{k}: {v}")
def read_trackmate()
-
Reads track data from a TrackMate XML file.
Returns
pandas.DataFrame
- DataFrame containing the track data.
Expand source code
def read_trackmate(): """ Reads track data from a TrackMate XML file. Returns ------- pandas.DataFrame DataFrame containing the track data. """ # Import track data using a custom function (trackmate_peak_import) # code gathered (but bugfixed) from https://github.com/hadim/pytrackmate df = trackmate_peak_import(s.TRACK, True) return df
def reader_function(path)
-
Take a path or list of paths and return a list of LayerData tuples.
Readers are expected to return data as a list of tuples, where each tuple is (data, [add_kwargs, [layer_type]]), "add_kwargs" and "layer_type" are both optional.
Parameters
path
:str
orlist
ofstr
- Path to file, or list of paths.
Returns
layer_data
:list
oftuples
- A list of LayerData tuples where each tuple in the list contains (data, metadata, layer_type), where data is a numpy array, metadata is a dict of keyword arguments for the corresponding viewer.add_* method in napari, and layer_type is a lower-case string naming the type of layer. Both "meta", and "layer_type" are optional. napari will default to layer_type=="image" if not provided
Expand source code
def reader_function(path): """Take a path or list of paths and return a list of LayerData tuples. Readers are expected to return data as a list of tuples, where each tuple is (data, [add_kwargs, [layer_type]]), "add_kwargs" and "layer_type" are both optional. Parameters ---------- path : str or list of str Path to file, or list of paths. Returns ------- layer_data : list of tuples A list of LayerData tuples where each tuple in the list contains (data, metadata, layer_type), where data is a numpy array, metadata is a dict of keyword arguments for the corresponding viewer.add_* method in napari, and layer_type is a lower-case string naming the type of layer. Both "meta", and "layer_type" are optional. napari will default to layer_type=="image" if not provided """ # handle both a string and a list of strings paths = [path] if isinstance(path, str) else path # load all files into array arrays = [np.load(_path) for _path in paths] # stack arrays into single array data = np.squeeze(np.stack(arrays)) # optional kwargs for the corresponding viewer.add_* method add_kwargs = {} layer_type = "image" # optional, default is "image" return [(data, add_kwargs, layer_type)]
def recalculate_text(scene)
-
Recalculate text for the current frame.
Parameters
scene
:bpy.types.Scene
- The scene in which rendering is performed.
Expand source code
def recalculate_text(scene): """ Recalculate text for the current frame. Parameters ---------- scene : bpy.types.Scene The scene in which rendering is performed. """ if s.LENGTH > 0: frame = s.START + C.scene.frame_current // s.LENGTH else: frame = s.START + C.scene.frame_current change_text(f'Frame: {frame}','Frame') try: # change_text(f"Metrics: \n# nuclei GT: {s.METRICS[frame]['nuclei_truth']}\n# nuclei predicted: {s.METRICS[frame]['nuclei_pred']}\n\nIoU: {s.METRICS[frame]['iou']:.2f}\nJI: {s.METRICS[frame]['ji']:.2f}\nPrecision: {s.METRICS[frame]['precision']:.2f}\nRecall: {s.METRICS[frame]['recall']:.2f}\nF1-score: {s.METRICS[frame]['f1']:.2f}",'Metrics') change_text(f"Metrics: \n\nIoU: {s.METRICS[frame]['iou']:.2f}\nJI: {s.METRICS[frame]['ji']:.2f}\nPrecision: {s.METRICS[frame]['precision']:.2f}\nRecall: {s.METRICS[frame]['recall']:.2f}\nF1-score: {s.METRICS[frame]['f1']:.2f}",'Metrics') except KeyError: pass
def refine_mesh(verts, faces)
-
Refines the mesh by applying Laplacian smoothing.
Parameters
verts
:numpy.ndarray
- Vertices of the mesh.
faces
:numpy.ndarray
- Faces of the mesh.
Returns
trimesh.Trimesh
- The smoothed mesh.
Expand source code
def refine_mesh(verts, faces): """ Refines the mesh by applying Laplacian smoothing. Parameters ---------- verts : numpy.ndarray Vertices of the mesh. faces : numpy.ndarray Faces of the mesh. Returns ------- trimesh.Trimesh The smoothed mesh. """ # Convert to trimesh object mesh = trimesh.Trimesh(vertices=verts, faces=faces) # Apply Laplacian smoothing smooth_mesh = trimesh.smoothing.filter_laplacian(mesh, iterations=5) return smooth_mesh
def register()
-
Register frame change handler.
Expand source code
def register(): """ Register frame change handler. """ A.handlers.frame_change_post.append(recalculate_text)
def remesh_objects(obj, vert_count)
-
Helper function to set the amount of vertices in an object to vert_count. This code is useful to get a uniform vertex count over objects that have to morph into one another.
Parameters
obj
:bpy.types.Object
- The object whose vertices need to be adjusted.
vert_count
:int
- The desired number of vertices for the object.
Expand source code
def remesh_objects(obj, vert_count): """ Helper function to set the amount of vertices in an object to vert_count. This code is useful to get a uniform vertex count over objects that have to morph into one another. Parameters ---------- obj : bpy.types.Object The object whose vertices need to be adjusted. vert_count : int The desired number of vertices for the object. """ if len(obj.data.vertices) > vert_count: # Apply the decimate modifier to ensure a certain number of vertices O.object.mode_set(mode='OBJECT') decimate_modifier = obj.modifiers.new(name="Decimate", type='DECIMATE') decimate_modifier.ratio = vert_count / len(obj.data.vertices) obj.select_set(True) C.view_layer.objects.active = obj # Apply the modifier O.object.modifier_apply(modifier="Decimate") if len(obj.data.vertices) < vert_count: obj.select_set(True) C.view_layer.objects.active = obj mesh = obj.data subdiv_num = int(math.ceil((vert_count-len(obj.data.vertices))/3)) step = int(math.floor(len(obj.data.vertices)/subdiv_num)) for i in range(0,subdiv_num): mesh.polygons[i*step].select = True O.object.mode_set(mode='EDIT') O.mesh.subdivide() O.object.mode_set(mode='OBJECT') O.object.select_all(action='DESELECT') # This method may end on a couple vertices too few or too much, recursive # calling fixes this problem. while len(obj.data.vertices) != vert_count: remesh_objects(obj, vert_count) # Shade object smooth for cleaner render object # Commented out for aesthetic reasons, for now. # with C.temp_override(selected_editable_objects=[obj]): # O.object.shade_smooth()
def rename_nuclei(objects, data_frame, i)
-
Scale, rotate, create submeshes and rename all nuclei from original 3D object.
Parameters
objects
:list
- List of objects to manipulate.
data_frame
:pandas.DataFrame
- DataFrame containing the tracking data.
i
:int
- Index of the current frame.
Expand source code
def rename_nuclei(objects, data_frame, i): """ Scale, rotate, create submeshes and rename all nuclei from original 3D object. Parameters ---------- objects: list List of objects to manipulate. data_frame: pandas.DataFrame DataFrame containing the tracking data. i: int Index of the current frame. """ nuclei = objects filt_nuclei = [] for ob in nuclei: if len(ob.data.vertices) < 50: # Select and delete the object D.objects.remove(ob, do_unlink=True) else: filt_nuclei.append(ob) # get all coordinates of nuclei objects and calculate the minimum cost # id allocation using the Hungarian algorithm all_coords = get_all_coords(filt_nuclei) min_cost_mat = min_cost(data_frame.loc[data_frame['frame'] == i-1], all_coords) # go over all objects to properties and manipulate them for obj in filt_nuclei: # set all objects to have the same vertex count remesh_objects(obj, 1000) # rename objects in Blender to their id as given by tracking file rename_obj(obj, min_cost_mat)
def rename_obj(obj, min_cost_mat)
-
Helper function to rename a nuclei to fit their match as calculated by the Hungarian algorithm. This method requires the min_cost_mat as calculated by the min_cost() function and renames the object to its corresponding ID in the tracking file.
Parameters
obj : bpy.types.Object The object to be renamed. min_cost_mat : dict A dictionary mapping coordinates to their corresponding IDs.
Expand source code
def rename_obj(obj, min_cost_mat): """ Helper function to rename a nuclei to fit their match as calculated by the Hungarian algorithm. This method requires the min_cost_mat as calculated by the min_cost() function and renames the object to its corresponding ID in the tracking file. Parameters ---------- obj : bpy.types.Object The object to be renamed. min_cost_mat : dict A dictionary mapping coordinates to their corresponding IDs. """ coords = (obj.location.x, obj.location.y, obj.location.z) formatted_coords = tuple(round(coord, 2) for coord in coords) # Rename objects to their correct ID as found in the mastodon track file. # Try-catch can be invoked when the tracking file or mesh prediction mispredicts, # which is not uncommon in scenarios like cell division. try: obj_id = min_cost_mat[formatted_coords] except KeyError: obj_id = 'Not_Found' obj.name = f"{obj_id}"
def render()
-
Render the scene and generate the output video.
Expand source code
def render(): """ Render the scene and generate the output video. """ renderer = RenderProgress() renderer.render_samples() renderer.render_video()
def rgb_to_hsv(color)
-
Convert RGB color representation to HSV.
Parameters
color
:tuple
- A tuple containing red, green, and blue components.
Returns
tuple
- HSV color tuple.
Expand source code
def rgb_to_hsv(color): """ Convert RGB color representation to HSV. Parameters ---------- color : tuple A tuple containing red, green, and blue components. Returns ------- tuple HSV color tuple. """ r, g, b = color return colorsys.rgb_to_hsv(r, g, b)
def scale_object(obj)
-
Scale, rotate and set object to the origin of the scene.
Parameters
obj
:bpy.types.Object
- Object to manipulate.
Expand source code
def scale_object(obj): """ Scale, rotate and set object to the origin of the scene. Parameters ---------- obj : bpy.types.Object Object to manipulate. """ C.view_layer.objects.active = obj ob = C.selected_objects[-1] # Set rotation of object to 0 degrees on all axes. Importing an image is often # done using an automatic 90 degree x-axis rotation, which hampers the renaming of nuclei. ob.rotation_euler = ( 0.0, 0.0, 0.0) # Set the origin to geometry, this places the object to 0,0,0 in the scene O.object.origin_set(type='GEOMETRY_ORIGIN') # Scale object to fit the camera of the scene. ob.scale = ( 0.045, 0.045, 0.045 )
def set_gradient_color(mode)
-
In the Gradient visualization mode, the Gradient material is complicated and always already included in the scene. Set this material to the legend, and change needed text to fit this.
Parameters
mode
:str
- The visualization mode that is set to the program.
Expand source code
def set_gradient_color(mode): """ In the Gradient visualization mode, the Gradient material is complicated and always already included in the scene. Set this material to the legend, and change needed text to fit this. Parameters ---------- mode : str The visualization mode that is set to the program. """ match mode: case 'Gradient': num = 2 case 'Colored Nuclei': num = 3 case _: num = 0 try: mat_obj = find_object(f'Mat_{num}') mat_obj.hide_render = False set_material(mat_obj, 'Gradient') mat_text = find_object(f'Mat_{num}_name') mat_text.hide_render = False change_text('Prediction',f'Mat_{2}_name') except ObjectNotFoundException: pass
def set_hue(obj, metric)
-
Set the hue of an object based on a metric value.
Parameters
obj
:bpy.types.Object
- The object to set the hue.
metric
:float
- Metric value to determine the hue [0..1].
Expand source code
def set_hue(obj, metric): """ Set the hue of an object based on a metric value. Parameters ---------- obj : bpy.types.Object The object to set the hue. metric : float Metric value to determine the hue [0..1]. """ try: hue = clamp_hue(metric) create_material(f'Base_hue_{hue:.2f}', (hue, 1, 0.43, 1.0), 'OPAQUE') set_material(obj, f'Base_hue_{hue:.2f}') except TypeError: print(f'Could not create material with hue {hue}')
def set_indicator(metric, frame=None)
-
Set the indicator based on metric value.
Parameters
metric
:float
- The metric value.
frame
:int
, optional- The frame to set the keyframe.
Expand source code
def set_indicator(metric, frame=None): """ Set the indicator based on metric value. Parameters ---------- metric : float The metric value. frame : int, optional The frame to set the keyframe. """ try: ind = find_object('Indicator') # Get, and change, the z-coordinate of the metric indicator object. Since it # is clamped to a path, the clamped value will set the indicator to the relative # position indicating the metric value on the metric bar. z = clamp_indicator(metric) ind.delta_location = (0, 0, z) # If a frame is given, set it as keyframe for when to reach that value if frame is not None: ind.keyframe_insert(data_path="delta_location", index=-1, frame=frame, group="Delta Transform") except ObjectNotFoundException: print('Setting metric indicator suspended')
def set_interpolation(obj, style)
-
Set interpolation style for object animation.
Parameters
obj
:bpy.types.Object
- The object to set interpolation for.
style
:str
- The style of interpolation, linear is used in most use cases.
Expand source code
def set_interpolation(obj, style): """ Set interpolation style for object animation. Parameters ---------- obj : bpy.types.Object The object to set interpolation for. style : str The style of interpolation, linear is used in most use cases. """ fcurves = obj.animation_data.action.fcurves for fcurve in fcurves: for kf in fcurve.keyframe_points: kf.interpolation = style
def set_legend_color(mat_name, num)
-
Function to set a legend item to be a color, and display it's correct name.
Parameters
mat_name
:str
- Material name that will be set to the text.
num
:int
- Enumerator of what legend item should be set.
Expand source code
def set_legend_color(mat_name, num): """ Function to set a legend item to be a color, and display it's correct name. Parameters ---------- mat_name : str Material name that will be set to the text. num : int Enumerator of what legend item should be set. """ # The legend items are named 'Mat_{1,2,3}', and will be changed in sequence change_text(mat_name, f'Mat_{num}_name') try: mat_obj = find_object(f'Mat_{num}') mat_obj.hide_render = False mat_text = find_object(f'Mat_{num}_name') mat_text.hide_render = False set_material(mat_obj, mat_name) except ObjectNotFoundException: pass
def set_material(obj, mat_name)
-
Set material to an object.
Parameters
obj
:bpy.types.Object
- Object to set the material to.
mat_name
:str
- Material to set. If None, it removes any existing materials.
Expand source code
def set_material(obj, mat_name): """ Set material to an object. Parameters ---------- obj : bpy.types.Object Object to set the material to. mat_name : str Material to set. If None, it removes any existing materials. """ try: mat = find_material(mat_name) if obj.data.materials: obj.data.materials[0] = mat else: obj.data.materials.append(mat) except MaterialNotFoundException: pass
def set_origin_nuclei(nuclei, name, i)
-
Sets the origin point for a group of nuclei objects and creates a parent empty object.
This function calculates the average location of a group of nuclei objects, sets their origins to the average location, and then creates a new empty object to parent all nuclei objects to.
Parameters
nuclei
:list
- A list of Blender objects representing the nuclei.
name
:str
- The name of the empty object that will be created to parent all nuclei objects.
Expand source code
def set_origin_nuclei(nuclei, name, i): """ Sets the origin point for a group of nuclei objects and creates a parent empty object. This function calculates the average location of a group of nuclei objects, sets their origins to the average location, and then creates a new empty object to parent all nuclei objects to. Parameters ---------- nuclei : list A list of Blender objects representing the nuclei. name : str The name of the empty object that will be created to parent all nuclei objects. """ index = i - s.START acc_location = Vector((0, 0, 0)) for obj in nuclei: O.object.select_all(action='DESELECT') obj.select_set(True) obj['Group_ID'] = obj.name.split('.')[0] obj.rotation_euler = ( 0.0, 0.0, 0.0) O.object.origin_set(type='ORIGIN_GEOMETRY') acc_location += obj.location # Calculate average location (center of mass) num_objects = len(nuclei) if num_objects > 0: average_location = acc_location / num_objects else: print("No mesh objects selected.") # Create a new empty object to parent all imported meshes O.object.empty_add() empty_object = C.object empty_object.location = average_location # Parent all mesh objects to the empty object for obj in nuclei: O.object.select_all(action='DESELECT') obj.select_set(True) O.object.parent_no_inverse_set(keep_transform=True) empty_object.name = name empty_object.location = Vector((0,0,0)) empty_object.scale = ( 0.045, 0.045, 0.045 ) set_rotation(empty_object, s.LENGTH * index, s.LENGTH + s.LENGTH * index) set_interpolation(empty_object,'LINEAR')
def set_parent(parent, child)
-
Sets a parent-child relationship between two objects. Useful for getting multiple objects to follow the same keyframes, or hiding them in the same line of code.
Parameters
parent
:bpy.types.Object
- The parent object.
child
:bpy.types.Object
- The child object.
Expand source code
def set_parent(parent, child): """ Sets a parent-child relationship between two objects. Useful for getting multiple objects to follow the same keyframes, or hiding them in the same line of code. Parameters ---------- parent : bpy.types.Object The parent object. child : bpy.types.Object The child object. """ child.select_set(True) parent.select_set(True) O.object.parent_set(type='OBJECT', keep_transform=True)
def set_rotation(obj, start, end)
-
Sets keyframes for rotation of an object.
Parameters
obj
:bpy.types.Object
- The object to rotate.
start
:int
- Frame number on which to start the rotation.
end
:int
- Frame number on which to end the rotation.
Expand source code
def set_rotation(obj, start, end): """ Sets keyframes for rotation of an object. Parameters ---------- obj : bpy.types.Object The object to rotate. start : int Frame number on which to start the rotation. end : int Frame number on which to end the rotation. """ # Set keyframes for rotation obj.rotation_euler.z = math.radians(0) # Set rotation to 0 degrees in radians obj.keyframe_insert(data_path="rotation_euler", index=2, frame=start) obj.rotation_euler.z = math.radians(360) # Set rotation to 360 degrees in radians obj.keyframe_insert(data_path="rotation_euler", index=2, frame=end)
def set_shape_keys(obj, target, index)
-
Helper function to set the frames at which the shape keys need to be a certain value.
Parameters
obj
:bpy.types.Object
- The object to morph.
target
:bpy.types.Object
- The object to morph into.
index
:int
- The index of the current frame.
Expand source code
def set_shape_keys(obj, target, index): """ Helper function to set the frames at which the shape keys need to be a certain value. Parameters ---------- obj : bpy.types.Object The object to morph. target : bpy.types.Object The object to morph into. index : int The index of the current frame. """ start_loc = obj.location start, end = s.LENGTH * index, s.LENGTH + s.LENGTH * index obj.keyframe_insert(data_path="location", frame=start) obj.location = target.location obj.keyframe_insert(data_path="location", frame=end) obj.location = start_loc shape_key = obj.data.shape_keys.key_blocks["Morph"] shape_key.keyframe_insert("value", frame=start) shape_key.value = 1 shape_key.keyframe_insert("value", frame=end)
def set_values(dict, viewer, mode)
-
Set global values from a dictionary and initialize processing.
Parameters:
dict : dict Dictionary containing configuration values. viewer : Napari viewer instance Napari viewer instance. mode : str Visualization mode.
Expand source code
def set_values(dict, viewer, mode): """ Set global values from a dictionary and initialize processing. Parameters: ----------- dict : dict Dictionary containing configuration values. viewer : Napari viewer instance Napari viewer instance. mode : str Visualization mode. """ global TRACK, START, END, LENGTH, SAMPLES, METRICS, E_FRAME, SRC_VID, FILENAME, DATA1, DATA2, GT_NAME, PRED_NAME, SHOW_TEXT, HUE_METRIC for k, v in dict.items(): globals()[k] = v if DATA1 is not None: GT_NAME += "_" if DATA2 is not None: PRED_NAME += "_" if GT_NAME == PRED_NAME: PRED_NAME = "pred-" + PRED_NAME # If both images are there, calculate metrics of comparison. for i in range(START, END + 1): clean_data1 = clean_data(DATA1, i) clean_data2 = clean_data(DATA2, i) METRICS[i] = metrics_dictionary(clean_data1, clean_data2) # Scale the data down for quicker computation, and to keep objects # in the frame. scale = max(math.floor(DATA2.shape[1] / 150), 1) DATA2 = downscale_image_by_sampling(DATA2, scale) scale = max(math.floor(DATA1.shape[1] / 150), 1) DATA1 = downscale_image_by_sampling(DATA1, scale) E_FRAME = (END - START) * LENGTH + LENGTH visualize(viewer, mode) # print_globals()
def track_ancestor(df, obj_id)
-
Finds the ancestor ID of a given ID in the DataFrame.
Parameters
df
:pandas.DataFrame
- The DataFrame containing the track data.
obj_id
:str
- The ID for which to find the ancestor.
Returns
str
orNone
- The ancestor ID if found, otherwise None.
Expand source code
def track_ancestor(df, obj_id): """ Finds the ancestor ID of a given ID in the DataFrame. Parameters ---------- df : pandas.DataFrame The DataFrame containing the track data. obj_id : str The ID for which to find the ancestor. Returns ------- str or None The ancestor ID if found, otherwise None. """ # First check if the original object has been found if 'Not_Found' in obj_id: print('Not found nucleus can not have an ancestor') return None # Get the row corresponding to the desired id row = df.loc[df['id'] == int(obj_id)] if not row.empty: track = row['track'].iat[0] frame = row['frame'].iat[0] # Filter rows where track and frame match the desired values ancestor_row = df[(df['track'] == track) & (df['frame'] == frame - 1)] if not ancestor_row.empty: ancestor_id = ancestor_row['id'].iat[0] return ancestor_id print(f"No ancestor found for {obj_id}.") else: print(f"ID {obj_id} not found in the DataFrame.") return None
def track_child(df, obj_id)
-
Finds the child ID of a given ID in the DataFrame.
Parameters
df
:pandas.DataFrame
- The DataFrame containing the track data.
obj_id
:str
- The ID for which to find the child.
Returns
str
orNone
- The child ID if found, otherwise None.
Expand source code
def track_child(df, obj_id): """ Finds the child ID of a given ID in the DataFrame. Parameters ---------- df : pandas.DataFrame The DataFrame containing the track data. obj_id : str The ID for which to find the child. Returns ------- str or None The child ID if found, otherwise None. """ # First check if the original object has been found if 'Not_Found' in obj_id: print('Not found nucleus can not have a child') return None # Get the row corresponding to the desired id row = df.loc[df['id'] == int(obj_id)] if not row.empty: track = row['track'].iat[0] frame = row['frame'].iat[0] # Filter rows where track and frame match the desired values child_row = df[(df['track'] == track) & (df['frame'] == frame + 1)] if not child_row.empty: child_id = child_row['id'].iat[0] return child_id else: print(f"No child found for {obj_id}.") else: print(f"ID {obj_id} not found in the DataFrame.") return None
def trackmate_peak_import(trackmate_xml_path, get_tracks=False)
-
Import detected peaks with TrackMate Fiji plugin.
Parameters
trackmate_xml_path
:str
- TrackMate XML file path.
get_tracks
:bool
- Whether to include tracks.
Returns
pandas.DataFrame
- DataFrame containing the imported peak data.
Expand source code
def trackmate_peak_import(trackmate_xml_path, get_tracks=False): """ Import detected peaks with TrackMate Fiji plugin. Parameters ---------- trackmate_xml_path : str TrackMate XML file path. get_tracks : bool Whether to include tracks. Returns ------- pandas.DataFrame DataFrame containing the imported peak data. """ # Parse the XML file root = et.fromstring(open(trackmate_xml_path).read()) # Define object labels mapping [CHANGED] to fit own format object_labels = { 'FRAME': 'frame', 'POSITION_X': 'x', 'POSITION_Y': 'y', 'POSITION_Z': 'z', 'ID': 'id', } # Extract features features = root.find('Model').find('FeatureDeclarations').find('SpotFeatures') # Original line [not functioning]: # features = [c.get('feature') for c in features.getchildren()] + ['ID'] features = [c.get('feature') for c in list(features)] + ['ID'] # Extract spots spots = root.find('Model').find('AllSpots') trajs = pd.DataFrame([]) objects = [] for frame in spots.findall('SpotsInFrame'): for spot in frame.findall('Spot'): single_object = [] for label in features: single_object.append(spot.get(label)) objects.append(single_object) # Create DataFrame from extracted objects trajs = pd.DataFrame(objects, columns=features) trajs = trajs.astype(float) # Apply initial filtering initial_filter = root.find("Settings").find("InitialSpotFilter") trajs = filter_spots(trajs, name=initial_filter.get('feature'), value=float(initial_filter.get('value')), isabove=True if initial_filter.get('isabove') == 'true' else False) # Apply filters spot_filters = root.find("Settings").find("SpotFilterCollection") for spot_filter in spot_filters.findall('Filter'): trajs = filter_spots(trajs, name=spot_filter.get('feature'), value=float(spot_filter.get('value')), isabove=True if spot_filter.get('isabove') == 'true' else False) # Rename columns and add track numbers trajs = trajs.loc[:, object_labels.keys()] trajs.columns = [object_labels[k] for k in object_labels.keys()] trajs['track'] = np.arange(trajs.shape[0]) # Get tracks if specified if get_tracks: filtered_track_ids = [int(track.get('TRACK_ID')) for track in root.find('Model').find('FilteredTracks').findall('TrackID')] label_id = 0 trajs['track'] = np.nan tracks = root.find('Model').find('AllTracks') for track in tracks.findall('Track'): track_id = int(track.get("TRACK_ID")) if track_id in filtered_track_ids: spot_ids = [(edge.get('SPOT_SOURCE_ID'), edge.get('SPOT_TARGET_ID'), edge.get('EDGE_TIME')) for edge in track.findall('Edge')] spot_ids = np.array(spot_ids).astype('float')[:, :2] spot_ids = set(spot_ids.flatten()) trajs.loc[trajs["id"].isin(spot_ids), "track"] = label_id label_id += 1 single_track = trajs.loc[trajs["track"].isnull()] trajs.loc[trajs["track"].isnull(), "track"] = label_id + np.arange(0, len(single_track)) # Convert certain columns to integer for row in ['frame', 'id', 'track']: trajs[row] = trajs[row].astype(int) return trajs
def unregister()
-
Unregister frame change handler if it exists.
Expand source code
def unregister(): """ Unregister frame change handler if it exists. """ try: A.handlers.frame_change_post.remove(recalculate_text) except ValueError: print("Handler recalculate_text not found in frame_change_post, skipping removal.")
def visualize(viewer, mode)
-
Visualize the data in Blender based on the selected mode.
Parameters:
viewer : Napari viewer instance Napari viewer instance. mode : str Visualization mode.
Expand source code
def visualize(viewer, mode): """ Visualize the data in Blender based on the selected mode. Parameters: ----------- viewer : Napari viewer instance Napari viewer instance. mode : str Visualization mode. """ # Revert the main file to the original state, this combats not being able # to run the script multiple times in a row. O.wm.revert_mainfile() match mode: case MODES.Transparant: tr = Transparant() tr.visualize() case MODES.Gradient: gr = Gradient() gr.visualize() case MODES.Tracked: tr = Tracked() tr.visualize() case MODES.Timelapse: tl = Timelapse() tl.visualize() case MODES.Microscopy: mc = Microscopy() mc.visualize() case _: print("Invalid mode") render() display(viewer)
def write_multiple(path: str, data: List[FullLayerData]) ‑> List[str]
-
Writes multiple layers of different types.
Parameters
path
:str
- A string path indicating where to save the data file(s).
data : A list of layer tuples. Tuples contain three elements: (data, meta, layer_type)
data
is the layer datameta
is a dictionary containing all other metadata attributes from the napari layer (excluding the.data
layer attribute).layer_type
is a string, eg: "image", "labels", "surface", etc.Returns
[path] : A list containing (potentially multiple) string paths to the saved file(s).
Expand source code
def write_multiple(path: str, data: List[FullLayerData]) -> List[str]: """Writes multiple layers of different types. Parameters ---------- path : str A string path indicating where to save the data file(s). data : A list of layer tuples. Tuples contain three elements: (data, meta, layer_type) `data` is the layer data `meta` is a dictionary containing all other metadata attributes from the napari layer (excluding the `.data` layer attribute). `layer_type` is a string, eg: "image", "labels", "surface", etc. Returns ------- [path] : A list containing (potentially multiple) string paths to the saved file(s). """ # implement your writer logic here ... # return path to any file(s) that were successfully written return [path]
def write_single_image(path: str, data: Any, meta: dict) ‑> List[str]
-
Writes a single image layer.
Parameters
path
:str
- A string path indicating where to save the image file.
data
:The layer data
- The
.data
attribute from the napari layer. meta
:dict
- A dictionary containing all other attributes from the napari layer
(excluding the
.data
layer attribute).
Returns
[path] : A list containing the string path to the saved file.
Expand source code
def write_single_image(path: str, data: Any, meta: dict) -> List[str]: """Writes a single image layer. Parameters ---------- path : str A string path indicating where to save the image file. data : The layer data The `.data` attribute from the napari layer. meta : dict A dictionary containing all other attributes from the napari layer (excluding the `.data` layer attribute). Returns ------- [path] : A list containing the string path to the saved file. """ # implement your writer logic here ... # return path to any file(s) that were successfully written return [path]
Classes
class CollectionNotFoundException (*args, **kwargs)
-
Custom exception for when a collection is not found in the scene.
Expand source code
class CollectionNotFoundException(Exception): """ Custom exception for when a collection is not found in the scene. """ pass
Ancestors
- builtins.Exception
- builtins.BaseException
class Gradient
-
Mode that compares predictions by coloring the predicted organoid. End-result is a prediction colored using a specific metric, and the volume being transparent.
Expand source code
class Gradient(Mode): """ Mode that compares predictions by coloring the predicted organoid. End-result is a prediction colored using a specific metric, and the volume being transparent. """ def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ dicts = {} dicts['texts'] = {} dicts['materials'] = {'Ground-Truth': (0.0, 0.0, 0.8, 0.3, 'BLEND')} if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': False, 'Indicator': False, 'Metrics': True} else: dicts['hide_obj'] = {'Gradient_Bar': False, 'Indicator': False, 'Metrics': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1, s.PRED_NAME: s.DATA2} return dicts def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ # Compute the relative index of the current frame. index = i - s.START # Compute metrics for the current frame objects. try: pred_obj = find_object(f'pred_{i}') gt_obj = find_object(f'gt_{i}') set_hue(pred_obj, s.METRICS[i][s.HUE_METRIC.value[0]]) set_material(gt_obj, 'Ground-Truth') except ObjectNotFoundException: print(f'Setting up frame {i} suspended') set_indicator(s.METRICS[i][s.HUE_METRIC.value[0]], s.LENGTH * index) change_text(s.HUE_METRIC.value[1], 'Metric') set_gradient_color('Gradient') super().set_scene(i) def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, False, i) set_origin_nuclei(nuclei, obj_name, i) def post_import(self): """ Mode specific code to be run after importing all frames. """ try: indicator = find_object('Indicator') mat_obj = find_object('Mat_2') mat_name = find_object('Mat_2_name') mat_obj.hide_render = False mat_name.hide_render = False except ObjectNotFoundException: print('Setting indicator object suspended') set_interpolation(indicator, 'CONSTANT')
Ancestors
- scripts.mode.Mode
- abc.ABC
Methods
def import_obj(self, obj_name, data, i)
-
Mode specific way of importing organoid objects.
Parameters
obj_name
:str
- Name of object to import/create.
data
:Any
- Data source for the object.
i
:int
- Index of current frame needed to be imported.
Expand source code
def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, False, i) set_origin_nuclei(nuclei, obj_name, i)
def init_dicts(self)
-
Mode specific objects/materials needed to be created or changed.
Expand source code
def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ dicts = {} dicts['texts'] = {} dicts['materials'] = {'Ground-Truth': (0.0, 0.0, 0.8, 0.3, 'BLEND')} if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': False, 'Indicator': False, 'Metrics': True} else: dicts['hide_obj'] = {'Gradient_Bar': False, 'Indicator': False, 'Metrics': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1, s.PRED_NAME: s.DATA2} return dicts
def post_import(self)
-
Mode specific code to be run after importing all frames.
Expand source code
def post_import(self): """ Mode specific code to be run after importing all frames. """ try: indicator = find_object('Indicator') mat_obj = find_object('Mat_2') mat_name = find_object('Mat_2_name') mat_obj.hide_render = False mat_name.hide_render = False except ObjectNotFoundException: print('Setting indicator object suspended') set_interpolation(indicator, 'CONSTANT')
def set_scene(self, i)
-
Mode specific code for importing each frame.
Parameters
i
:int
- Enumerator of current frame.
Expand source code
def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ # Compute the relative index of the current frame. index = i - s.START # Compute metrics for the current frame objects. try: pred_obj = find_object(f'pred_{i}') gt_obj = find_object(f'gt_{i}') set_hue(pred_obj, s.METRICS[i][s.HUE_METRIC.value[0]]) set_material(gt_obj, 'Ground-Truth') except ObjectNotFoundException: print(f'Setting up frame {i} suspended') set_indicator(s.METRICS[i][s.HUE_METRIC.value[0]], s.LENGTH * index) change_text(s.HUE_METRIC.value[1], 'Metric') set_gradient_color('Gradient') super().set_scene(i)
class MODES (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
Enumeration for different modes of visualization.
Attributes: Transparant: Mode that compares predictions by Boolean operations. Gradient: Mode that compares predictions by coloring the predicted organoid. Tracked: Mode that tracks individual organoids over time. Timelapse: Mode that shows the movement of organoids over time.
Expand source code
class MODES(Enum): """ Enumeration for different modes of visualization. Attributes: Transparant: Mode that compares predictions by Boolean operations. Gradient: Mode that compares predictions by coloring the predicted organoid. Tracked: Mode that tracks individual organoids over time. Timelapse: Mode that shows the movement of organoids over time. """ Transparant = 'transparant' Microscopy = 'microscopy' Gradient = 'gradient' Tracked = 'tracked' Timelapse = 'timelapse'
Ancestors
- enum.Enum
Class variables
var Gradient
var Microscopy
var Timelapse
var Tracked
var Transparant
class MaterialNotFoundException (*args, **kwargs)
-
Custom exception for material not found in the scene.
Expand source code
class MaterialNotFoundException(Exception): """ Custom exception for material not found in the scene. """ pass
Ancestors
- builtins.Exception
- builtins.BaseException
class Microscopy
-
Mode that compares raw microscopy data with a prediction. End-result is an transparant overlay containing microscopy data, and an object that contains the segmentation. The former is automated but is not a perfect representation of the nuclei in space.
Expand source code
class Microscopy(Mode): """ Mode that compares raw microscopy data with a prediction. End-result is an transparant overlay containing microscopy data, and an object that contains the segmentation. The former is automated but is not a perfect representation of the nuclei in space. """ def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ dicts = {} dicts['texts'] = {} dicts['materials'] = {'Ground-Truth': (0.0, 0.0, 0.8, 0.15, 'BLEND'), 'Prediction': (0.6, 1.0, 0.8, 1.0, 'OPAQUE')} if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': True, 'Metrics': True, 'Frame': False} else: dicts['hide_obj'] = {'Gradient_Bar': True, 'Metrics': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1, s.PRED_NAME: s.DATA2} return dicts def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, False, i) set_origin_nuclei(nuclei, obj_name, i) def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ # Compute the relative index of the current frame. index = i - s.START # Compute metrics for the current frame objects. try: pred_obj = find_object(f'pred_{i}') gt_obj = find_object(f'gt_{i}') set_material(pred_obj, 'Prediction') set_material(gt_obj, 'Ground-Truth') except ObjectNotFoundException: print(f'Setting up frame {i} suspended') super().set_scene(i)
Ancestors
- scripts.mode.Mode
- abc.ABC
Methods
def import_obj(self, obj_name, data, i)
-
Mode specific way of importing organoid objects.
Parameters
obj_name
:str
- Name of object to import/create.
data
:Any
- Data source for the object.
i
:int
- Index of current frame needed to be imported.
Expand source code
def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, False, i) set_origin_nuclei(nuclei, obj_name, i)
def init_dicts(self)
-
Mode specific objects/materials needed to be created or changed.
Expand source code
def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ dicts = {} dicts['texts'] = {} dicts['materials'] = {'Ground-Truth': (0.0, 0.0, 0.8, 0.15, 'BLEND'), 'Prediction': (0.6, 1.0, 0.8, 1.0, 'OPAQUE')} if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': True, 'Metrics': True, 'Frame': False} else: dicts['hide_obj'] = {'Gradient_Bar': True, 'Metrics': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1, s.PRED_NAME: s.DATA2} return dicts
def set_scene(self, i)
-
Mode specific code for importing each frame.
Parameters
i
:int
- Enumerator of current frame.
Expand source code
def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ # Compute the relative index of the current frame. index = i - s.START # Compute metrics for the current frame objects. try: pred_obj = find_object(f'pred_{i}') gt_obj = find_object(f'gt_{i}') set_material(pred_obj, 'Prediction') set_material(gt_obj, 'Ground-Truth') except ObjectNotFoundException: print(f'Setting up frame {i} suspended') super().set_scene(i)
class Mode
-
Parent class for all modes of visualization.
Expand source code
class Mode(ABC): """ Parent class for all modes of visualization. """ @abstractmethod def init_dicts(self): """ Method that will initialize the dictionaries of objects/materials to be created or changed before importing the scene. """ @abstractmethod def import_obj(self, obj_name, src, i): """ Method to import an object into the scene. Parameters ---------- obj_name : str Name of object to import/create. src : str File location where data about object can be found. i : int Index of current frame needed to be imported. """ def pre_import(self): """" Method that can be called to change the scene before importing frames. """ # Register frame counter for rendering register() # Populate dictionaries using child implementation, and call method to change # scene according to their content. dicts = self.init_dicts() change_scene_obj(dicts) return dicts['objs'] def post_import(self): """ Method that can be called to change the scene after importing frames. """ def set_scene(self, i): """ Method that changes the scene per imported frame. Parameters ---------- i : int Enumerator of which frame is being added in the loop, starts at 0 """ # Get index that corresponds with the absolute frame number index = i - s.START try: # Apply transformations to objects that are used in all methods coll = find_collection(f"Frame_{i}") for obj in coll.all_objects[:]: # if ('gt' in obj.name or 'inner' in obj.name or 'pred' in obj.name) and obj.type == "MESH": # set_rotation(obj, s.LENGTH * index, s.LENGTH + s.LENGTH * index) # set_interpolation(obj,'LINEAR') hide_in_render(obj, index, s.LENGTH) except CollectionNotFoundException: print(f'Setting up frame {i} suspended due to not finding collection \'Frame_{i}\'') # @thread_worker def visualize(self): """ Code that calls the full-pipeline of visualization. """ # Call pre-scene import code objs = self.pre_import() # Loop through frames for i in range(s.START, s.END + 1): # Create collections for each absolute frame number coll_target = D.collections.get(f"Frame_{i}") if coll_target is None: coll_target = D.collections.new(name=f"Frame_{i}") C.scene.collection.children.link(coll_target) C.view_layer.active_layer_collection = \ C.view_layer.layer_collection.children[f"Frame_{i}"] # Go over the dictionary that contains desired object names and their # data source, and import them into the scene. for obj_name, data in objs.items(): self.import_obj(obj_name+str(i), data, i) # Call child specific code for importing a frame at index i self.set_scene(i) # Call post-scene import code self.post_import()
Ancestors
- abc.ABC
Subclasses
- scripts.compare.Gradient
- scripts.compare.Microscopy
- scripts.compare.Timelapse
- scripts.compare.Tracked
- scripts.compare.Transparant
Methods
def import_obj(self, obj_name, src, i)
-
Method to import an object into the scene.
Parameters
obj_name
:str
- Name of object to import/create.
src
:str
- File location where data about object can be found.
i
:int
- Index of current frame needed to be imported.
Expand source code
@abstractmethod def import_obj(self, obj_name, src, i): """ Method to import an object into the scene. Parameters ---------- obj_name : str Name of object to import/create. src : str File location where data about object can be found. i : int Index of current frame needed to be imported. """
def init_dicts(self)
-
Method that will initialize the dictionaries of objects/materials to be created or changed before importing the scene.
Expand source code
@abstractmethod def init_dicts(self): """ Method that will initialize the dictionaries of objects/materials to be created or changed before importing the scene. """
def post_import(self)
-
Method that can be called to change the scene after importing frames.
Expand source code
def post_import(self): """ Method that can be called to change the scene after importing frames. """
def pre_import(self)
-
" Method that can be called to change the scene before importing frames.
Expand source code
def pre_import(self): """" Method that can be called to change the scene before importing frames. """ # Register frame counter for rendering register() # Populate dictionaries using child implementation, and call method to change # scene according to their content. dicts = self.init_dicts() change_scene_obj(dicts) return dicts['objs']
def set_scene(self, i)
-
Method that changes the scene per imported frame.
Parameters
i
:int
- Enumerator of which frame is being added in the loop, starts at 0
Expand source code
def set_scene(self, i): """ Method that changes the scene per imported frame. Parameters ---------- i : int Enumerator of which frame is being added in the loop, starts at 0 """ # Get index that corresponds with the absolute frame number index = i - s.START try: # Apply transformations to objects that are used in all methods coll = find_collection(f"Frame_{i}") for obj in coll.all_objects[:]: # if ('gt' in obj.name or 'inner' in obj.name or 'pred' in obj.name) and obj.type == "MESH": # set_rotation(obj, s.LENGTH * index, s.LENGTH + s.LENGTH * index) # set_interpolation(obj,'LINEAR') hide_in_render(obj, index, s.LENGTH) except CollectionNotFoundException: print(f'Setting up frame {i} suspended due to not finding collection \'Frame_{i}\'')
def visualize(self)
-
Code that calls the full-pipeline of visualization.
Expand source code
def visualize(self): """ Code that calls the full-pipeline of visualization. """ # Call pre-scene import code objs = self.pre_import() # Loop through frames for i in range(s.START, s.END + 1): # Create collections for each absolute frame number coll_target = D.collections.get(f"Frame_{i}") if coll_target is None: coll_target = D.collections.new(name=f"Frame_{i}") C.scene.collection.children.link(coll_target) C.view_layer.active_layer_collection = \ C.view_layer.layer_collection.children[f"Frame_{i}"] # Go over the dictionary that contains desired object names and their # data source, and import them into the scene. for obj_name, data in objs.items(): self.import_obj(obj_name+str(i), data, i) # Call child specific code for importing a frame at index i self.set_scene(i) # Call post-scene import code self.post_import()
class ObjectNotFoundException (*args, **kwargs)
-
Custom exception for when an object is not found in the scene.
Expand source code
class ObjectNotFoundException(Exception): """ Custom exception for when an object is not found in the scene. """ pass
Ancestors
- builtins.Exception
- builtins.BaseException
class ROTATION (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
Enumeration for different rotation speeds.
Attributes: No_Rotation: No rotation. Picture Mode. Very_Slow: Very slow rotation speed. 300 frames per rotation. Slow: Slow rotation speed. 200 frames per rotation. Normal: Normal rotation speed. 150 frames per rotation. Quick: Quick rotation speed. 100 frames per rotation. Very_Quick: Very quick rotation speed. 50 frames per rotation.
Expand source code
class ROTATION(Enum): """ Enumeration for different rotation speeds. Attributes: No_Rotation: No rotation. Picture Mode. Very_Slow: Very slow rotation speed. 300 frames per rotation. Slow: Slow rotation speed. 200 frames per rotation. Normal: Normal rotation speed. 150 frames per rotation. Quick: Quick rotation speed. 100 frames per rotation. Very_Quick: Very quick rotation speed. 50 frames per rotation. """ No_Rotation = 1 Very_Slow = 300 Slow = 200 Normal = 150 Quick = 100 Very_Quick = 50
Ancestors
- enum.Enum
Class variables
var No_Rotation
var Normal
var Quick
var Slow
var Very_Quick
var Very_Slow
class RenderProgress
-
Expand source code
class RenderProgress: def __init__(self): self.root = Tk() self.mainframe = None self.progress_bar = None self.progress_text = None self.progress_time = None self.start_time = None def init_progress_bar(self): """ Initializes the progress bar for rendering. """ screen_width = self.root.winfo_screenwidth() screen_height = self.root.winfo_screenheight() x = (screen_width - 450) // 2 # 450 is the width of the window y = (screen_height - 130) // 2 # 130 is the height of the window self.root.geometry(f"450x130+{x}+{y}") # Set window size and position self.root.title("Rendering") self.root.resizable(False, False) self.mainframe = ttk.Frame(self.root, padding="3 3 12 12") self.mainframe.grid(column=0, row=0, sticky=(N, W, E, S)) self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) self.progress_bar = ttk.Progressbar(self.mainframe, maximum=100, length=420) self.progress_bar.place(x=10, y=50) self.progress_text = Label(self.mainframe, text="Progress: 0%") self.progress_text.place(x=180, y=15) self.progress_time = Label(self.mainframe, text="Time Left: Calculating..") self.progress_time.place(x=160, y=90) def render_progress_callback(self, scene): """ Callback function to update the progress bar during rendering. Parameters ---------- scene : bpy.types.Scene The current Blender scene. """ frame = scene.frame_current total_frames = scene.frame_end - scene.frame_start + 1 progress = int((frame - scene.frame_start) / total_frames * 100) progress = min(progress, 100) # Ensure progress does not exceed 100% current_time = datetime.datetime.now() elapsed_time = (current_time - self.start_time).total_seconds() time_per_frame = elapsed_time / (frame - scene.frame_start + 1) remaining_frames = total_frames - (frame - scene.frame_start + 1) estimated_remaining_time = remaining_frames * time_per_frame remaining_time_str = str(datetime.timedelta(seconds=int(estimated_remaining_time))) # Use the main thread to update the GUI self.root.after(0, self.update_gui, progress, remaining_time_str) def update_gui(self, progress, remaining_time_str): """ Updates the GUI elements with the current progress and estimated remaining time. Parameters ---------- progress : int The current progress percentage. remaining_time_str : str The estimated remaining time as a string. """ self.progress_bar['value'] = progress self.progress_text['text'] = f"Progress: {progress}%" self.progress_time['text'] = f"Time Left: {remaining_time_str}" def optimize_render_settings(self): """ Optimizes the render settings for faster rendering. """ C.scene.render.use_motion_blur = False C.scene.render.use_simplify = True C.scene.render.use_border = False def render_video(self): """ Renders the video to the specified path. Parameters ---------- path : str The file path where the rendered video will be saved. start : int The starting frame number. end : int The ending frame number. """ C.scene.render.filepath = str(Path(s.SRC_VID / s.FILENAME)) C.scene.frame_start = s.S_FRAME C.scene.frame_end = s.E_FRAME C.scene.render.engine = 'BLENDER_EEVEE' C.scene.render.image_settings.file_format = 'FFMPEG' C.scene.render.ffmpeg.format = 'MPEG4' C.scene.render.ffmpeg.codec = 'H264' self.optimize_render_settings() self.start_time = datetime.datetime.now() self.init_progress_bar() def render(): def handler(scene): self.render_progress_callback(scene) A.handlers.frame_change_pre.append(handler) try: O.render.render(animation=True) finally: A.handlers.frame_change_pre.remove(handler) self.root.after(0, self.root.destroy) threading.Thread(target=render).start() self.root.mainloop() def render_samples(self): """ Sets the TAA render samples for EEVEE renderer. """ render_settings = C.scene.eevee render_settings.taa_render_samples = s.SAMPLES
Methods
def init_progress_bar(self)
-
Initializes the progress bar for rendering.
Expand source code
def init_progress_bar(self): """ Initializes the progress bar for rendering. """ screen_width = self.root.winfo_screenwidth() screen_height = self.root.winfo_screenheight() x = (screen_width - 450) // 2 # 450 is the width of the window y = (screen_height - 130) // 2 # 130 is the height of the window self.root.geometry(f"450x130+{x}+{y}") # Set window size and position self.root.title("Rendering") self.root.resizable(False, False) self.mainframe = ttk.Frame(self.root, padding="3 3 12 12") self.mainframe.grid(column=0, row=0, sticky=(N, W, E, S)) self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) self.progress_bar = ttk.Progressbar(self.mainframe, maximum=100, length=420) self.progress_bar.place(x=10, y=50) self.progress_text = Label(self.mainframe, text="Progress: 0%") self.progress_text.place(x=180, y=15) self.progress_time = Label(self.mainframe, text="Time Left: Calculating..") self.progress_time.place(x=160, y=90)
def optimize_render_settings(self)
-
Optimizes the render settings for faster rendering.
Expand source code
def optimize_render_settings(self): """ Optimizes the render settings for faster rendering. """ C.scene.render.use_motion_blur = False C.scene.render.use_simplify = True C.scene.render.use_border = False
def render_progress_callback(self, scene)
-
Callback function to update the progress bar during rendering.
Parameters
scene
:bpy.types.Scene
- The current Blender scene.
Expand source code
def render_progress_callback(self, scene): """ Callback function to update the progress bar during rendering. Parameters ---------- scene : bpy.types.Scene The current Blender scene. """ frame = scene.frame_current total_frames = scene.frame_end - scene.frame_start + 1 progress = int((frame - scene.frame_start) / total_frames * 100) progress = min(progress, 100) # Ensure progress does not exceed 100% current_time = datetime.datetime.now() elapsed_time = (current_time - self.start_time).total_seconds() time_per_frame = elapsed_time / (frame - scene.frame_start + 1) remaining_frames = total_frames - (frame - scene.frame_start + 1) estimated_remaining_time = remaining_frames * time_per_frame remaining_time_str = str(datetime.timedelta(seconds=int(estimated_remaining_time))) # Use the main thread to update the GUI self.root.after(0, self.update_gui, progress, remaining_time_str)
def render_samples(self)
-
Sets the TAA render samples for EEVEE renderer.
Expand source code
def render_samples(self): """ Sets the TAA render samples for EEVEE renderer. """ render_settings = C.scene.eevee render_settings.taa_render_samples = s.SAMPLES
def render_video(self)
-
Renders the video to the specified path.
Parameters
path
:str
- The file path where the rendered video will be saved.
start
:int
- The starting frame number.
end
:int
- The ending frame number.
Expand source code
def render_video(self): """ Renders the video to the specified path. Parameters ---------- path : str The file path where the rendered video will be saved. start : int The starting frame number. end : int The ending frame number. """ C.scene.render.filepath = str(Path(s.SRC_VID / s.FILENAME)) C.scene.frame_start = s.S_FRAME C.scene.frame_end = s.E_FRAME C.scene.render.engine = 'BLENDER_EEVEE' C.scene.render.image_settings.file_format = 'FFMPEG' C.scene.render.ffmpeg.format = 'MPEG4' C.scene.render.ffmpeg.codec = 'H264' self.optimize_render_settings() self.start_time = datetime.datetime.now() self.init_progress_bar() def render(): def handler(scene): self.render_progress_callback(scene) A.handlers.frame_change_pre.append(handler) try: O.render.render(animation=True) finally: A.handlers.frame_change_pre.remove(handler) self.root.after(0, self.root.destroy) threading.Thread(target=render).start() self.root.mainloop()
def update_gui(self, progress, remaining_time_str)
-
Updates the GUI elements with the current progress and estimated remaining time.
Parameters
progress
:int
- The current progress percentage.
remaining_time_str
:str
- The estimated remaining time as a string.
Expand source code
def update_gui(self, progress, remaining_time_str): """ Updates the GUI elements with the current progress and estimated remaining time. Parameters ---------- progress : int The current progress percentage. remaining_time_str : str The estimated remaining time as a string. """ self.progress_bar['value'] = progress self.progress_text['text'] = f"Progress: {progress}%" self.progress_time['text'] = f"Time Left: {remaining_time_str}"
class SAMPLES (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
Enumeration for different sampling resolutions. Multiples of 26 to leverage the batch size of Blender rendering to it's maximum.
Attributes: Low: Low sampling resolution. Medium: Medium sampling resolution. High: High sampling resolution.
Expand source code
class SAMPLES(Enum): """ Enumeration for different sampling resolutions. Multiples of 26 to leverage the batch size of Blender rendering to it's maximum. Attributes: Low: Low sampling resolution. Medium: Medium sampling resolution. High: High sampling resolution. """ Low = 1 Medium = 26 High = 52
Ancestors
- enum.Enum
Class variables
var High
var Low
var Medium
class Timelapse
-
Mode that shows the movement of organoids over time. This mode separates all nuclei, tracks them and links them using colors, or shades of colors if cell division occurs.
Expand source code
class Timelapse(Mode): """ Mode that shows the movement of organoids over time. This mode separates all nuclei, tracks them and links them using colors, or shades of colors if cell division occurs. """ def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ global data_frame data_frame = read_trackmate() dicts = {} dicts['texts'] = {} dicts['materials'] = {} for i in range(0, 5): dicts['materials'].update({f'Track {i+1}': (0.2 * i, 1.0, 0.8, 1.0, 'BLEND')}) if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': True, 'Legend': True, 'Metrics': True} else: dicts['hide_obj'] = {'Gradient_Bar': True, 'Legend': True, 'Metrics': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1} return dicts def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, True, i) set_origin_nuclei(nuclei, obj_name, i) rename_nuclei(nuclei, data_frame, i + s.FRAME_OFFSET) def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ index = i - s.START divided_nuclei = [] try: coll = find_collection(f"Frame_{i}") mesh_objects = [obj for obj in coll.all_objects if obj.type == 'MESH'] for obj in mesh_objects: # Show objects when needed in sequence render if index == 0: track_id = get_track(data_frame, obj.name.split('.')[0]) set_material(obj, f'Track {track_id}') else: ancestor_id = track_ancestor(data_frame, obj.name.split('.')[0]) if ancestor_id is None: divided_nuclei.append(obj.name) hide_in_render(obj, index, s.LENGTH) mitosis_nuclei[i] = divided_nuclei divided_nuclei = [] except CollectionNotFoundException: print(f'Setting up frame {i} suspended') def post_import(self): """ Mode specific code to be run after importing all frames. """ for i in range(s.START, s.END): index = i - s.START try: coll = find_collection(f"Frame_{i}") mesh_objects = [obj for obj in coll.all_objects if obj.type == 'MESH'] for obj in mesh_objects: # Apply shrinkwrap modifiers to allow for parents of this object to morph child_id = track_child(data_frame, obj.name.split('.')[0]) if child_id is not None: try: child = find_object(str(child_id)) if obj.material_slots: mat_name = obj.material_slots[obj.active_material_index].material.name set_material(child, mat_name) apply_shape_key(obj, child, index) except ObjectNotFoundException: print(f'Setting shrinkwrap keyframe from {child_id} to {obj.name} suspended') else: sign = 1 for div_nucleus in mitosis_nuclei[i+1]: sign *= -1 try: child = find_object(str(div_nucleus)) dupl_obj = duplicate_object(obj, f'{obj.name}-{div_nucleus}') apply_shape_key(dupl_obj, child, index) child_mat = create_child_material(obj, sign * 0.04) set_material(child, child_mat) set_material(dupl_obj, child_mat) except ObjectNotFoundException: print(f'Setting shrinkwrap keyframe from {child_id} to {obj.name} suspended') obj.name = f'{obj.name}.' clean_duplicate_objs() except CollectionNotFoundException: print(f'Setting up shape keys for frame {i} suspended')
Ancestors
- scripts.mode.Mode
- abc.ABC
Methods
def import_obj(self, obj_name, data, i)
-
Mode specific way of importing organoid objects.
Parameters
obj_name
:str
- Name of object to import/create.
data
:Any
- Data source for the object.
i
:int
- Index of current frame needed to be imported.
Expand source code
def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, True, i) set_origin_nuclei(nuclei, obj_name, i) rename_nuclei(nuclei, data_frame, i + s.FRAME_OFFSET)
def init_dicts(self)
-
Mode specific objects/materials needed to be created or changed.
Expand source code
def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ global data_frame data_frame = read_trackmate() dicts = {} dicts['texts'] = {} dicts['materials'] = {} for i in range(0, 5): dicts['materials'].update({f'Track {i+1}': (0.2 * i, 1.0, 0.8, 1.0, 'BLEND')}) if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': True, 'Legend': True, 'Metrics': True} else: dicts['hide_obj'] = {'Gradient_Bar': True, 'Legend': True, 'Metrics': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1} return dicts
def post_import(self)
-
Mode specific code to be run after importing all frames.
Expand source code
def post_import(self): """ Mode specific code to be run after importing all frames. """ for i in range(s.START, s.END): index = i - s.START try: coll = find_collection(f"Frame_{i}") mesh_objects = [obj for obj in coll.all_objects if obj.type == 'MESH'] for obj in mesh_objects: # Apply shrinkwrap modifiers to allow for parents of this object to morph child_id = track_child(data_frame, obj.name.split('.')[0]) if child_id is not None: try: child = find_object(str(child_id)) if obj.material_slots: mat_name = obj.material_slots[obj.active_material_index].material.name set_material(child, mat_name) apply_shape_key(obj, child, index) except ObjectNotFoundException: print(f'Setting shrinkwrap keyframe from {child_id} to {obj.name} suspended') else: sign = 1 for div_nucleus in mitosis_nuclei[i+1]: sign *= -1 try: child = find_object(str(div_nucleus)) dupl_obj = duplicate_object(obj, f'{obj.name}-{div_nucleus}') apply_shape_key(dupl_obj, child, index) child_mat = create_child_material(obj, sign * 0.04) set_material(child, child_mat) set_material(dupl_obj, child_mat) except ObjectNotFoundException: print(f'Setting shrinkwrap keyframe from {child_id} to {obj.name} suspended') obj.name = f'{obj.name}.' clean_duplicate_objs() except CollectionNotFoundException: print(f'Setting up shape keys for frame {i} suspended')
def set_scene(self, i)
-
Mode specific code for importing each frame.
Parameters
i
:int
- Enumerator of current frame.
Expand source code
def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ index = i - s.START divided_nuclei = [] try: coll = find_collection(f"Frame_{i}") mesh_objects = [obj for obj in coll.all_objects if obj.type == 'MESH'] for obj in mesh_objects: # Show objects when needed in sequence render if index == 0: track_id = get_track(data_frame, obj.name.split('.')[0]) set_material(obj, f'Track {track_id}') else: ancestor_id = track_ancestor(data_frame, obj.name.split('.')[0]) if ancestor_id is None: divided_nuclei.append(obj.name) hide_in_render(obj, index, s.LENGTH) mitosis_nuclei[i] = divided_nuclei divided_nuclei = [] except CollectionNotFoundException: print(f'Setting up frame {i} suspended')
class Tracked
-
Parent class for all modes of visualization.
Expand source code
class Tracked(Mode): def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ global data_frame data_frame = read_trackmate() dicts = {} dicts['texts'] = {} dicts['materials'] = { 'Not Predicted': (0.45, 0.4, 0.8, 0.8, 'OPAQUE'), 'False Prediction': (0.8, 0.75, 0.8, 1.0, 'OPAQUE') } if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': False, 'Metrics': True, 'Indicator': True} else: dicts['hide_obj'] = {'Gradient_Bar': False, 'Metrics': True, 'Indicator': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1, s.PRED_NAME: s.DATA2} return dicts def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, True, i) set_origin_nuclei(nuclei, obj_name, i) rename_nuclei(nuclei, data_frame, i + s.FRAME_OFFSET) def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ index = i - s.START try: coll = find_collection(f"Frame_{i}") for obj in coll.all_objects[:]: if "pred" in obj.name or "gt" in obj.name: color_nuclei(obj, i) except CollectionNotFoundException: print(f'Setting up frame {i} suspended') super().set_scene(i)
Ancestors
- scripts.mode.Mode
- abc.ABC
Methods
def import_obj(self, obj_name, data, i)
-
Mode specific way of importing organoid objects.
Parameters
obj_name
:str
- Name of object to import/create.
data
:Any
- Data source for the object.
i
:int
- Index of current frame needed to be imported.
Expand source code
def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, True, i) set_origin_nuclei(nuclei, obj_name, i) rename_nuclei(nuclei, data_frame, i + s.FRAME_OFFSET)
def init_dicts(self)
-
Mode specific objects/materials needed to be created or changed.
Expand source code
def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ global data_frame data_frame = read_trackmate() dicts = {} dicts['texts'] = {} dicts['materials'] = { 'Not Predicted': (0.45, 0.4, 0.8, 0.8, 'OPAQUE'), 'False Prediction': (0.8, 0.75, 0.8, 1.0, 'OPAQUE') } if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': False, 'Metrics': True, 'Indicator': True} else: dicts['hide_obj'] = {'Gradient_Bar': False, 'Metrics': True, 'Indicator': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1, s.PRED_NAME: s.DATA2} return dicts
def set_scene(self, i)
-
Mode specific code for importing each frame.
Parameters
i
:int
- Enumerator of current frame.
Expand source code
def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ index = i - s.START try: coll = find_collection(f"Frame_{i}") for obj in coll.all_objects[:]: if "pred" in obj.name or "gt" in obj.name: color_nuclei(obj, i) except CollectionNotFoundException: print(f'Setting up frame {i} suspended') super().set_scene(i)
class Transparant
-
Mode that compares predictions by Boolean operations. End-result is an intersecting object containing True Positives, and two objects that are only predicted in one of the two predictions which we call False Negatives and False Positives.
Expand source code
class Transparant(Mode): """ Mode that compares predictions by Boolean operations. End-result is an intersecting object containing True Positives, and two objects that are only predicted in one of the two predictions which we call False Negatives and False Positives. """ def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ dicts = {} dicts['texts'] = {} dicts['materials'] = { 'True Positive': (0.0, 0.0, 0.8, 1.0, 'OPAQUE'), 'False Negative': (0.035, 1.0, 0.8, 0.35, 'BLEND'), 'False Positive': (0.6, 1.0, 0.8, 0.35, 'BLEND') } if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': True, 'Metrics': False} else: dicts['hide_obj'] = {'Gradient_Bar': True, 'Metrics': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1, s.PRED_NAME: s.DATA2} return dicts def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, False, i) set_origin_nuclei(nuclei, obj_name, i) def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ try: inner = find_object(f'inner_{i}') except ObjectNotFoundException: # If the inner object does not exist, perform boolean operations try: gt_obj = find_object(f'gt_{i}') comp_obj = find_object(f'pred_{i}') create_overlap(gt_obj, comp_obj) inner = find_object(f'inner_{i}') except ObjectNotFoundException: print(f'Setting up frame {i} suspended') # Set materials for objects based on the boolean operation results set_material(inner, 'True Positive') set_material(gt_obj, 'False Negative') set_material(comp_obj, 'False Positive') super().set_scene(i)
Ancestors
- scripts.mode.Mode
- abc.ABC
Methods
def import_obj(self, obj_name, data, i)
-
Mode specific way of importing organoid objects.
Parameters
obj_name
:str
- Name of object to import/create.
data
:Any
- Data source for the object.
i
:int
- Index of current frame needed to be imported.
Expand source code
def import_obj(self, obj_name, data, i): """ Mode specific way of importing organoid objects. Parameters ---------- obj_name : str Name of object to import/create. data : Any Data source for the object. i : int Index of current frame needed to be imported. """ nuclei = import_object(obj_name, data, False, i) set_origin_nuclei(nuclei, obj_name, i)
def init_dicts(self)
-
Mode specific objects/materials needed to be created or changed.
Expand source code
def init_dicts(self): """ Mode specific objects/materials needed to be created or changed. """ dicts = {} dicts['texts'] = {} dicts['materials'] = { 'True Positive': (0.0, 0.0, 0.8, 1.0, 'OPAQUE'), 'False Negative': (0.035, 1.0, 0.8, 0.35, 'BLEND'), 'False Positive': (0.6, 1.0, 0.8, 0.35, 'BLEND') } if s.SHOW_TEXT: dicts['hide_obj'] = {'Gradient_Bar': True, 'Metrics': False} else: dicts['hide_obj'] = {'Gradient_Bar': True, 'Metrics': True, 'Frame': True} dicts['objs'] = {s.GT_NAME: s.DATA1, s.PRED_NAME: s.DATA2} return dicts
def set_scene(self, i)
-
Mode specific code for importing each frame.
Parameters
i
:int
- Enumerator of current frame.
Expand source code
def set_scene(self, i): """ Mode specific code for importing each frame. Parameters ---------- i : int Enumerator of current frame. """ try: inner = find_object(f'inner_{i}') except ObjectNotFoundException: # If the inner object does not exist, perform boolean operations try: gt_obj = find_object(f'gt_{i}') comp_obj = find_object(f'pred_{i}') create_overlap(gt_obj, comp_obj) inner = find_object(f'inner_{i}') except ObjectNotFoundException: print(f'Setting up frame {i} suspended') # Set materials for objects based on the boolean operation results set_material(inner, 'True Positive') set_material(gt_obj, 'False Negative') set_material(comp_obj, 'False Positive') super().set_scene(i)