Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92f85ff1f3 | ||
| 63c593f09a |
Binary file not shown.
@@ -77,7 +77,7 @@ class Layer():
|
|||||||
for f in range(self.frame_count):
|
for f in range(self.frame_count):
|
||||||
mask_frame = mask[f, :, :, :]
|
mask_frame = mask[f, :, :, :]
|
||||||
if smooth > 0:
|
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",
|
mode="nearest",
|
||||||
size=smooth)
|
size=smooth)
|
||||||
# mask_frame = scipy.median_filter(
|
# mask_frame = scipy.median_filter(
|
||||||
@@ -87,7 +87,7 @@ class Layer():
|
|||||||
for f in range(self.frame_count):
|
for f in range(self.frame_count):
|
||||||
mask_frame = mask[:, :, :]
|
mask_frame = mask[:, :, :]
|
||||||
if smooth > 0:
|
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",
|
mode="nearest",
|
||||||
size=smooth)
|
size=smooth)
|
||||||
# mask_frame = scipy.median_filter(
|
# mask_frame = scipy.median_filter(
|
||||||
@@ -119,10 +119,6 @@ class Layer():
|
|||||||
if progress_callback:
|
if progress_callback:
|
||||||
progress_callback(f, self.frame_count)
|
progress_callback(f, self.frame_count)
|
||||||
|
|
||||||
# frame = ski.resize(data[f, :, :, :],
|
|
||||||
# shape,
|
|
||||||
# preserve_range=True,
|
|
||||||
# anti_aliasing=data.dtype.kind != "b")
|
|
||||||
frame = data[f, :, :, :, :]
|
frame = data[f, :, :, :, :]
|
||||||
if smooth > 0:
|
if smooth > 0:
|
||||||
frame = scipy.median_filter(frame.astype(np.float32),
|
frame = scipy.median_filter(frame.astype(np.float32),
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ def local_lib_not_updated():
|
|||||||
not_update = get_node_version() != addon_version
|
not_update = get_node_version() != addon_version
|
||||||
return use_local and not_update
|
return use_local and not_update
|
||||||
|
|
||||||
|
|
||||||
def get_output_node(node_group):
|
def get_output_node(node_group):
|
||||||
try:
|
try:
|
||||||
output_node = get_nodes_by_type(node_group,
|
output_node = get_nodes_by_type(node_group,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from .common import get_file_prop, get_node_lib_path, set_file_prop
|
from .common import get_file_prop, set_file_prop
|
||||||
from ..exceptions import Incompatible, NoFound
|
from ..exceptions import NoFound
|
||||||
|
|
||||||
from ..constants import LATEST_NODE_LIB_PATH, VERSIONS
|
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.
|
# unannotate below for local debug in node lib file.
|
||||||
# node_group = bpy.data.node_groups[node_type]
|
# node_group = bpy.data.node_groups[node_type]
|
||||||
# return node_group
|
# 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:
|
if get_file_prop("node_version") is None:
|
||||||
set_file_prop("node_version", addon_version)
|
set_file_prop("node_version", addon_version)
|
||||||
|
|
||||||
local_lib_path = None
|
local_lib = None
|
||||||
for node_group in bpy.data.node_groups:
|
for node_group in bpy.data.node_groups:
|
||||||
if node_group.name.startswith("BioxelNodes"):
|
if node_group.name.startswith("BioxelNodes"):
|
||||||
lib = node_group.library
|
lib = node_group.library
|
||||||
if lib:
|
if lib:
|
||||||
lib_path = Path(bpy.path.abspath(lib.filepath)).resolve()
|
lib_path = Path(bpy.path.abspath(lib.filepath)).resolve()
|
||||||
if lib_path != addon_lib_path:
|
if lib_path != addon_lib_path:
|
||||||
local_lib_path = lib_path
|
local_lib = lib.filepath
|
||||||
break
|
break
|
||||||
|
|
||||||
# local lib first
|
# local lib first
|
||||||
lib_path = local_lib_path or addon_lib_path
|
lib_path_str = local_lib or str(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:
|
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')
|
raise NoFound('No custom node found')
|
||||||
|
|
||||||
return node_group
|
return node_tree
|
||||||
|
|
||||||
|
|
||||||
def assign_node_group(node, node_type: str):
|
def assign_node_tree(node, node_tree):
|
||||||
node.node_tree = bpy.data.node_groups[node_type]
|
node.node_tree = node_tree
|
||||||
node.width = 200.0
|
node.width = 200.0
|
||||||
node.name = node_type
|
node.name = node_tree.name
|
||||||
return node
|
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:
|
if node.select:
|
||||||
node.select = False
|
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")
|
node = node_group.nodes.new("GeometryNodeGroup")
|
||||||
assign_node_group(node, node_type)
|
assign_node_tree(node, node_tree)
|
||||||
|
|
||||||
node.label = node_label
|
node.label = node_label
|
||||||
node.show_options = False
|
node.show_options = False
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "bioxelnodes"
|
id = "bioxelnodes"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
name = "Bioxel Nodes"
|
name = "Bioxel Nodes"
|
||||||
tagline = "For scientific volumetric data visualization in Blender"
|
tagline = "For scientific volumetric data visualization in Blender"
|
||||||
maintainer = "Ma Nan <icrdr2010@outlook.com>"
|
maintainer = "Ma Nan <icrdr2010@outlook.com>"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from pathlib import Path
|
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.3.x", "node_version": (0, 3, 3)},
|
||||||
{"label": "v0.2.x", "node_version": (0, 2, 9)}]
|
{"label": "v0.2.x", "node_version": (0, 2, 9)}]
|
||||||
|
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ class AddCutter():
|
|||||||
if len(cut_nodes) == 0:
|
if len(cut_nodes) == 0:
|
||||||
cutter_node = add_node_to_graph("ObjectCutter",
|
cutter_node = add_node_to_graph("ObjectCutter",
|
||||||
node_group,
|
node_group,
|
||||||
node_label=name,
|
node_label=f"{self.cutter_type.capitalize()} Cutter",
|
||||||
use_link=get_use_link())
|
use_link=get_use_link())
|
||||||
cutter_node.inputs[0].default_value = self.cutter_type.capitalize()
|
cutter_node.inputs[0].default_value = self.cutter_type.capitalize()
|
||||||
cutter_node.inputs[1].default_value = cutter_obj
|
cutter_node.inputs[1].default_value = cutter_obj
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class ParseVolumetricData(bpy.types.Operator):
|
|||||||
read_as: bpy.props.EnumProperty(name="Read as",
|
read_as: bpy.props.EnumProperty(name="Read as",
|
||||||
default="SCALAR",
|
default="SCALAR",
|
||||||
items=[("SCALAR", "Scalar", ""),
|
items=[("SCALAR", "Scalar", ""),
|
||||||
("LABEL", "Labels", ""),
|
("LABEL", "Label", ""),
|
||||||
("COLOR", "Color", "")]) # type: ignore
|
("COLOR", "Color", "")]) # type: ignore
|
||||||
|
|
||||||
series_id: bpy.props.EnumProperty(name="Select Series",
|
series_id: bpy.props.EnumProperty(name="Select Series",
|
||||||
@@ -284,6 +284,10 @@ class ParseVolumetricData(bpy.types.Operator):
|
|||||||
self.report({"ERROR"}, "Invaild label data.")
|
self.report({"ERROR"}, "Invaild label data.")
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
if self.label_count == 0:
|
||||||
|
self.report({"ERROR"}, "Get no label.")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
orig_shape = self.meta['xyz_shape']
|
orig_shape = self.meta['xyz_shape']
|
||||||
orig_spacing = self.meta['spacing']
|
orig_spacing = self.meta['spacing']
|
||||||
|
|
||||||
@@ -418,7 +422,7 @@ class ParseVolumetricData(bpy.types.Operator):
|
|||||||
series_id = "empty"
|
series_id = "empty"
|
||||||
|
|
||||||
label = "{:<20} {:>1}".format(f"{study_description}>{series_description}({series_modality})",
|
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
|
series_items[series_id] = label
|
||||||
|
|
||||||
@@ -500,10 +504,13 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
|
|||||||
|
|
||||||
label_count: bpy.props.IntProperty() # type: ignore
|
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",
|
read_as: bpy.props.EnumProperty(name="Read as",
|
||||||
default="SCALAR",
|
default="SCALAR",
|
||||||
items=[("SCALAR", "Scalar", ""),
|
items=[("SCALAR", "Scalar", ""),
|
||||||
("LABEL", "Labels", ""),
|
("LABEL", "Label", ""),
|
||||||
("COLOR", "Color", "")]) # type: ignore
|
("COLOR", "Color", "")]) # type: ignore
|
||||||
|
|
||||||
bioxel_size: bpy.props.FloatProperty(name="Bioxel Size (Larger size means small resolution)",
|
bioxel_size: bpy.props.FloatProperty(name="Bioxel Size (Larger size means small resolution)",
|
||||||
@@ -616,12 +623,15 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
|
|||||||
progress_callback = progress_callback_factory(name_i,
|
progress_callback = progress_callback_factory(name_i,
|
||||||
progress,
|
progress,
|
||||||
progress_step)
|
progress_step)
|
||||||
|
label_data = data == np.full_like(data, i+1)
|
||||||
|
label_data = label_data.astype(np.float32)
|
||||||
try:
|
try:
|
||||||
layer = Layer(data=data == np.full_like(data, i+1),
|
layer = Layer(data=label_data,
|
||||||
name=name_i,
|
name=name_i,
|
||||||
kind=kind)
|
kind=kind)
|
||||||
|
|
||||||
layer.resize(shape=shape,
|
layer.resize(shape=shape,
|
||||||
|
smooth=self.smooth,
|
||||||
progress_callback=progress_callback)
|
progress_callback=progress_callback)
|
||||||
|
|
||||||
layer.affine = affine
|
layer.affine = affine
|
||||||
@@ -924,6 +934,8 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
|
|||||||
if self.read_as == "SCALAR":
|
if self.read_as == "SCALAR":
|
||||||
panel.prop(self, "split_channel",
|
panel.prop(self, "split_channel",
|
||||||
text=f"Split Channel as Multi Layer")
|
text=f"Split Channel as Multi Layer")
|
||||||
|
elif self.read_as == "LABEL":
|
||||||
|
panel.prop(self, "smooth")
|
||||||
|
|
||||||
panel.label(
|
panel.label(
|
||||||
text="Dimension Order: [Frame, (X-axis,Y-axis,Z-axis), Channel]")
|
text="Dimension Order: [Frame, (X-axis,Y-axis,Z-axis), Channel]")
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import bpy
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from ..exceptions import NoContent
|
from ..exceptions import NoContent
|
||||||
from ..bioxel.layer import Layer
|
from ..bioxel.layer import Layer
|
||||||
from ..bioxelutils.node import add_node_to_graph
|
from ..bioxelutils.node import add_node_to_graph
|
||||||
@@ -199,7 +198,7 @@ class RetimeLayer(bpy.types.Operator, LayerOperator):
|
|||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
|
|
||||||
class RelocateLayer(bpy.types.Operator, LayerOperator):
|
class RelocateLayer(bpy.types.Operator):
|
||||||
bl_idname = "bioxelnodes.relocate_layer"
|
bl_idname = "bioxelnodes.relocate_layer"
|
||||||
bl_label = "Relocate Layer Cache"
|
bl_label = "Relocate Layer Cache"
|
||||||
bl_description = "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
|
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):
|
def operate(self, layer_obj, context):
|
||||||
layer_obj.data.filepath = self.filepath
|
layer_obj.data.filepath = self.filepath
|
||||||
|
|
||||||
@@ -259,7 +268,7 @@ class ResampleLayer(bpy.types.Operator, OutputLayerOperator):
|
|||||||
bl_description = "Resample value"
|
bl_description = "Resample value"
|
||||||
bl_icon = "ALIASED"
|
bl_icon = "ALIASED"
|
||||||
|
|
||||||
smooth: bpy.props.IntProperty(name="Smooth Iteration",
|
smooth: bpy.props.IntProperty(name="Smooth Size",
|
||||||
default=0,
|
default=0,
|
||||||
soft_min=0, soft_max=5,
|
soft_min=0, soft_max=5,
|
||||||
options={"SKIP_SAVE"}) # type: ignore
|
options={"SKIP_SAVE"}) # type: ignore
|
||||||
@@ -389,7 +398,7 @@ class FillByThreshold(bpy.types.Operator, FillOperator):
|
|||||||
if self.invert else data > self.threshold
|
if self.invert else data > self.threshold
|
||||||
|
|
||||||
new_layer = orig_layer.copy()
|
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 \
|
new_layer.name = self.new_layer_name \
|
||||||
or f"{orig_layer.name}_F-{self.threshold:.2f}"
|
or f"{orig_layer.name}_F-{self.threshold:.2f}"
|
||||||
return new_layer
|
return new_layer
|
||||||
@@ -419,7 +428,7 @@ class FillByRange(bpy.types.Operator, FillOperator):
|
|||||||
(data > self.from_min) & (data < self.from_max)
|
(data > self.from_min) & (data < self.from_max)
|
||||||
|
|
||||||
new_layer = orig_layer.copy()
|
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 \
|
new_layer.name = self.new_layer_name \
|
||||||
or f"{orig_layer.name}_F-{self.from_min:.2f}-{self.from_max:.2f}"
|
or f"{orig_layer.name}_F-{self.from_min:.2f}-{self.from_max:.2f}"
|
||||||
return new_layer
|
return new_layer
|
||||||
@@ -431,7 +440,7 @@ class FillByLabel(bpy.types.Operator, FillOperator):
|
|||||||
bl_description = "Fill value by label"
|
bl_description = "Fill value by label"
|
||||||
bl_icon = "MESH_CAPSULE"
|
bl_icon = "MESH_CAPSULE"
|
||||||
|
|
||||||
smooth: bpy.props.IntProperty(name="Smooth Iteration",
|
smooth: bpy.props.IntProperty(name="Smooth Size",
|
||||||
default=0,
|
default=0,
|
||||||
soft_min=0, soft_max=5,
|
soft_min=0, soft_max=5,
|
||||||
options={"SKIP_SAVE"}) # type: ignore
|
options={"SKIP_SAVE"}) # type: ignore
|
||||||
@@ -451,7 +460,7 @@ class FillByLabel(bpy.types.Operator, FillOperator):
|
|||||||
mask = 1 - mask
|
mask = 1 - mask
|
||||||
|
|
||||||
new_layer = orig_layer.copy()
|
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 \
|
new_layer.name = self.new_layer_name \
|
||||||
or f"{orig_layer.name}_F-{label_layer.name}"
|
or f"{orig_layer.name}_F-{label_layer.name}"
|
||||||
return new_layer
|
return new_layer
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from ..bioxelutils.common import is_incompatible, local_lib_not_updated
|
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
|
from ..utils import get_use_link
|
||||||
|
|
||||||
|
|
||||||
@@ -41,14 +41,14 @@ class AddNode(bpy.types.Operator):
|
|||||||
"Current addon verison is not compatible to this file. If you insist on editing this file please keep the same addon version.")
|
"Current addon verison is not compatible to this file. If you insist on editing this file please keep the same addon version.")
|
||||||
return {'CANCELLED'}
|
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(
|
bpy.ops.node.add_node(
|
||||||
'INVOKE_DEFAULT',
|
'INVOKE_DEFAULT',
|
||||||
type='GeometryNodeGroup',
|
type='GeometryNodeGroup',
|
||||||
use_transform=True
|
use_transform=True
|
||||||
)
|
)
|
||||||
node = bpy.context.active_node
|
node = bpy.context.active_node
|
||||||
assign_node_group(node, self.node_type)
|
assign_node_tree(node, node_tree)
|
||||||
node.label = self.node_label
|
node.label = self.node_label
|
||||||
|
|
||||||
node.show_options = False
|
node.show_options = False
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "bioxelnodes"
|
name = "bioxelnodes"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Ma Nan <icrdr2010@outlook.com>"]
|
authors = ["Ma Nan <icrdr2010@outlook.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
Reference in New Issue
Block a user