Add All Folders

This commit is contained in:
soorena62
2026-02-08 04:38:10 +03:30
commit 9bb57e4799
191 changed files with 8469 additions and 0 deletions
@@ -0,0 +1,13 @@
from core.contracts import RequirementContract, DataContract
def check_compatibility(
provider: DataContract,
requirement: RequirementContract
):
# Central compatibility entry point
try:
requirement.is_satisfied_by(provider)
return True, "Compatible"
except ValueError as e:
return False, str(e)
@@ -0,0 +1,14 @@
from enum import Enum
from pydantic import BaseModel
class CompatibilityLevel(str, Enum):
OK = "OK"
WARNING = "WARNING"
ERROR = "ERROR"
class CompatibilityResult(BaseModel):
level: CompatibilityLevel
message: str
suggestion: str | None = None
+36
View File
@@ -0,0 +1,36 @@
from pydantic import BaseModel
from typing import Literal, Set
class DataContract(BaseModel):
# Describes what a node produces
data_type: str
provides: Set[str] = set()
class ImageDataContract(DataContract):
data_type: Literal["image"] = "image"
modality: Literal["CT", "MR"]
dim: Literal["2D", "3D"]
has_mask: bool = False
provides: Set[str] = {"image"}
class MaskDataContract(DataContract):
data_type: Literal["mask"] = "mask"
modality: Literal["CT", "MR"]
dim: Literal["2D", "3D"]
provides: Set[str] = {"mask"}
class FeatureTableContract(DataContract):
data_type: Literal["table"] = "table"
provides: Set[str] = {"features"}
class RequirementContract(BaseModel):
# Describes what a node requires
data_type: str
def is_satisfied_by(self, provided: DataContract):
raise NotImplementedError
@@ -0,0 +1,21 @@
from pydantic import BaseModel
from typing import Literal, Set, Optional
class ImageDataContract(BaseModel):
data_type: Literal["image"] = "image"
modality: Literal["CT", "PET", "MR"]
dim: Literal["2D", "3D", "2_5D"]
geometry_id: Optional[str] = None
has_mask: bool = False
class MaskDataContract(BaseModel):
data_type: Literal["mask"] = "mask"
modality: Literal["CT", "PET", "MR"]
dim: Literal["2D", "3D", "2_5D"]
geometry_id: Optional[str] = None
class RadiomicsFeatureTableContract(BaseModel):
data_type: Literal["table"] = "table"
+15
View File
@@ -0,0 +1,15 @@
from typing import Dict
from core.contracts import DataContract, RequirementContract
class Node:
# Base class for all processing nodes
name: str
inputs: Dict[str, RequirementContract]
outputs: Dict[str, DataContract]
def __init__(self, name, inputs=None, outputs=None):
self.name = name
self.inputs = inputs or {}
self.outputs = outputs or {}
+12
View File
@@ -0,0 +1,12 @@
class Workflow:
def __init__(self):
self.connections = []
def connect(self, out_node, out_port, in_node, in_port):
self.connections.append((out_node, out_port, in_node, in_port))
def get_connection(self, in_node, in_port):
for o_node, o_port, i_node, i_port in self.connections:
if i_node == in_node and i_port == in_port:
return o_node, o_port
raise RuntimeError("Missing connection")
@@ -0,0 +1,34 @@
class WorkflowExecutor:
def __init__(self, graph):
self.graph = graph
self.cache = {}
def execute_from_writer(self, writer_node):
# Execute dependencies first
inputs = {}
for port_name, requirement in writer_node.inputs.items():
src_node, src_port = self.graph.get_connection(writer_node, port_name)
inputs[port_name] = self._execute_node(src_node)
writer_node.run(**inputs)
def _execute_node(self, node):
if node in self.cache:
return self.cache[node]
# Reader
if hasattr(node, "load_data"):
result = node.load_data()
self.cache[node] = result
return result
# Processor
inputs = {}
for port_name in node.inputs:
src_node, _ = self.graph.get_connection(node, port_name)
inputs[port_name] = self._execute_node(src_node)
result = node.run(**inputs)
self.cache[node] = result
return result
+19
View File
@@ -0,0 +1,19 @@
from core.workflow import Graph
from nodes.image_reader import ImageReader
from nodes.feature_extractor import FeatureExtractor
from nodes.feature_writer import FeatureWriter
graph = Graph()
reader = ImageReader(modality="CT")
extractor = FeatureExtractor()
writer = FeatureWriter()
# ImageReader → FeatureExtractor
ok, msg = graph.can_connect(reader, "image", extractor, "image")
print("Reader → Extractor:", ok, msg)
# FeatureExtractor → FeatureWriter
ok, msg = graph.can_connect(extractor, "features", writer, "features")
print("Extractor → Writer:", ok, msg)
+14
View File
@@ -0,0 +1,14 @@
import sys
from PySide6.QtWidgets import QApplication
from gui.visual_editor import VisualEditor
def main():
app = QApplication(sys.argv)
window = VisualEditor()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
+164
View File
@@ -0,0 +1,164 @@
import sys
from PySide6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QTextEdit,
QPushButton, QLabel, QSplitter
)
from PySide6.QtCore import Qt
from PySide6 import QtGui
from NodeGraphQt import NodeGraph, BaseNode
from core.workflow import Graph
from nodes.image_reader import ImageReader
from nodes.feature_extractor import FeatureExtractor
from nodes.feature_writer import FeatureWriter
# GUI node wrappers around core nodes
class ImageReaderNode(BaseNode):
__identifier__ = "radiuma.gui"
NODE_NAME = "ImageReader"
def __init__(self):
super().__init__()
self.add_output("image")
self.core_node = ImageReader()
class FeatureExtractorNode(BaseNode):
__identifier__ = "radiuma.gui"
NODE_NAME = "FeatureExtractor"
def __init__(self):
super().__init__()
self.add_input("image")
self.add_output("features")
self.core_node = FeatureExtractor()
class FeatureWriterNode(BaseNode):
__identifier__ = "radiuma.gui"
NODE_NAME = "FeatureWriter"
def __init__(self):
super().__init__()
self.add_input("features")
self.core_node = FeatureWriter()
class VisualEditor(QMainWindow):
# Visual workflow editor with dynamic semantic validation
def __init__(self):
super().__init__()
self.setWindowTitle("Radiuma R&D Semantic Workflow Editor")
self.resize(1200, 800)
self.graph_engine = Graph()
self.graph = NodeGraph()
self.viewer = self.graph.widget
self.graph.register_node(ImageReaderNode)
self.graph.register_node(FeatureExtractorNode)
self.graph.register_node(FeatureWriterNode)
self.log_panel = QTextEdit()
self.log_panel.setReadOnly(True)
btn_reader = QPushButton("Add ImageReader")
btn_extractor = QPushButton("Add FeatureExtractor")
btn_writer = QPushButton("Add FeatureWriter")
btn_reader.clicked.connect(
lambda: self.graph.create_node("radiuma.gui.ImageReaderNode")
)
btn_extractor.clicked.connect(
lambda: self.graph.create_node("radiuma.gui.FeatureExtractorNode")
)
btn_writer.clicked.connect(
lambda: self.graph.create_node("radiuma.gui.FeatureWriterNode")
)
left_panel = QWidget()
left_layout = QVBoxLayout(left_panel)
left_layout.addWidget(QLabel("Nodes"))
left_layout.addWidget(btn_reader)
left_layout.addWidget(btn_extractor)
left_layout.addWidget(btn_writer)
left_layout.addStretch()
top_splitter = QSplitter(Qt.Horizontal)
top_splitter.addWidget(left_panel)
top_splitter.addWidget(self.viewer)
main_splitter = QSplitter(Qt.Vertical)
main_splitter.addWidget(top_splitter)
main_splitter.addWidget(self.log_panel)
main_splitter.setSizes([600, 200])
self.setCentralWidget(main_splitter)
self.graph.port_connected.connect(self._on_port_connected)
self.graph.port_disconnected.connect(self._on_port_disconnected)
def log(self, msg, level="INFO"):
self.log_panel.append(f"[{level}] {msg}")
def _on_port_connected(self, port_a, port_b):
node_a = port_a.node()
node_b = port_b.node()
core_a = getattr(node_a, "core_node", None)
core_b = getattr(node_b, "core_node", None)
if not core_a or not core_b:
self._color_connection(port_a, port_b, (200, 200, 200))
self.log("Missing core node mapping", "ERROR")
return
# Normalize out → in
if port_a.type_() == "out":
out_node, out_port = core_a, port_a.name()
in_node, in_port = core_b, port_b.name()
gui_out, gui_in = port_a, port_b
else:
out_node, out_port = core_b, port_b.name()
in_node, in_port = core_a, port_a.name()
gui_out, gui_in = port_b, port_a
ok, msg = self.graph_engine.can_connect(
out_node, out_port, in_node, in_port
)
if ok:
self._color_connection(gui_out, gui_in, (46, 204, 113))
self.log(msg, "INFO")
else:
self._color_connection(gui_out, gui_in, (231, 76, 60))
self.log(msg, "ERROR")
def _on_port_disconnected(self, port_a, port_b):
self._color_connection(port_a, port_b, (180, 180, 180))
self.log("Disconnected ports", "INFO")
def _color_connection(self, port_a, port_b, color):
# Patch connection paint dynamically
scene = self.graph._viewer.scene()
for item in scene.items():
if hasattr(item, "port1") and hasattr(item, "port2"):
if {item.port1, item.port2} == {port_a, port_b}:
setattr(item, "_custom_color", color)
if not hasattr(item, "_patched"):
original_paint = item.paint
def patched_paint(painter, option, widget=None,
_orig=original_paint, _item=item):
pen = painter.pen()
pen.setColor(QtGui.QColor(*_item._custom_color))
painter.setPen(pen)
_orig(painter, option, widget)
item.paint = patched_paint
item._patched = True
item.update()
return
@@ -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