feat: Update .gitattributes and .gitignore for improved asset management and ignore patterns
chore: Add app.json and omoolab.css for Obsidian integration refactor: Clean up AGENTS.md and remove outdated test files
This commit is contained in:
132
.gitattributes
vendored
132
.gitattributes
vendored
@@ -1 +1,131 @@
|
||||
*.blend filter=lfs diff=lfs merge=lfs -text
|
||||
# Define macros (only works in top-level gitattributes files)
|
||||
[attr]lfs filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# 3D models
|
||||
*.collada lfs
|
||||
*.dae lfs
|
||||
*.dxf lfs
|
||||
*.FBX lfs
|
||||
*.fbx lfs
|
||||
*.jas lfs
|
||||
*.lws lfs
|
||||
*.lxo lfs
|
||||
*.obj lfs
|
||||
*.ply lfs
|
||||
*.skp lfs
|
||||
*.stl lfs
|
||||
*.stp lfs
|
||||
*.usd lfs
|
||||
*.usda lfs
|
||||
*.usdc lfs
|
||||
*.usdz lfs
|
||||
*.glb lfs
|
||||
*.gltf lfs
|
||||
|
||||
# Audio
|
||||
*.aif lfs
|
||||
*.aiff lfs
|
||||
*.it lfs
|
||||
*.mod lfs
|
||||
*.mp3 lfs
|
||||
*.ogg lfs
|
||||
*.s3m lfs
|
||||
*.wav lfs
|
||||
*.xm lfs
|
||||
|
||||
# Video
|
||||
*.asf lfs
|
||||
*.avi lfs
|
||||
*.flv lfs
|
||||
*.mov lfs
|
||||
*.mp4 lfs
|
||||
*.mpeg lfs
|
||||
*.mpg lfs
|
||||
*.ogv lfs
|
||||
*.wmv lfs
|
||||
*.mkv lfs
|
||||
|
||||
# Images
|
||||
*.bmp lfs
|
||||
*.exr lfs
|
||||
*.gif lfs
|
||||
*.hdr lfs
|
||||
*.iff lfs
|
||||
*.jpeg lfs
|
||||
*.jpg lfs
|
||||
*.pict lfs
|
||||
*.png lfs
|
||||
*.psd lfs
|
||||
*.tga lfs
|
||||
*.tif lfs
|
||||
*.tiff lfs
|
||||
*.webp lfs
|
||||
|
||||
# Compressed Archive
|
||||
*.7z lfs
|
||||
*.bz2 lfs
|
||||
*.gz lfs
|
||||
*.rar lfs
|
||||
*.tar lfs
|
||||
*.zip lfs
|
||||
|
||||
# Compiled Dynamic Library
|
||||
*.dll lfs
|
||||
*.pdb lfs
|
||||
*.so lfs
|
||||
|
||||
# Fonts
|
||||
*.otf lfs
|
||||
*.ttf lfs
|
||||
*.woff lfs
|
||||
*.woff2 lfs
|
||||
|
||||
# Executable/Installer
|
||||
*.apk lfs
|
||||
*.exe lfs
|
||||
|
||||
# Documents
|
||||
*.pdf lfs
|
||||
*.doc lfs
|
||||
*.xls lfs
|
||||
*.ppt lfs
|
||||
*.docx lfs
|
||||
*.xlsx lfs
|
||||
*.pptx lfs
|
||||
|
||||
# Adobe
|
||||
*.psd lfs
|
||||
*.ai lfs
|
||||
*.aep lfs
|
||||
*.prproj lfs
|
||||
*.spp lfs
|
||||
|
||||
# Zbrush
|
||||
*.zbr lfs
|
||||
*.zpr lfs
|
||||
*.ztl lfs
|
||||
*.c4d lfs
|
||||
|
||||
# Autodesk
|
||||
*.max lfs
|
||||
*.ma lfs
|
||||
*.mb lfs
|
||||
*.3dm lfs
|
||||
*.3ds lfs
|
||||
|
||||
# Blender
|
||||
*.blend lfs
|
||||
*.blend1 lfs
|
||||
*.c4d lfs
|
||||
|
||||
# Imaging
|
||||
*.dicom lfs
|
||||
*.dcm lfs
|
||||
*.nii lfs
|
||||
*.ome lfs
|
||||
*.map lfs
|
||||
*.mrc lfs
|
||||
|
||||
# Others
|
||||
*.abr lfs
|
||||
*.tpl lfs
|
||||
304
.gitignore
vendored
304
.gitignore
vendored
@@ -1,6 +1,51 @@
|
||||
# Tempary files
|
||||
temp
|
||||
sandbox.*
|
||||
notes/
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
.env.*
|
||||
!.env.example
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
.secrets
|
||||
.secrets.*
|
||||
!.secrets.example
|
||||
secrets/
|
||||
SECRETS/
|
||||
secrets.bak/
|
||||
|
||||
# Claude
|
||||
.claudian
|
||||
|
||||
# Obsidian
|
||||
|
||||
.obsidian/*
|
||||
!.obsidian/snippets
|
||||
!.obsidian/app.json
|
||||
|
||||
*.canvas
|
||||
|
||||
# Keep odox for extract to openwiki
|
||||
!.obsidian/plugins
|
||||
.obsidian/plugins/*
|
||||
!.obsidian/plugins/odox
|
||||
|
||||
# Blender
|
||||
*.blend1
|
||||
blender_assets.cats.txt~
|
||||
blendcache_*
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
@@ -20,7 +65,6 @@ parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
@@ -28,8 +72,8 @@ share/python-wheels/
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
@@ -47,8 +91,10 @@ htmlcov/
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
@@ -71,6 +117,7 @@ instance/
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
@@ -81,30 +128,70 @@ profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
# Pipfile.lock
|
||||
|
||||
# celery beat schedule file
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
# poetry.lock
|
||||
# poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
# pdm.lock
|
||||
# pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
# pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# Redis
|
||||
*.rdb
|
||||
*.aof
|
||||
*.pid
|
||||
|
||||
# RabbitMQ
|
||||
mnesia/
|
||||
rabbitmq/
|
||||
rabbitmq-data/
|
||||
|
||||
# ActiveMQ
|
||||
activemq-data/
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
@@ -123,18 +210,183 @@ dmypy.json
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
pythonlib*
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
*.ipynb
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
.secrets
|
||||
.vdb
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
# .idea/
|
||||
|
||||
!scipy_ndimage/*/**
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Blender
|
||||
*.blend1
|
||||
blendcache_*
|
||||
*.cats.txt~
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
tests/data
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
.output
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Sveltekit cache directory
|
||||
.svelte-kit/
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Firebase cache directory
|
||||
.firebase/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# pnpm
|
||||
.pnpm-store
|
||||
|
||||
# yarn v3
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Vite files
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
.vite/
|
||||
22
.obsidian/app.json
vendored
Normal file
22
.obsidian/app.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"showInlineTitle": false,
|
||||
"useMarkdownLinks": true,
|
||||
"pdfExportSettings": {
|
||||
"pageSize": "Letter",
|
||||
"landscape": false,
|
||||
"margin": "0",
|
||||
"downscalePercent": 100
|
||||
},
|
||||
"userIgnoreFilters": [
|
||||
"notes/assets/"
|
||||
],
|
||||
"showUnsupportedFiles": false,
|
||||
"attachmentFolderPath": "notes/assets",
|
||||
"livePreview": true,
|
||||
"showIndentGuide": false,
|
||||
"alwaysUpdateLinks": true,
|
||||
"openBehavior": "file:README",
|
||||
"newFileLocation": "folder",
|
||||
"newFileFolderPath": "notes",
|
||||
"newLinkFormat": "relative"
|
||||
}
|
||||
30
.obsidian/snippets/omoolab.css
vendored
Normal file
30
.obsidian/snippets/omoolab.css
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/* not show label in canvas */
|
||||
.canvas-node-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.canvas-node-container:hover + .canvas-node-label {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
/* image caption */
|
||||
.image-embed {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.image-embed[alt]:after {
|
||||
content: attr(alt);
|
||||
display: block;
|
||||
padding-bottom: 12px;
|
||||
line-height: 2.0;
|
||||
font-style: italic;
|
||||
color: var(--text-faint);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/* fix image in canvas */
|
||||
.image-embed {
|
||||
padding: 0;
|
||||
}
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.11
|
||||
236
AGENTS.md
236
AGENTS.md
@@ -1,227 +1,19 @@
|
||||
# BioxelNodes - Agent Development Guide
|
||||
## 总规范
|
||||
|
||||
This file contains essential information for agentic coding agents working on the BioxelNodes codebase.
|
||||
- 说明性内容(例如提交说明、文档、注释、mermaid),以中文为主,专有名词、约定俗称可用英文
|
||||
|
||||
## Project Overview
|
||||
## Markdown 规范
|
||||
|
||||
BioxelNodes is a Blender addon for scientific volumetric data visualization. It integrates with Blender's Geometry Nodes and Cycles rendering engine to process and visualize medical/scientific data.
|
||||
- 不使用`---`分割器
|
||||
- `# Heading` 一级标题仅用于开头
|
||||
- mermaid 节点名用英文字符
|
||||
|
||||
**Language:** Python 3.11
|
||||
**Framework:** Blender Python API (bpy)
|
||||
**Package Manager:** uv
|
||||
## 代码规范
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Environment Setup
|
||||
```bash
|
||||
# Install dependencies
|
||||
uv sync
|
||||
```
|
||||
|
||||
### Build Commands
|
||||
```bash
|
||||
# Build for specific platform
|
||||
uv run build.py <platform> # platforms: windows-x64, linux-x64, macos-arm64, macos-x64
|
||||
```
|
||||
|
||||
### Unit Test
|
||||
```bash
|
||||
uv run pytest tests/
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
```bash
|
||||
# Format code (autopep8)
|
||||
autopep8 --in-place --recursive src/
|
||||
```
|
||||
|
||||
### Documentation
|
||||
```bash
|
||||
# Serve documentation locally
|
||||
uv run mkdocs serve
|
||||
|
||||
# Build documentation
|
||||
uv run mike deploy --push --update-aliases 0.2.x latest
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
### Formatting
|
||||
- **4-space indentation**
|
||||
- **PEP 8** compliance enforced via autopep8
|
||||
|
||||
### Naming Conventions
|
||||
- **snake_case** for functions, variables, and files
|
||||
- **PascalCase** for classes (Blender convention)
|
||||
- **UPPER_CASE** for constants
|
||||
- **bioxel_** prefix for addon-specific properties
|
||||
|
||||
### Import Organization
|
||||
```python
|
||||
# Standard library imports first
|
||||
import pathlib
|
||||
import uuid
|
||||
|
||||
# Third-party imports
|
||||
import bpy
|
||||
import numpy as np
|
||||
import SimpleITK as sitk
|
||||
|
||||
# Relative imports for internal modules
|
||||
from ..exceptions import CancelledByUser
|
||||
from ..utils import get_layer_obj
|
||||
```
|
||||
|
||||
### Type Hints
|
||||
- Use sparingly, mainly for Blender property annotations
|
||||
- Focus on function signatures and complex data structures
|
||||
- Example: `def process_data(data: np.ndarray) -> np.ndarray:`
|
||||
|
||||
## Architecture Patterns
|
||||
|
||||
### Auto-Registration System
|
||||
- Uses `auto_load.py` for automatic class registration
|
||||
- Classes are discovered automatically via reflection
|
||||
- Registration order resolved via topological sorting
|
||||
- All Blender classes must inherit from appropriate base types
|
||||
|
||||
### Module Structure
|
||||
```
|
||||
src/bioxelnodes/
|
||||
├── __init__.py # Addon entry point, asset library setup
|
||||
├── auto_load.py # Auto-registration system
|
||||
├── constants.py # Constants and paths
|
||||
├── utils.py # Utility functions
|
||||
├── preferences.py # Addon preferences
|
||||
├── props.py # Blender properties
|
||||
├── panels.py # UI panels
|
||||
├── menus.py # UI menus
|
||||
├── node.py # Node utilities
|
||||
├── layer.py # Layer management
|
||||
├── operators/ # Blender operators
|
||||
├── bioxel/ # Core bioxel functionality
|
||||
└── assets/ # Blender assets and node libraries
|
||||
```
|
||||
|
||||
### Blender Integration Patterns
|
||||
|
||||
#### Property Groups
|
||||
```python
|
||||
class BioxelProperties(bpy.types.PropertyGroup):
|
||||
bl_label = "Bioxel Properties"
|
||||
|
||||
bioxel_custom_prop: bpy.props.StringProperty(
|
||||
name="Custom Property",
|
||||
default="",
|
||||
description="Description for UI"
|
||||
)
|
||||
```
|
||||
|
||||
#### Operators
|
||||
```python
|
||||
class BIOXEL_OT_custom_operator(bpy.types.Operator):
|
||||
bl_idname = "bioxel.custom_operator"
|
||||
bl_label = "Custom Operator"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
# Operator logic here
|
||||
return {'FINISHED'}
|
||||
```
|
||||
|
||||
#### Panels
|
||||
```python
|
||||
class BIOXEL_PT_custom_panel(bpy.types.Panel):
|
||||
bl_label = "Custom Panel"
|
||||
bl_idname = "BIOXEL_PT_custom_panel"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Bioxel"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
# UI drawing code here
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Error Handling Patterns
|
||||
- Use try-catch blocks for file operations
|
||||
- Handle user cancellation gracefully
|
||||
- Report progress for long operations using `bioxel_progress_factor` and `bioxel_progress_text`
|
||||
- Log warnings with print statements for debugging
|
||||
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
### Core Libraries
|
||||
- **bpy** (Blender Python API) - Core integration
|
||||
- **numpy** - Array processing
|
||||
- **SimpleITK** - Medical image processing
|
||||
- **h5py** - HDF5 file handling
|
||||
|
||||
### Scientific Libraries
|
||||
- **matplotlib** - Plotting and visualization
|
||||
- **transforms3d** - 3D transformations
|
||||
- **pyometiff** - OME-TIFF support
|
||||
- **mrcfile** - MRC file format support
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Making Changes
|
||||
1. Identify the appropriate module (operators, panels, bioxel, etc.)
|
||||
2. Follow existing naming conventions and patterns
|
||||
3. Use auto-registration - no manual registration needed
|
||||
4. Test with Blender's Python console
|
||||
5. Format code with autopep8 before committing
|
||||
|
||||
### Adding New Features
|
||||
1. Create appropriate classes in relevant modules
|
||||
2. Use proper prefixes (BIOXEL_OT_, BIOXEL_PT_, etc.)
|
||||
3. Add to auto_load system automatically discovers new classes
|
||||
4. Update documentation if needed
|
||||
|
||||
### File Operations
|
||||
- Use `pathlib.Path` for path operations
|
||||
- Check file existence before operations
|
||||
- Handle permissions and missing files gracefully
|
||||
- Use appropriate file formats (HDF5 for data, PNG for images)
|
||||
|
||||
## Blender Integration Notes
|
||||
|
||||
### Asset Library
|
||||
- Automatically managed via `add_asset_library_if_missing()`
|
||||
- Located at `NODE_LIB_DIRPATH`
|
||||
- Uses "PACK" import method
|
||||
- Name: "O Bioxel"
|
||||
|
||||
### Progress Reporting
|
||||
- Use `bioxel_progress_factor` (0.0 to 1.0) for progress
|
||||
- Use `bioxel_progress_text` for status messages
|
||||
- Update via WindowManager properties
|
||||
|
||||
### Layer Management
|
||||
- Layers stored as HDF5 files
|
||||
- Container objects manage multiple layers
|
||||
- Use `layer.py` for layer operations
|
||||
- Support for various medical image formats
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Blender API
|
||||
- Always check context validity
|
||||
- Handle different Blender versions (check `bpy.app.version`)
|
||||
- Use proper property types for UI
|
||||
- Register classes in correct order (auto_load handles this)
|
||||
|
||||
### Performance
|
||||
- Use NumPy for array operations
|
||||
- Avoid expensive operations in UI draw calls
|
||||
- Cache computed values where appropriate
|
||||
- Use background threads for long operations
|
||||
|
||||
### File I/O
|
||||
- Always close file handles
|
||||
- Use context managers for file operations
|
||||
- Handle large files in chunks
|
||||
- Validate file formats before processing
|
||||
- 避免嵌套结构
|
||||
- 特殊的、非常规的需要写注释
|
||||
- 变量、函数名、Docstring 用全英文
|
||||
- 同一事物用统一表达
|
||||
- 使用的依赖越少越好
|
||||
- 选用简单直接的方式实现
|
||||
- 代码、执行方式改变,调整已有的文档、计划,而非新建
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# BioxelNodes Unit Tests
|
||||
|
||||
This directory contains unit tests for the bioxel module.
|
||||
|
||||
## Test Data
|
||||
|
||||
Test files should be placed in `tests/data/`:
|
||||
|
||||
## Running Tests
|
||||
|
||||
Run all tests:
|
||||
```bash
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
Run specific test file:
|
||||
```bash
|
||||
uv run pytest tests/test_bioxel.py
|
||||
```
|
||||
|
||||
Run specific test class:
|
||||
```bash
|
||||
uv run pytest tests/test_bioxel.py::TestDataClass
|
||||
```
|
||||
|
||||
Run with verbose output:
|
||||
```bash
|
||||
uv run pytest -v
|
||||
```
|
||||
|
||||
Run with coverage:
|
||||
```bash
|
||||
uv run pytest --cov=bioxelnodes.bioxel --cov-report=html
|
||||
```
|
||||
|
||||
## Test Categories
|
||||
|
||||
- **TestDataClass**: Data class initialization and properties
|
||||
- **TestDataToLayers**: Data.to_layers() method
|
||||
- **TestUtilityFunctions**: Utility functions
|
||||
- **TestLayerClass**: Layer class functionality
|
||||
- **TestConstants**: Module constants
|
||||
- **TestEdgeCases**: Edge cases and error handling
|
||||
@@ -1,539 +0,0 @@
|
||||
"""
|
||||
Unit tests for bioxel module.
|
||||
|
||||
Tests cover:
|
||||
- Data class functionality
|
||||
- Parsing NRRD and DICOM formats
|
||||
- Layer creation and transformation
|
||||
- Utility functions
|
||||
"""
|
||||
|
||||
from bioxelnodes.bioxel import (
|
||||
Data,
|
||||
read,
|
||||
read_meta,
|
||||
calc_layer_shape,
|
||||
calc_layer_size,
|
||||
Layer,
|
||||
)
|
||||
import pytest
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
||||
|
||||
|
||||
# Test data files
|
||||
NRRD_FILE = Path(__file__).parent / "data/VHP_M.nrrd"
|
||||
DICOM_FILE = Path(__file__).parent / "data/VHP_M_CT_Head/IM000001.dcm"
|
||||
DICOM_DIR = Path(__file__).parent / "data/VHP_M_CT_Head/"
|
||||
|
||||
|
||||
class TestDataClass:
|
||||
"""Test Data class initialization and properties"""
|
||||
|
||||
def test_data_initialization(self):
|
||||
"""Test basic Data initialization with NRRD"""
|
||||
data = Data(filepath=str(NRRD_FILE), series_id="")
|
||||
|
||||
assert data.filepath is not None
|
||||
assert data.series_id == ""
|
||||
assert data._data is None
|
||||
assert data._meta is None
|
||||
|
||||
def test_read_meta_nrrd(self):
|
||||
"""Test reading only meta from NRRD file"""
|
||||
data_obj = read_meta(str(NRRD_FILE))
|
||||
|
||||
assert data_obj._data is None
|
||||
assert data_obj._meta is not None
|
||||
assert "name" in data_obj.meta
|
||||
assert "spacing" in data_obj.meta
|
||||
assert "affine" in data_obj.meta
|
||||
assert "xyz_shape" in data_obj.meta
|
||||
assert "frame_count" in data_obj.meta
|
||||
assert "channel_count" in data_obj.meta
|
||||
assert "dtype" in data_obj.meta
|
||||
|
||||
def test_read_meta_dicom(self):
|
||||
"""Test reading only meta from DICOM file"""
|
||||
data_obj = read_meta(str(DICOM_FILE), series_id="")
|
||||
|
||||
assert data_obj._data is None
|
||||
assert data_obj._meta is not None
|
||||
assert "name" in data_obj.meta
|
||||
|
||||
def test_read_full_data_nrrd(self):
|
||||
"""Test reading full NRRD data"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
assert data_obj._data is not None
|
||||
assert data_obj._meta is not None
|
||||
assert isinstance(data_obj.data, np.ndarray)
|
||||
assert data_obj.data.ndim == 5
|
||||
|
||||
def test_read_full_data_dicom(self):
|
||||
"""Test reading full DICOM data"""
|
||||
data_obj = read(str(DICOM_FILE), series_id="")
|
||||
|
||||
assert data_obj._data is not None
|
||||
assert data_obj._meta is not None
|
||||
assert isinstance(data_obj.data, np.ndarray)
|
||||
|
||||
def test_data_properties_nrrd(self):
|
||||
"""Test Data property accessors"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
assert hasattr(data_obj, "name")
|
||||
assert hasattr(data_obj, "description")
|
||||
assert hasattr(data_obj, "shape")
|
||||
assert hasattr(data_obj, "xyz_shape")
|
||||
assert hasattr(data_obj, "spacing")
|
||||
assert hasattr(data_obj, "affine")
|
||||
assert hasattr(data_obj, "frame_count")
|
||||
assert hasattr(data_obj, "channel_count")
|
||||
assert hasattr(data_obj, "dtype")
|
||||
|
||||
assert isinstance(data_obj.shape, tuple)
|
||||
assert isinstance(data_obj.xyz_shape, tuple)
|
||||
assert isinstance(data_obj.spacing, tuple)
|
||||
assert isinstance(data_obj.affine, np.ndarray)
|
||||
assert isinstance(data_obj.dtype, np.dtype)
|
||||
|
||||
def test_data_lazy_loading(self):
|
||||
"""Test lazy loading behavior"""
|
||||
data_obj = Data(filepath=str(NRRD_FILE), series_id="")
|
||||
|
||||
assert data_obj.is_loaded() is False
|
||||
|
||||
data_obj.load_meta()
|
||||
assert data_obj._data is None
|
||||
assert data_obj._meta is not None
|
||||
|
||||
data_obj.load_data()
|
||||
assert data_obj._data is not None
|
||||
assert data_obj.is_loaded() is True
|
||||
|
||||
def test_shape_property(self):
|
||||
"""Test shape property returns TXYZC format"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
shape = data_obj.shape
|
||||
assert len(shape) == 5
|
||||
assert shape[0] == data_obj.meta["frame_count"]
|
||||
assert shape[1:4] == data_obj.meta["xyz_shape"]
|
||||
assert shape[4] == data_obj.meta["channel_count"]
|
||||
|
||||
def test_load_meta_method(self):
|
||||
"""Test load_meta() method exists and works"""
|
||||
data_obj = Data(filepath=str(NRRD_FILE), series_id="")
|
||||
|
||||
assert hasattr(data_obj, "load_meta")
|
||||
|
||||
data_obj.load_meta()
|
||||
assert data_obj._meta is not None
|
||||
assert data_obj._data is None
|
||||
|
||||
|
||||
class TestDataToLayers:
|
||||
"""Test Data.to_layers() method"""
|
||||
|
||||
def test_to_layers_dicom_scalar(self):
|
||||
"""Test converting DICOM data to scalar layers"""
|
||||
data_obj = read(str(DICOM_FILE), series_id="")
|
||||
|
||||
layers = data_obj.to_layers(kind="scalar", bioxel_size=2.0)
|
||||
|
||||
assert len(layers) == 1
|
||||
assert layers[0].kind == "scalar"
|
||||
assert isinstance(layers[0], Layer)
|
||||
|
||||
def test_to_layers_nrrd_scalar(self):
|
||||
"""Test converting NRRD data to scalar layers"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
layers = data_obj.to_layers(kind="scalar", bioxel_size=2.0)
|
||||
|
||||
assert len(layers) == 1
|
||||
assert layers[0].kind == "scalar"
|
||||
assert isinstance(layers[0], Layer)
|
||||
|
||||
def test_to_layers_with_remap(self):
|
||||
"""Test data remapping"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
layers = data_obj.to_layers(kind="scalar", bioxel_size=2.0, remap=True)
|
||||
|
||||
assert len(layers) == 1
|
||||
assert layers[0].data.dtype == np.float32
|
||||
assert np.all(layers[0].data >= 0)
|
||||
assert np.all(layers[0].data <= 1)
|
||||
|
||||
def test_to_layers_with_bioxel_size(self):
|
||||
"""Test with different bioxel sizes"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
layers_1x = data_obj.to_layers(kind="scalar", bioxel_size=1.0)
|
||||
layers_2x = data_obj.to_layers(kind="scalar", bioxel_size=2.0)
|
||||
|
||||
assert len(layers_1x) == 1
|
||||
assert len(layers_2x) == 1
|
||||
|
||||
# Larger bioxel size means smaller layer
|
||||
shape_1x = layers_1x[0].shape
|
||||
shape_2x = layers_2x[0].shape
|
||||
assert shape_2x[0] <= shape_1x[0]
|
||||
assert shape_2x[1] <= shape_1x[1]
|
||||
assert shape_2x[2] <= shape_1x[2]
|
||||
|
||||
def test_to_layers_with_smooth(self):
|
||||
"""Test with smoothing parameter"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
layers = data_obj.to_layers(kind="scalar", bioxel_size=2.0, smooth=1)
|
||||
|
||||
assert len(layers) == 1
|
||||
assert isinstance(layers[0], Layer)
|
||||
|
||||
def test_to_layers_frame_source_first_frame(self):
|
||||
"""Test frame_source = '-1' (first frame only)"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
layers = data_obj.to_layers(kind="scalar", bioxel_size=2.0, frame_source="-1")
|
||||
|
||||
assert len(layers) == 1
|
||||
assert layers[0].frame_count == 1
|
||||
|
||||
|
||||
class TestUtilityFunctions:
|
||||
"""Test utility functions"""
|
||||
|
||||
def test_calc_layer_shape(self):
|
||||
"""Test layer shape calculation"""
|
||||
shape = calc_layer_shape(
|
||||
bioxel_size=1.0, orig_shape=(100, 100, 100), orig_spacing=(1.0, 1.0, 1.0)
|
||||
)
|
||||
|
||||
assert len(shape) == 3
|
||||
assert shape[0] == 100
|
||||
assert shape[1] == 100
|
||||
assert shape[2] == 100
|
||||
|
||||
def test_calc_layer_shape_small_bioxel(self):
|
||||
"""Test layer shape with small bioxel size"""
|
||||
shape = calc_layer_shape(
|
||||
bioxel_size=2.0, orig_shape=(100, 100, 100), orig_spacing=(1.0, 1.0, 1.0)
|
||||
)
|
||||
|
||||
assert len(shape) == 3
|
||||
assert shape[0] == 50
|
||||
assert shape[1] == 50
|
||||
assert shape[2] == 50
|
||||
|
||||
def test_calc_layer_shape_minimum(self):
|
||||
"""Test layer shape doesn't go below 1"""
|
||||
shape = calc_layer_shape(
|
||||
bioxel_size=100.0, orig_shape=(100, 100, 100), orig_spacing=(1.0, 1.0, 1.0)
|
||||
)
|
||||
|
||||
assert shape == (1, 1, 1)
|
||||
|
||||
def test_calc_layer_size(self):
|
||||
"""Test layer size calculation"""
|
||||
size = calc_layer_size(shape=(100, 100, 100), bioxel_size=1.0, scale=1.0)
|
||||
|
||||
assert len(size) == 3
|
||||
assert size[0] == 100.0
|
||||
assert size[1] == 100.0
|
||||
assert size[2] == 100.0
|
||||
|
||||
def test_calc_layer_size_with_scale(self):
|
||||
"""Test layer size with scale factor"""
|
||||
size = calc_layer_size(shape=(100, 100, 100), bioxel_size=1.0, scale=0.01)
|
||||
|
||||
assert len(size) == 3
|
||||
assert size[0] == 1.0
|
||||
assert size[1] == 1.0
|
||||
assert size[2] == 1.0
|
||||
|
||||
|
||||
class TestLayerClass:
|
||||
"""Test Layer class functionality"""
|
||||
|
||||
def test_layer_initialization(self):
|
||||
"""Test Layer initialization"""
|
||||
data = np.random.rand(1, 8, 8, 8, 1).astype(np.float32)
|
||||
layer = Layer(data=data, name="test_layer", kind="scalar")
|
||||
|
||||
assert layer.name == "test_layer"
|
||||
assert layer.kind == "scalar"
|
||||
assert np.array_equal(layer.data, data)
|
||||
|
||||
def test_layer_properties(self):
|
||||
"""Test Layer property accessors"""
|
||||
data = np.random.rand(1, 8, 8, 8, 1).astype(np.float32)
|
||||
layer = Layer(
|
||||
data=data, name="test_layer", kind="scalar", affine=np.identity(4)
|
||||
)
|
||||
|
||||
assert hasattr(layer, "bioxel_size")
|
||||
assert hasattr(layer, "shape")
|
||||
assert hasattr(layer, "dtype")
|
||||
assert hasattr(layer, "origin")
|
||||
assert hasattr(layer, "euler")
|
||||
assert hasattr(layer, "frame_count")
|
||||
assert hasattr(layer, "channel_count")
|
||||
assert hasattr(layer, "min")
|
||||
assert hasattr(layer, "max")
|
||||
|
||||
def test_layer_shape_property(self):
|
||||
"""Test layer shape returns XYZ (not TXYZC)"""
|
||||
data = np.random.rand(1, 8, 8, 8, 1).astype(np.float32)
|
||||
layer = Layer(data=data, name="test", kind="scalar")
|
||||
|
||||
shape = layer.shape
|
||||
assert len(shape) == 3
|
||||
assert shape[0] == 8
|
||||
assert shape[1] == 8
|
||||
assert shape[2] == 8
|
||||
|
||||
def test_layer_affine_property(self):
|
||||
"""Test affine transformation matrix"""
|
||||
data = np.random.rand(1, 8, 8, 8, 1).astype(np.float32)
|
||||
affine = np.array(
|
||||
[
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
]
|
||||
)
|
||||
layer = Layer(data=data, name="test", kind="scalar", affine=affine)
|
||||
|
||||
result_affine = layer.affine
|
||||
assert result_affine.shape == (4, 4)
|
||||
assert np.allclose(result_affine, affine)
|
||||
|
||||
def test_layer_min_max(self):
|
||||
"""Test layer min/max properties"""
|
||||
data = np.random.rand(1, 8, 8, 8, 1).astype(np.float32)
|
||||
layer = Layer(data=data, name="test", kind="scalar")
|
||||
|
||||
assert isinstance(layer.min, float)
|
||||
assert isinstance(layer.max, float)
|
||||
assert layer.min <= layer.max
|
||||
|
||||
def test_layer_copy(self):
|
||||
"""Test layer deep copy"""
|
||||
data = np.random.rand(1, 8, 8, 8, 1).astype(np.float32)
|
||||
layer = Layer(data=data, name="test", kind="scalar")
|
||||
layer_copy = layer.copy()
|
||||
|
||||
assert layer_copy.name == layer.name
|
||||
assert layer_copy.kind == layer.kind
|
||||
assert np.array_equal(layer_copy.data, layer.data)
|
||||
|
||||
assert layer_copy is not layer
|
||||
layer_copy.data[0, 0, 0, 0, 0] = 999.0
|
||||
assert layer.data[0, 0, 0, 0, 0] != 999.0
|
||||
|
||||
def test_layer_invalid_data_shape(self):
|
||||
"""Test Layer raises error for invalid data shape"""
|
||||
data = np.random.rand(8, 8, 8).astype(np.float32)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Layer(data=data, name="test", kind="scalar")
|
||||
|
||||
def test_layer_invalid_affine_shape(self):
|
||||
"""Test Layer raises error for invalid affine shape"""
|
||||
data = np.random.rand(1, 8, 8, 8, 1).astype(np.float32)
|
||||
affine = np.identity(3)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Layer(data=data, name="test", kind="scalar", affine=affine)
|
||||
|
||||
|
||||
class TestConstants:
|
||||
"""Test module constants"""
|
||||
|
||||
def test_support_exts_exists(self):
|
||||
"""Test SUPPORT_EXTS constant"""
|
||||
from bioxelnodes.bioxel import SUPPORT_EXTS
|
||||
|
||||
assert isinstance(SUPPORT_EXTS, list)
|
||||
assert len(SUPPORT_EXTS) > 0
|
||||
assert ".h5" in SUPPORT_EXTS
|
||||
assert ".nrrd" in SUPPORT_EXTS
|
||||
assert ".nii.gz" in SUPPORT_EXTS
|
||||
assert ".dcm" in SUPPORT_EXTS
|
||||
|
||||
def test_dicom_exts_exists(self):
|
||||
"""Test DICOM_EXTS constant"""
|
||||
from bioxelnodes.bioxel import DICOM_EXTS
|
||||
|
||||
assert isinstance(DICOM_EXTS, list)
|
||||
assert "" in DICOM_EXTS
|
||||
assert ".dcm" in DICOM_EXTS
|
||||
assert ".DCM" in DICOM_EXTS
|
||||
|
||||
def test_ome_exts_exists(self):
|
||||
"""Test OME_EXTS constant"""
|
||||
from bioxelnodes.bioxel import OME_EXTS
|
||||
|
||||
assert isinstance(OME_EXTS, list)
|
||||
assert ".tiff" in OME_EXTS
|
||||
assert ".ome.tiff" in OME_EXTS
|
||||
|
||||
def test_mrc_exts_exists(self):
|
||||
"""Test MRC_EXTS constant"""
|
||||
from bioxelnodes.bioxel import MRC_EXTS
|
||||
|
||||
assert isinstance(MRC_EXTS, list)
|
||||
assert ".mrc" in MRC_EXTS
|
||||
|
||||
|
||||
class TestEdgeCases:
|
||||
"""Test edge cases and error handling"""
|
||||
|
||||
def test_nonexistent_file(self):
|
||||
"""Test handling of nonexistent file"""
|
||||
with pytest.raises(FileNotFoundError):
|
||||
data_obj = read("/nonexistent/file.nii.gz", "")
|
||||
|
||||
def test_frame_count_property_multiframe(self):
|
||||
"""Test frame_count property for multi-frame data"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
assert data_obj.frame_count >= 1
|
||||
assert data_obj.frame_count == data_obj.meta["frame_count"]
|
||||
|
||||
def test_channel_count_property(self):
|
||||
"""Test channel_count property"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
assert data_obj.channel_count >= 1
|
||||
assert data_obj.channel_count == data_obj.meta["channel_count"]
|
||||
|
||||
def test_nrrd_format_support(self):
|
||||
"""Test NRRD format is in supported extensions"""
|
||||
from bioxelnodes.bioxel import SUPPORT_EXTS
|
||||
|
||||
assert ".nrrd" in SUPPORT_EXTS
|
||||
|
||||
def test_dicom_format_support(self):
|
||||
"""Test DICOM format is in supported extensions"""
|
||||
from bioxelnodes.bioxel import SUPPORT_EXTS
|
||||
|
||||
assert ".dcm" in SUPPORT_EXTS
|
||||
assert "" in SUPPORT_EXTS
|
||||
|
||||
|
||||
class TestRealDataFiles:
|
||||
"""Test with real data files"""
|
||||
|
||||
def test_nrrd_file_exists(self):
|
||||
"""Test NRRD test file exists"""
|
||||
assert NRRD_FILE.exists()
|
||||
assert NRRD_FILE.is_file()
|
||||
|
||||
def test_dicom_file_exists(self):
|
||||
"""Test DICOM test file exists"""
|
||||
assert DICOM_FILE.exists()
|
||||
assert DICOM_FILE.is_file()
|
||||
|
||||
def test_dicom_directory_exists(self):
|
||||
"""Test DICOM directory exists"""
|
||||
assert DICOM_DIR.exists()
|
||||
assert DICOM_DIR.is_dir()
|
||||
|
||||
def test_read_nrrd_data(self):
|
||||
"""Test reading real NRRD data"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
assert data_obj._data is not None
|
||||
assert data_obj._meta is not None
|
||||
assert isinstance(data_obj.data, np.ndarray)
|
||||
assert data_obj.data.size > 0
|
||||
|
||||
def test_read_dicom_data(self):
|
||||
"""Test reading real DICOM data"""
|
||||
data_obj = read(str(DICOM_FILE), series_id="")
|
||||
|
||||
assert data_obj._data is not None
|
||||
assert data_obj._meta is not None
|
||||
assert isinstance(data_obj.data, np.ndarray)
|
||||
assert data_obj.data.size > 0
|
||||
|
||||
def test_nrrd_meta_contains_required_fields(self):
|
||||
"""Test NRRD meta contains all required fields"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
meta = data_obj.meta
|
||||
|
||||
required_fields = [
|
||||
"name",
|
||||
"description",
|
||||
"spacing",
|
||||
"affine",
|
||||
"xyz_shape",
|
||||
"frame_count",
|
||||
"channel_count",
|
||||
"dtype",
|
||||
]
|
||||
|
||||
for field in required_fields:
|
||||
assert field in meta, f"Meta missing field: {field}"
|
||||
|
||||
def test_dicom_meta_contains_required_fields(self):
|
||||
"""Test DICOM meta contains all required fields"""
|
||||
data_obj = read(str(DICOM_FILE), series_id="")
|
||||
meta = data_obj.meta
|
||||
|
||||
required_fields = [
|
||||
"name",
|
||||
"description",
|
||||
"spacing",
|
||||
"affine",
|
||||
"xyz_shape",
|
||||
"frame_count",
|
||||
"channel_count",
|
||||
"dtype",
|
||||
]
|
||||
|
||||
for field in required_fields:
|
||||
assert field in meta, f"Meta missing field: {field}"
|
||||
|
||||
def test_data_to_layers_with_real_nrrd(self):
|
||||
"""Test to_layers with real NRRD data"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
|
||||
layers = data_obj.to_layers(kind="scalar", bioxel_size=3.0)
|
||||
|
||||
assert len(layers) > 0
|
||||
assert all(isinstance(layer, Layer) for layer in layers)
|
||||
|
||||
def test_data_to_layers_with_real_dicom(self):
|
||||
"""Test to_layers with real DICOM data"""
|
||||
data_obj = read(str(DICOM_FILE), series_id="")
|
||||
|
||||
layers = data_obj.to_layers(kind="scalar", bioxel_size=3.0)
|
||||
|
||||
assert len(layers) > 0
|
||||
assert all(isinstance(layer, Layer) for layer in layers)
|
||||
|
||||
def test_nrrd_spacing_values(self):
|
||||
"""Test NRRD spacing values are reasonable"""
|
||||
data_obj = read(str(NRRD_FILE))
|
||||
spacing = data_obj.spacing
|
||||
|
||||
assert len(spacing) == 3
|
||||
assert all(s > 0 for s in spacing)
|
||||
|
||||
def test_dicom_spacing_values(self):
|
||||
"""Test DICOM spacing values are reasonable"""
|
||||
data_obj = read(str(DICOM_FILE), series_id="")
|
||||
spacing = data_obj.spacing
|
||||
|
||||
assert len(spacing) == 3
|
||||
assert all(s > 0 for s in spacing)
|
||||
Reference in New Issue
Block a user