Add All Folders
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,72 @@
|
||||
import json
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from loguru import logger
|
||||
|
||||
class WorkflowState:
|
||||
def __init__(self, state_dir: Path):
|
||||
self.state_dir = state_dir
|
||||
self.state_file = state_dir / "state.json"
|
||||
self._lock = threading.Lock()
|
||||
self._state = {"steps": {}, "cancelled": False}
|
||||
self.state_dir.mkdir(parents=True, exist_ok=True)
|
||||
if self.state_file.exists():
|
||||
self._state = json.loads(self.state_file.read_text())
|
||||
|
||||
def mark_success(self, step_id: str, outputs: dict):
|
||||
with self._lock:
|
||||
self._state["steps"][step_id] = {"status": "success", "outputs": outputs}
|
||||
self._write()
|
||||
|
||||
def mark_failed(self, step_id: str, error: str):
|
||||
with self._lock:
|
||||
self._state["steps"][step_id] = {"status": "failed", "error": error}
|
||||
self._write()
|
||||
|
||||
def get_status(self, step_id: str):
|
||||
return self._state["steps"].get(step_id, {}).get("status", "pending")
|
||||
|
||||
def get_outputs(self, step_id: str):
|
||||
return self._state["steps"].get(step_id, {}).get("outputs", {})
|
||||
|
||||
def set_cancelled(self, flag: bool):
|
||||
with self._lock:
|
||||
self._state["cancelled"] = flag
|
||||
self._write()
|
||||
|
||||
def is_cancelled(self) -> bool:
|
||||
return self._state.get("cancelled", False)
|
||||
|
||||
def _write(self):
|
||||
self.state_file.write_text(json.dumps(self._state, indent=2))
|
||||
|
||||
class Node:
|
||||
def __init__(self, step_id: str, run_fn, inputs: list, outputs: list):
|
||||
self.step_id = step_id
|
||||
self.run_fn = run_fn
|
||||
self.inputs = inputs
|
||||
self.outputs = outputs
|
||||
|
||||
class Workflow:
|
||||
def __init__(self, nodes: list[Node], state: WorkflowState):
|
||||
self.nodes = nodes
|
||||
self.state = state
|
||||
|
||||
def execute(self):
|
||||
for node in self.nodes:
|
||||
status = self.state.get_status(node.step_id)
|
||||
if status == "success":
|
||||
logger.info(f"Skip {node.step_id}: already successful.")
|
||||
continue
|
||||
if self.state.is_cancelled():
|
||||
logger.warning("Execution cancelled. Stopping workflow.")
|
||||
break
|
||||
try:
|
||||
logger.info(f"Run {node.step_id}")
|
||||
outputs = node.run_fn(self.state)
|
||||
self.state.mark_success(node.step_id, outputs)
|
||||
logger.info(f"Success {node.step_id}")
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed {node.step_id}: {e}")
|
||||
self.state.mark_failed(node.step_id, str(e))
|
||||
break # Stop on failure, allow resume later
|
||||
@@ -0,0 +1,39 @@
|
||||
from dagster import op, job, In
|
||||
import os, json
|
||||
from app.features.pysera_node import run_pysera
|
||||
|
||||
DATA_DIR = os.path.join("data", "images")
|
||||
MASK_DIR = os.path.join("data", "masks")
|
||||
OUTPUT_DIR = os.path.join("app", "storage", "artifacts")
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
@op(ins={"filename": In(str)})
|
||||
def load_image(filename: str) -> str:
|
||||
# Return normalized image file path (do not load arrays/objects)
|
||||
return os.path.normpath(os.path.join(DATA_DIR, filename))
|
||||
|
||||
@op(ins={"filename": In(str)})
|
||||
def load_mask(filename: str) -> str:
|
||||
# Return normalized mask file path (do not load arrays/objects)
|
||||
return os.path.normpath(os.path.join(MASK_DIR, filename))
|
||||
|
||||
@op
|
||||
def extract_features(img_path: str, mask_path: str) -> str:
|
||||
# Validate file paths and call PySERA
|
||||
if not os.path.exists(img_path):
|
||||
raise FileNotFoundError(f"Image path not found: {img_path}")
|
||||
if not os.path.exists(mask_path):
|
||||
raise FileNotFoundError(f"Mask path not found: {mask_path}")
|
||||
|
||||
features = run_pysera(img_path, mask_path)
|
||||
out_path = os.path.join(OUTPUT_DIR, "CT_pitch_features.json")
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(features, f, indent=2)
|
||||
return out_path
|
||||
|
||||
@job
|
||||
def radiuma_job():
|
||||
# Compose ops: produce file paths, then extract features
|
||||
img = load_image()
|
||||
mask = load_mask()
|
||||
extract_features(img, mask)
|
||||
@@ -0,0 +1,79 @@
|
||||
import os
|
||||
import pysera
|
||||
from app.engine import radiuma_assets
|
||||
from app.engine.dagster_builder import radiuma_job
|
||||
from dagster import materialize, FilesystemIOManager
|
||||
|
||||
# Main workflow runner for Radiuma using PySERA and Dagster
|
||||
|
||||
def run_workflow():
|
||||
# Define input image and mask paths
|
||||
image_path = os.path.normpath("data/images/CT_pitch.nii.gz")
|
||||
mask_path = os.path.normpath("data/masks/CT_pitch_mask.nii.gz")
|
||||
output_dir = os.path.normpath("./results")
|
||||
|
||||
# Run PySERA radiomics workflow
|
||||
# Changed dimensions from "3d" to "1st,2D"
|
||||
result = pysera.process_batch(
|
||||
image_input=image_path,
|
||||
mask_input=mask_path,
|
||||
output_path=output_dir,
|
||||
categories="all",
|
||||
dimensions="1st,2D", # <-- only change applied here
|
||||
apply_preprocessing=True,
|
||||
)
|
||||
print("✅ PySERA workflow finished." if result.get("success") else "❌ PySERA workflow failed.")
|
||||
|
||||
|
||||
def run_pipeline():
|
||||
# Run Dagster job with sample configuration
|
||||
result = radiuma_job.execute_in_process(
|
||||
run_config={
|
||||
"ops": {
|
||||
"load_image": {"inputs": {"filename": {"value": "CT_pitch.nii.gz"}}},
|
||||
"load_mask": {"inputs": {"filename": {"value": "CT_pitch_mask.nii.gz"}}},
|
||||
}
|
||||
}
|
||||
)
|
||||
print("✅ Dagster job finished.")
|
||||
print(result)
|
||||
|
||||
|
||||
def run_assets():
|
||||
# Ensure artifacts directory exists
|
||||
base_dir = os.path.normpath("app/storage/artifacts")
|
||||
os.makedirs(base_dir, exist_ok=True)
|
||||
|
||||
# Use FilesystemIOManager for asset storage
|
||||
io_manager = FilesystemIOManager(base_dir=base_dir)
|
||||
|
||||
# Materialize all assets defined in radiuma_assets
|
||||
result = materialize(
|
||||
[
|
||||
radiuma_assets.raw_image,
|
||||
radiuma_assets.raw_mask,
|
||||
radiuma_assets.registered_image,
|
||||
radiuma_assets.fused_image,
|
||||
radiuma_assets.features,
|
||||
],
|
||||
resources={"io_manager": io_manager},
|
||||
)
|
||||
|
||||
if result.success:
|
||||
print("✅ Assets materialized successfully.")
|
||||
print(f"Storage base_dir: {base_dir}")
|
||||
|
||||
# Print all materialized asset keys
|
||||
for event in result.get_asset_materialization_events():
|
||||
print(f"Asset built: {event.asset_key}")
|
||||
else:
|
||||
print("❌ Asset execution failed.")
|
||||
print(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🚀 Starting Radiuma unified run...\n")
|
||||
run_workflow()
|
||||
run_pipeline()
|
||||
run_assets()
|
||||
print("\n🎯 All workflows completed in one run.")
|
||||
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
import SimpleITK as sitk
|
||||
from dagster import asset
|
||||
|
||||
DATA_DIR = os.path.join("data", "images")
|
||||
MASK_DIR = os.path.join("data", "masks")
|
||||
ARTIFACTS_DIR = os.path.join("app", "storage", "artifacts")
|
||||
os.makedirs(ARTIFACTS_DIR, exist_ok=True)
|
||||
|
||||
@asset
|
||||
def raw_image() -> str:
|
||||
# Return existing image path
|
||||
path = os.path.normpath(os.path.join(DATA_DIR, "CT_pitch.nii.gz"))
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"raw_image: file not found at {path}")
|
||||
return path
|
||||
|
||||
@asset
|
||||
def raw_mask() -> str:
|
||||
path = os.path.normpath(os.path.join(MASK_DIR, "CT_pitch_mask.nii.gz"))
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"raw_mask: file not found at {path}")
|
||||
return path
|
||||
|
||||
@asset
|
||||
def registered_image(raw_image: str) -> str:
|
||||
# Read the raw image and write a registered copy
|
||||
img = sitk.ReadImage(raw_image)
|
||||
registered = sitk.Cast(img, img.GetPixelID()) # identity registration
|
||||
out_path = os.path.normpath(os.path.join(ARTIFACTS_DIR, "CT_pitch_registered.nii.gz"))
|
||||
sitk.WriteImage(registered, out_path)
|
||||
return out_path
|
||||
|
||||
@asset
|
||||
def fused_image(registered_image: str, raw_mask: str) -> str:
|
||||
img = sitk.ReadImage(registered_image)
|
||||
mask = sitk.ReadImage(raw_mask)
|
||||
mask_uint8 = sitk.Cast(mask, sitk.sitkUInt8)
|
||||
fused = sitk.Mask(img, mask_uint8)
|
||||
out_path = os.path.normpath(os.path.join(ARTIFACTS_DIR, "CT_pitch_fused.nii.gz"))
|
||||
sitk.WriteImage(fused, out_path)
|
||||
return out_path
|
||||
|
||||
@asset
|
||||
def features(fused_image: str) -> str:
|
||||
import json, numpy as np
|
||||
img = sitk.ReadImage(fused_image)
|
||||
arr = sitk.GetArrayFromImage(img)
|
||||
feats = {
|
||||
"shape": list(arr.shape),
|
||||
"spacing": list(img.GetSpacing()),
|
||||
"intensity_sum": float(np.sum(arr)),
|
||||
}
|
||||
out_path = os.path.normpath(os.path.join(ARTIFACTS_DIR, "CT_pitch_asset_features.json"))
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(feats, f, indent=2)
|
||||
return out_path
|
||||
@@ -0,0 +1,69 @@
|
||||
import json
|
||||
import numpy as np
|
||||
from dagster import op
|
||||
import SimpleITK as sitk
|
||||
from pathlib import Path
|
||||
# Write Your codes Here:
|
||||
|
||||
|
||||
ARTIFACTS = Path("artifacts")
|
||||
ARTIFACTS.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
@op
|
||||
def image_reader(path: str):
|
||||
"""Medical image reading (MRI, CT, PET)."""
|
||||
img = sitk.ReadImage(path)
|
||||
# Save the processed version for display in the GUI (optional)
|
||||
sitk.WriteImage(img, str(ARTIFACTS / "processed_image.nii.gz"))
|
||||
return img
|
||||
|
||||
@op
|
||||
def image_registration(fixed_img, moving_img):
|
||||
"""Aligning two medical images."""
|
||||
reg = sitk.ImageRegistrationMethod()
|
||||
reg.SetMetricAsMeanSquares()
|
||||
reg.SetOptimizerAsGradientDescent(learningRate=1.0, numberOfIterations=100)
|
||||
reg.SetInterpolator(sitk.sitkLinear)
|
||||
transform = reg.Execute(fixed_img, moving_img)
|
||||
registered = sitk.Resample(
|
||||
moving_img, fixed_img, transform, sitk.sitkLinear, 0.0, moving_img.GetPixelID()
|
||||
)
|
||||
sitk.WriteImage(registered, str(ARTIFACTS / "registered_image.nii.gz"))
|
||||
return registered
|
||||
|
||||
@op
|
||||
def image_fusion(img1, img2):
|
||||
"""Combining two medical images (simple fusion)."""
|
||||
fused = sitk.Cast((img1 + img2) / 2, sitk.sitkFloat32)
|
||||
sitk.WriteImage(fused, str(ARTIFACTS / "fused_image.nii.gz"))
|
||||
return fused
|
||||
|
||||
@op
|
||||
def image_extraction(img):
|
||||
"""Extracting simple features from images."""
|
||||
arr = sitk.GetArrayFromImage(img)
|
||||
features = {
|
||||
"mean_intensity": float(np.mean(arr)),
|
||||
"std_intensity": float(np.std(arr)),
|
||||
"min_intensity": float(np.min(arr)),
|
||||
"max_intensity": float(np.max(arr)),
|
||||
}
|
||||
(ARTIFACTS / "features.json").write_text(json.dumps(features, indent=2))
|
||||
return features
|
||||
|
||||
@op
|
||||
def image_writer(img):
|
||||
"""Save final output."""
|
||||
out_path = ARTIFACTS / "output.nii.gz"
|
||||
sitk.WriteImage(img, str(out_path))
|
||||
return str(out_path)
|
||||
|
||||
OPS = {
|
||||
"reader": image_reader,
|
||||
"registration": image_registration,
|
||||
"fusion": image_fusion,
|
||||
"extraction": image_extraction,
|
||||
"writer": image_writer,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user