streamtrace 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,218 @@
1
+ Metadata-Version: 2.4
2
+ Name: streamtrace
3
+ Version: 0.1.0
4
+ Summary: Streamtrace SDK — decorator-based ML pipeline contracts for clinical AI
5
+ Author: James Mihalich
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/james-mihalich/streamtrac-sdk
8
+ Project-URL: Repository, https://github.com/james-mihalich/streamtrac-sdk
9
+ Keywords: machine learning,pipeline,mlops,clinical ai,dag,decorators,medical imaging
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ Provides-Extra: medical
21
+ Requires-Dist: nibabel>=3.2; extra == "medical"
22
+ Requires-Dist: pydicom>=2.3; extra == "medical"
23
+ Requires-Dist: numpy>=1.24; extra == "medical"
24
+ Provides-Extra: imaging
25
+ Requires-Dist: Pillow>=9.0; extra == "imaging"
26
+ Requires-Dist: matplotlib>=3.5; extra == "imaging"
27
+ Requires-Dist: numpy>=1.24; extra == "imaging"
28
+ Provides-Extra: plotting
29
+ Requires-Dist: matplotlib>=3.5; extra == "plotting"
30
+ Requires-Dist: plotly>=5.0; extra == "plotting"
31
+ Requires-Dist: bokeh>=3.0; extra == "plotting"
32
+ Provides-Extra: data
33
+ Requires-Dist: pandas>=1.5; extra == "data"
34
+ Requires-Dist: numpy>=1.24; extra == "data"
35
+ Provides-Extra: mesh
36
+ Requires-Dist: trimesh>=3.9; extra == "mesh"
37
+ Requires-Dist: numpy>=1.24; extra == "mesh"
38
+ Provides-Extra: all
39
+ Requires-Dist: nibabel>=3.2; extra == "all"
40
+ Requires-Dist: pydicom>=2.3; extra == "all"
41
+ Requires-Dist: Pillow>=9.0; extra == "all"
42
+ Requires-Dist: matplotlib>=3.5; extra == "all"
43
+ Requires-Dist: plotly>=5.0; extra == "all"
44
+ Requires-Dist: bokeh>=3.0; extra == "all"
45
+ Requires-Dist: pandas>=1.5; extra == "all"
46
+ Requires-Dist: trimesh>=3.9; extra == "all"
47
+ Requires-Dist: numpy>=1.24; extra == "all"
48
+
49
+ # streamtrace
50
+
51
+ Decorator-based ML pipeline contracts for clinical AI. Define your pipeline once with typed decorators, get a validated execution DAG and a deployable frontend app.
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install streamtrace
57
+ ```
58
+
59
+ Install extras for the output types your pipeline produces:
60
+
61
+ ```bash
62
+ pip install streamtrace[medical] # NIfTI + DICOM (nibabel, pydicom, numpy)
63
+ pip install streamtrace[imaging] # images + matplotlib (Pillow, matplotlib, numpy)
64
+ pip install streamtrace[plotting] # interactive plots (matplotlib, plotly, bokeh)
65
+ pip install streamtrace[data] # tabular output (pandas, numpy)
66
+ pip install streamtrace[mesh] # 3D geometry (trimesh, numpy)
67
+ pip install streamtrace[all] # everything
68
+ ```
69
+
70
+ ## Quick start
71
+
72
+ ```python
73
+ import streamtrace as st
74
+
75
+ @st.app(title="Cardiac Segmentation", version="1.0.0",
76
+ docker_base="pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime",
77
+ requirements=["nibabel", "scipy"])
78
+ class CardiacSegmentation:
79
+
80
+ @st.input(title="CT Scan",
81
+ widget=st.FileInput(accepted_types=[st.InputDataType.NIFTI]),
82
+ returns="scan")
83
+ def load_scan(self, path):
84
+ import nibabel as nib
85
+ return nib.load(path).get_fdata()
86
+
87
+ @st.input(title="Threshold",
88
+ widget=st.TextInput(default="0.5", placeholder="0.0 – 1.0"),
89
+ returns="threshold")
90
+ def set_threshold(self, value):
91
+ return float(value)
92
+
93
+ @st.preprocess(returns="normalized")
94
+ def normalize(self, scan):
95
+ lo, hi = scan.min(), scan.max()
96
+ return (scan - lo) / (hi - lo + 1e-8)
97
+
98
+ @st.infer(returns="raw_mask",
99
+ device="cuda",
100
+ weights_path="checkpoints/best.pth")
101
+ def segment(self, normalized, threshold):
102
+ import torch
103
+ model = torch.load("checkpoints/best.pth", map_location="cpu")
104
+ model.eval()
105
+ with torch.no_grad():
106
+ t = torch.FloatTensor(normalized).unsqueeze(0).unsqueeze(0)
107
+ pred = model(t).squeeze().numpy()
108
+ return (pred > threshold).astype("uint8")
109
+
110
+ @st.postprocess(returns="mask")
111
+ def clean(self, raw_mask):
112
+ from scipy.ndimage import binary_fill_holes
113
+ return binary_fill_holes(raw_mask).astype("uint8")
114
+
115
+ @st.output(title="Segmentation",
116
+ widget=st.FileOutput(data_type=st.OutputDataType.NIFTI,
117
+ filename="segmentation.nii.gz"))
118
+ def save(self, mask):
119
+ import nibabel as nib
120
+ return nib.Nifti1Image(mask, affine=None)
121
+ ```
122
+
123
+ ## Pipeline contract
124
+
125
+ A Streamtrace pipeline is a class decorated with `@st.app`. Each method in the class maps to one node in the execution DAG via its decorator. Wiring is automatic: a method's parameter names are matched against the `returns=` alias of other nodes.
126
+
127
+ ```
128
+ @st.input → @st.preprocess → @st.infer → @st.postprocess → @st.output
129
+ ```
130
+
131
+ ### Decorators
132
+
133
+ | Decorator | Purpose | Key args |
134
+ |-----------|---------|---------|
135
+ | `@st.app` | Marks the pipeline class | `title`, `version`, `description`, `docker_base`, `requirements` |
136
+ | `@st.input` | User-provided value (file, text, etc.) | `title`, `widget`, `required`, `returns` |
137
+ | `@st.preprocess` | Data preparation before inference | `title`, `returns` |
138
+ | `@st.infer` | Model inference step | `title`, `returns`, `device`, `weights_path` |
139
+ | `@st.postprocess` | Clean-up after inference | `title`, `returns` |
140
+ | `@st.output` | Final (or intermediate) result | `title`, `widget`, `returns`, `intermediate` |
141
+
142
+ ### Wiring via `returns`
143
+
144
+ ```python
145
+ @st.input(returns="scan") # produces key "scan"
146
+ def load_scan(self, path): ...
147
+
148
+ @st.preprocess(returns="normalized")
149
+ def normalize(self, scan): ... # "scan" resolves to load_scan's output
150
+
151
+ @st.infer(returns="mask")
152
+ def segment(self, normalized): ... # "normalized" resolves to normalize's output
153
+ ```
154
+
155
+ If `returns` is omitted the method name is used as the output key.
156
+
157
+ ## Input widgets
158
+
159
+ | Widget | Description |
160
+ |--------|-------------|
161
+ | `FileInput(accepted_types=[...], max_size_mb=500)` | File upload |
162
+ | `TextInput(default="", placeholder="", max_length=None)` | Single-line text |
163
+
164
+ **Accepted file types** — `InputDataType.ANY`, `.NIFTI`, `.DICOM`, `.PNG`, `.NUMPY`
165
+
166
+ ## Output widgets
167
+
168
+ | Widget | Description |
169
+ |--------|-------------|
170
+ | `FileOutput(data_type=OutputDataType.File, filename=None)` | Generic file download |
171
+ | `ImageOutput(caption="", max_size_mb=50)` | PNG / matplotlib figure |
172
+ | `PlotOutput(interactive=True)` | Plotly / Bokeh / matplotlib |
173
+ | `MetricOutput(label="", precision=3)` | Numeric value or dict of metrics |
174
+
175
+ **Output data types** — `File`, `Image`, `Plot`, `NIFTI`, `DICOM`, `Mesh`, `CSV`, `JSON`, `Text`, `Metric`
176
+
177
+ Intermediate outputs are uploaded to the frontend while the pipeline is still running:
178
+
179
+ ```python
180
+ @st.output(title="Slice Preview", widget=st.ImageOutput(), intermediate=True)
181
+ def preview(self, normalized): ...
182
+ ```
183
+
184
+ ## DAG inspection
185
+
186
+ ```python
187
+ from streamtrace import build_dag
188
+
189
+ dag = build_dag(CardiacSegmentation)
190
+
191
+ # Validate wiring before deploying
192
+ errors = dag.validate()
193
+ if errors:
194
+ for e in errors:
195
+ print(e)
196
+
197
+ # Topological execution order
198
+ for node in dag.topological_sort():
199
+ print(node.phase, "→", node.name, "produces", node.output_key)
200
+
201
+ # JSON schema (used by streamtrace push)
202
+ import json
203
+ print(json.dumps(dag.to_schema(), indent=2))
204
+ ```
205
+
206
+ ## Streamtrace CLI
207
+
208
+ The SDK is the contract layer. The [streamtrace CLI](https://github.com/james-mihalich/streamtrac-sdk) handles the rest:
209
+
210
+ ```bash
211
+ streamtrace init # surveys your codebase and classifies existing code
212
+ streamtrace configure # builds and validates a complete executable pipeline
213
+ streamtrace push # deploys to Streamtrace infrastructure
214
+ ```
215
+
216
+ ## License
217
+
218
+ MIT
@@ -0,0 +1,170 @@
1
+ # streamtrace
2
+
3
+ Decorator-based ML pipeline contracts for clinical AI. Define your pipeline once with typed decorators, get a validated execution DAG and a deployable frontend app.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install streamtrace
9
+ ```
10
+
11
+ Install extras for the output types your pipeline produces:
12
+
13
+ ```bash
14
+ pip install streamtrace[medical] # NIfTI + DICOM (nibabel, pydicom, numpy)
15
+ pip install streamtrace[imaging] # images + matplotlib (Pillow, matplotlib, numpy)
16
+ pip install streamtrace[plotting] # interactive plots (matplotlib, plotly, bokeh)
17
+ pip install streamtrace[data] # tabular output (pandas, numpy)
18
+ pip install streamtrace[mesh] # 3D geometry (trimesh, numpy)
19
+ pip install streamtrace[all] # everything
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```python
25
+ import streamtrace as st
26
+
27
+ @st.app(title="Cardiac Segmentation", version="1.0.0",
28
+ docker_base="pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime",
29
+ requirements=["nibabel", "scipy"])
30
+ class CardiacSegmentation:
31
+
32
+ @st.input(title="CT Scan",
33
+ widget=st.FileInput(accepted_types=[st.InputDataType.NIFTI]),
34
+ returns="scan")
35
+ def load_scan(self, path):
36
+ import nibabel as nib
37
+ return nib.load(path).get_fdata()
38
+
39
+ @st.input(title="Threshold",
40
+ widget=st.TextInput(default="0.5", placeholder="0.0 – 1.0"),
41
+ returns="threshold")
42
+ def set_threshold(self, value):
43
+ return float(value)
44
+
45
+ @st.preprocess(returns="normalized")
46
+ def normalize(self, scan):
47
+ lo, hi = scan.min(), scan.max()
48
+ return (scan - lo) / (hi - lo + 1e-8)
49
+
50
+ @st.infer(returns="raw_mask",
51
+ device="cuda",
52
+ weights_path="checkpoints/best.pth")
53
+ def segment(self, normalized, threshold):
54
+ import torch
55
+ model = torch.load("checkpoints/best.pth", map_location="cpu")
56
+ model.eval()
57
+ with torch.no_grad():
58
+ t = torch.FloatTensor(normalized).unsqueeze(0).unsqueeze(0)
59
+ pred = model(t).squeeze().numpy()
60
+ return (pred > threshold).astype("uint8")
61
+
62
+ @st.postprocess(returns="mask")
63
+ def clean(self, raw_mask):
64
+ from scipy.ndimage import binary_fill_holes
65
+ return binary_fill_holes(raw_mask).astype("uint8")
66
+
67
+ @st.output(title="Segmentation",
68
+ widget=st.FileOutput(data_type=st.OutputDataType.NIFTI,
69
+ filename="segmentation.nii.gz"))
70
+ def save(self, mask):
71
+ import nibabel as nib
72
+ return nib.Nifti1Image(mask, affine=None)
73
+ ```
74
+
75
+ ## Pipeline contract
76
+
77
+ A Streamtrace pipeline is a class decorated with `@st.app`. Each method in the class maps to one node in the execution DAG via its decorator. Wiring is automatic: a method's parameter names are matched against the `returns=` alias of other nodes.
78
+
79
+ ```
80
+ @st.input → @st.preprocess → @st.infer → @st.postprocess → @st.output
81
+ ```
82
+
83
+ ### Decorators
84
+
85
+ | Decorator | Purpose | Key args |
86
+ |-----------|---------|---------|
87
+ | `@st.app` | Marks the pipeline class | `title`, `version`, `description`, `docker_base`, `requirements` |
88
+ | `@st.input` | User-provided value (file, text, etc.) | `title`, `widget`, `required`, `returns` |
89
+ | `@st.preprocess` | Data preparation before inference | `title`, `returns` |
90
+ | `@st.infer` | Model inference step | `title`, `returns`, `device`, `weights_path` |
91
+ | `@st.postprocess` | Clean-up after inference | `title`, `returns` |
92
+ | `@st.output` | Final (or intermediate) result | `title`, `widget`, `returns`, `intermediate` |
93
+
94
+ ### Wiring via `returns`
95
+
96
+ ```python
97
+ @st.input(returns="scan") # produces key "scan"
98
+ def load_scan(self, path): ...
99
+
100
+ @st.preprocess(returns="normalized")
101
+ def normalize(self, scan): ... # "scan" resolves to load_scan's output
102
+
103
+ @st.infer(returns="mask")
104
+ def segment(self, normalized): ... # "normalized" resolves to normalize's output
105
+ ```
106
+
107
+ If `returns` is omitted the method name is used as the output key.
108
+
109
+ ## Input widgets
110
+
111
+ | Widget | Description |
112
+ |--------|-------------|
113
+ | `FileInput(accepted_types=[...], max_size_mb=500)` | File upload |
114
+ | `TextInput(default="", placeholder="", max_length=None)` | Single-line text |
115
+
116
+ **Accepted file types** — `InputDataType.ANY`, `.NIFTI`, `.DICOM`, `.PNG`, `.NUMPY`
117
+
118
+ ## Output widgets
119
+
120
+ | Widget | Description |
121
+ |--------|-------------|
122
+ | `FileOutput(data_type=OutputDataType.File, filename=None)` | Generic file download |
123
+ | `ImageOutput(caption="", max_size_mb=50)` | PNG / matplotlib figure |
124
+ | `PlotOutput(interactive=True)` | Plotly / Bokeh / matplotlib |
125
+ | `MetricOutput(label="", precision=3)` | Numeric value or dict of metrics |
126
+
127
+ **Output data types** — `File`, `Image`, `Plot`, `NIFTI`, `DICOM`, `Mesh`, `CSV`, `JSON`, `Text`, `Metric`
128
+
129
+ Intermediate outputs are uploaded to the frontend while the pipeline is still running:
130
+
131
+ ```python
132
+ @st.output(title="Slice Preview", widget=st.ImageOutput(), intermediate=True)
133
+ def preview(self, normalized): ...
134
+ ```
135
+
136
+ ## DAG inspection
137
+
138
+ ```python
139
+ from streamtrace import build_dag
140
+
141
+ dag = build_dag(CardiacSegmentation)
142
+
143
+ # Validate wiring before deploying
144
+ errors = dag.validate()
145
+ if errors:
146
+ for e in errors:
147
+ print(e)
148
+
149
+ # Topological execution order
150
+ for node in dag.topological_sort():
151
+ print(node.phase, "→", node.name, "produces", node.output_key)
152
+
153
+ # JSON schema (used by streamtrace push)
154
+ import json
155
+ print(json.dumps(dag.to_schema(), indent=2))
156
+ ```
157
+
158
+ ## Streamtrace CLI
159
+
160
+ The SDK is the contract layer. The [streamtrace CLI](https://github.com/james-mihalich/streamtrac-sdk) handles the rest:
161
+
162
+ ```bash
163
+ streamtrace init # surveys your codebase and classifies existing code
164
+ streamtrace configure # builds and validates a complete executable pipeline
165
+ streamtrace push # deploys to Streamtrace infrastructure
166
+ ```
167
+
168
+ ## License
169
+
170
+ MIT
@@ -0,0 +1,57 @@
1
+ """
2
+ Streamtrace SDK — decorator-based ML pipeline contracts for clinical AI.
3
+
4
+ Usage:
5
+ import streamtrace as st
6
+
7
+ @st.app(title="Cardiac Segmentation", version="1.0.0")
8
+ class MyPipeline:
9
+
10
+ @st.input(title="CT Scan", widget=st.FileInput(...), returns="scan")
11
+ def load_scan(self, path): ...
12
+
13
+ @st.preprocess(title="Normalize", returns="normalized")
14
+ def normalize(self, scan): ...
15
+
16
+ @st.infer(title="Segment", returns="mask", weights_path="model.pt")
17
+ def segment(self, normalized): ...
18
+
19
+ @st.postprocess(title="Clean Mask", returns="clean_mask")
20
+ def clean(self, mask): ...
21
+
22
+ @st.output(title="Result", widget=st.FileOutput(...))
23
+ def save(self, clean_mask): ...
24
+ """
25
+
26
+ from .decorators.app_decorator import app, get_app_metadata, is_app
27
+ from .decorators.input_decorator import input, get_input_metadata, is_input
28
+ from .decorators.preprocess_decorator import preprocess, get_preprocess_metadata, is_preprocess
29
+ from .decorators.infer_decorator import infer, get_infer_metadata, is_infer
30
+ from .decorators.postprocess_decorator import postprocess, get_postprocess_metadata, is_postprocess
31
+ from .decorators.output_decorator import output, get_output_metadata, is_output
32
+
33
+ from .widgets.input_widgets import FileInput, TextInput, InputDataType
34
+ from .widgets.output_widgets import FileOutput, ImageOutput, PlotOutput, MetricOutput, OutputDataType
35
+
36
+ from .dag import build_dag, DAG, Node
37
+
38
+ __version__ = "0.1.0"
39
+
40
+ __all__ = [
41
+ # App
42
+ "app", "get_app_metadata", "is_app",
43
+ # Node decorators
44
+ "input", "get_input_metadata", "is_input",
45
+ "preprocess", "get_preprocess_metadata", "is_preprocess",
46
+ "infer", "get_infer_metadata", "is_infer",
47
+ "postprocess", "get_postprocess_metadata", "is_postprocess",
48
+ "output", "get_output_metadata", "is_output",
49
+ # Input widgets
50
+ "FileInput", "TextInput", "InputDataType",
51
+ # Output widgets
52
+ "FileOutput", "ImageOutput", "PlotOutput", "MetricOutput", "OutputDataType",
53
+ # DAG
54
+ "build_dag", "DAG", "Node",
55
+ # Version
56
+ "__version__",
57
+ ]