Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/dev #36

Merged
merged 4 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions bioxelnodes/bioxel/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,23 @@
"""

SUPPORT_EXTS = ['', '.dcm', '.DCM', '.DICOM', '.ima', '.IMA',
'.ome.tiff', '.ome.tif',
'.tif', '.TIF', '.tiff', '.TIFF',
'.mrc', '.mrc.gz', '.map', '.map.gz',
'.bmp', '.BMP',
'.png', '.PNG',
'.jpg', '.JPG', '.jpeg', '.JPEG',
'.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',
'.ome.tiff', '.ome.tif',
'.mrc', '.mrc.gz', '.map', '.map.gz']
'.gz']

OME_EXTS = ['.ome.tiff', '.ome.tif',
'.tif', '.TIF', '.tiff', '.TIFF']
Expand Down Expand Up @@ -68,7 +69,7 @@ def get_file_name(filepath: Path):
return filepath.name.removesuffix(ext).replace(" ", "-")


def get_file_index(filepath: Path):
def get_file_number(filepath: Path) -> str:
name = get_file_name(filepath)
digits = ""

Expand All @@ -85,15 +86,18 @@ def get_file_index(filepath: Path):
break

# Reverse the digits string to get the correct order
last_number = digits[::-1]

return int(last_number) if last_number != "" else 0
return digits[::-1]


def get_sequence_name(filepath: Path) -> str:
name = get_file_name(filepath)
index = get_file_index(filepath)
return name.removesuffix(str(index))
number = get_file_number(filepath)
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):
Expand All @@ -102,9 +106,10 @@ def collect_sequence(filepath: Path):
if f.is_file() \
and get_ext(filepath) == get_ext(f) \
and get_sequence_name(filepath) == get_sequence_name(f):
index = get_file_index(f)
index = get_sequence_index(f)
file_dict[index] = f

# reomve isolated seq file
for key in file_dict.copy().keys():
if not file_dict.get(key+1) \
and not file_dict.get(key-1):
Expand Down
2 changes: 1 addition & 1 deletion bioxelnodes/blender_manifest.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
schema_version = "1.0.0"

id = "bioxelnodes"
version = "0.3.1"
version = "0.3.2"
name = "Bioxel Nodes"
tagline = "For scientific volumetric data visualization in Blender"
maintainer = "Ma Nan <[email protected]>"
Expand Down
103 changes: 53 additions & 50 deletions bioxelnodes/operators/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,6 @@
import SimpleITK as sitk
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):
shape = (int(orig_shape[0] / bioxel_size * orig_spacing[0]),
Expand Down Expand Up @@ -69,24 +56,20 @@ class ImportVolumetricData():
bl_options = {'UNDO'}

filepath: bpy.props.StringProperty(subtype="FILE_PATH") # type: ignore
directory: bpy.props.StringProperty(subtype='DIR_PATH') # type: ignore

read_as = "scalar"

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',
filepath=self.filepath,
directory=self.directory,
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)
bpy.ops.bioxelnodes.parse_volumetric_data('INVOKE_DEFAULT',
filepath=self.filepath,
skip_read_as=True,
read_as=self.read_as)

return {'FINISHED'}

Expand All @@ -113,7 +96,7 @@ class BIOXELNODES_FH_ImportVolumetricData(bpy.types.FileHandler):
bl_idname = "BIOXELNODES_FH_ImportVolumetricData"
bl_label = "File handler for dicom import"
bl_import_operator = "bioxelnodes.parse_volumetric_data"
bl_file_extensions = ";".join(FH_EXTS)
bl_file_extensions = ";".join(SUPPORT_EXTS)

@classmethod
def poll_drop(cls, context):
Expand All @@ -135,21 +118,26 @@ def get_series_ids(self, context):

class ParseVolumetricData(bpy.types.Operator):
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_options = {'UNDO'}

meta = None
thread = None
_timer = None
container_obj_name = ""

progress: bpy.props.FloatProperty(name="Progress",
options={"SKIP_SAVE"},
default=1) # 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) # type: ignore

skip_series_select: bpy.props.BoolProperty(name="Skip Sries Select",
default=True) # type: ignore

read_as: bpy.props.EnumProperty(name="Read as",
default="scalar",
Expand All @@ -166,7 +154,7 @@ def execute(self, context):
data_path = Path(self.filepath).resolve()
ext = get_ext(data_path)
if ext not in SUPPORT_EXTS:
self.report({"WARNING"}, "Not supported extension.")
self.report({"WARNING"}, "Not supported format.")
return {'CANCELLED'}

print("Collecting Meta Data...")
Expand Down Expand Up @@ -297,12 +285,19 @@ def modal(self, context, event):
return {'FINISHED'}

def invoke(self, context, event):
if not self.filepath and not self.directory:
if not self.filepath:
return {'CANCELLED'}

data_path = Path(self.filepath).resolve()
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
if ext in DICOM_EXTS:
data_dirpath = data_path.parent
Expand Down Expand Up @@ -358,37 +353,48 @@ def get_meta(key):
series_item.label = value

if len(series_items.keys()) > 1:
self.skip_series_select = False
context.window_manager.invoke_props_dialog(self,
width=400,
title="Which series to import?")
title=title)
return {'RUNNING_MODAL'}
elif len(series_items.keys()) == 1:
self.series_id = list(series_items.keys())[0]
else:
self.report({"ERROR"}, "Get no vaild series.")
return {'CANCELLED'}

self.execute(context)
return {'RUNNING_MODAL'}
if self.skip_read_as:
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):
layout = self.layout
layout.label(
text='Detect multi-series in DICOM, pick one')
layout.prop(self, "series_id")
if not self.skip_read_as:
layout.label(
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):
items = [("-1", "None (Static)", "")]
items = [("-1", "None (1 frame)", "")]
orig_shape = tuple(self.orig_shape)
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:
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:
items.append(("1", f"X (Get {orig_shape[0]} frames)", ""))
items.append(("2", f"Y (Get {orig_shape[1]} frames)", ""))
items.append(("3", f"Z (Get {orig_shape[2]} frames)", ""))
items.append(("1", f"X ({orig_shape[0]} frames)", ""))
items.append(("2", f"Y ({orig_shape[1]} frames)", ""))
items.append(("3", f"Z ({orig_shape[2]} frames)", ""))

return items

Expand Down Expand Up @@ -681,12 +687,9 @@ def modal(self, context, event):
return {'FINISHED'}

def invoke(self, context, event):
if self.read_as == "label":
volume_dtype = "Label"
elif self.read_as == "scalar":
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)"
layer_kind = self.read_as.capitalize()
title = f"Add to **{self.container_obj_name}**, As {layer_kind}" \
if self.container_obj_name != "" else f"Init a Container, As {layer_kind}"
context.window_manager.invoke_props_dialog(self,
width=500,
title=title)
Expand Down
12 changes: 6 additions & 6 deletions bioxelnodes/operators/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ class SaveStagedData(bpy.types.Operator):
bl_label = "Save Staged Data"
bl_description = "Save all staged data in this file for sharing"

save_layer: bpy.props.BoolProperty(
name="Save Layer VDB Cache",
save_cache: bpy.props.BoolProperty(
name="Save Layer Caches",
default=True,
) # type: ignore

Expand Down Expand Up @@ -112,11 +112,11 @@ def execute(self, context):

self.report({"INFO"}, f"Successfully saved to {output_path}")

if self.save_layer:
if self.save_cache:
fails = []
for layer in get_all_layer_objs():
try:
save_layer(layer, self.cache_dir)
save_layer_cache(layer, self.cache_dir)
except:
fails.append(layer)

Expand All @@ -140,15 +140,15 @@ def poll(cls, context):
def draw(self, context):
layout = self.layout
panel = layout.box()
panel.prop(self, "save_layer")
panel.prop(self, "save_cache")
panel.prop(self, "cache_dir")
panel = layout.box()
panel.prop(self, "save_lib")
panel.prop(self, "lib_dir")


class SaveCaches(bpy.types.Operator):
bl_idname = "bioxelnodes.save_layers"
bl_idname = "bioxelnodes.save_caches"
bl_label = "Save Caches"
bl_description = "Save Container's caches to directory."

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "bioxelnodes"
version = "0.3.1"
version = "0.3.2"
description = ""
authors = ["Ma Nan <[email protected]>"]
license = "MIT"
Expand Down
Loading