Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/source/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Description of model evaluation
| `pipeline_name` | `Optional[str]` | Pipeline name (Pipeline names must exist in Processing.pipelines) |
| `start_date_time` | `datetime (timezone-aware)` | Start date time |
| `end_date_time` | `Optional[datetime (timezone-aware)]` | End date time |
| `output_path` | `Optional[AssetPath]` | Output path (Path to processing outputs, if stored.) |
| `output_path` | `Optional[List[AssetPath]]` | Output path (Path to processing outputs, if stored.) |
| `output_parameters` | `Optional[dict]` | Outputs (Output parameters) |
| `notes` | `Optional[str]` | Notes |
| `resources` | Optional[[ResourceUsage](processing.md#resourceusage)] | Process resource usage |
Expand Down Expand Up @@ -76,7 +76,7 @@ Description of model training
| `pipeline_name` | `Optional[str]` | Pipeline name (Pipeline names must exist in Processing.pipelines) |
| `start_date_time` | `datetime (timezone-aware)` | Start date time |
| `end_date_time` | `Optional[datetime (timezone-aware)]` | End date time |
| `output_path` | `Optional[AssetPath]` | Output path (Path to processing outputs, if stored.) |
| `output_path` | `Optional[List[AssetPath]]` | Output path (Path to processing outputs, if stored.) |
| `output_parameters` | `Optional[dict]` | Outputs (Output parameters) |
| `notes` | `Optional[str]` | Notes |
| `resources` | Optional[[ResourceUsage](processing.md#resourceusage)] | Process resource usage |
Expand Down
2 changes: 1 addition & 1 deletion docs/source/processing.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Description of a single processing step
| `pipeline_name` | `Optional[str]` | Pipeline name (Pipeline names must exist in Processing.pipelines) |
| `start_date_time` | `datetime (timezone-aware)` | Start date time |
| `end_date_time` | `Optional[datetime (timezone-aware)]` | End date time |
| `output_path` | `Optional[AssetPath]` | Output path (Path to processing outputs, if stored.) |
| `output_path` | `Optional[List[AssetPath]]` | Output path (Path to processing outputs, if stored.) |
| `output_parameters` | `Optional[dict]` | Outputs (Output parameters) |
| `notes` | `Optional[str]` | Notes |
| `resources` | Optional[[ResourceUsage](processing.md#resourceusage)] | Process resource usage |
Expand Down
2 changes: 1 addition & 1 deletion examples/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"augmentation": True,
},
),
output_path="./trained_model.h5",
output_path=["./trained_model.h5"],
start_date_time=now,
end_date_time=now,
train_performance=[
Expand Down
10 changes: 5 additions & 5 deletions examples/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
stage=ProcessStage.PROCESSING,
start_date_time=t,
end_date_time=t,
output_path="/path/to/outputs",
output_path=["./path/to/output"],
pipeline_name="Imaging processing pipeline",
code=example_code.model_copy(
update=dict(
Expand Down Expand Up @@ -90,7 +90,7 @@
stage=ProcessStage.PROCESSING,
start_date_time=t,
end_date_time=t,
output_path="/path/to/outputs",
output_path=["./path/to/output"],
code=example_code.model_copy(
update=dict(
parameters={"u": 7, "z": True},
Expand All @@ -104,7 +104,7 @@
stage=ProcessStage.PROCESSING,
start_date_time=t,
end_date_time=t,
output_path="/path/to/output",
output_path=["./path/to/output"],
code=example_code.model_copy(
update=dict(
parameters={"a": 2, "b": -2},
Expand All @@ -117,7 +117,7 @@
process_type=ProcessName.ANALYSIS,
start_date_time=t,
end_date_time=t,
output_path="/path/to/outputs",
output_path=["./path/to/output"],
code=example_code.model_copy(
update=dict(
parameters={"size": 7},
Expand All @@ -131,7 +131,7 @@
process_type=ProcessName.ANALYSIS,
start_date_time=t,
end_date_time=t,
output_path="/path/to/outputs",
output_path=["./path/to/output"],
code=example_code.model_copy(
update=dict(
parameters={"u": 7, "z": True},
Expand Down
12 changes: 11 additions & 1 deletion src/aind_data_schema/core/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,23 @@ class DataProcess(DataModel):
end_date_time: Optional[Annotated[AwareDatetimeWithDefault, TimeValidation.AFTER]] = Field(
default=None, title="End date time"
)
output_path: Optional[AssetPath] = Field(
output_path: Optional[List[AssetPath]] = Field(
default=None, title="Output path", description="Path to processing outputs, if stored."
)
output_parameters: Optional[GenericModel] = Field(default=None, description="Output parameters", title="Outputs")
notes: Optional[str] = Field(default=None, title="Notes", validate_default=True)
resources: Optional[ResourceUsage] = Field(default=None, title="Process resource usage")

@field_validator("output_path", mode="before")
def validate_output_path(cls, value) -> Optional[List[AssetPath]]:
"""Validator for output_path to ensure it's a list even if a single path is provided
"""
if value is None:
return value
if not isinstance(value, list):
value = [value]
return [AssetPath(path) for path in value]

@field_validator("notes", mode="after")
def validate_other(cls, value: Optional[str], info: ValidationInfo) -> Optional[str]:
"""Validator for other/notes"""
Expand Down
4 changes: 2 additions & 2 deletions tests/test_composability_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ def test_add_processing_objects(self):
experimenters=["Dr. Dan"],
process_type=ProcessName.ANALYSIS,
stage=ProcessStage.PROCESSING,
output_path="/path/to/outputs1",
output_path=["./path/to/output"],
start_date_time=t,
end_date_time=t,
code=Code(
Expand All @@ -272,7 +272,7 @@ def test_add_processing_objects(self):
experimenters=["Dr. Jane"],
process_type=ProcessName.COMPRESSION,
stage=ProcessStage.PROCESSING,
output_path="/path/to/outputs2",
output_path=["./path/to/output"],
start_date_time=t,
end_date_time=t,
code=Code(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_imaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def test_registration(self):
experimenters=["Dr. Dan"],
start_date_time=datetime.now(tz=timezone.utc),
end_date_time=datetime.now(tz=timezone.utc),
output_path="/some/path",
output_path=["./some/path"],
code=Code(
url="https://github.com/abcd",
parameters=parameters,
Expand Down
6 changes: 3 additions & 3 deletions tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def setUpClass(cls) -> None:
experimenters=["Dr. Dan"],
process_type=ProcessName.ANALYSIS,
stage=ProcessStage.ANALYSIS,
output_path="/path/to/outputs",
output_path=["./path/to/output"],
start_date_time=t,
end_date_time=t,
code=Code(
Expand Down Expand Up @@ -828,7 +828,7 @@ def test_validate_time_constraints_processing(self):
experimenters=["Dr. Dan"],
process_type=ProcessName.ANALYSIS,
stage=ProcessStage.ANALYSIS,
output_path="/path/to/outputs",
output_path=["./path/to/output"],
start_date_time=datetime(2023, 4, 3, 20, 0, 0, tzinfo=timezone.utc), # After acquisition
end_date_time=datetime(2023, 4, 3, 21, 0, 0, tzinfo=timezone.utc),
code=Code(
Expand All @@ -855,7 +855,7 @@ def test_validate_time_constraints_processing(self):
experimenters=["Dr. Dan"],
process_type=ProcessName.ANALYSIS,
stage=ProcessStage.ANALYSIS,
output_path="/path/to/outputs",
output_path=["./path/to/output"],
start_date_time=datetime(2023, 4, 3, 17, 0, 0, tzinfo=timezone.utc), # Before acquisition start
end_date_time=datetime(2023, 4, 3, 21, 0, 0, tzinfo=timezone.utc),
code=Code(
Expand Down
52 changes: 52 additions & 0 deletions tests/test_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from aind_data_schema_models.units import MemoryUnit

from aind_data_schema.components.identifiers import Code, DataAsset
from aind_data_schema.components.wrappers import AssetPath
from aind_data_schema.core.processing import (
DataProcess,
Processing,
Expand Down Expand Up @@ -118,6 +119,57 @@ def test_resource_usage_unit_validators(self):
# p = Processing(data_processes=[])
# self.assertIsNotNone(p)

def _make_data_process(self, **kwargs) -> DataProcess:
"""Helper to create a minimal DataProcess with overridable fields"""
defaults = dict(
experimenters=["Dr. Dan"],
process_type=ProcessName.COMPRESSION,
stage=ProcessStage.PROCESSING,
code=code,
start_date_time=t,
end_date_time=t,
)
defaults.update(kwargs)
return DataProcess(**defaults)

def test_output_path_none(self):
"""output_path defaults to None when not provided"""
dp = self._make_data_process()
self.assertIsNone(dp.output_path)

def test_output_path_single_string(self):
"""A single string is wrapped into a one-element list of AssetPath"""
dp = self._make_data_process(output_path="./outputs/result.nwb")
self.assertIsInstance(dp.output_path, list)
self.assertEqual(len(dp.output_path), 1)
self.assertIsInstance(dp.output_path[0], AssetPath)
self.assertEqual(str(dp.output_path[0]), "outputs/result.nwb")

def test_output_path_single_asset_path(self):
"""A single AssetPath is wrapped into a one-element list"""
dp = self._make_data_process(output_path=AssetPath("outputs/result.nwb"))
self.assertIsInstance(dp.output_path, list)
self.assertEqual(len(dp.output_path), 1)
self.assertIsInstance(dp.output_path[0], AssetPath)

def test_output_path_list_of_strings(self):
"""A list of strings is converted to a list of AssetPath"""
paths = ["outputs/a.nwb", "outputs/b.nwb"]
dp = self._make_data_process(output_path=paths)
self.assertIsInstance(dp.output_path, list)
self.assertEqual(len(dp.output_path), 2)
for item in dp.output_path:
self.assertIsInstance(item, AssetPath)
self.assertEqual([str(p) for p in dp.output_path], paths)

def test_output_path_list_of_asset_paths(self):
"""A list of AssetPath objects passes through unchanged"""
paths = [AssetPath("outputs/a.nwb"), AssetPath("outputs/b.nwb")]
dp = self._make_data_process(output_path=paths)
self.assertEqual(len(dp.output_path), 2)
for item in dp.output_path:
self.assertIsInstance(item, AssetPath)

def test_unique_process_names(self):
"""Test that process names are unique within a Processing object"""

Expand Down
Loading