Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21e0b85662 | ||
| 38fb888f00 | |||
|
|
2429eef5e0 | ||
| 2563f398da | |||
| 0cad35e2da | |||
| 65edf40164 | |||
| c3f88b7846 | |||
|
|
088d9c1edb | ||
| a6ec769a9b |
Binary file not shown.
@@ -3,6 +3,7 @@ import numpy as np
|
|||||||
|
|
||||||
from . import scipy
|
from . import scipy
|
||||||
from . import skimage as ski
|
from . import skimage as ski
|
||||||
|
from . import scipy as ndi
|
||||||
|
|
||||||
# 3rd-party
|
# 3rd-party
|
||||||
import transforms3d
|
import transforms3d
|
||||||
@@ -77,14 +78,18 @@ class Layer():
|
|||||||
raise Exception("Mask frame count is not same as ")
|
raise Exception("Mask frame count is not same as ")
|
||||||
for f in range(self.frame_count):
|
for f in range(self.frame_count):
|
||||||
mask_frame = mask[f, :, :, :]
|
mask_frame = mask[f, :, :, :]
|
||||||
mask_frame = scipy.median_filter(
|
mask_frame = scipy.minimum_filter(
|
||||||
mask_frame.astype(np.float32), size=2)
|
mask_frame.astype(np.float32), size=3)
|
||||||
|
# mask_frame = scipy.median_filter(
|
||||||
|
# mask_frame.astype(np.float32), size=2)
|
||||||
mask_frames += (mask_frame,)
|
mask_frames += (mask_frame,)
|
||||||
elif mask.ndim == 3:
|
elif mask.ndim == 3:
|
||||||
for f in range(self.frame_count):
|
for f in range(self.frame_count):
|
||||||
mask_frame = mask[:, :, :]
|
mask_frame = mask[:, :, :]
|
||||||
mask_frame = scipy.median_filter(
|
mask_frame = scipy.minimum_filter(
|
||||||
mask_frame.astype(np.float32), size=2)
|
mask_frame.astype(np.float32), size=3)
|
||||||
|
# mask_frame = scipy.median_filter(
|
||||||
|
# mask_frame.astype(np.float32), size=2)
|
||||||
mask_frames += (mask_frame,)
|
mask_frames += (mask_frame,)
|
||||||
else:
|
else:
|
||||||
raise Exception("Mask shape order should be TXYZ or XYZ")
|
raise Exception("Mask shape order should be TXYZ or XYZ")
|
||||||
@@ -93,36 +98,43 @@ class Layer():
|
|||||||
_mask = np.expand_dims(_mask, axis=-1)
|
_mask = np.expand_dims(_mask, axis=-1)
|
||||||
self.data = _mask * value + (1-_mask) * self.data
|
self.data = _mask * value + (1-_mask) * self.data
|
||||||
|
|
||||||
def resize(self, shape:tuple, progress_callback=None):
|
def resize(self, shape: tuple, progress_callback=None):
|
||||||
if len(shape) != 3:
|
if len(shape) != 3:
|
||||||
raise Exception("Shape must be 3 dim")
|
raise Exception("Shape must be 3 dim")
|
||||||
|
|
||||||
data = self.data
|
data = self.data
|
||||||
|
order = 0 if self.dtype == bool else 1
|
||||||
|
|
||||||
# TXYZC > TXYZ
|
# TXYZC > TXYZ
|
||||||
if self.kind in ['label', 'scalar']:
|
if self.kind in ['label', 'scalar']:
|
||||||
data = np.amax(data, -1)
|
data = np.amax(data, -1)
|
||||||
|
|
||||||
if self.kind in ['scalar']:
|
# if self.kind in ['scalar']:
|
||||||
dtype = data.dtype
|
# dtype = data.dtype
|
||||||
data = data.astype(np.float32)
|
# data = data.astype(np.float32)
|
||||||
|
|
||||||
data_frames = ()
|
data_frames = ()
|
||||||
for f in range(self.frame_count):
|
for f in range(self.frame_count):
|
||||||
if progress_callback:
|
if progress_callback:
|
||||||
progress_callback(f, self.frame_count)
|
progress_callback(f, self.frame_count)
|
||||||
|
|
||||||
frame = ski.resize(data[f, :, :, :],
|
# frame = ski.resize(data[f, :, :, :],
|
||||||
shape,
|
# shape,
|
||||||
preserve_range=True,
|
# preserve_range=True,
|
||||||
anti_aliasing=data.dtype.kind != "b")
|
# anti_aliasing=data.dtype.kind != "b")
|
||||||
|
|
||||||
|
factors = np.divide(self.shape, shape)
|
||||||
|
zoom_factors = [1 / f for f in factors]
|
||||||
|
frame = ndi.zoom(data[f, :, :, :],
|
||||||
|
zoom_factors,
|
||||||
|
order=order)
|
||||||
|
|
||||||
data_frames += (frame,)
|
data_frames += (frame,)
|
||||||
|
|
||||||
data = np.stack(data_frames)
|
data = np.stack(data_frames)
|
||||||
|
|
||||||
if self.kind in ['scalar']:
|
# if self.kind in ['scalar']:
|
||||||
data = data.astype(dtype)
|
# data = data.astype(dtype)
|
||||||
|
|
||||||
# TXYZ > TXYZC
|
# TXYZ > TXYZC
|
||||||
if self.kind in ['label', 'scalar']:
|
if self.kind in ['label', 'scalar']:
|
||||||
|
|||||||
@@ -14,22 +14,23 @@ Convert any volumetric data to 3D numpy array with order TXYZC
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
SUPPORT_EXTS = ['', '.dcm', '.DCM', '.DICOM', '.ima', '.IMA',
|
SUPPORT_EXTS = ['', '.dcm', '.DCM', '.DICOM', '.ima', '.IMA',
|
||||||
|
'.ome.tiff', '.ome.tif',
|
||||||
|
'.tif', '.TIF', '.tiff', '.TIFF',
|
||||||
|
'.mrc', '.mrc.gz', '.map', '.map.gz',
|
||||||
'.bmp', '.BMP',
|
'.bmp', '.BMP',
|
||||||
|
'.png', '.PNG',
|
||||||
|
'.jpg', '.JPG', '.jpeg', '.JPEG',
|
||||||
'.PIC', '.pic',
|
'.PIC', '.pic',
|
||||||
'.gipl', '.gipl.gz',
|
'.gipl', '.gipl.gz',
|
||||||
'.jpg', '.JPG', '.jpeg', '.JPEG',
|
|
||||||
'.lsm', '.LSM',
|
'.lsm', '.LSM',
|
||||||
'.tif', '.TIF', '.tiff', '.TIFF',
|
|
||||||
'.mnc', '.MNC',
|
'.mnc', '.MNC',
|
||||||
'.mrc', '.rec',
|
'.mrc', '.rec',
|
||||||
'.mha', '.mhd',
|
'.mha', '.mhd',
|
||||||
'.hdf', '.h4', '.hdf4', '.he2', '.h5', '.hdf5', '.he5',
|
'.hdf', '.h4', '.hdf4', '.he2', '.h5', '.hdf5', '.he5',
|
||||||
'.nia', '.nii', '.nii.gz', '.hdr', '.img', '.img.gz',
|
'.nia', '.nii', '.nii.gz', '.hdr', '.img', '.img.gz',
|
||||||
'.nrrd', '.nhdr',
|
'.nrrd', '.nhdr',
|
||||||
'.png', '.PNG',
|
|
||||||
'.vtk',
|
'.vtk',
|
||||||
'.ome.tiff', '.ome.tif',
|
'.gz']
|
||||||
'.mrc', '.mrc.gz', '.map', '.map.gz']
|
|
||||||
|
|
||||||
OME_EXTS = ['.ome.tiff', '.ome.tif',
|
OME_EXTS = ['.ome.tiff', '.ome.tif',
|
||||||
'.tif', '.TIF', '.tiff', '.TIFF']
|
'.tif', '.TIF', '.tiff', '.TIFF']
|
||||||
@@ -68,7 +69,7 @@ def get_file_name(filepath: Path):
|
|||||||
return filepath.name.removesuffix(ext).replace(" ", "-")
|
return filepath.name.removesuffix(ext).replace(" ", "-")
|
||||||
|
|
||||||
|
|
||||||
def get_file_index(filepath: Path):
|
def get_file_number(filepath: Path) -> str:
|
||||||
name = get_file_name(filepath)
|
name = get_file_name(filepath)
|
||||||
digits = ""
|
digits = ""
|
||||||
|
|
||||||
@@ -85,15 +86,18 @@ def get_file_index(filepath: Path):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Reverse the digits string to get the correct order
|
# Reverse the digits string to get the correct order
|
||||||
last_number = digits[::-1]
|
return digits[::-1]
|
||||||
|
|
||||||
return int(last_number) if last_number != "" else 0
|
|
||||||
|
|
||||||
|
|
||||||
def get_sequence_name(filepath: Path) -> str:
|
def get_sequence_name(filepath: Path) -> str:
|
||||||
name = get_file_name(filepath)
|
name = get_file_name(filepath)
|
||||||
index = get_file_index(filepath)
|
number = get_file_number(filepath)
|
||||||
return name.removesuffix(str(index))
|
return name.removesuffix(number)
|
||||||
|
|
||||||
|
|
||||||
|
def get_sequence_index(filepath: Path) -> int:
|
||||||
|
number = get_file_number(filepath)
|
||||||
|
return int(number) if number != "" else 0
|
||||||
|
|
||||||
|
|
||||||
def collect_sequence(filepath: Path):
|
def collect_sequence(filepath: Path):
|
||||||
@@ -102,9 +106,10 @@ def collect_sequence(filepath: Path):
|
|||||||
if f.is_file() \
|
if f.is_file() \
|
||||||
and get_ext(filepath) == get_ext(f) \
|
and get_ext(filepath) == get_ext(f) \
|
||||||
and get_sequence_name(filepath) == get_sequence_name(f):
|
and get_sequence_name(filepath) == get_sequence_name(f):
|
||||||
index = get_file_index(f)
|
index = get_sequence_index(f)
|
||||||
file_dict[index] = f
|
file_dict[index] = f
|
||||||
|
|
||||||
|
# reomve isolated seq file
|
||||||
for key in file_dict.copy().keys():
|
for key in file_dict.copy().keys():
|
||||||
if not file_dict.get(key+1) \
|
if not file_dict.get(key+1) \
|
||||||
and not file_dict.get(key-1):
|
and not file_dict.get(key-1):
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
from ._interpolation import zoom
|
from ._interpolation import zoom
|
||||||
from ._filters import gaussian_filter, median_filter
|
from ._filters import (gaussian_filter,
|
||||||
|
median_filter,
|
||||||
|
maximum_filter,
|
||||||
|
minimum_filter)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "bioxelnodes"
|
id = "bioxelnodes"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
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>"
|
||||||
@@ -20,7 +20,7 @@ wheels = [
|
|||||||
"./wheels/SimpleITK-2.3.1-cp311-cp311-macosx_11_0_arm64.whl",
|
"./wheels/SimpleITK-2.3.1-cp311-cp311-macosx_11_0_arm64.whl",
|
||||||
"./wheels/SimpleITK-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
"./wheels/SimpleITK-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
||||||
"./wheels/lxml-5.2.2-cp311-cp311-win_amd64.whl",
|
"./wheels/lxml-5.2.2-cp311-cp311-win_amd64.whl",
|
||||||
"./wheels/lxml-5.2.2-cp311-cp311-macosx_10_9_universal2.whl",
|
"./wheels/lxml-5.2.2-cp311-cp311-macosx_10_9_arm64.whl",
|
||||||
"./wheels/lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl",
|
"./wheels/lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl",
|
||||||
"./wheels/lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
"./wheels/lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
||||||
"./wheels/h5py-3.11.0-cp311-cp311-win_amd64.whl",
|
"./wheels/h5py-3.11.0-cp311-cp311-win_amd64.whl",
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
import bmesh
|
import bmesh
|
||||||
|
|
||||||
from ..nodes import custom_nodes
|
from ..nodes import custom_nodes
|
||||||
from ..bioxel.io import load_container, save_container
|
from ..bioxel.io import load_container, save_container
|
||||||
|
from ..bioxelutils.layer import get_all_layer_objs
|
||||||
from ..bioxelutils.container import (container_to_obj, obj_to_container,
|
from ..bioxelutils.container import (container_to_obj, obj_to_container,
|
||||||
get_container_objs_from_selection)
|
get_container_objs_from_selection)
|
||||||
from ..bioxelutils.node import get_nodes_by_type, move_node_between_nodes, move_node_to_node
|
from ..bioxelutils.node import get_nodes_by_type, move_node_between_nodes, move_node_to_node
|
||||||
from .utils import get_cache_dir, select_object
|
from .utils import change_render_setting, get_cache_dir, get_preferences, select_object
|
||||||
|
|
||||||
|
|
||||||
class SaveContainer(bpy.types.Operator):
|
class SaveContainer(bpy.types.Operator):
|
||||||
@@ -59,11 +61,16 @@ class LoadContainer(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
load_path = self.filepath
|
load_path = self.filepath
|
||||||
container = load_container(self.filepath)
|
container = load_container(self.filepath)
|
||||||
|
is_first_import = len(get_all_layer_objs()) == 0
|
||||||
|
|
||||||
container_obj = container_to_obj(container,
|
container_obj = container_to_obj(container,
|
||||||
scene_scale=0.01,
|
scene_scale=0.01,
|
||||||
cache_dir=get_cache_dir(context))
|
cache_dir=get_cache_dir(context))
|
||||||
select_object(container_obj)
|
select_object(container_obj)
|
||||||
|
|
||||||
|
if is_first_import:
|
||||||
|
change_render_setting(context)
|
||||||
|
|
||||||
self.report({"INFO"}, f"Successfully load {load_path}")
|
self.report({"INFO"}, f"Successfully load {load_path}")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|||||||
@@ -17,26 +17,13 @@ from ..bioxelutils.container import (Container,
|
|||||||
get_container_objs_from_selection)
|
get_container_objs_from_selection)
|
||||||
from ..bioxel.parse import (DICOM_EXTS, SUPPORT_EXTS,
|
from ..bioxel.parse import (DICOM_EXTS, SUPPORT_EXTS,
|
||||||
get_ext, parse_volumetric_data)
|
get_ext, parse_volumetric_data)
|
||||||
from .utils import (get_cache_dir, get_preferences,
|
from .utils import (change_render_setting, get_cache_dir, get_preferences,
|
||||||
progress_update, progress_bar, select_object)
|
progress_update, progress_bar, select_object)
|
||||||
|
|
||||||
# 3rd-party
|
# 3rd-party
|
||||||
import SimpleITK as sitk
|
import SimpleITK as sitk
|
||||||
import transforms3d
|
import transforms3d
|
||||||
|
|
||||||
FH_EXTS = ['', '.dcm', '.DCM', '.DICOM', '.ima', '.IMA',
|
|
||||||
'.gipl', '.gipl.gz',
|
|
||||||
'.mnc', '.MNC',
|
|
||||||
'.mrc', '.rec',
|
|
||||||
'.mha', '.mhd',
|
|
||||||
'.nia', '.nii', '.nii.gz', '.hdr', '.img', '.img.gz',
|
|
||||||
'.hdf', '.h4', '.hdf4', '.he2', '.h5', '.hdf5', '.he5',
|
|
||||||
'.nrrd', '.nhdr',
|
|
||||||
'.vtk',
|
|
||||||
'.gz'
|
|
||||||
'.ome.tiff', '.ome.tif',
|
|
||||||
'.mrc', '.mrc.gz', '.map', '.map.gz']
|
|
||||||
|
|
||||||
|
|
||||||
def get_layer_shape(bioxel_size: float, orig_shape: tuple, orig_spacing: tuple):
|
def get_layer_shape(bioxel_size: float, orig_shape: tuple, orig_spacing: tuple):
|
||||||
shape = (int(orig_shape[0] / bioxel_size * orig_spacing[0]),
|
shape = (int(orig_shape[0] / bioxel_size * orig_spacing[0]),
|
||||||
@@ -69,24 +56,20 @@ class ImportVolumetricData():
|
|||||||
bl_options = {'UNDO'}
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # type: ignore
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # type: ignore
|
||||||
directory: bpy.props.StringProperty(subtype='DIR_PATH') # type: ignore
|
|
||||||
|
|
||||||
read_as = "scalar"
|
read_as = "scalar"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
container_objs = get_container_objs_from_selection()
|
data_path = Path(self.filepath).resolve()
|
||||||
|
ext = get_ext(data_path)
|
||||||
|
if ext not in SUPPORT_EXTS:
|
||||||
|
self.report({"WARNING"}, "Not supported format.")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
if len(container_objs) > 0:
|
bpy.ops.bioxelnodes.parse_volumetric_data('INVOKE_DEFAULT',
|
||||||
bpy.ops.bioxelnodes.parse_volumetric_data('INVOKE_DEFAULT',
|
filepath=self.filepath,
|
||||||
filepath=self.filepath,
|
skip_read_as=True,
|
||||||
directory=self.directory,
|
read_as=self.read_as)
|
||||||
container_obj_name=container_objs[0].name,
|
|
||||||
read_as=self.read_as)
|
|
||||||
else:
|
|
||||||
bpy.ops.bioxelnodes.parse_volumetric_data('INVOKE_DEFAULT',
|
|
||||||
filepath=self.filepath,
|
|
||||||
directory=self.directory,
|
|
||||||
read_as=self.read_as)
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@@ -113,7 +96,7 @@ class BIOXELNODES_FH_ImportVolumetricData(bpy.types.FileHandler):
|
|||||||
bl_idname = "BIOXELNODES_FH_ImportVolumetricData"
|
bl_idname = "BIOXELNODES_FH_ImportVolumetricData"
|
||||||
bl_label = "File handler for dicom import"
|
bl_label = "File handler for dicom import"
|
||||||
bl_import_operator = "bioxelnodes.parse_volumetric_data"
|
bl_import_operator = "bioxelnodes.parse_volumetric_data"
|
||||||
bl_file_extensions = ";".join(FH_EXTS)
|
bl_file_extensions = ";".join(SUPPORT_EXTS)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll_drop(cls, context):
|
def poll_drop(cls, context):
|
||||||
@@ -135,21 +118,28 @@ def get_series_ids(self, context):
|
|||||||
|
|
||||||
class ParseVolumetricData(bpy.types.Operator):
|
class ParseVolumetricData(bpy.types.Operator):
|
||||||
bl_idname = "bioxelnodes.parse_volumetric_data"
|
bl_idname = "bioxelnodes.parse_volumetric_data"
|
||||||
bl_label = "Import Volumetric Data"
|
bl_label = "Import Volumetric Data (BioxelNodes)"
|
||||||
bl_description = "Import Volumetric Data as Layer"
|
bl_description = "Import Volumetric Data as Layer"
|
||||||
bl_options = {'UNDO'}
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
meta = None
|
meta = None
|
||||||
thread = None
|
thread = None
|
||||||
_timer = None
|
_timer = None
|
||||||
|
container_obj_name = ""
|
||||||
|
|
||||||
progress: bpy.props.FloatProperty(name="Progress",
|
progress: bpy.props.FloatProperty(name="Progress",
|
||||||
options={"SKIP_SAVE"},
|
options={"SKIP_SAVE"},
|
||||||
default=1) # type: ignore
|
default=1) # type: ignore
|
||||||
|
|
||||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # type: ignore
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # type: ignore
|
||||||
directory: bpy.props.StringProperty(subtype='DIR_PATH') # type: ignore
|
|
||||||
container_obj_name: bpy.props.StringProperty() # type: ignore
|
skip_read_as: bpy.props.BoolProperty(name="Skip Read As",
|
||||||
|
default=False,
|
||||||
|
options={"SKIP_SAVE"}) # type: ignore
|
||||||
|
|
||||||
|
skip_series_select: bpy.props.BoolProperty(name="Skip Sries Select",
|
||||||
|
default=True,
|
||||||
|
options={"SKIP_SAVE"}) # type: ignore
|
||||||
|
|
||||||
read_as: bpy.props.EnumProperty(name="Read as",
|
read_as: bpy.props.EnumProperty(name="Read as",
|
||||||
default="scalar",
|
default="scalar",
|
||||||
@@ -166,7 +156,7 @@ class ParseVolumetricData(bpy.types.Operator):
|
|||||||
data_path = Path(self.filepath).resolve()
|
data_path = Path(self.filepath).resolve()
|
||||||
ext = get_ext(data_path)
|
ext = get_ext(data_path)
|
||||||
if ext not in SUPPORT_EXTS:
|
if ext not in SUPPORT_EXTS:
|
||||||
self.report({"WARNING"}, "Not supported extension.")
|
self.report({"WARNING"}, "Not supported format.")
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
print("Collecting Meta Data...")
|
print("Collecting Meta Data...")
|
||||||
@@ -297,12 +287,19 @@ class ParseVolumetricData(bpy.types.Operator):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
if not self.filepath and not self.directory:
|
if not self.filepath:
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
data_path = Path(self.filepath).resolve()
|
data_path = Path(self.filepath).resolve()
|
||||||
ext = get_ext(data_path)
|
ext = get_ext(data_path)
|
||||||
|
|
||||||
|
container_objs = get_container_objs_from_selection()
|
||||||
|
if len(container_objs) > 0:
|
||||||
|
self.container_obj_name = container_objs[0].name
|
||||||
|
|
||||||
|
title = f"Add to **{self.container_obj_name}**" \
|
||||||
|
if self.container_obj_name != "" else f"Init a Container"
|
||||||
|
|
||||||
# Series Selection
|
# Series Selection
|
||||||
if ext in DICOM_EXTS:
|
if ext in DICOM_EXTS:
|
||||||
data_dirpath = data_path.parent
|
data_dirpath = data_path.parent
|
||||||
@@ -358,9 +355,10 @@ class ParseVolumetricData(bpy.types.Operator):
|
|||||||
series_item.label = value
|
series_item.label = value
|
||||||
|
|
||||||
if len(series_items.keys()) > 1:
|
if len(series_items.keys()) > 1:
|
||||||
|
self.skip_series_select = False
|
||||||
context.window_manager.invoke_props_dialog(self,
|
context.window_manager.invoke_props_dialog(self,
|
||||||
width=400,
|
width=400,
|
||||||
title="Which series to import?")
|
title=title)
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
elif len(series_items.keys()) == 1:
|
elif len(series_items.keys()) == 1:
|
||||||
self.series_id = list(series_items.keys())[0]
|
self.series_id = list(series_items.keys())[0]
|
||||||
@@ -368,27 +366,37 @@ class ParseVolumetricData(bpy.types.Operator):
|
|||||||
self.report({"ERROR"}, "Get no vaild series.")
|
self.report({"ERROR"}, "Get no vaild series.")
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
self.execute(context)
|
if self.skip_read_as:
|
||||||
return {'RUNNING_MODAL'}
|
self.execute(context)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
else:
|
||||||
|
context.window_manager.invoke_props_dialog(self,
|
||||||
|
width=400,
|
||||||
|
title=title)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.label(
|
if not self.skip_read_as:
|
||||||
text='Detect multi-series in DICOM, pick one')
|
layout.label(
|
||||||
layout.prop(self, "series_id")
|
text='What kind is the data read as?')
|
||||||
|
layout.prop(self, "read_as")
|
||||||
|
if not self.skip_series_select:
|
||||||
|
layout.label(text='Which series to import?')
|
||||||
|
layout.prop(self, "series_id")
|
||||||
|
|
||||||
|
|
||||||
def get_sequence_sources(self, context):
|
def get_sequence_sources(self, context):
|
||||||
items = [("-1", "None (Static)", "")]
|
items = [("-1", "None (1 frame)", "")]
|
||||||
orig_shape = tuple(self.orig_shape)
|
orig_shape = tuple(self.orig_shape)
|
||||||
if self.frame_count > 1:
|
if self.frame_count > 1:
|
||||||
items.append(("0", f"Frame (Get {self.frame_count} frames)", ""))
|
items.append(("0", f"Frame ({self.frame_count} frames)", ""))
|
||||||
elif self.frame_count == 1 and self.channel_count > 1:
|
elif self.frame_count == 1 and self.channel_count > 1:
|
||||||
items.append(("4", f"Channel (Get {self.channel_count} frames)", ""))
|
items.append(("4", f"Channel ({self.channel_count} frames)", ""))
|
||||||
elif self.frame_count == 1 and self.channel_count == 1:
|
elif self.frame_count == 1 and self.channel_count == 1:
|
||||||
items.append(("1", f"X (Get {orig_shape[0]} frames)", ""))
|
items.append(("1", f"X ({orig_shape[0]} frames)", ""))
|
||||||
items.append(("2", f"Y (Get {orig_shape[1]} frames)", ""))
|
items.append(("2", f"Y ({orig_shape[1]} frames)", ""))
|
||||||
items.append(("3", f"Z (Get {orig_shape[2]} frames)", ""))
|
items.append(("3", f"Z ({orig_shape[2]} frames)", ""))
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
@@ -674,37 +682,16 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
|
|||||||
select_object(container_obj)
|
select_object(container_obj)
|
||||||
|
|
||||||
# Change render setting for better result
|
# Change render setting for better result
|
||||||
preferences = get_preferences(context)
|
if is_first_import:
|
||||||
if preferences.do_change_render_setting and is_first_import:
|
change_render_setting(context)
|
||||||
bpy.context.scene.render.engine = 'CYCLES'
|
|
||||||
try:
|
|
||||||
bpy.context.scene.cycles.shading_system = True
|
|
||||||
bpy.context.scene.cycles.volume_bounces = 12
|
|
||||||
bpy.context.scene.cycles.transparent_max_bounces = 16
|
|
||||||
bpy.context.scene.cycles.volume_preview_step_rate = 10
|
|
||||||
bpy.context.scene.cycles.volume_step_rate = 10
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
bpy.context.scene.eevee.use_taa_reprojection = False
|
|
||||||
bpy.context.scene.eevee.volumetric_tile_size = '2'
|
|
||||||
bpy.context.scene.eevee.volumetric_shadow_samples = 128
|
|
||||||
bpy.context.scene.eevee.volumetric_samples = 256
|
|
||||||
bpy.context.scene.eevee.use_volumetric_shadows = True
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.report({"INFO"}, "Successfully Imported")
|
self.report({"INFO"}, "Successfully Imported")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
if self.read_as == "label":
|
layer_kind = self.read_as.capitalize()
|
||||||
volume_dtype = "Label"
|
title = f"Add to **{self.container_obj_name}**, As {layer_kind}" \
|
||||||
elif self.read_as == "scalar":
|
if self.container_obj_name != "" else f"Init a Container, As {layer_kind}"
|
||||||
volume_dtype = "Scalar"
|
|
||||||
title = f"As {volume_dtype} Opitons (Add to Container: {self.container_obj_name})" \
|
|
||||||
if self.container_obj_name != "" else f"As {volume_dtype} Options (Init a Container)"
|
|
||||||
context.window_manager.invoke_props_dialog(self,
|
context.window_manager.invoke_props_dialog(self,
|
||||||
width=500,
|
width=500,
|
||||||
title=title)
|
title=title)
|
||||||
|
|||||||
@@ -232,7 +232,6 @@ class CombineLabels(bpy.types.Operator, LabelOperator):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
base_obj = label_objs[0]
|
base_obj = label_objs[0]
|
||||||
label_objs = label_objs[1:]
|
label_objs = label_objs[1:]
|
||||||
container_obj = base_obj.parent
|
|
||||||
|
|
||||||
base_layer = obj_to_layer(base_obj)
|
base_layer = obj_to_layer(base_obj)
|
||||||
modified_layer = base_layer.copy()
|
modified_layer = base_layer.copy()
|
||||||
@@ -241,7 +240,7 @@ class CombineLabels(bpy.types.Operator, LabelOperator):
|
|||||||
for label_obj in label_objs:
|
for label_obj in label_objs:
|
||||||
label_layer = obj_to_layer(label_obj)
|
label_layer = obj_to_layer(label_obj)
|
||||||
label_layer.resize(base_layer.shape)
|
label_layer.resize(base_layer.shape)
|
||||||
modified_layer.data = np.maximum(base_layer.data, label_layer.data)
|
modified_layer.data = np.maximum(modified_layer.data, label_layer.data)
|
||||||
label_names.append(label_layer.name)
|
label_names.append(label_layer.name)
|
||||||
|
|
||||||
modified_layer.name = f"{'-'.join(label_names)}-Combined"
|
modified_layer.name = f"{'-'.join(label_names)}-Combined"
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ class SaveStagedData(bpy.types.Operator):
|
|||||||
bl_label = "Save Staged Data"
|
bl_label = "Save Staged Data"
|
||||||
bl_description = "Save all staged data in this file for sharing"
|
bl_description = "Save all staged data in this file for sharing"
|
||||||
|
|
||||||
save_layer: bpy.props.BoolProperty(
|
save_cache: bpy.props.BoolProperty(
|
||||||
name="Save Layer VDB Cache",
|
name="Save Layer Caches",
|
||||||
default=True,
|
default=True,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
@@ -112,11 +112,11 @@ class SaveStagedData(bpy.types.Operator):
|
|||||||
|
|
||||||
self.report({"INFO"}, f"Successfully saved to {output_path}")
|
self.report({"INFO"}, f"Successfully saved to {output_path}")
|
||||||
|
|
||||||
if self.save_layer:
|
if self.save_cache:
|
||||||
fails = []
|
fails = []
|
||||||
for layer in get_all_layer_objs():
|
for layer in get_all_layer_objs():
|
||||||
try:
|
try:
|
||||||
save_layer(layer, self.cache_dir)
|
save_layer_cache(layer, self.cache_dir)
|
||||||
except:
|
except:
|
||||||
fails.append(layer)
|
fails.append(layer)
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ class SaveStagedData(bpy.types.Operator):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
panel = layout.box()
|
panel = layout.box()
|
||||||
panel.prop(self, "save_layer")
|
panel.prop(self, "save_cache")
|
||||||
panel.prop(self, "cache_dir")
|
panel.prop(self, "cache_dir")
|
||||||
panel = layout.box()
|
panel = layout.box()
|
||||||
panel.prop(self, "save_lib")
|
panel.prop(self, "save_lib")
|
||||||
@@ -148,7 +148,7 @@ class SaveStagedData(bpy.types.Operator):
|
|||||||
|
|
||||||
|
|
||||||
class SaveCaches(bpy.types.Operator):
|
class SaveCaches(bpy.types.Operator):
|
||||||
bl_idname = "bioxelnodes.save_layers"
|
bl_idname = "bioxelnodes.save_caches"
|
||||||
bl_label = "Save Caches"
|
bl_label = "Save Caches"
|
||||||
bl_description = "Save Container's caches to directory."
|
bl_description = "Save Container's caches to directory."
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,29 @@ from pathlib import Path
|
|||||||
from .. import __package__ as base_package
|
from .. import __package__ as base_package
|
||||||
|
|
||||||
|
|
||||||
|
def change_render_setting(context):
|
||||||
|
preferences = get_preferences(context)
|
||||||
|
if preferences.do_change_render_setting:
|
||||||
|
bpy.context.scene.render.engine = 'CYCLES'
|
||||||
|
try:
|
||||||
|
bpy.context.scene.cycles.shading_system = True
|
||||||
|
bpy.context.scene.cycles.volume_bounces = 12
|
||||||
|
bpy.context.scene.cycles.transparent_max_bounces = 16
|
||||||
|
bpy.context.scene.cycles.volume_preview_step_rate = 10
|
||||||
|
bpy.context.scene.cycles.volume_step_rate = 10
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
bpy.context.scene.eevee.use_taa_reprojection = False
|
||||||
|
bpy.context.scene.eevee.volumetric_tile_size = '2'
|
||||||
|
bpy.context.scene.eevee.volumetric_shadow_samples = 128
|
||||||
|
bpy.context.scene.eevee.volumetric_samples = 256
|
||||||
|
bpy.context.scene.eevee.use_volumetric_shadows = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def select_object(target_obj):
|
def select_object(target_obj):
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.data.objects:
|
||||||
obj.select_set(False)
|
obj.select_set(False)
|
||||||
|
|||||||
23
build.py
23
build.py
@@ -53,6 +53,11 @@ def build_extension(platform: Platform, python_version="3.11") -> None:
|
|||||||
if any([package in f.name for package in packages_to_remove]):
|
if any([package in f.name for package in packages_to_remove]):
|
||||||
f.unlink(missing_ok=True)
|
f.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
elif platform.blender_tag == "macos-arm64" and \
|
||||||
|
"lxml" in f.name and "universal2" in f.name:
|
||||||
|
f.rename(Path(f.parent,
|
||||||
|
f.name.replace("universal2", "arm64")))
|
||||||
|
|
||||||
for ndimage_filepath in scipy_ndimage_dirpath.iterdir():
|
for ndimage_filepath in scipy_ndimage_dirpath.iterdir():
|
||||||
to_filepath = Path("./bioxelnodes/bioxel/scipy", ndimage_filepath.name)
|
to_filepath = Path("./bioxelnodes/bioxel/scipy", ndimage_filepath.name)
|
||||||
shutil.copy(ndimage_filepath, to_filepath)
|
shutil.copy(ndimage_filepath, to_filepath)
|
||||||
@@ -61,14 +66,18 @@ def build_extension(platform: Platform, python_version="3.11") -> None:
|
|||||||
with toml_filepath.open("r") as file:
|
with toml_filepath.open("r") as file:
|
||||||
manifest = tomlkit.parse(file.read())
|
manifest = tomlkit.parse(file.read())
|
||||||
|
|
||||||
build = tomlkit.table(True)
|
manifest["platforms"] = [platform.blender_tag]
|
||||||
generated = tomlkit.table()
|
manifest["wheels"] = [f"./wheels/{f.name}"
|
||||||
generated["platforms"] = [platform.blender_tag]
|
for f in wheel_dirpath.glob('*.whl')]
|
||||||
generated["wheels"] = [f"./wheels/{f.name}"
|
|
||||||
for f in wheel_dirpath.glob('*.whl')]
|
|
||||||
|
|
||||||
build.append('generated', generated)
|
# build = tomlkit.table(True)
|
||||||
manifest.append('build', build)
|
# generated = tomlkit.table()
|
||||||
|
# generated["platforms"] = [platform.blender_tag]
|
||||||
|
# generated["wheels"] = [f"./wheels/{f.name}"
|
||||||
|
# for f in wheel_dirpath.glob('*.whl')]
|
||||||
|
|
||||||
|
# build.append('generated', generated)
|
||||||
|
# manifest.append('build', build)
|
||||||
|
|
||||||
# Write the updated TOML file
|
# Write the updated TOML file
|
||||||
with toml_filepath.open("w") as file:
|
with toml_filepath.open("w") as file:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "bioxelnodes"
|
name = "bioxelnodes"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
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