Add All Folders
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
from typing import Set
|
||||
from core.node import Node
|
||||
from core.contracts import (
|
||||
RequirementContract,
|
||||
ImageDataContract,
|
||||
FeatureTableContract
|
||||
)
|
||||
|
||||
|
||||
class ImageRequirement(RequirementContract):
|
||||
data_type: str = "image"
|
||||
modality: Set[str]
|
||||
dim: Set[str]
|
||||
requires_mask: bool = False
|
||||
|
||||
def is_satisfied_by(self, provided: ImageDataContract):
|
||||
# Validate semantic compatibility
|
||||
if provided.data_type != self.data_type:
|
||||
raise ValueError("Data type mismatch")
|
||||
|
||||
if provided.modality not in self.modality:
|
||||
raise ValueError("Modality mismatch")
|
||||
|
||||
if provided.dim not in self.dim:
|
||||
raise ValueError("Dimension mismatch")
|
||||
|
||||
if self.requires_mask and not provided.has_mask:
|
||||
raise ValueError("Mask is required but not provided")
|
||||
|
||||
|
||||
class FeatureExtractor(Node):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name="FeatureExtractor",
|
||||
inputs={
|
||||
"image": ImageRequirement(
|
||||
modality={"CT"},
|
||||
dim={"3D"},
|
||||
requires_mask=False
|
||||
)
|
||||
},
|
||||
outputs={
|
||||
"features": FeatureTableContract()
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
import json
|
||||
from core.node import Node
|
||||
from core.compatibility_result import CompatibilityResult, CompatibilityLevel
|
||||
|
||||
|
||||
class FeatureWriteRequirement:
|
||||
data_type: str = "table"
|
||||
|
||||
def check(self, provided):
|
||||
if provided.data_type != self.data_type:
|
||||
return CompatibilityResult(
|
||||
level=CompatibilityLevel.ERROR,
|
||||
message="FeatureWriter requires feature table input"
|
||||
)
|
||||
return CompatibilityResult(
|
||||
level=CompatibilityLevel.OK,
|
||||
message="Compatible"
|
||||
)
|
||||
|
||||
|
||||
class FeatureWriter(Node):
|
||||
def __init__(self, output_path: str):
|
||||
self.output_path = output_path
|
||||
|
||||
super().__init__(
|
||||
name="FeatureWriter",
|
||||
inputs={"features": FeatureWriteRequirement()},
|
||||
outputs={}
|
||||
)
|
||||
|
||||
def run(self, features):
|
||||
with open(self.output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(features, f, indent=2)
|
||||
@@ -0,0 +1,37 @@
|
||||
import nibabel as nib
|
||||
from core.node import Node
|
||||
from core.contracts_radiomics import ImageDataContract
|
||||
from pathlib import Path
|
||||
import uuid
|
||||
|
||||
|
||||
class ImageReader(Node):
|
||||
def __init__(self, path: str):
|
||||
self.path = Path(path)
|
||||
|
||||
# Read image header only
|
||||
img = nib.load(str(self.path))
|
||||
header = img.header
|
||||
|
||||
# Infer metadata
|
||||
dim = "3D" if img.ndim == 3 else "2D"
|
||||
modality = header.get("descrip", b"CT").decode(errors="ignore") or "CT"
|
||||
|
||||
geometry_id = str(uuid.uuid4())
|
||||
|
||||
super().__init__(
|
||||
name="ImageReader",
|
||||
outputs={
|
||||
"image": ImageDataContract(
|
||||
modality=modality,
|
||||
dim=dim,
|
||||
geometry_id=geometry_id,
|
||||
has_mask=False
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
def load_data(self):
|
||||
# Load full image data only when execution starts
|
||||
img = nib.load(str(self.path))
|
||||
return img.get_fdata()
|
||||
@@ -0,0 +1,35 @@
|
||||
import nibabel as nib
|
||||
from core.node import Node
|
||||
from core.compatibility_result import CompatibilityResult, CompatibilityLevel
|
||||
|
||||
|
||||
class ImageWriteRequirement:
|
||||
data_type: str = "image"
|
||||
|
||||
def check(self, provided):
|
||||
if provided.data_type != self.data_type:
|
||||
return CompatibilityResult(
|
||||
level=CompatibilityLevel.ERROR,
|
||||
message="ImageWriter requires image input"
|
||||
)
|
||||
return CompatibilityResult(
|
||||
level=CompatibilityLevel.OK,
|
||||
message="Compatible"
|
||||
)
|
||||
|
||||
|
||||
class ImageWriter(Node):
|
||||
def __init__(self, output_path: str):
|
||||
self.output_path = output_path
|
||||
|
||||
super().__init__(
|
||||
name="ImageWriter",
|
||||
inputs={"image": ImageWriteRequirement()},
|
||||
outputs={}
|
||||
)
|
||||
|
||||
def run(self, image_array):
|
||||
nib.save(
|
||||
nib.Nifti1Image(image_array, None),
|
||||
self.output_path
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
import nibabel as nib
|
||||
from core.node import Node
|
||||
from core.contracts_radiomics import MaskDataContract
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class MaskReader(Node):
|
||||
def __init__(self, path: str, geometry_id: str):
|
||||
self.path = Path(path)
|
||||
|
||||
img = nib.load(str(self.path))
|
||||
dim = "3D" if img.ndim == 3 else "2D"
|
||||
|
||||
super().__init__(
|
||||
name="MaskReader",
|
||||
outputs={
|
||||
"mask": MaskDataContract(
|
||||
modality="CT",
|
||||
dim=dim,
|
||||
geometry_id=geometry_id
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
def load_data(self):
|
||||
img = nib.load(str(self.path))
|
||||
return img.get_fdata()
|
||||
@@ -0,0 +1,23 @@
|
||||
import pysera
|
||||
from core.node import Node
|
||||
from core.contracts_radiomics import RadiomicsFeatureTableContract
|
||||
|
||||
|
||||
class RadiomicsFeatureGenerator(Node):
|
||||
def __init__(self, output_path="./results"):
|
||||
self.output_path = output_path
|
||||
|
||||
super().__init__(
|
||||
name="RadiomicsFeatureGenerator",
|
||||
inputs={"image": None, "mask": None},
|
||||
outputs={"features": RadiomicsFeatureTableContract()}
|
||||
)
|
||||
|
||||
def run(self, image_path, mask_path):
|
||||
result = pysera.process_batch(
|
||||
image_input=image_path,
|
||||
mask_input=mask_path,
|
||||
output_path=self.output_path,
|
||||
report="info"
|
||||
)
|
||||
return result
|
||||
Reference in New Issue
Block a user