Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caf184c327 | ||
| 488b05f062 | |||
|
|
2447a47269 | ||
| 9380342b4f | |||
|
|
9b36e9d911 | ||
| c9a961d5e8 | |||
| 77878888ca | |||
|
|
50e8564027 | ||
| f07ac1c70c |
30
README.md
30
README.md
@@ -2,29 +2,33 @@
|
|||||||
|
|
||||||
# Bioxel Nodes
|
# Bioxel Nodes
|
||||||
|
|
||||||

|
[](https://blender.org/)
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
[](https://discord.gg/pYkNyq2TjE)
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 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
|
||||||
|
|
||||||
| Format | EXT | Test |
|
| Format | EXT | Test |
|
||||||
| ------ | ---------------------------------------- | ------- |
|
| ------ | ---------------------------------------- | ------- |
|
||||||
| DICOM | .dcm, .DICOM | ✅ pass |
|
| DICOM | .dcm, .DCM, .DICOM, .ima, .IMA | ✅ pass |
|
||||||
| BMP | .bmp, .BMP | ✅ pass |
|
| BMP | .bmp, .BMP | ✅ pass |
|
||||||
| JPEG | .jpg, .JPG, .jpeg, .JPEG | ✅ pass |
|
| JPEG | .jpg, .JPG, .jpeg, .JPEG | ✅ pass |
|
||||||
| PNG | .png, .PNG | ✅ pass |
|
| PNG | .png, .PNG | ✅ pass |
|
||||||
@@ -32,6 +36,8 @@ Bioxel Nodes is a Blender addon for scientific volumetric data visualization. It
|
|||||||
| Nifti | .nia, .nii, .nii.gz, .hdr, .img, .img.gz | ✅ pass |
|
| Nifti | .nia, .nii, .nii.gz, .hdr, .img, .img.gz | ✅ pass |
|
||||||
| Nrrd | .nrrd, .nhdr | ✅ pass |
|
| Nrrd | .nrrd, .nhdr | ✅ pass |
|
||||||
| HDF5 | .hdf, .h4, .hdf4, .he2, .h5, .hdf5, .he5 | ✅ pass |
|
| HDF5 | .hdf, .h4, .hdf4, .he2, .h5, .hdf5, .he5 | ✅ pass |
|
||||||
|
| OME | .ome.tiff, .ome.tif | ✅ pass |
|
||||||
|
| MRC | .mrc, .mrc.gz, .map, .map.gz | ✅ pass |
|
||||||
|
|
||||||
## Support 4D volumetric data
|
## Support 4D volumetric data
|
||||||
|
|
||||||
@@ -53,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**
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ bl_info = {
|
|||||||
"author": "Ma Nan",
|
"author": "Ma Nan",
|
||||||
"description": "",
|
"description": "",
|
||||||
"blender": (4, 1, 0),
|
"blender": (4, 1, 0),
|
||||||
"version": (0, 2, 8),
|
"version": (0, 2, 9),
|
||||||
"location": "File -> Import",
|
"location": "File -> Import",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
"category": "Node"
|
"category": "Node"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -40,7 +43,7 @@ SEQUENCE_EXTS = ['.bmp', '.BMP',
|
|||||||
'.tif', '.TIF', '.tiff', '.TIFF',
|
'.tif', '.TIF', '.tiff', '.TIFF',
|
||||||
'.png', '.PNG']
|
'.png', '.PNG']
|
||||||
|
|
||||||
DICOM_EXTS = ['', '.dcm', '.DCM', '.DICOM']
|
DICOM_EXTS = ['', '.dcm', '.DCM', '.DICOM', '.ima', '.IMA']
|
||||||
|
|
||||||
FH_EXTS = ['', '.dcm', '.DCM', '.DICOM', '.ima', '.IMA',
|
FH_EXTS = ['', '.dcm', '.DCM', '.DICOM', '.ima', '.IMA',
|
||||||
'.gipl', '.gipl.gz',
|
'.gipl', '.gipl.gz',
|
||||||
@@ -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,69 +198,70 @@ 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']
|
||||||
|
if ome_volume.ndim == 2:
|
||||||
|
ome_order = ome_order.replace("T", "")\
|
||||||
|
.replace("C", "").replace("Z", "")
|
||||||
|
bioxel_order = (ome_order.index('X'),
|
||||||
|
ome_order.index('Y'))
|
||||||
|
volume = np.transpose(ome_volume, bioxel_order)
|
||||||
|
volume = np.expand_dims(volume, axis=0) # expend frame
|
||||||
|
volume = np.expand_dims(volume, axis=-1) # expend Z
|
||||||
|
volume = np.expand_dims(volume, axis=-1) # expend channel
|
||||||
|
|
||||||
|
elif ome_volume.ndim == 3:
|
||||||
|
# -> XYZC
|
||||||
|
ome_order = ome_order.replace("T", "").replace("C", "")
|
||||||
|
bioxel_order = (ome_order.index('X'),
|
||||||
|
ome_order.index('Y'),
|
||||||
|
ome_order.index('Z'))
|
||||||
|
volume = np.transpose(ome_volume, bioxel_order)
|
||||||
|
volume = np.expand_dims(volume, axis=0) # expend frame
|
||||||
|
volume = np.expand_dims(volume, axis=-1) # expend channel
|
||||||
|
elif ome_volume.ndim == 4:
|
||||||
|
# -> XYZC
|
||||||
|
ome_order = ome_order.replace("T", "")
|
||||||
|
bioxel_order = (ome_order.index('X'),
|
||||||
|
ome_order.index('Y'),
|
||||||
|
ome_order.index('Z'),
|
||||||
|
ome_order.index('C'))
|
||||||
|
volume = np.transpose(ome_volume, bioxel_order)
|
||||||
|
volume = np.expand_dims(volume, axis=0) # expend frame
|
||||||
|
elif ome_volume.ndim == 5:
|
||||||
|
# -> TXYZC
|
||||||
|
bioxel_order = (ome_order.index('T'),
|
||||||
|
ome_order.index('X'),
|
||||||
|
ome_order.index('Y'),
|
||||||
|
ome_order.index('Z'),
|
||||||
|
ome_order.index('C'))
|
||||||
|
volume = np.transpose(ome_volume, bioxel_order)
|
||||||
|
|
||||||
|
shape = volume.shape[1:4]
|
||||||
|
|
||||||
|
try:
|
||||||
|
spacing = (metadata['PhysicalSizeX'],
|
||||||
|
metadata['PhysicalSizeY'],
|
||||||
|
metadata['PhysicalSizeZ'])
|
||||||
|
except:
|
||||||
|
spacing = (1, 1, 1)
|
||||||
|
|
||||||
|
name = Path(filepath).name.removesuffix(ext).replace(" ", "-")
|
||||||
|
meta = {
|
||||||
|
"name": name,
|
||||||
|
"description": "",
|
||||||
|
"shape": shape,
|
||||||
|
"spacing": spacing,
|
||||||
|
"origin": (0, 0, 0),
|
||||||
|
"direction": (1, 0, 0, 0, 1, 0, 0, 0, 1),
|
||||||
|
"frame_count": volume.shape[0],
|
||||||
|
"channel_count": volume.shape[-1],
|
||||||
|
"is_oriented": False
|
||||||
|
}
|
||||||
except:
|
except:
|
||||||
ome_order = "TCZYX"
|
...
|
||||||
|
|
||||||
if ome_volume.ndim == 2:
|
# Parsing with SimpleITK
|
||||||
ome_order = ome_order.replace("T", "")\
|
if volume is None:
|
||||||
.replace("C", "").replace("Z", "")
|
print("Parsing with SimpleITK...")
|
||||||
bioxel_order = (ome_order.index('X'),
|
|
||||||
ome_order.index('Y'))
|
|
||||||
volume = np.transpose(ome_volume, bioxel_order)
|
|
||||||
volume = np.expand_dims(volume, axis=0) # expend frame
|
|
||||||
volume = np.expand_dims(volume, axis=-1) # expend Z
|
|
||||||
volume = np.expand_dims(volume, axis=-1) # expend channel
|
|
||||||
|
|
||||||
elif ome_volume.ndim == 3:
|
|
||||||
# -> XYZC
|
|
||||||
ome_order = ome_order.replace("T", "").replace("C", "")
|
|
||||||
bioxel_order = (ome_order.index('X'),
|
|
||||||
ome_order.index('Y'),
|
|
||||||
ome_order.index('Z'))
|
|
||||||
volume = np.transpose(ome_volume, bioxel_order)
|
|
||||||
volume = np.expand_dims(volume, axis=0) # expend frame
|
|
||||||
volume = np.expand_dims(volume, axis=-1) # expend channel
|
|
||||||
elif ome_volume.ndim == 4:
|
|
||||||
# -> XYZC
|
|
||||||
ome_order = ome_order.replace("T", "")
|
|
||||||
bioxel_order = (ome_order.index('X'),
|
|
||||||
ome_order.index('Y'),
|
|
||||||
ome_order.index('Z'),
|
|
||||||
ome_order.index('C'))
|
|
||||||
volume = np.transpose(ome_volume, bioxel_order)
|
|
||||||
volume = np.expand_dims(volume, axis=0) # expend frame
|
|
||||||
elif ome_volume.ndim == 5:
|
|
||||||
# -> TXYZC
|
|
||||||
bioxel_order = (ome_order.index('T'),
|
|
||||||
ome_order.index('X'),
|
|
||||||
ome_order.index('Y'),
|
|
||||||
ome_order.index('Z'),
|
|
||||||
ome_order.index('C'))
|
|
||||||
volume = np.transpose(ome_volume, bioxel_order)
|
|
||||||
|
|
||||||
shape = volume.shape[1:4]
|
|
||||||
|
|
||||||
try:
|
|
||||||
spacing = (metadata['PhysicalSizeX'],
|
|
||||||
metadata['PhysicalSizeY'],
|
|
||||||
metadata['PhysicalSizeZ'])
|
|
||||||
except:
|
|
||||||
spacing = (1, 1, 1)
|
|
||||||
|
|
||||||
name = Path(filepath).name.removesuffix(ext).replace(" ", "-")
|
|
||||||
meta = {
|
|
||||||
"name": name,
|
|
||||||
"description": "",
|
|
||||||
"shape": shape,
|
|
||||||
"spacing": spacing,
|
|
||||||
"origin": (0, 0, 0),
|
|
||||||
"direction": (1, 0, 0, 0, 1, 0, 0, 0, 1),
|
|
||||||
"frame_count": volume.shape[0],
|
|
||||||
"channel_count": volume.shape[-1],
|
|
||||||
"is_oriented": False
|
|
||||||
}
|
|
||||||
|
|
||||||
else:
|
|
||||||
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:
|
||||||
itk_volume = sitk.ReadImage(sequence)
|
try:
|
||||||
name = get_sequence_name(filepath).replace(" ", "-")
|
itk_volume = sitk.ReadImage(sequence)
|
||||||
description = ""
|
name = get_sequence_name(filepath).replace(" ", "-")
|
||||||
|
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 \
|
||||||
|
|||||||
@@ -1,23 +1,34 @@
|
|||||||
|
[中文文档](https://uj6xfhbzp0.feishu.cn/wiki/Qx3VwHuNPimeI8kr6nDcvl1DnHf?from=from_copylink)
|
||||||
|
|
||||||
# Bioxel Nodes
|
# Bioxel Nodes
|
||||||
|
|
||||||
|
[](https://blender.org/)
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
[](https://discord.gg/pYkNyq2TjE)
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 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
|
||||||
|
|
||||||
| Format | EXT | Test |
|
| Format | EXT | Test |
|
||||||
| ------ | ---------------------------------------- | ------- |
|
| ------ | ---------------------------------------- | ------- |
|
||||||
| DICOM | .dcm, .DICOM | ✅ pass |
|
| DICOM | .dcm, .DCM, .DICOM, .ima, .IMA | ✅ pass |
|
||||||
| BMP | .bmp, .BMP | ✅ pass |
|
| BMP | .bmp, .BMP | ✅ pass |
|
||||||
| JPEG | .jpg, .JPG, .jpeg, .JPEG | ✅ pass |
|
| JPEG | .jpg, .JPG, .jpeg, .JPEG | ✅ pass |
|
||||||
| PNG | .png, .PNG | ✅ pass |
|
| PNG | .png, .PNG | ✅ pass |
|
||||||
@@ -25,6 +36,8 @@ Bioxel Nodes is a Blender addon for scientific volumetric data visualization. It
|
|||||||
| Nifti | .nia, .nii, .nii.gz, .hdr, .img, .img.gz | ✅ pass |
|
| Nifti | .nia, .nii, .nii.gz, .hdr, .img, .img.gz | ✅ pass |
|
||||||
| Nrrd | .nrrd, .nhdr | ✅ pass |
|
| Nrrd | .nrrd, .nhdr | ✅ pass |
|
||||||
| HDF5 | .hdf, .h4, .hdf4, .he2, .h5, .hdf5, .he5 | ✅ pass |
|
| HDF5 | .hdf, .h4, .hdf4, .he2, .h5, .hdf5, .he5 | ✅ pass |
|
||||||
|
| OME | .ome.tiff, .ome.tif | ✅ pass |
|
||||||
|
| MRC | .mrc, .mrc.gz, .map, .map.gz | ✅ pass |
|
||||||
|
|
||||||
## Support 4D volumetric data
|
## Support 4D volumetric data
|
||||||
|
|
||||||
@@ -46,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**
|
||||||
|
|
||||||
@@ -57,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
|
|
||||||
- One-click bake model with texture
|
- Better multi-format import experience
|
||||||
- AI Segmentation to Generate Labels
|
- One-click bake model with texture
|
||||||
|
- AI Segmentation to Generate Labels
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ First thing first, you need to get your volumetric data ready in disk. Bioxel No
|
|||||||
|
|
||||||
| Format | EXT | Test |
|
| Format | EXT | Test |
|
||||||
| ------ | ---------------------------------------- | ------- |
|
| ------ | ---------------------------------------- | ------- |
|
||||||
| DICOM | .dcm, .DICOM | ✅ pass |
|
| DICOM | .dcm, .DCM, .DICOM, .ima, .IMA | ✅ pass |
|
||||||
| BMP | .bmp, .BMP | ✅ pass |
|
| BMP | .bmp, .BMP | ✅ pass |
|
||||||
| JPEG | .jpg, .JPG, .jpeg, .JPEG | ✅ pass |
|
| JPEG | .jpg, .JPG, .jpeg, .JPEG | ✅ pass |
|
||||||
| PNG | .png, .PNG | ✅ pass |
|
| PNG | .png, .PNG | ✅ pass |
|
||||||
@@ -21,6 +21,8 @@ First thing first, you need to get your volumetric data ready in disk. Bioxel No
|
|||||||
| LSM | .lsm, .LSM | yet |
|
| LSM | .lsm, .LSM | yet |
|
||||||
| MINC | .mnc, .MNC | yet |
|
| MINC | .mnc, .MNC | yet |
|
||||||
| MRC | .mrc, .rec | yet |
|
| MRC | .mrc, .rec | yet |
|
||||||
|
| OME | .ome.tiff, .ome.tif | ✅ pass |
|
||||||
|
| MRC | .mrc, .mrc.gz, .map, .map.gz | ✅ pass |
|
||||||
|
|
||||||
## Download Data from Internet
|
## Download Data from Internet
|
||||||
|
|
||||||
@@ -32,10 +34,12 @@ If you don't have any volumetric data, you can access open research data from li
|
|||||||
| ------------------------------------------------------------------------------------ | ------------------ |
|
| ------------------------------------------------------------------------------------ | ------------------ |
|
||||||
| [MorphoSource](https://www.morphosource.org/) | Open Research Data |
|
| [MorphoSource](https://www.morphosource.org/) | Open Research Data |
|
||||||
| [Dryad](https://datadryad.org) | Open Research Data |
|
| [Dryad](https://datadryad.org) | Open Research Data |
|
||||||
|
| [Cell Image Library](http://cellimagelibrary.org/home) | Cells |
|
||||||
| [OpenOrganelle](https://openorganelle.janelia.org/datasets) | Cells |
|
| [OpenOrganelle](https://openorganelle.janelia.org/datasets) | Cells |
|
||||||
|
| [Allen Cell Explorer](https://www.allencell.org/3d-cell-viewer.html) | Cells |
|
||||||
|
| [EMDB](https://www.ebi.ac.uk/emdb/) | Protein, Viruses |
|
||||||
| [Embodi3D](https://www.embodi3d.com/files/category/37-medical-scans/) | Medical Images |
|
| [Embodi3D](https://www.embodi3d.com/files/category/37-medical-scans/) | Medical Images |
|
||||||
| [Github](https://github.com/sfikas/medical-imaging-datasets) | Medical Images |
|
| [Github](https://github.com/sfikas/medical-imaging-datasets) | Medical Images |
|
||||||
| [NIHR](https://nhsx.github.io/open-source-imaging-data-sets/) | Medical Images |
|
| [NIHR](https://nhsx.github.io/open-source-imaging-data-sets/) | Medical Images |
|
||||||
| [Medical Segmentation Decathlon](http://medicaldecathlon.com/) | Medical Images |
|
| [Medical Segmentation Decathlon](http://medicaldecathlon.com/) | Medical Images |
|
||||||
| [Allen Cell Explorer](https://www.allencell.org/3d-cell-viewer.html) | Cells |
|
|
||||||
| [Visible Human Project](https://www.nlm.nih.gov/research/visible/visible_human.html) | Medical Images |
|
| [Visible Human Project](https://www.nlm.nih.gov/research/visible/visible_human.html) | Medical Images |
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ schema_version = "1.0.0"
|
|||||||
# Example of manifest file for a Blender extension
|
# Example of manifest file for a Blender extension
|
||||||
# Change the values according to your extension
|
# Change the values according to your extension
|
||||||
id = "bioxelnodes"
|
id = "bioxelnodes"
|
||||||
version = "0.2.8"
|
version = "0.2.9"
|
||||||
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 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "bioxelnodes"
|
name = "bioxelnodes"
|
||||||
version = "0.2.8"
|
version = "0.2.9"
|
||||||
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