fix some import issue

This commit is contained in:
2024-07-23 12:04:30 +08:00
parent 77878888ca
commit c9a961d5e8
5 changed files with 168 additions and 102 deletions

View File

@@ -2,23 +2,27 @@
# Bioxel Nodes # Bioxel Nodes
![Static Badge](https://img.shields.io/badge/Blender-orange?style=for-the-badge&logo=blender&logoColor=white) ![Static Badge](https://img.shields.io/badge/Blender-orange?style=for-the-badge&logo=blender&logoColor=white&color=black)
![GitHub License](https://img.shields.io/github/license/OmooLab/BioxelNodes?style=for-the-badge) ![GitHub License](https://img.shields.io/github/license/OmooLab/BioxelNodes?style=for-the-badge&labelColor=black)
![GitHub Release](https://img.shields.io/github/v/release/OmooLab/BioxelNodes?style=for-the-badge) ![GitHub Release](https://img.shields.io/github/v/release/OmooLab/BioxelNodes?style=for-the-badge&labelColor=black)
![GitHub Repo stars](https://img.shields.io/github/stars/OmooLab/BioxelNodes?style=for-the-badge) ![GitHub Repo stars](https://img.shields.io/github/stars/OmooLab/BioxelNodes?style=for-the-badge&labelColor=black)
![Discord](https://img.shields.io/discord/1265129134397587457?style=for-the-badge&logo=discord&label=Discord&labelColor=white&color=black&link=https%3A%2F%2Fdiscord.gg%2FpYkNyq2TjE)
Bioxel Nodes is a Blender addon for scientific volumetric data visualization. It using Blender's powerful **Geometry Nodes** and **Cycles** to process and render volumetric data. Bioxel Nodes is a Blender addon for scientific volumetric data visualization. It using Blender's powerful **Geometry Nodes** and **Cycles** to process and render volumetric data.
![cover](https://omoolab.github.io/BioxelNodes/latest/assets/cover.png) ![cover](https://omoolab.github.io/BioxelNodes/latest/assets/cover.png)
- Fantastic rendering result, also support EEVEE NEXT - Fantastic rendering result, also support EEVEE NEXT.
- Support multiple formats - Support multiple formats.
- Support 4D volumetric data - Support 4D volumetric data.
- All kinds of cutters - All kinds of cutters.
- Simple and powerful nodes - Simple and powerful nodes.
- Based on blender natively, can work without addon. - Based on blender natively, can work without addon.
**Click [Getting Started](https://omoolab.github.io/BioxelNodes/latest/installation) to begin your journey into visualizing volumetric data!** **Read the [getting started](https://omoolab.github.io/BioxelNodes/latest/installation) to begin your journey into visualizing volumetric data!**
Welcome to our [discord server](https://discord.gg/pYkNyq2TjE), if you have any problems with this add-on.
## Support Multiple Formats ## Support Multiple Formats
@@ -55,7 +59,7 @@ Bioxel Nodes is a Blender addon for scientific volumetric data visualization. It
- Only works with Cycles CPU , Cycles GPU (OptiX), EEVEE - Only works with Cycles CPU , Cycles GPU (OptiX), EEVEE
- Section surface cannot be generated when convert to mesh (will be supported soon) - Section surface cannot be generated when convert to mesh (will be supported soon)
## Compatibile to Newer Version ## Compatible to Newer Version
**Updating this addon may break old files, so read the following carefully before updating** **Updating this addon may break old files, so read the following carefully before updating**

View File

@@ -175,6 +175,9 @@ class ParseVolumetricData(bpy.types.Operator):
progress_callback=progress_callback) progress_callback=progress_callback)
except CancelledByUser: except CancelledByUser:
return return
except RuntimeError:
self.has_error = True
return
if cancel(): if cancel():
return return
@@ -183,6 +186,8 @@ class ParseVolumetricData(bpy.types.Operator):
# Init cancel flag # Init cancel flag
self.is_cancelled = False self.is_cancelled = False
self.has_error = False
# Create the thread # Create the thread
self.thread = threading.Thread(target=parse_volumetric_data_func, self.thread = threading.Thread(target=parse_volumetric_data_func,
args=(self, context, lambda: self.is_cancelled)) args=(self, context, lambda: self.is_cancelled))
@@ -229,6 +234,11 @@ class ParseVolumetricData(bpy.types.Operator):
self.report({"WARNING"}, "Canncelled by user.") self.report({"WARNING"}, "Canncelled by user.")
return {'CANCELLED'} return {'CANCELLED'}
# Check if thread is cancelled by user
if self.has_error:
self.report({"ERROR"}, "Fail to parse, something went wrong.")
return {'CANCELLED'}
# If not canncelled... # If not canncelled...
for key, value in self.meta.items(): for key, value in self.meta.items():
print(f"{key}: {value}") print(f"{key}: {value}")
@@ -239,9 +249,12 @@ class ParseVolumetricData(bpy.types.Operator):
orig_spacing[1], orig_spacing[2]) orig_spacing[1], orig_spacing[2])
bioxel_size = max(min_size, 1.0) bioxel_size = max(min_size, 1.0)
layer_size = get_layer_size(orig_shape, layer_shape = get_layer_shape(1, orig_shape, orig_spacing)
layer_size = get_layer_size(layer_shape,
bioxel_size) bioxel_size)
log10 = math.floor(math.log10(max(*layer_size))) log10 = math.floor(math.log10(max(*layer_size)))
log10 = max(1,log10)
log10 = min(3,log10)
scene_scale = math.pow(10, -log10) scene_scale = math.pow(10, -log10)
if self.container: if self.container:
@@ -700,6 +713,7 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
container.matrix_world = mat_ras2blender @ mat_scene_scale container.matrix_world = mat_ras2blender @ mat_scene_scale
container.name = container_name container.name = container_name
container.show_in_front = True
container.data.name = container_name container.data.name = container_name
container['bioxel_container'] = True container['bioxel_container'] = True
@@ -741,8 +755,8 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
layer.hide_select = True layer.hide_select = True
layer.hide_render = True layer.hide_render = True
layer.hide_viewport = True layer.hide_viewport = True
layer.data.display.use_slice = True # layer.data.display.use_slice = True
layer.data.display.density = 1e-05 # layer.data.display.density = 1e-05
layer['bioxel_layer'] = True layer['bioxel_layer'] = True
layer['bioxel_layer_type'] = layer_info['type'] layer['bioxel_layer_type'] = layer_info['type']
@@ -811,11 +825,8 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
# Change render setting for better result # Change render setting for better result
preferences = context.preferences.addons[__package__].preferences preferences = context.preferences.addons[__package__].preferences
if preferences.do_change_render_setting and self.is_first_import: if preferences.do_change_render_setting and self.is_first_import:
if bpy.app.version < (4, 2, 0):
bpy.context.scene.render.engine = 'CYCLES' bpy.context.scene.render.engine = 'CYCLES'
try: try:
bpy.context.scene.cycles.shading_system = True bpy.context.scene.cycles.shading_system = True
bpy.context.scene.cycles.volume_bounces = 12 bpy.context.scene.cycles.volume_bounces = 12

View File

@@ -89,8 +89,8 @@ def deep_copy_layer(vdb_path, base_layer, name):
copyed_layer.hide_select = True copyed_layer.hide_select = True
copyed_layer.hide_render = True copyed_layer.hide_render = True
copyed_layer.hide_viewport = True copyed_layer.hide_viewport = True
copyed_layer.data.display.use_slice = True # copyed_layer.data.display.use_slice = True
copyed_layer.data.display.density = 1e-05 # copyed_layer.data.display.density = 1e-05
copyed_layer['bioxel_layer'] = True copyed_layer['bioxel_layer'] = True
copyed_layer['bioxel_layer_type'] = base_layer['bioxel_layer_type'] copyed_layer['bioxel_layer_type'] = base_layer['bioxel_layer_type']

View File

@@ -11,6 +11,9 @@ try:
except: except:
... ...
"""
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',
'.bmp', '.BMP', '.bmp', '.BMP',
@@ -136,7 +139,11 @@ def parse_volumetric_data(filepath: str, series_id="", progress_callback=None):
if len(sequence) > 1: if len(sequence) > 1:
is_sequence = True is_sequence = True
if ext in MRC_EXTS and not is_sequence: volume = None
# Parsing with mrcfile
if volume is None and ext in MRC_EXTS and not is_sequence:
print("Parsing with mrcfile...")
# TODO: much to do with mrc # TODO: much to do with mrc
with mrcfile.open(filepath) as mrc: with mrcfile.open(filepath) as mrc:
volume = mrc.data volume = mrc.data
@@ -159,10 +166,10 @@ def parse_volumetric_data(filepath: str, series_id="", progress_callback=None):
elif mrc.is_volume_stack(): elif mrc.is_volume_stack():
volume = np.expand_dims(volume, axis=-1) # expend channel volume = np.expand_dims(volume, axis=-1) # expend channel
name = Path(filepath).name.removesuffix(ext).replace(" ", "-") name = Path(filepath).name.removesuffix(ext).replace(" ", "-")
shape = volume.shape[1:4] shape = volume.shape[1:4]
spacing = (mrc.voxel_size.x, mrc.voxel_size.y, mrc.voxel_size.z) spacing = (mrc.voxel_size.x, mrc.voxel_size.y, mrc.voxel_size.z)
meta = { meta = {
"name": name, "name": name,
"description": "", "description": "",
@@ -175,7 +182,9 @@ def parse_volumetric_data(filepath: str, series_id="", progress_callback=None):
"is_oriented": False "is_oriented": False
} }
elif ext in OME_EXTS and not is_sequence: # Parsing with OMETIFFReader
if volume is None and ext in OME_EXTS and not is_sequence:
print("Parsing with OMETIFFReader...")
reader = OMETIFFReader(fpath=filepath) reader = OMETIFFReader(fpath=filepath)
ome_volume, metadata, xml_metadata = reader.read() ome_volume, metadata, xml_metadata = reader.read()
@@ -189,9 +198,6 @@ def parse_volumetric_data(filepath: str, series_id="", progress_callback=None):
# for key in metadata: # for key in metadata:
# print(f"{key},{metadata[key]}") # print(f"{key},{metadata[key]}")
ome_order = metadata['DimOrder BF Array'] ome_order = metadata['DimOrder BF Array']
except:
ome_order = "TCZYX"
if ome_volume.ndim == 2: if ome_volume.ndim == 2:
ome_order = ome_order.replace("T", "")\ ome_order = ome_order.replace("T", "")\
.replace("C", "").replace("Z", "") .replace("C", "").replace("Z", "")
@@ -250,8 +256,12 @@ def parse_volumetric_data(filepath: str, series_id="", progress_callback=None):
"channel_count": volume.shape[-1], "channel_count": volume.shape[-1],
"is_oriented": False "is_oriented": False
} }
except:
...
else: # Parsing with SimpleITK
if volume is None:
print("Parsing with SimpleITK...")
if ext in DICOM_EXTS: if ext in DICOM_EXTS:
dir_path = Path(filepath).resolve().parent dir_path = Path(filepath).resolve().parent
reader = sitk.ImageSeriesReader() reader = sitk.ImageSeriesReader()
@@ -294,10 +304,13 @@ def parse_volumetric_data(filepath: str, series_id="", progress_callback=None):
name = name.replace(" ", "-") name = name.replace(" ", "-")
description = description.replace(" ", "-") description = description.replace(" ", "-")
elif ext in SEQUENCE_EXTS: elif ext in SEQUENCE_EXTS and is_sequence:
try:
itk_volume = sitk.ReadImage(sequence) itk_volume = sitk.ReadImage(sequence)
name = get_sequence_name(filepath).replace(" ", "-") name = get_sequence_name(filepath).replace(" ", "-")
description = "" description = ""
except RuntimeError as e:
raise e
else: else:
itk_volume = sitk.ReadImage(filepath) itk_volume = sitk.ReadImage(filepath)
name = Path(filepath).name.removesuffix(ext).replace(" ", "-") name = Path(filepath).name.removesuffix(ext).replace(" ", "-")
@@ -311,7 +324,33 @@ def parse_volumetric_data(filepath: str, series_id="", progress_callback=None):
if not progressing: if not progressing:
raise CancelledByUser raise CancelledByUser
if itk_volume.GetDimension() == 3: if itk_volume.GetDimension() == 2:
volume = sitk.GetArrayFromImage(itk_volume)
if volume.ndim == 3:
volume = np.transpose(volume, (1, 0, 2))
volume = np.expand_dims(volume, axis=-2) # expend Z
else:
volume = np.transpose(volume)
volume = np.expand_dims(volume, axis=-1) # expend Z
volume = np.expand_dims(volume, axis=-1) # expend channel
volume = np.expand_dims(volume, axis=0) # expend frame
meta = {
"name": name,
"description": description,
"shape": volume.shape[1:4],
"spacing": (1, 1, 1),
"origin": (0, 0, 0),
"direction": (1, 0, 0, 0, 1, 0, 0, 0, 1),
"frame_count": 1,
"channel_count": volume.shape[-1],
"is_oriented": False
}
elif itk_volume.GetDimension() == 3:
itk_volume = sitk.DICOMOrient(itk_volume, 'RAS') itk_volume = sitk.DICOMOrient(itk_volume, 'RAS')
volume = sitk.GetArrayFromImage(itk_volume) volume = sitk.GetArrayFromImage(itk_volume)
@@ -332,12 +371,12 @@ def parse_volumetric_data(filepath: str, series_id="", progress_callback=None):
"spacing": tuple(itk_volume.GetSpacing()), "spacing": tuple(itk_volume.GetSpacing()),
"origin": tuple(itk_volume.GetOrigin()), "origin": tuple(itk_volume.GetOrigin()),
"direction": tuple(itk_volume.GetDirection()), "direction": tuple(itk_volume.GetDirection()),
"frame_count": volume.shape[0], "frame_count": 1,
"channel_count": volume.shape[-1], "channel_count": volume.shape[-1],
"is_oriented": True "is_oriented": True
} }
if itk_volume.GetDimension() == 4: elif itk_volume.GetDimension() == 4:
# FIXME: not sure... # FIXME: not sure...
direction = np.array(itk_volume.GetDirection()) direction = np.array(itk_volume.GetDirection())
direction = direction.reshape(3, 3) if itk_volume.GetDimension() == 3 \ direction = direction.reshape(3, 3) if itk_volume.GetDimension() == 3 \

View File

@@ -1,17 +1,28 @@
[中文文档](https://uj6xfhbzp0.feishu.cn/wiki/Qx3VwHuNPimeI8kr6nDcvl1DnHf?from=from_copylink)
# Bioxel Nodes # Bioxel Nodes
![Static Badge](https://img.shields.io/badge/Blender-orange?style=for-the-badge&logo=blender&logoColor=white&color=black)
![GitHub License](https://img.shields.io/github/license/OmooLab/BioxelNodes?style=for-the-badge&labelColor=black)
![GitHub Release](https://img.shields.io/github/v/release/OmooLab/BioxelNodes?style=for-the-badge&labelColor=black)
![GitHub Repo stars](https://img.shields.io/github/stars/OmooLab/BioxelNodes?style=for-the-badge&labelColor=black)
![Discord](https://img.shields.io/discord/1265129134397587457?style=for-the-badge&logo=discord&label=Discord&labelColor=white&color=black&link=https%3A%2F%2Fdiscord.gg%2FpYkNyq2TjE)
Bioxel Nodes is a Blender addon for scientific volumetric data visualization. It using Blender's powerful **Geometry Nodes** and **Cycles** to process and render volumetric data. Bioxel Nodes is a Blender addon for scientific volumetric data visualization. It using Blender's powerful **Geometry Nodes** and **Cycles** to process and render volumetric data.
![cover](assets/cover.png) ![cover](https://omoolab.github.io/BioxelNodes/latest/assets/cover.png)
- Fantastic rendering result, also support EEVEE NEXT - Fantastic rendering result, also support EEVEE NEXT.
- Support multiple formats - Support multiple formats.
- Support 4D volumetric data - Support 4D volumetric data.
- All kinds of cutters - All kinds of cutters.
- Simple and powerful nodes - Simple and powerful nodes.
- Based on blender natively, can work without addon. - Based on blender natively, can work without addon.
**Click [Getting Started](https://omoolab.github.io/BioxelNodes/latest/installation) to begin your journey into visualizing volumetric data!** **Read the [getting started](https://omoolab.github.io/BioxelNodes/latest/installation) to begin your journey into visualizing volumetric data!**
Welcome to our [discord server](https://discord.gg/pYkNyq2TjE), if you have any problems with this add-on.
## Support Multiple Formats ## Support Multiple Formats
@@ -48,7 +59,7 @@ Bioxel Nodes is a Blender addon for scientific volumetric data visualization. It
- Only works with Cycles CPU , Cycles GPU (OptiX), EEVEE - Only works with Cycles CPU , Cycles GPU (OptiX), EEVEE
- Section surface cannot be generated when convert to mesh (will be supported soon) - Section surface cannot be generated when convert to mesh (will be supported soon)
## Compatibile to Newer Version ## Compatible to Newer Version
**Updating this addon may break old files, so read the following carefully before updating** **Updating this addon may break old files, so read the following carefully before updating**
@@ -59,6 +70,7 @@ After the addon update, your old project files may not work either, this may be
Also, unlike the newer versions, the older shaders are not based on OSL, so if you find that you can't render volumes, you need to turn on **Open Shading Language (OSL)** in the Render Settings. Also, unlike the newer versions, the older shaders are not based on OSL, so if you find that you can't render volumes, you need to turn on **Open Shading Language (OSL)** in the Render Settings.
## Roadmap ## Roadmap
- Better multi-format import experience - Better multi-format import experience
- One-click bake model with texture - One-click bake model with texture
- AI Segmentation to Generate Labels - AI Segmentation to Generate Labels