From 63c593f09a0bcbd1e947e8c910fee97697cb265e Mon Sep 17 00:00:00 2001 From: Ma Nan Date: Thu, 19 Sep 2024 12:27:39 +0800 Subject: [PATCH] fix: relocate cannot work on missing layer fix: import fail if label are all zero fix: add cutter name to node fix: cannot append node lib file on NAS (UNC path) fix: join component cause disappear feat: smooth label feat: better topo on cutout by range --- .../assets/Nodes/BioxelNodes_latest.blend | 4 +- bioxelnodes/bioxel/layer.py | 8 +--- bioxelnodes/bioxelutils/common.py | 5 ++- bioxelnodes/bioxelutils/node.py | 43 ++++++++++--------- bioxelnodes/blender_manifest.toml | 2 +- bioxelnodes/constants.py | 2 +- bioxelnodes/operators/container.py | 2 +- bioxelnodes/operators/io.py | 22 +++++++--- bioxelnodes/operators/layer.py | 23 +++++++--- bioxelnodes/operators/node.py | 6 +-- pyproject.toml | 2 +- 11 files changed, 69 insertions(+), 50 deletions(-) diff --git a/bioxelnodes/assets/Nodes/BioxelNodes_latest.blend b/bioxelnodes/assets/Nodes/BioxelNodes_latest.blend index ae27a75..ea7f435 100644 --- a/bioxelnodes/assets/Nodes/BioxelNodes_latest.blend +++ b/bioxelnodes/assets/Nodes/BioxelNodes_latest.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a3d686d86c7519ea46bd7d8a4699076395de719a1c3165aca56b41cae070456 -size 8986342 +oid sha256:213a99f77511d03133780bb9056f39dd2bf57b75cf12810cc4957f6b74450abb +size 9002987 diff --git a/bioxelnodes/bioxel/layer.py b/bioxelnodes/bioxel/layer.py index 33020c1..4837b51 100644 --- a/bioxelnodes/bioxel/layer.py +++ b/bioxelnodes/bioxel/layer.py @@ -77,7 +77,7 @@ def fill(self, value: float, mask: np.ndarray, smooth: int = 0): for f in range(self.frame_count): mask_frame = mask[f, :, :, :] if smooth > 0: - mask_frame = scipy.minimum_filter(mask_frame.astype(np.float32), + mask_frame = scipy.median_filter(mask_frame.astype(np.float32), mode="nearest", size=smooth) # mask_frame = scipy.median_filter( @@ -87,7 +87,7 @@ def fill(self, value: float, mask: np.ndarray, smooth: int = 0): for f in range(self.frame_count): mask_frame = mask[:, :, :] if smooth > 0: - mask_frame = scipy.minimum_filter(mask_frame.astype(np.float32), + mask_frame = scipy.median_filter(mask_frame.astype(np.float32), mode="nearest", size=smooth) # mask_frame = scipy.median_filter( @@ -119,10 +119,6 @@ def resize(self, shape: tuple, smooth: int = 0, progress_callback=None): if progress_callback: progress_callback(f, self.frame_count) - # frame = ski.resize(data[f, :, :, :], - # shape, - # preserve_range=True, - # anti_aliasing=data.dtype.kind != "b") frame = data[f, :, :, :, :] if smooth > 0: frame = scipy.median_filter(frame.astype(np.float32), diff --git a/bioxelnodes/bioxelutils/common.py b/bioxelnodes/bioxelutils/common.py index d5876f5..b2bb850 100644 --- a/bioxelnodes/bioxelutils/common.py +++ b/bioxelnodes/bioxelutils/common.py @@ -259,11 +259,12 @@ def local_lib_not_updated(): not_update = get_node_version() != addon_version return use_local and not_update + def get_output_node(node_group): try: output_node = get_nodes_by_type(node_group, 'NodeGroupOutput')[0] except: output_node = node_group.nodes.new("NodeGroupOutput") - - return output_node \ No newline at end of file + + return output_node diff --git a/bioxelnodes/bioxelutils/node.py b/bioxelnodes/bioxelutils/node.py index 973b7cc..57707db 100644 --- a/bioxelnodes/bioxelutils/node.py +++ b/bioxelnodes/bioxelutils/node.py @@ -1,13 +1,13 @@ from pathlib import Path import bpy -from .common import get_file_prop, get_node_lib_path, set_file_prop -from ..exceptions import Incompatible, NoFound +from .common import get_file_prop, set_file_prop +from ..exceptions import NoFound from ..constants import LATEST_NODE_LIB_PATH, VERSIONS -def get_node_group(node_type: str, use_link=True): +def get_node_tree(node_type: str, use_link=True): # unannotate below for local debug in node lib file. # node_group = bpy.data.node_groups[node_type] # return node_group @@ -19,36 +19,37 @@ def get_node_group(node_type: str, use_link=True): if get_file_prop("node_version") is None: set_file_prop("node_version", addon_version) - local_lib_path = None + local_lib = None for node_group in bpy.data.node_groups: if node_group.name.startswith("BioxelNodes"): lib = node_group.library if lib: lib_path = Path(bpy.path.abspath(lib.filepath)).resolve() if lib_path != addon_lib_path: - local_lib_path = lib_path + local_lib = lib.filepath break # local lib first - lib_path = local_lib_path or addon_lib_path - bpy.ops.wm.append('EXEC_DEFAULT', - directory=f"{lib_path.as_posix()}/NodeTree", - filename=node_type, - link=use_link, - use_recursive=True, - do_reuse_local_id=True) - node_group = bpy.data.node_groups.get(node_type) - - if node_group is None: + lib_path_str = local_lib or str(addon_lib_path) + + with bpy.data.libraries.load(lib_path_str, + link=use_link, + relative=True) as (data_from, data_to): + data_to.node_groups = [n for n in data_from.node_groups + if n == node_type] + + node_tree = data_to.node_groups[0] + + if node_tree is None: raise NoFound('No custom node found') - return node_group + return node_tree -def assign_node_group(node, node_type: str): - node.node_tree = bpy.data.node_groups[node_type] +def assign_node_tree(node, node_tree): + node.node_tree = node_tree node.width = 200.0 - node.name = node_type + node.name = node_tree.name return node @@ -61,9 +62,9 @@ def add_node_to_graph(node_name: str, node_group, node_label=None, use_link=True if node.select: node.select = False - get_node_group(node_type, use_link) + node_tree = get_node_tree(node_type, use_link) node = node_group.nodes.new("GeometryNodeGroup") - assign_node_group(node, node_type) + assign_node_tree(node, node_tree) node.label = node_label node.show_options = False diff --git a/bioxelnodes/blender_manifest.toml b/bioxelnodes/blender_manifest.toml index 224984a..39bfbc4 100644 --- a/bioxelnodes/blender_manifest.toml +++ b/bioxelnodes/blender_manifest.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "bioxelnodes" -version = "1.0.1" +version = "1.0.2" name = "Bioxel Nodes" tagline = "For scientific volumetric data visualization in Blender" maintainer = "Ma Nan " diff --git a/bioxelnodes/constants.py b/bioxelnodes/constants.py index 173f508..5cc3d5f 100644 --- a/bioxelnodes/constants.py +++ b/bioxelnodes/constants.py @@ -1,6 +1,6 @@ from pathlib import Path -VERSIONS = [{"label": "Latest", "node_version": (1, 0, 1)}, +VERSIONS = [{"label": "Latest", "node_version": (1, 0, 2)}, {"label": "v0.3.x", "node_version": (0, 3, 3)}, {"label": "v0.2.x", "node_version": (0, 2, 9)}] diff --git a/bioxelnodes/operators/container.py b/bioxelnodes/operators/container.py index 5c523ac..9dbedd9 100644 --- a/bioxelnodes/operators/container.py +++ b/bioxelnodes/operators/container.py @@ -343,7 +343,7 @@ def execute(self, context): if len(cut_nodes) == 0: cutter_node = add_node_to_graph("ObjectCutter", node_group, - node_label=name, + node_label=f"{self.cutter_type.capitalize()} Cutter", use_link=get_use_link()) cutter_node.inputs[0].default_value = self.cutter_type.capitalize() cutter_node.inputs[1].default_value = cutter_obj diff --git a/bioxelnodes/operators/io.py b/bioxelnodes/operators/io.py index b32ad33..2b55991 100644 --- a/bioxelnodes/operators/io.py +++ b/bioxelnodes/operators/io.py @@ -164,7 +164,7 @@ class ParseVolumetricData(bpy.types.Operator): read_as: bpy.props.EnumProperty(name="Read as", default="SCALAR", items=[("SCALAR", "Scalar", ""), - ("LABEL", "Labels", ""), + ("LABEL", "Label", ""), ("COLOR", "Color", "")]) # type: ignore series_id: bpy.props.EnumProperty(name="Select Series", @@ -284,6 +284,10 @@ def modal(self, context, event): self.report({"ERROR"}, "Invaild label data.") return {'CANCELLED'} + if self.label_count == 0: + self.report({"ERROR"}, "Get no label.") + return {'CANCELLED'} + orig_shape = self.meta['xyz_shape'] orig_spacing = self.meta['spacing'] @@ -418,8 +422,8 @@ def get_meta(key): series_id = "empty" label = "{:<20} {:>1}".format(f"{study_description}>{series_description}({series_modality})", - f"({size_x}x{size_y})x{count}") - + f"({size_x}x{size_y})x{count}") + series_items[series_id] = label for series_id, label in series_items.items(): @@ -500,10 +504,13 @@ class ImportVolumetricDataDialog(bpy.types.Operator): label_count: bpy.props.IntProperty() # type: ignore + smooth: bpy.props.IntProperty(name="Smooth Size (Larger takes longer time)", + default=3) # type: ignore + read_as: bpy.props.EnumProperty(name="Read as", default="SCALAR", items=[("SCALAR", "Scalar", ""), - ("LABEL", "Labels", ""), + ("LABEL", "Label", ""), ("COLOR", "Color", "")]) # type: ignore bioxel_size: bpy.props.FloatProperty(name="Bioxel Size (Larger size means small resolution)", @@ -616,12 +623,15 @@ def progress_callback(frame, total): progress_callback = progress_callback_factory(name_i, progress, progress_step) + label_data = data == np.full_like(data, i+1) + label_data = label_data.astype(np.float32) try: - layer = Layer(data=data == np.full_like(data, i+1), + layer = Layer(data=label_data, name=name_i, kind=kind) layer.resize(shape=shape, + smooth=self.smooth, progress_callback=progress_callback) layer.affine = affine @@ -924,6 +934,8 @@ def draw(self, context): if self.read_as == "SCALAR": panel.prop(self, "split_channel", text=f"Split Channel as Multi Layer") + elif self.read_as == "LABEL": + panel.prop(self, "smooth") panel.label( text="Dimension Order: [Frame, (X-axis,Y-axis,Z-axis), Channel]") diff --git a/bioxelnodes/operators/layer.py b/bioxelnodes/operators/layer.py index 49da89f..599ca6f 100644 --- a/bioxelnodes/operators/layer.py +++ b/bioxelnodes/operators/layer.py @@ -4,7 +4,6 @@ import numpy as np - from ..exceptions import NoContent from ..bioxel.layer import Layer from ..bioxelutils.node import add_node_to_graph @@ -199,7 +198,7 @@ def invoke(self, context, event): return self.execute(context) -class RelocateLayer(bpy.types.Operator, LayerOperator): +class RelocateLayer(bpy.types.Operator): bl_idname = "bioxelnodes.relocate_layer" bl_label = "Relocate Layer Cache" bl_description = "Relocate layer cache" @@ -207,6 +206,16 @@ class RelocateLayer(bpy.types.Operator, LayerOperator): filepath: bpy.props.StringProperty(subtype="FILE_PATH") # type: ignore + def execute(self, context): + layer_obj = get_selected_layer(context) + if layer_obj == None: + self.report({"WARNING"}, "Get no layer.") + return {'FINISHED'} + + self.operate(layer_obj, context) + + return {'FINISHED'} + def operate(self, layer_obj, context): layer_obj.data.filepath = self.filepath @@ -259,7 +268,7 @@ class ResampleLayer(bpy.types.Operator, OutputLayerOperator): bl_description = "Resample value" bl_icon = "ALIASED" - smooth: bpy.props.IntProperty(name="Smooth Iteration", + smooth: bpy.props.IntProperty(name="Smooth Size", default=0, soft_min=0, soft_max=5, options={"SKIP_SAVE"}) # type: ignore @@ -389,7 +398,7 @@ def operate(self, orig_layer: Layer, context): if self.invert else data > self.threshold new_layer = orig_layer.copy() - new_layer.fill(self.fill_value, mask, 3) + new_layer.fill(self.fill_value, mask, 0) new_layer.name = self.new_layer_name \ or f"{orig_layer.name}_F-{self.threshold:.2f}" return new_layer @@ -419,7 +428,7 @@ def operate(self, orig_layer: Layer, context): (data > self.from_min) & (data < self.from_max) new_layer = orig_layer.copy() - new_layer.fill(self.fill_value, mask, 3) + new_layer.fill(self.fill_value, mask, 0) new_layer.name = self.new_layer_name \ or f"{orig_layer.name}_F-{self.from_min:.2f}-{self.from_max:.2f}" return new_layer @@ -431,7 +440,7 @@ class FillByLabel(bpy.types.Operator, FillOperator): bl_description = "Fill value by label" bl_icon = "MESH_CAPSULE" - smooth: bpy.props.IntProperty(name="Smooth Iteration", + smooth: bpy.props.IntProperty(name="Smooth Size", default=0, soft_min=0, soft_max=5, options={"SKIP_SAVE"}) # type: ignore @@ -451,7 +460,7 @@ def operate(self, orig_layer: Layer, context): mask = 1 - mask new_layer = orig_layer.copy() - new_layer.fill(self.fill_value, mask, 3) + new_layer.fill(self.fill_value, mask, 0) new_layer.name = self.new_layer_name \ or f"{orig_layer.name}_F-{label_layer.name}" return new_layer diff --git a/bioxelnodes/operators/node.py b/bioxelnodes/operators/node.py index f831e94..e0d1229 100644 --- a/bioxelnodes/operators/node.py +++ b/bioxelnodes/operators/node.py @@ -1,7 +1,7 @@ import bpy from ..bioxelutils.common import is_incompatible, local_lib_not_updated -from ..bioxelutils.node import assign_node_group, get_node_group +from ..bioxelutils.node import assign_node_tree, get_node_tree from ..utils import get_use_link @@ -41,14 +41,14 @@ def execute(self, context): "Current addon verison is not compatible to this file. If you insist on editing this file please keep the same addon version.") return {'CANCELLED'} - get_node_group(self.node_type, get_use_link()) + node_tree = get_node_tree(self.node_type, get_use_link()) bpy.ops.node.add_node( 'INVOKE_DEFAULT', type='GeometryNodeGroup', use_transform=True ) node = bpy.context.active_node - assign_node_group(node, self.node_type) + assign_node_tree(node, node_tree) node.label = self.node_label node.show_options = False diff --git a/pyproject.toml b/pyproject.toml index 697c612..435b468 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bioxelnodes" -version = "1.0.1" +version = "1.0.2" description = "" authors = ["Ma Nan "] license = "MIT"