scistack 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.
- scistack-0.1.0/.gitignore +3 -0
- scistack-0.1.0/PKG-INFO +406 -0
- scistack-0.1.0/README.md +376 -0
- scistack-0.1.0/canonical-hash/README.md +37 -0
- scistack-0.1.0/canonical-hash/pyproject.toml +91 -0
- scistack-0.1.0/path-gen/README.md +30 -0
- scistack-0.1.0/path-gen/pyproject.toml +88 -0
- scistack-0.1.0/pipelinedb-lib/README.md +0 -0
- scistack-0.1.0/pipelinedb-lib/pyproject.toml +54 -0
- scistack-0.1.0/pyproject.toml +59 -0
- scistack-0.1.0/sci-matlab/README.md +143 -0
- scistack-0.1.0/sci-matlab/pyproject.toml +30 -0
- scistack-0.1.0/scidb/README.md +5 -0
- scistack-0.1.0/scidb/pyproject.toml +62 -0
- scistack-0.1.0/scidb/tests/README.md +128 -0
- scistack-0.1.0/scidb-net/README.md +244 -0
- scistack-0.1.0/scidb-net/pyproject.toml +39 -0
- scistack-0.1.0/sciduck/README.md +28 -0
- scistack-0.1.0/sciduck/pyproject.toml +79 -0
- scistack-0.1.0/scifor/README.md +124 -0
- scistack-0.1.0/scifor/pyproject.toml +55 -0
- scistack-0.1.0/scihist-lib/README.md +41 -0
- scistack-0.1.0/scihist-lib/pyproject.toml +57 -0
- scistack-0.1.0/scirun-lib/README.md +41 -0
- scistack-0.1.0/scirun-lib/pyproject.toml +55 -0
- scistack-0.1.0/thunk-lib/README.md +213 -0
- scistack-0.1.0/thunk-lib/pyproject.toml +100 -0
scistack-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scistack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scientific data pipeline framework — versioning, lineage, and caching for research workflows
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: data-management,lineage,pipeline,provenance,reproducibility,scientific-computing
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Database
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Requires-Dist: scidb>=0.1.0
|
|
19
|
+
Provides-Extra: all
|
|
20
|
+
Requires-Dist: sci-matlab>=0.1.0; extra == 'all'
|
|
21
|
+
Requires-Dist: scidb-net>=0.1.0; extra == 'all'
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
25
|
+
Provides-Extra: matlab
|
|
26
|
+
Requires-Dist: sci-matlab>=0.1.0; extra == 'matlab'
|
|
27
|
+
Provides-Extra: net
|
|
28
|
+
Requires-Dist: scidb-net>=0.1.0; extra == 'net'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# SciDB
|
|
32
|
+
|
|
33
|
+
## Better Research Tools, Better Research Outcomes
|
|
34
|
+
|
|
35
|
+
SciDB is a database framework purpose-built for scientific data analysis. It gives you a structured, versioned, and queryable home for every piece of data your pipeline produces — from raw signals to final results — with near zero infrastructure code on your part, and **zero changes to your analysis code**.
|
|
36
|
+
|
|
37
|
+
It works natively in both **Python** and **MATLAB**.
|
|
38
|
+
|
|
39
|
+
## The Problem
|
|
40
|
+
|
|
41
|
+
Every scientist who writes analysis code eventually builds the same thing: a tangle of folders, naming conventions, and bookkeeping scripts to track which data came from where, which version of a function produced it, and whether it's already been computed.
|
|
42
|
+
|
|
43
|
+
That infrastructure code is never the point. But it eats weeks of your time, it's fragile, and it's different in every lab.
|
|
44
|
+
|
|
45
|
+
**Scientists want to focus on the science, not data management.**
|
|
46
|
+
|
|
47
|
+
SciDB replaces all of it with three ideas:
|
|
48
|
+
|
|
49
|
+
- **Named variable types** — instead of files on disk, your data lives in typed database tables you can query by metadata
|
|
50
|
+
- **Automatic lineage** — a simple decorator records exactly what function and inputs produced each result
|
|
51
|
+
- **Computation caching** — if you've already computed something, SciDB knows and skips it
|
|
52
|
+
|
|
53
|
+
With SciDB, your analysis scripts contain _only_ analysis logic. The infrastructure is handled for you.
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install scidb
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This pulls in all core dependencies (`sciduckdb`, `thunk`, `scipathgen`, `canonicalhash`, `scirun`).
|
|
64
|
+
|
|
65
|
+
For development (editable installs of all packages):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/mtillman14/general-sqlite-database
|
|
69
|
+
cd general-sqlite-database
|
|
70
|
+
./dev-install.sh
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### One-Time Setup
|
|
74
|
+
|
|
75
|
+
Every project starts by configuring a database. You do this once.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from scidb import configure_database
|
|
79
|
+
|
|
80
|
+
db = configure_database(
|
|
81
|
+
"my_experiment_data.duckdb", # DuckDB file for data + lineage
|
|
82
|
+
dataset_schema_keys=["subject", "session"], # How your dataset is organized
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`dataset_schema_keys` describes the structure of your experiment. If your data is organized by subject and session, say so — SciDB uses this to let you save and query data naturally.
|
|
87
|
+
|
|
88
|
+
### Define Your Variable Types
|
|
89
|
+
|
|
90
|
+
Each kind of data in your pipeline gets its own type. This is just a one-liner:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from scidb import BaseVariable
|
|
94
|
+
|
|
95
|
+
class RawEMG(BaseVariable):
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
class FilteredEMG(BaseVariable):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
class MaxActivation(BaseVariable):
|
|
102
|
+
pass
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
That's it. No configuration, no serialization code. SciDB handles numpy arrays, scalars, lists, dicts, and DataFrames natively.
|
|
106
|
+
|
|
107
|
+
### Save and Load Data
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import numpy as np
|
|
111
|
+
|
|
112
|
+
# Save with metadata that matches your schema
|
|
113
|
+
RawEMG.save(np.random.randn(1000), subject=1, session="baseline")
|
|
114
|
+
|
|
115
|
+
# Load it back — by the metadata you care about
|
|
116
|
+
raw = RawEMG.load(subject=1, session="baseline")
|
|
117
|
+
print(raw.data) # your numpy array
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Track Lineage Automatically
|
|
121
|
+
|
|
122
|
+
Which function created that variable? Were the most recent settings used the last time I ran this?
|
|
123
|
+
|
|
124
|
+
Wrap your analysis functions with `@thunk` and SciDB records which functions produced what **and the input variable values** — automatically:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from scidb import thunk
|
|
128
|
+
|
|
129
|
+
@thunk
|
|
130
|
+
def bandpass_filter(signal, low_hz, high_hz):
|
|
131
|
+
# your filtering logic
|
|
132
|
+
return filtered_signal
|
|
133
|
+
|
|
134
|
+
@thunk
|
|
135
|
+
def compute_max(signal):
|
|
136
|
+
return float(np.max(np.abs(signal)))
|
|
137
|
+
|
|
138
|
+
# Run the pipeline — lineage is tracked behind the scenes
|
|
139
|
+
raw = RawEMG.load(subject=1, session="baseline")
|
|
140
|
+
filtered = bandpass_filter(raw, low_hz=20, high_hz=450)
|
|
141
|
+
max_val = compute_max(filtered)
|
|
142
|
+
|
|
143
|
+
# Save results
|
|
144
|
+
FilteredEMG.save(filtered, subject=1, session="baseline")
|
|
145
|
+
MaxActivation.save(max_val, subject=1, session="baseline")
|
|
146
|
+
|
|
147
|
+
# Later: "What function produced this, and with what settings?"
|
|
148
|
+
provenance = db.get_provenance(FilteredEMG, subject=1, session="baseline")
|
|
149
|
+
print(provenance["function_name"]) # "bandpass_filter"
|
|
150
|
+
print(provenance["constants"]) # {"low_hz": 20, "high_hz": 450}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Your functions stay clean, no boilerplate required. They receive normal numpy arrays and return normal values. The `@thunk` decorator handles all the bookkeeping at the boundary.
|
|
154
|
+
|
|
155
|
+
If the `@thunk` decorator is still too close to your code for your test, wrap it in a `Thunk()` call later on:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
|
|
159
|
+
from scidb.thunk import Thunk
|
|
160
|
+
|
|
161
|
+
compute_max = Thunk(compute_max)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Run the same pipeline again and every step is skipped** — SciDB recognizes the same function + same inputs and returns the cached result instantly.
|
|
165
|
+
|
|
166
|
+
## Scaling Up with `for_each()`
|
|
167
|
+
|
|
168
|
+
Real experiments can have dozens of subjects and conditions, or thousands. SciDB can handle it all, using `for_each()` runs your pipeline over every combination automatically:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from scidb import for_each
|
|
172
|
+
|
|
173
|
+
# 5 subjects
|
|
174
|
+
for_each(
|
|
175
|
+
bandpass_filter,
|
|
176
|
+
inputs={"signal": RawEMG},
|
|
177
|
+
outputs=[FilteredEMG],
|
|
178
|
+
subject=[1, 2, 3, 4, 5],
|
|
179
|
+
session=["baseline", "post"],
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# 10,000 subjects
|
|
183
|
+
for_each(
|
|
184
|
+
bandpass_filter,
|
|
185
|
+
inputs={"signal": RawEMG},
|
|
186
|
+
outputs=[FilteredEMG],
|
|
187
|
+
subject=range(1,10000),
|
|
188
|
+
session=["baseline", "post"],
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Specify subject list of any size
|
|
192
|
+
subject_list = config["subjects"] # Load from some configuration file
|
|
193
|
+
for_each(
|
|
194
|
+
bandpass_filter,
|
|
195
|
+
inputs={"signal": RawEMG},
|
|
196
|
+
outputs=[FilteredEMG],
|
|
197
|
+
subject=subject_list,
|
|
198
|
+
session=["baseline", "post"],
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This loads `RawEMG` for each subject/session combination, runs `bandpass_filter`, and saves the result as `FilteredEMG` — multiple iterations, zero boilerplate. If a subject is missing data, that iteration is skipped gracefully. In the future, logging support is planned to document what ran successfully and what failed, and why.
|
|
203
|
+
|
|
204
|
+
Need one input to stay fixed while others iterate? Use `Fixed`:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from scidb import Fixed
|
|
208
|
+
|
|
209
|
+
for_each(
|
|
210
|
+
compare_to_baseline,
|
|
211
|
+
inputs={
|
|
212
|
+
"baseline": Fixed(RawEMG, session="baseline"), # always load baseline
|
|
213
|
+
"current": RawEMG, # iterates normally
|
|
214
|
+
},
|
|
215
|
+
outputs=[Delta],
|
|
216
|
+
subject=[1, 2, 3, 4, 5],
|
|
217
|
+
session=["post_1", "post_2", "post_3"],
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Powerful Querying
|
|
222
|
+
|
|
223
|
+
Because your data lives in a real database (not scattered files), querying is simple and powerful:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
# Load one specific record
|
|
227
|
+
emg = FilteredEMG.load(subject=3, session="post")
|
|
228
|
+
|
|
229
|
+
# Load all sessions for a subject — returns a list
|
|
230
|
+
all_sessions = FilteredEMG.load(subject=3)
|
|
231
|
+
for var in all_sessions:
|
|
232
|
+
print(var.metadata["session"], var.data.shape)
|
|
233
|
+
|
|
234
|
+
# Load everything as a DataFrame for analysis
|
|
235
|
+
import pandas as pd
|
|
236
|
+
df = MaxActivation.load_all(as_df=True)
|
|
237
|
+
# subject session data
|
|
238
|
+
# 1 baseline 0.82
|
|
239
|
+
# 1 post 1.47
|
|
240
|
+
# 2 baseline 0.91
|
|
241
|
+
# ...
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
No folder traversal. No filename parsing. No `results_v2_final_FINAL.csv`. Just ask for what you want by the metadata that matters.
|
|
245
|
+
|
|
246
|
+
### Your Data Is Not Locked Away
|
|
247
|
+
|
|
248
|
+
Worried that putting data in a database means you can't see or inspect it? Don't be. SciDB uses [DuckDB](https://duckdb.org/) under the hood, and every variable type gets a human-readable **view** that you can query directly with SQL — in DBeaver, the DuckDB CLI, or any tool that speaks SQL.
|
|
249
|
+
|
|
250
|
+
For example, the `MaxActivation` view looks like this:
|
|
251
|
+
|
|
252
|
+
| subject | session | value |
|
|
253
|
+
| ------- | -------- | ----- |
|
|
254
|
+
| 1 | baseline | 0.82 |
|
|
255
|
+
| 1 | post | 1.47 |
|
|
256
|
+
| 2 | baseline | 0.91 |
|
|
257
|
+
| 2 | post | 1.38 |
|
|
258
|
+
| 3 | baseline | 0.76 |
|
|
259
|
+
| 3 | post | 1.22 |
|
|
260
|
+
|
|
261
|
+
You can query it directly:
|
|
262
|
+
|
|
263
|
+
```sql
|
|
264
|
+
SELECT subject, session, value
|
|
265
|
+
FROM MaxActivation;
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Or use database viewer tools like [DBeaver](https://dbeaver.com) to view the database directly.
|
|
269
|
+
|
|
270
|
+
Your data is always one SQL query or visualization away — no Python or MATLAB required.
|
|
271
|
+
|
|
272
|
+
## Works in MATLAB Too
|
|
273
|
+
|
|
274
|
+
SciDB isn't Python-only. The entire framework works in MATLAB with a nearly identical API:
|
|
275
|
+
|
|
276
|
+
```matlab
|
|
277
|
+
% One-time setup
|
|
278
|
+
scidb.configure_database("my_experiment.duckdb", ["subject", "session"], "pipeline.db");
|
|
279
|
+
|
|
280
|
+
% Save and load
|
|
281
|
+
RawEMG().save(randn(1000, 1), subject=1, session="baseline");
|
|
282
|
+
raw = RawEMG().load(subject=1, session="baseline");
|
|
283
|
+
|
|
284
|
+
% Lineage-tracked functions
|
|
285
|
+
filter_fn = scidb.Thunk(@bandpass_filter);
|
|
286
|
+
filtered = filter_fn(raw, 20, 450);
|
|
287
|
+
FilteredEMG().save(filtered, subject=1, session="baseline");
|
|
288
|
+
|
|
289
|
+
% Batch processing
|
|
290
|
+
scidb.for_each(@bandpass_filter, ...
|
|
291
|
+
struct('signal', RawEMG()), ...
|
|
292
|
+
{FilteredEMG()}, ...
|
|
293
|
+
subject=[1 2 3 4 5], ...
|
|
294
|
+
session=["baseline" "post"]);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Data saved from MATLAB can be loaded in Python and vice versa. Lineage chains are continuous across languages. Use whichever language fits the task.
|
|
298
|
+
|
|
299
|
+
## What a Full Pipeline Looks Like
|
|
300
|
+
|
|
301
|
+
Here's a complete, realistic pipeline from setup to results:
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
from scidb import BaseVariable, configure_database, thunk, for_each
|
|
305
|
+
|
|
306
|
+
# --- Setup (once per project) ---
|
|
307
|
+
db = configure_database("gait_study.duckdb", # Where your data is stored
|
|
308
|
+
["subject", "session", "trial"], # How your dataset is organized
|
|
309
|
+
"pipeline.db" # Where lineage is tracked
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# --- Define variable types ---
|
|
313
|
+
class RawKinematicData(BaseVariable):
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
class StepLength(BaseVariable):
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
class MeanStepLength(BaseVariable):
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
class RawForce(BaseVariable):
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
class FilteredForce(BaseVariable):
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
# --- Define processing functions ---
|
|
329
|
+
@thunk
|
|
330
|
+
def extract_step_length(kinematic_data):
|
|
331
|
+
# your biomechanics logic here
|
|
332
|
+
return step_lengths
|
|
333
|
+
|
|
334
|
+
@thunk
|
|
335
|
+
def compute_mean(values):
|
|
336
|
+
return float(np.mean(values))
|
|
337
|
+
|
|
338
|
+
# --- Run the pipeline ---
|
|
339
|
+
for_each(
|
|
340
|
+
extract_step_length,
|
|
341
|
+
inputs={"kinematic_data": RawKinematicData},
|
|
342
|
+
outputs=[StepLength],
|
|
343
|
+
subject=[1, 2, 3],
|
|
344
|
+
session=["pre", "post"],
|
|
345
|
+
trial=[1, 2, 3, 4, 5],
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
for_each(
|
|
349
|
+
compute_mean,
|
|
350
|
+
inputs={"values": StepLength},
|
|
351
|
+
outputs=[MeanStepLength],
|
|
352
|
+
subject=[1, 2, 3],
|
|
353
|
+
session=["pre", "post"],
|
|
354
|
+
trial=[1, 2, 3, 4, 5],
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
for_each(
|
|
358
|
+
filter_data,
|
|
359
|
+
inputs={"values": RawForce, "smoothing": 0.2},
|
|
360
|
+
outputs=[FilteredForce],
|
|
361
|
+
subject=[1, 2, 3],
|
|
362
|
+
session=["pre", "post"],
|
|
363
|
+
trial=[1, 2, 3, 4, 5],
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# --- Analyze results ---
|
|
367
|
+
df = MeanStepLength.load_all(as_df=True)
|
|
368
|
+
print(df.groupby("session")["data"].mean())
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
That's the entire pipeline. No file I/O code. No path management. No version tracking logic. Just the science.
|
|
372
|
+
|
|
373
|
+
Want to change the function logic? SciDB will automatically detect the change, and will re-run that processing step on the next run of the script. Want to change a setting to the function? SciDB will detect that too, and re-run the processing step. Data will be saved to the database, **and the previous data will be preserved**. Understanding the effect of analysis decisions on our results has never been easier.
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
for_each(
|
|
377
|
+
filter_data,
|
|
378
|
+
inputs={"values": RawForce, "smoothing": 0.3}, # Changed from smoothing=0.2
|
|
379
|
+
outputs=[FilteredForce],
|
|
380
|
+
subject=[1, 2, 3],
|
|
381
|
+
session=["pre", "post"],
|
|
382
|
+
trial=[1, 2, 3, 4, 5],
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# Get the FilteredForce created with smoothing=0.2
|
|
386
|
+
filtered_force0_2 = FilteredForce.load(smoothing=0.2) # Returns all subjects, sessions, and trials.
|
|
387
|
+
|
|
388
|
+
# Get the FilteredForce created with smoothing=0.3
|
|
389
|
+
filtered_force0_3 = FilteredForce.load(smoothing=0.3) # Returns all subjects, sessions, and trials.
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## The Bigger Picture: Shareable Pipelines
|
|
393
|
+
|
|
394
|
+
By abstracting away all infrastructure — file paths, storage formats, naming conventions — SciDB decouples your analysis logic from your local environment. Your pipeline code contains _only_ the scientific computation.
|
|
395
|
+
|
|
396
|
+
This opens the door to **truly portable, shareable data processing pipelines.** When a pipeline is just a sequence of typed functions with declared inputs and outputs, it can be shared, reproduced, and built upon by anyone — regardless of how their data is organized on disk.
|
|
397
|
+
|
|
398
|
+
Today, sharing a pipeline means sharing a pile of scripts with hardcoded paths and implicit assumptions. With SciDB, the pipeline _is_ the science, and the infrastructure adapts to wherever it runs.
|
|
399
|
+
|
|
400
|
+
## Learn More
|
|
401
|
+
|
|
402
|
+
- [Quickstart Guide](docs/quickstart.md) — Get running in 5 minutes
|
|
403
|
+
- [VO2 Max Walkthrough](docs/guide/walkthrough.md) — Full example pipeline with design explanations
|
|
404
|
+
- [Variables Guide](docs/guide/variables.md) — Deep dive into variable types
|
|
405
|
+
- [Lineage Guide](docs/guide/lineage.md) — How provenance tracking works
|
|
406
|
+
- [API Reference](docs/api.md) — Complete API documentation
|