feat: add more format support
This commit is contained in:
@@ -6,71 +6,114 @@ import numpy as np
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
import mathutils
|
||||
from .utils import calc_bbox_verts, extract_last_number, get_bioxels_obj, get_node_by_type, show_message
|
||||
import math
|
||||
from .utils import calc_bbox_verts, get_text_index, get_bioxels_obj, get_node_by_type, show_message
|
||||
from .nodes import custom_nodes
|
||||
from .props import BIOXELNODES_Series
|
||||
try:
|
||||
import SimpleITK as sitk
|
||||
except:
|
||||
...
|
||||
|
||||
SUPPORT_EXTS = ['.dcm', '.tif', '.tiff', '.png', '.bmp', '', '.jpg']
|
||||
SUPPORT_EXTS = ['.dcm', '.DICOM',
|
||||
'.bmp', '.BMP',
|
||||
'.PIC', '.pic',
|
||||
'.gipl', '.gipl.gz',
|
||||
'.jpg', '.JPG', '.jpeg', '.JPEG',
|
||||
'.lsm', '.LSM',
|
||||
'.tif', '.TIF', '.tiff', '.TIFF',
|
||||
'.mnc', '.MNC',
|
||||
'.mrc', '.rec',
|
||||
'.mha', '.mhd',
|
||||
'.hdf', '.h4', '.hdf4', '.he2', '.h5', '.hdf5', '.he5',
|
||||
'.nia', '.nii', '.nii.gz', '.hdr', '.img', '.img.gz',
|
||||
'.nrrd', '.nhdr',
|
||||
'.png', '.PNG',
|
||||
'.vtk']
|
||||
|
||||
SEQUENCE_EXTS = ['.dcm', '.DICOM',
|
||||
'.bmp', '.BMP',
|
||||
'.jpg', '.JPG', '.jpeg', '.JPEG',
|
||||
'.tif', '.TIF', '.tiff', '.TIFF',
|
||||
'.png', '.PNG']
|
||||
|
||||
DICOM_EXTS = ['.dcm', '.DICOM']
|
||||
|
||||
FH_EXTS = ['.dcm', '.DICOM',
|
||||
'.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']
|
||||
|
||||
|
||||
def get_data_files(filepath: str):
|
||||
def get_bioxels_shape(bioxel_size: float, orig_shape: tuple, orig_spacing: tuple):
|
||||
return (
|
||||
math.ceil(orig_shape[0] / bioxel_size * orig_spacing[0]),
|
||||
math.ceil(orig_shape[1] / bioxel_size * orig_spacing[1]),
|
||||
math.ceil(orig_shape[2] / bioxel_size * orig_spacing[2]),
|
||||
)
|
||||
|
||||
|
||||
def collect_image_sequence(filepath: str):
|
||||
file_path = Path(filepath).resolve()
|
||||
suffix = file_path.suffix
|
||||
|
||||
files = list(file_path.parent.iterdir())
|
||||
files = [f for f in files if f.is_file() and f.suffix == suffix]
|
||||
files = [f for f in files if f.is_file()
|
||||
and get_ext(file_path) == get_ext(f)
|
||||
and get_text_index(f.stem)]
|
||||
|
||||
def last_number(file: Path):
|
||||
return extract_last_number(file.stem)
|
||||
def get_index(f: Path):
|
||||
return get_text_index(f.stem)
|
||||
|
||||
files.sort(key=last_number)
|
||||
files = [str(f) for f in files]
|
||||
return files
|
||||
files.sort(key=get_index)
|
||||
sequence = [str(f) for f in files]
|
||||
return sequence
|
||||
|
||||
|
||||
def on_orig_spacing_changed(self, context):
|
||||
if self.auto:
|
||||
self.auto = False
|
||||
new_orig_spacing = tuple(self.orig_spacing)
|
||||
|
||||
orig_shape = tuple(self.orig_shape)
|
||||
bioxel_size = float(self.bioxel_size)
|
||||
|
||||
self.bioxels_shape = (
|
||||
int(orig_shape[0] / bioxel_size * new_orig_spacing[0]),
|
||||
int(orig_shape[1] / bioxel_size * new_orig_spacing[1]),
|
||||
int(orig_shape[2] / bioxel_size * new_orig_spacing[2]),
|
||||
)
|
||||
def get_ext(filepath: str):
|
||||
file_path = Path(filepath)
|
||||
if file_path.name.endswith(".nii.gz"):
|
||||
return ".nii.gz"
|
||||
elif file_path.name.endswith(".img.gz"):
|
||||
return ".img.gz"
|
||||
elif file_path.name.endswith(".gipl.gz"):
|
||||
return ".gipl.gz"
|
||||
else:
|
||||
self.auto = True
|
||||
return file_path.suffix
|
||||
|
||||
|
||||
def on_bioxel_size_changed(self, context):
|
||||
if self.auto:
|
||||
self.auto = False
|
||||
new_bioxel_size = float(self.bioxel_size)
|
||||
|
||||
orig_shape = tuple(self.orig_shape)
|
||||
orig_spacing = tuple(self.orig_spacing)
|
||||
|
||||
self.bioxels_shape = (
|
||||
int(orig_shape[0] / new_bioxel_size * orig_spacing[0]),
|
||||
int(orig_shape[1] / new_bioxel_size * orig_spacing[1]),
|
||||
int(orig_shape[2] / new_bioxel_size * orig_spacing[2]),
|
||||
)
|
||||
def read_image(filepath: str, series_id=""):
|
||||
ext = get_ext(filepath)
|
||||
dir_path = Path(filepath).resolve().parent
|
||||
filename = Path(filepath).resolve().stem
|
||||
if ext in DICOM_EXTS:
|
||||
reader = sitk.ImageSeriesReader()
|
||||
reader.MetaDataDictionaryArrayUpdateOn()
|
||||
reader.LoadPrivateTagsOn()
|
||||
series_files = reader.GetGDCMSeriesFileNames(
|
||||
str(dir_path), series_id)
|
||||
reader.SetFileNames(series_files)
|
||||
image = reader.Execute()
|
||||
elif ext in SEQUENCE_EXTS and get_text_index(filename):
|
||||
sequence = collect_image_sequence(filepath)
|
||||
image = sitk.ReadImage(sequence)
|
||||
else:
|
||||
self.auto = True
|
||||
image = sitk.ReadImage(filepath)
|
||||
if image.GetDimension() == 2:
|
||||
image = sitk.ReadImage([filepath])
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def on_info_changed(self, context):
|
||||
for region in context.area.regions:
|
||||
if region.type == "UI":
|
||||
region.tag_redraw()
|
||||
|
||||
|
||||
class ImportDICOMDialog(bpy.types.Operator):
|
||||
bl_idname = "bioxelnodes.import_dicom_dialog"
|
||||
class ImportImageDialog(bpy.types.Operator):
|
||||
bl_idname = "bioxelnodes.import_image_dialog"
|
||||
bl_label = "Volume Data as Bioxels"
|
||||
bl_description = "Import Volume Data as Bioxels (VDB)."
|
||||
bl_description = "Import Volume Data as Bioxels (VDB)"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
filepath: bpy.props.StringProperty(
|
||||
@@ -78,64 +121,62 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
options={'HIDDEN'}
|
||||
) # type: ignore
|
||||
|
||||
bioxels_shape: bpy.props.IntVectorProperty(
|
||||
name="Bioxels Shape (ReadOnly)",
|
||||
min=0,
|
||||
default=(100, 100, 100)
|
||||
series_id: bpy.props.StringProperty() # type: ignore
|
||||
|
||||
read_as: bpy.props.EnumProperty(
|
||||
name="Read as",
|
||||
default="scalar",
|
||||
items=[("scalar", "Scalar", ""), ("labels", "Labels", "")]
|
||||
) # type: ignore
|
||||
|
||||
label_index: bpy.props.IntProperty(
|
||||
name="Label Index",
|
||||
default=1
|
||||
) # type: ignore
|
||||
|
||||
bioxel_size: bpy.props.FloatProperty(
|
||||
name="Bioxel Size",
|
||||
name="Bioxel Size (Larger size means small resolution)",
|
||||
soft_min=0.1, soft_max=10.0,
|
||||
min=1e-2, max=1e2,
|
||||
default=1,
|
||||
update=on_bioxel_size_changed
|
||||
) # type: ignore
|
||||
|
||||
orig_spacing: bpy.props.FloatVectorProperty(
|
||||
name="Original Spacing",
|
||||
default=(1, 1, 1),
|
||||
update=on_orig_spacing_changed
|
||||
) # type: ignore
|
||||
|
||||
orig_shape: bpy.props.IntVectorProperty(
|
||||
name="Original Shape",
|
||||
default=(100, 100, 100),
|
||||
options={'HIDDEN'}
|
||||
) # type: ignore
|
||||
|
||||
auto: bpy.props.BoolProperty(
|
||||
name="Auto Setting",
|
||||
default=False,
|
||||
options={'HIDDEN'}
|
||||
default=(100, 100, 100)
|
||||
) # type: ignore
|
||||
|
||||
scene_scale: bpy.props.FloatProperty(
|
||||
name="Scene Scale",
|
||||
name="Scene Scale (Bioxels Unit pre Blender Unit)",
|
||||
soft_min=0.001, soft_max=100.0,
|
||||
min=1e-6, max=1e6,
|
||||
default=0.01,
|
||||
) # type: ignore
|
||||
|
||||
do_add_segmentnode: bpy.props.BoolProperty(
|
||||
name="Add Segment Node",
|
||||
do_orient: bpy.props.BoolProperty(
|
||||
name="Orient to RAS",
|
||||
default=True,
|
||||
) # type: ignore
|
||||
|
||||
do_change_render_setting: bpy.props.BoolProperty(
|
||||
name="Change Render Setting",
|
||||
default=True,
|
||||
invert_scalar: bpy.props.BoolProperty(
|
||||
name="Invert Scalar (Background value maybe higher than object)",
|
||||
default=False,
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
files = get_data_files(self.filepath)
|
||||
name = Path(self.filepath).parent.name
|
||||
bioxels_name = Path(self.filepath).parent.name \
|
||||
if get_ext(self.filepath) in SEQUENCE_EXTS \
|
||||
else Path(self.filepath).name.removesuffix(get_ext(self.filepath))
|
||||
|
||||
import SimpleITK as sitk
|
||||
image = sitk.ReadImage(files)
|
||||
image = read_image(self.filepath, self.series_id)
|
||||
|
||||
bioxel_size = float(self.bioxel_size)
|
||||
orig_spacing = tuple(self.orig_spacing)
|
||||
bioxel_size = self.bioxel_size
|
||||
orig_spacing = self.orig_spacing
|
||||
image_spacing = image.GetSpacing()
|
||||
image_shape = image.GetSize()
|
||||
|
||||
@@ -145,11 +186,8 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
image_spacing[2] / orig_spacing[2] * bioxel_size
|
||||
)
|
||||
|
||||
bioxels_shape = (
|
||||
int(image_shape[0] / bioxel_size * orig_spacing[0]),
|
||||
int(image_shape[1] / bioxel_size * orig_spacing[1]),
|
||||
int(image_shape[2] / bioxel_size * orig_spacing[2]),
|
||||
)
|
||||
bioxels_shape = get_bioxels_shape(
|
||||
bioxel_size, image_shape, orig_spacing)
|
||||
|
||||
print("Resampling...")
|
||||
image = sitk.Resample(
|
||||
@@ -164,13 +202,14 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
outputPixelType=image.GetPixelID(),
|
||||
)
|
||||
|
||||
print("Orienting to RAS...")
|
||||
image = sitk.DICOMOrient(image, 'RAS')
|
||||
if self.do_orient:
|
||||
print("Orienting to RAS...")
|
||||
image = sitk.DICOMOrient(image, 'RAS')
|
||||
|
||||
array = sitk.GetArrayFromImage(image)
|
||||
orig_dtype = str(array.dtype)
|
||||
print(f"Coverting Dtype from {orig_dtype} to float...")
|
||||
array = array.astype(float)
|
||||
print("Oriented Origin:", image.GetOrigin())
|
||||
print("Oriented Direction:", image.GetDirection())
|
||||
|
||||
# return {'FINISHED'}
|
||||
|
||||
# ITK indices, by convention, are [i,j,k] while NumPy indices are [k,j,i]
|
||||
# https://www.slicer.org/wiki/Coordinate_systems
|
||||
@@ -179,27 +218,62 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
# R (ight) i -> k -> x
|
||||
# A (nterior) j -> j -> y
|
||||
# S (uperior) k -> i -> z
|
||||
|
||||
array = sitk.GetArrayFromImage(image)
|
||||
array = np.transpose(array)
|
||||
bioxels_max = float(np.max(array))
|
||||
bioxels_min = float(np.min(array))
|
||||
|
||||
orig_dtype = str(array.dtype)
|
||||
# print(f"Coverting Dtype from {orig_dtype} to float...")
|
||||
# array = array.astype(float)
|
||||
if array.ndim == 4:
|
||||
if array.shape[3] == 3 and self.read_as == "scalar":
|
||||
# RGB -> Grayscale
|
||||
array = np.dot(array[..., :3], [0.2989, 0.5870, 0.1140])
|
||||
else:
|
||||
array = array[:, :, :, 0]
|
||||
|
||||
bioxels_shape = array.shape
|
||||
|
||||
print("Bioxel Size:", bioxel_size)
|
||||
print("Bioxels Shape:", bioxels_shape)
|
||||
|
||||
bioxels_offset = 0.0
|
||||
if bioxels_min < 0 and orig_dtype[0] != "u":
|
||||
bioxels_offset = -bioxels_min
|
||||
array = array + np.full_like(array, bioxels_offset)
|
||||
|
||||
if self.read_as == "labels":
|
||||
array = array == np.full_like(array, self.label_index)
|
||||
bioxels_default_threshold = 1.0
|
||||
bioxels_max = 1.0
|
||||
bioxels_min = 0.0
|
||||
bioxels_name = f"{bioxels_name}_{self.label_index}"
|
||||
else:
|
||||
if self.invert_scalar:
|
||||
array = -array
|
||||
|
||||
orig_max = float(np.max(array))
|
||||
orig_min = float(np.min(array))
|
||||
orig_median = float(np.median(array))
|
||||
orig_percentile80 = float(np.percentile(array, 80)) \
|
||||
if self.invert_scalar else float(np.percentile(array, 80))
|
||||
|
||||
# if (orig_dtype[0] != "u" and orig_min < 0) \
|
||||
# or (orig_dtype[0] == "u" and self.invert_scalar):
|
||||
if orig_min < 0:
|
||||
bioxels_offset = -orig_min
|
||||
array = array + np.full_like(array, bioxels_offset)
|
||||
|
||||
bioxels_default_threshold = orig_percentile80
|
||||
bioxels_max = float(np.max(array))
|
||||
bioxels_min = float(np.min(array))
|
||||
print("Offseted Max:", bioxels_max)
|
||||
print("Offseted Min:", bioxels_min)
|
||||
|
||||
# Build VDB
|
||||
grid = vdb.FloatGrid()
|
||||
grid.copyFromArray(array.copy())
|
||||
stat_table = [("Max", orig_max),
|
||||
("Min", orig_min),
|
||||
("Median", orig_median),
|
||||
("Percentile 80%", orig_percentile80),
|
||||
("Max (Offset)", bioxels_max),
|
||||
("Min (Offset)", bioxels_min)]
|
||||
|
||||
print("Bioxels Value Stat: Max:")
|
||||
for stat in stat_table:
|
||||
print("| {: >20} | {: >20} |".format(*stat))
|
||||
|
||||
# # Build VDB
|
||||
grid = vdb.BoolGrid() if self.read_as == "labels" else vdb.FloatGrid()
|
||||
grid.copyFromArray(array.copy().astype(float))
|
||||
|
||||
# After sitk.DICOMOrient(), origin and direction will also orient base on LPS
|
||||
# so we need to convert them into RAS
|
||||
@@ -210,22 +284,26 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
to_up='Y'
|
||||
).to_4x4()
|
||||
|
||||
origin = image.GetOrigin()
|
||||
direction = image.GetDirection()
|
||||
|
||||
mat_location = mathutils.Matrix.Translation(
|
||||
mathutils.Vector(image.GetOrigin())
|
||||
mathutils.Vector(origin)
|
||||
)
|
||||
|
||||
mat_rotation = mathutils.Matrix(
|
||||
np.array(image.GetDirection()).reshape((3, 3))
|
||||
np.array(direction).reshape((3, 3))
|
||||
).to_4x4()
|
||||
|
||||
mat_scale = mathutils.Matrix.Scale(
|
||||
bioxel_size, 4
|
||||
)
|
||||
|
||||
transfrom = mat_lps2ras @ mat_location @ mat_rotation @ mat_scale
|
||||
transfrom = mat_lps2ras @ mat_location @ mat_rotation @ mat_scale \
|
||||
if self.do_orient else mat_location @ mat_rotation @ mat_scale
|
||||
|
||||
grid.transform = vdb.createLinearTransform(transfrom.transposed())
|
||||
grid.name = "value"
|
||||
grid.name = "scalar"
|
||||
|
||||
preferences = context.preferences.addons[__package__].preferences
|
||||
vdb_dirpath = Path(preferences.cache_dir, 'VDBs')
|
||||
@@ -243,8 +321,8 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
bioxels_obj = bpy.context.active_object
|
||||
|
||||
# Set props to VDB object
|
||||
bioxels_obj.name = f"{name}_Bioxels"
|
||||
bioxels_obj.data.name = f"{name}_Bioxels"
|
||||
bioxels_obj.name = f"{bioxels_name}_Bioxels"
|
||||
bioxels_obj.data.name = f"{bioxels_name}_Bioxels"
|
||||
|
||||
# Make transformation
|
||||
scene_scale = float(self.scene_scale)
|
||||
@@ -289,8 +367,8 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
vert.co = bbox_transform @ mathutils.Vector(bbox_verts[index])
|
||||
|
||||
bioxels_obj.parent = container_obj
|
||||
container_obj.name = name
|
||||
container_obj.data.name = name
|
||||
container_obj.name = bioxels_name
|
||||
container_obj.data.name = bioxels_name
|
||||
container_obj.visible_camera = False
|
||||
container_obj.visible_diffuse = False
|
||||
container_obj.visible_glossy = False
|
||||
@@ -315,7 +393,7 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
# Create BioxelNodes to VDB object
|
||||
print(f"Creating BioxelNodes to the VDB...")
|
||||
|
||||
if self.do_add_segmentnode:
|
||||
if preferences.do_add_segmentnode:
|
||||
nodes = node_tree.nodes
|
||||
links = node_tree.links
|
||||
|
||||
@@ -324,11 +402,14 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
|
||||
segment_node = custom_nodes.add_node(
|
||||
nodes, 'BioxelNodes_Segment')
|
||||
|
||||
segment_node.inputs['Threshold'].default_value = bioxels_default_threshold
|
||||
|
||||
links.new(input_node.outputs[0], segment_node.inputs[0])
|
||||
links.new(segment_node.outputs[0], output_node.inputs[0])
|
||||
|
||||
# Change render setting for better result
|
||||
if self.do_change_render_setting:
|
||||
if preferences.do_change_render_setting:
|
||||
bpy.context.scene.render.engine = 'CYCLES'
|
||||
bpy.context.scene.cycles.volume_bounces = 12
|
||||
bpy.context.scene.cycles.transparent_max_bounces = 64
|
||||
@@ -338,37 +419,78 @@ class ImportDICOMDialog(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
# print(tuple(self.orig_shape))
|
||||
self.auto = True
|
||||
self.bioxel_size = 1
|
||||
context.window_manager.invoke_props_dialog(self)
|
||||
min_size = min(
|
||||
self.orig_spacing[0], self.orig_spacing[1], self.orig_spacing[2])
|
||||
default_size = 1.0
|
||||
self.bioxel_size = min_size if min_size > default_size else default_size
|
||||
context.window_manager.invoke_props_dialog(self, width=400)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
bioxels_shape = get_bioxels_shape(
|
||||
self.bioxel_size, self.orig_shape, self.orig_spacing)
|
||||
bioxel_count = bioxels_shape[0] * bioxels_shape[1] * bioxels_shape[2]
|
||||
text = f"Shape will be: {str(bioxels_shape)} {bioxel_count:,} "
|
||||
if bioxel_count > 100000000:
|
||||
text += "**TOO LARGE!**"
|
||||
|
||||
panel = layout.box()
|
||||
panel.prop(self, "bioxel_size")
|
||||
row = panel.row()
|
||||
row.prop(self, "orig_spacing")
|
||||
panel.label(text=text)
|
||||
|
||||
panel = layout.box()
|
||||
panel.prop(self, "read_as")
|
||||
if self.read_as == "labels":
|
||||
panel.prop(self, "label_index")
|
||||
else:
|
||||
panel.prop(self, "invert_scalar")
|
||||
|
||||
panel = layout.box()
|
||||
panel.prop(self, "scene_scale")
|
||||
panel.prop(self, "do_orient")
|
||||
|
||||
|
||||
def get_series_ids(self, context):
|
||||
items = []
|
||||
for index, series_id in enumerate(self.series_ids):
|
||||
items.append((
|
||||
series_id.id,
|
||||
series_id.label,
|
||||
"",
|
||||
index
|
||||
))
|
||||
|
||||
return items
|
||||
|
||||
|
||||
class ReadDICOM(bpy.types.Operator):
|
||||
bl_idname = "bioxelnodes.read_dicom"
|
||||
bl_label = "Volume Data as Bioxels"
|
||||
bl_description = "Import Volume Data as Bioxels (VDB)."
|
||||
bl_description = "Import Volume Data as Bioxels (VDB)"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
filepath: bpy.props.StringProperty(
|
||||
subtype="FILE_PATH"
|
||||
) # type: ignore
|
||||
|
||||
ok: bpy.props.BoolProperty(
|
||||
default=True
|
||||
series_id: bpy.props.EnumProperty(
|
||||
name="Select Series",
|
||||
items=get_series_ids
|
||||
) # type: ignore
|
||||
|
||||
series_ids: bpy.props.CollectionProperty(
|
||||
type=BIOXELNODES_Series) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
if Path(self.filepath).suffix not in SUPPORT_EXTS:
|
||||
self.report({"WARNING"}, "Not Supported extension.")
|
||||
ext = get_ext(self.filepath)
|
||||
if ext not in SUPPORT_EXTS:
|
||||
self.report({"WARNING"}, "Not supported extension.")
|
||||
return {'CANCELLED'}
|
||||
|
||||
files = get_data_files(self.filepath)
|
||||
|
||||
import SimpleITK as sitk
|
||||
|
||||
image = sitk.ReadImage(files)
|
||||
image = read_image(self.filepath, self.series_id)
|
||||
|
||||
print("Collecting Meta Data...")
|
||||
print("Original Shape:", image.GetSize())
|
||||
@@ -376,11 +498,12 @@ class ReadDICOM(bpy.types.Operator):
|
||||
print("Original Origin:", image.GetOrigin())
|
||||
print("Original Direction:", image.GetDirection())
|
||||
|
||||
bpy.ops.bioxelnodes.import_dicom_dialog(
|
||||
bpy.ops.bioxelnodes.import_image_dialog(
|
||||
'INVOKE_DEFAULT',
|
||||
filepath=self.filepath,
|
||||
orig_shape=image.GetSize(),
|
||||
orig_spacing=image.GetSpacing()
|
||||
orig_spacing=image.GetSpacing(),
|
||||
series_id=self.series_id or ""
|
||||
)
|
||||
|
||||
self.report({"INFO"}, "Successfully Readed.")
|
||||
@@ -395,16 +518,43 @@ class ReadDICOM(bpy.types.Operator):
|
||||
if not self.filepath:
|
||||
return {'CANCELLED'}
|
||||
|
||||
show_message('Reading image data, it may take a while',
|
||||
show_message('Reading image data, it may take a while...',
|
||||
'Please be patient...')
|
||||
|
||||
if get_ext(self.filepath) == '.dcm':
|
||||
dir_path = Path(self.filepath).parent
|
||||
reader = sitk.ImageSeriesReader()
|
||||
reader.MetaDataDictionaryArrayUpdateOn()
|
||||
reader.LoadPrivateTagsOn()
|
||||
series_ids = reader.GetGDCMSeriesIDs(str(dir_path))
|
||||
|
||||
for _id in series_ids:
|
||||
series_id = self.series_ids.add()
|
||||
series_id.id = _id
|
||||
series_id.label = _id
|
||||
|
||||
if len(series_ids) > 1:
|
||||
context.window_manager.invoke_props_dialog(self, width=400)
|
||||
return {'RUNNING_MODAL'}
|
||||
else:
|
||||
self.series_id = series_ids[0]
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "series_id")
|
||||
layout.label(
|
||||
text="Reading image data, it may take a while...")
|
||||
layout.label(
|
||||
text="Please be patient...")
|
||||
|
||||
class ImportDICOM(bpy.types.Operator):
|
||||
bl_idname = "bioxelnodes.import_dicom"
|
||||
|
||||
class ImportImage(bpy.types.Operator):
|
||||
bl_idname = "bioxelnodes.import_image"
|
||||
bl_label = "Volume Data as Bioxels"
|
||||
bl_description = "Import Volume Data as Bioxels (VDB)."
|
||||
bl_description = "Import Volume Data as Bioxels (VDB)"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
filepath: bpy.props.StringProperty(
|
||||
@@ -423,21 +573,24 @@ class ImportDICOM(bpy.types.Operator):
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
class BIOXELNODES_FH_ImportDicom(bpy.types.FileHandler):
|
||||
bl_idname = "BIOXELNODES_FH_ImportDicom"
|
||||
bl_label = "File handler for dicom import"
|
||||
bl_import_operator = "bioxelnodes.read_dicom"
|
||||
bl_file_extensions = ".dcm"
|
||||
try:
|
||||
class BIOXELNODES_FH_ImportImage(bpy.types.FileHandler):
|
||||
bl_idname = "BIOXELNODES_FH_ImportImage"
|
||||
bl_label = "File handler for dicom import"
|
||||
bl_import_operator = "bioxelnodes.read_dicom"
|
||||
bl_file_extensions = ";".join(FH_EXTS)
|
||||
|
||||
@classmethod
|
||||
def poll_drop(cls, context):
|
||||
return (context.area and context.area.type == 'VIEW_3D')
|
||||
@classmethod
|
||||
def poll_drop(cls, context):
|
||||
return (context.area and context.area.type == 'VIEW_3D')
|
||||
except:
|
||||
...
|
||||
|
||||
|
||||
class ExportVDB(bpy.types.Operator):
|
||||
bl_idname = "bioxelnodes.export_vdb"
|
||||
bl_label = "Bioxels as VDB"
|
||||
bl_description = "Export Bioxels original VDB data."
|
||||
bl_description = "Export Bioxels original VDB data"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
filepath: bpy.props.StringProperty(
|
||||
|
||||
Reference in New Issue
Block a user