Skip to content
Open
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
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,31 @@ The following projects have adopted Neuroshapes:
# Formats and standards
All schemas in this repository conform to the [W3C SHACL recommendation](https://www.w3.org/TR/shacl) and are serialized using [JSON-LD](https://www.w3.org/TR/2014/REC-json-ld-20140116/).

## Using YAML templates for manual data entry

For researchers who need to manually enter experimental metadata, we provide human-friendly YAML templates in the `templates/yaml/` directory. These templates are easier to edit than JSON-LD and can be converted automatically.

### Quick start

```bash
# Copy a template
cp templates/yaml/subject.yaml my_experiment.yaml

# Fill in your data with any text editor
nano my_experiment.yaml

# Convert to JSON-LD
python scripts/yaml_to_jsonld.py my_experiment.yaml my_experiment.json
```

Available templates:
- `subject.yaml` - Animal/subject information
- `slice.yaml` - Brain slice preparation
- `patched_slice.yaml` - Electrophysiology recording
- `reconstructed_cell.yaml` - Complete reconstruction workflow

See [templates/yaml/README.md](templates/yaml/README.md) for detailed documentation.

## Testing shapes with examples

Two different tests are executed in the unittest. The first test validates that schemas conform with the SHACL specifications.
Expand Down Expand Up @@ -86,7 +111,7 @@ Tests require python > 3.6, and pytest. To run them follow next:
python3 -m venv env
source env/bin/activate
# install requirements
pip install pytest pyshacl
pip install -r requirements.txt
# run tests
pytest

Expand Down
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pytest>=7.0.0
rdflib>=6.0.0
pyshacl>=0.17.0
PyYAML>=6.0
103 changes: 103 additions & 0 deletions scripts/yaml_to_jsonld.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""
Convert YAML templates to JSON-LD format compatible with Neuroshapes schemas.

Usage:
python yaml_to_jsonld.py input.yaml output.json
python yaml_to_jsonld.py input.yaml output.json --validate
"""

import yaml
import json
import sys
from pathlib import Path
from typing import Dict, Any

CONTEXT = "https://incf.github.io/neuroshapes/contexts/data.json"

TYPE_MAPPINGS = {
"Subject": "nsg:Subject",
"Slice": "nsg:Slice",
"PatchedSlice": "nsg:PatchedSlice",
"FixedStainedSlice": "nsg:FixedStainedSlice",
"ImagedSlice": "nsg:ImagedSlice",
"LabeledCell": "nsg:LabeledCell",
"ReconstructedCell": "nsg:ReconstructedCell"
}


def clean_entity(entity_data: Dict[str, Any]) -> Dict[str, Any]:
"""Remove empty fields from entity data."""
cleaned = {}
for key, value in entity_data.items():
if value == "" or value is None:
continue
if isinstance(value, dict):
nested = clean_entity(value)
if nested:
cleaned[key] = nested
Comment on lines +30 to +38

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clean_entity function doesn't handle list/array values. If a field contains a list (e.g., multiple researchers or coordinates as arrays), the function will not recursively clean nested dictionaries within those lists, potentially leaving empty values in list items.

Suggested change
"""Remove empty fields from entity data."""
cleaned = {}
for key, value in entity_data.items():
if value == "" or value is None:
continue
if isinstance(value, dict):
nested = clean_entity(value)
if nested:
cleaned[key] = nested
"""Remove empty fields from entity data, including within nested dicts and lists."""
cleaned = {}
for key, value in entity_data.items():
# Skip top-level empty strings and None values
if value == "" or value is None:
continue
if isinstance(value, dict):
nested = clean_entity(value)
if nested:
cleaned[key] = nested
elif isinstance(value, list):
cleaned_list = []
for item in value:
if item == "" or item is None:
continue
if isinstance(item, dict):
nested_item = clean_entity(item)
if nested_item:
cleaned_list.append(nested_item)
else:
cleaned_list.append(item)
if cleaned_list:
cleaned[key] = cleaned_list

Copilot uses AI. Check for mistakes.
else:
cleaned[key] = value
return cleaned


def yaml_to_jsonld(yaml_data: Dict[str, Any]) -> Dict[str, Any]:
"""Convert YAML structure to JSON-LD with proper @type and @context."""
jsonld = {
"@context": CONTEXT,
"@graph": []
}

for entity_type, entity_data in yaml_data.items():
if entity_type not in TYPE_MAPPINGS:
continue

if not entity_data:
continue

cleaned_data = clean_entity(entity_data)
if not cleaned_data:
continue

entity = {
"@type": TYPE_MAPPINGS[entity_type],
**cleaned_data
}
jsonld["@graph"].append(entity)

return jsonld


def main():
if len(sys.argv) < 3:
print(__doc__)
sys.exit(1)

input_path = Path(sys.argv[1])
output_path = Path(sys.argv[2])

if not input_path.exists():
print(f"Error: Input file {input_path} not found")
sys.exit(1)

try:
with open(input_path) as f:
yaml_data = yaml.safe_load(f)
except yaml.YAMLError as e:
print(f"Error parsing YAML: {e}")
sys.exit(1)

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code doesn't handle the case where yaml.safe_load returns None (which happens with empty YAML files). This will cause a TypeError when yaml_to_jsonld tries to iterate over yaml_data.items(). Consider adding a check after line 85 to handle None or non-dict yaml_data.

Suggested change
sys.exit(1)
sys.exit(1)
# Handle empty or invalid YAML root structures
if yaml_data is None:
yaml_data = {}
elif not isinstance(yaml_data, dict):
print("Error: YAML root element must be a mapping/object")
sys.exit(1)

Copilot uses AI. Check for mistakes.

jsonld_data = yaml_to_jsonld(yaml_data)

with open(output_path, 'w') as f:
json.dump(jsonld_data, f, indent=2)

Comment on lines +92 to +94

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script doesn't check if the output directory exists before trying to write the file. If the user specifies an output path in a non-existent directory (e.g., "output/data/my_experiment.json"), the script will fail with a FileNotFoundError. Consider creating parent directories or providing a clearer error message.

Suggested change
with open(output_path, 'w') as f:
json.dump(jsonld_data, f, indent=2)
# Ensure the output directory exists before writing the file
try:
output_path.parent.mkdir(parents=True, exist_ok=True)
except OSError as e:
print(f"Error: Could not create output directory {output_path.parent}: {e}")
sys.exit(1)
try:
with open(output_path, 'w') as f:
json.dump(jsonld_data, f, indent=2)
except OSError as e:
print(f"Error: Could not write to output file {output_path}: {e}")
sys.exit(1)

Copilot uses AI. Check for mistakes.
print(f"Converted {input_path} to {output_path}")

if "--validate" in sys.argv:
print("\nNote: Validation against SHACL schemas not yet implemented")
print("Please use existing validation tools in tests/")


if __name__ == "__main__":
main()
Comment on lines +71 to +103

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main() function in the script lacks test coverage. Consider adding tests that verify command-line argument parsing, file I/O operations, error handling for missing files, and the --validate flag behavior to ensure the script functions correctly as a command-line tool.

Copilot uses AI. Check for mistakes.
49 changes: 49 additions & 0 deletions templates/yaml/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# YAML Templates for Neuroshapes

Human-readable templates for manual data entry. These templates can be filled out and converted to JSON-LD for validation.

## Usage

1. Copy the relevant template file
2. Fill in your experimental data
3. Convert to JSON-LD using `scripts/yaml_to_jsonld.py`
4. Validate against Neuroshapes schemas

## Available Templates

- `subject.yaml` - Subject/animal information
- `slice.yaml` - Brain slice preparation
- `patched_slice.yaml` - Electrophysiology recording
- `reconstructed_cell.yaml` - Complete neuron reconstruction workflow

## Example

```bash
# Copy a template
cp templates/yaml/subject.yaml my_experiment.yaml

# Edit with your data
nano my_experiment.yaml

# Convert to JSON-LD
python scripts/yaml_to_jsonld.py my_experiment.yaml my_experiment.json

# Validate (optional)
python scripts/yaml_to_jsonld.py my_experiment.yaml my_experiment.json --validate
```

## Why YAML?

- **Human-readable**: Cleaner syntax than JSON
- **Less error-prone**: No bracket matching issues
- **Compact**: Uses indentation instead of separators
- **JSON-compatible**: YAML is a superset of JSON
- **Better for manual editing**: Easier to see hierarchy

## Contributing

When creating new templates:
1. Follow the existing naming convention
2. Include helpful comments for each field
3. Provide example values where appropriate
4. Test conversion to JSON-LD
11 changes: 11 additions & 0 deletions templates/yaml/example_subject.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Example: Mouse Hippocampus Experiment

Subject:
id: "M001"
species: "Mus musculus"
strain: "C57BL/6"
sex: "Male"
age: "P21"
animal_weight: "25g"
date: "2024-03-15"
comment: "Healthy juvenile mouse"
14 changes: 14 additions & 0 deletions templates/yaml/patched_slice.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Electrophysiology Recording Template

PatchedSlice:
protocol: ""
person: ""
date: ""
name: ""
brainLocation: ""
bathSolution: ""
temperature: "" # e.g., "32C"
recordingType: "" # e.g., "whole-cell patch clamp"
intracellularSolution: ""
generated: ""
Comment on lines +4 to +13

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The patched_slice.yaml template lacks the detailed descriptive comments that subject.yaml provides. For consistency and better usability, consider adding descriptive comments above each field (similar to subject.yaml) explaining what each field represents, expected formats, and providing examples.

Suggested change
protocol: ""
person: ""
date: ""
name: ""
brainLocation: ""
bathSolution: ""
temperature: "" # e.g., "32C"
recordingType: "" # e.g., "whole-cell patch clamp"
intracellularSolution: ""
generated: ""
# Name or identifier of the electrophysiology protocol used for this slice.
# Example: "Standard whole-cell patch clamp in acute slices v1"
protocol: ""
# Full name or unique identifier of the person performing the recording.
# Example: "Jane Doe"
person: ""
# Date of the recording session.
# Recommended format: "YYYY-MM-DD" (ISO 8601 date).
# Example: "2024-03-15"
date: ""
# Descriptive name for this patched slice or recording.
# Often combines animal/subject ID and slice/recording number.
# Example: "MouseA_Slice3_Cell1"
name: ""
# Brain region and, optionally, laterality or layer for the recorded slice.
# Example: "Primary visual cortex (V1), layer 2/3, left hemisphere"
brainLocation: ""
# Description or identifier of the extracellular (bath) solution used during recording.
# This can be the full composition or a reference to a standard solution.
# Example: "ACSF (in mM: 125 NaCl, 2.5 KCl, 2 CaCl2, 1 MgCl2, 25 NaHCO3, 25 glucose)"
bathSolution: ""
# Temperature of the bath solution during recording, including units.
# Example: "32C" or "32 °C"
temperature: ""
# Type of electrophysiological recording performed.
# Example: "whole-cell patch clamp", "cell-attached", "current clamp", "voltage clamp"
recordingType: ""
# Description or identifier of the internal (intracellular) solution in the pipette.
# This can be the full composition or a reference to a standard internal.
# Example: "K-gluconate based internal, 135 K-gluconate, 10 HEPES, 10 phosphocreatine, 4 Mg-ATP, 0.3 Na-GTP"
intracellularSolution: ""
# Timestamp or date-time when this entry/template was generated.
# Recommended format: ISO 8601 date-time.
# Example: "2024-03-15T14:32:00Z"
generated: ""
# Free-text notes or comments about this recording or slice (optional).
# Example: "Cell became leaky after 15 minutes; exclude from summary analysis."

Copilot uses AI. Check for mistakes.
comment: ""
77 changes: 77 additions & 0 deletions templates/yaml/reconstructed_cell.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Complete Neuron Reconstruction Workflow Template
# This template covers the full experimental pipeline from animal to reconstructed cell

Subject:
id: ""
species: ""
strain: ""
sex: ""
age: ""
animal_weight: ""
date: ""
comment: ""

Slice:
protocol: ""
person: ""
date: ""
solution: ""
brainLocation: ""
cuttingThickness: "" # e.g., "300um"
generated: ""
hemisphere: "" # "left" or "right"
slicingPlane: "" # e.g., "sagittal", "coronal"
slicingAngle: ""
comment: ""

PatchedSlice:
protocol: ""
person: ""
date: ""
name: ""
brainLocation: ""
bathSolution: ""
temperature: "" # e.g., "32C"
recordingType: ""
intracellularSolution: ""
generated: ""
comment: ""

FixedStainedSlice:
protocol: ""
person: ""
date: ""
name: ""
comment: ""

ImagedSlice:
protocol: ""
person: ""
date: ""
name: ""
generated: ""
comment: ""

LabeledCell:
name: ""
brainLocation: ""
coordinatesInBrainAtlas:
rostrocaudal: ""
lateral: ""
dorsal: ""
locationInSlice: ""
putativeMType: ""
generated: ""
comment: ""

ReconstructedCell:
protocol: ""
person: ""
date: ""
name: ""
mType: ""
axonProjection: ""
compressionCorrection: ""
shrinkageCorrection: ""
geometryCorrected: ""
comment: ""
Comment on lines +5 to +77

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reconstructed_cell.yaml template lacks descriptive comments for all entity fields. Given its complexity with 7 different entity types, detailed documentation would be especially helpful for users to understand what each field represents and what values are expected. Consider adding descriptive comments similar to subject.yaml.

Suggested change
id: ""
species: ""
strain: ""
sex: ""
age: ""
animal_weight: ""
date: ""
comment: ""
Slice:
protocol: ""
person: ""
date: ""
solution: ""
brainLocation: ""
cuttingThickness: "" # e.g., "300um"
generated: ""
hemisphere: "" # "left" or "right"
slicingPlane: "" # e.g., "sagittal", "coronal"
slicingAngle: ""
comment: ""
PatchedSlice:
protocol: ""
person: ""
date: ""
name: ""
brainLocation: ""
bathSolution: ""
temperature: "" # e.g., "32C"
recordingType: ""
intracellularSolution: ""
generated: ""
comment: ""
FixedStainedSlice:
protocol: ""
person: ""
date: ""
name: ""
comment: ""
ImagedSlice:
protocol: ""
person: ""
date: ""
name: ""
generated: ""
comment: ""
LabeledCell:
name: ""
brainLocation: ""
coordinatesInBrainAtlas:
rostrocaudal: ""
lateral: ""
dorsal: ""
locationInSlice: ""
putativeMType: ""
generated: ""
comment: ""
ReconstructedCell:
protocol: ""
person: ""
date: ""
name: ""
mType: ""
axonProjection: ""
compressionCorrection: ""
shrinkageCorrection: ""
geometryCorrected: ""
comment: ""
id: "" # Unique identifier for the animal/subject (e.g., animal ID or lab code)
species: "" # Species name, preferably Latin binomial (e.g., "Mus musculus")
strain: "" # Strain or line information (e.g., "C57BL/6J", transgenic line, etc.)
sex: "" # Biological sex of the subject (e.g., "male", "female", "unknown")
age: "" # Age of the subject with units (e.g., "P30", "12 weeks")
animal_weight: "" # Body weight at experiment time with units (e.g., "25 g")
date: "" # Date associated with the subject (e.g., birth, arrival, or experiment start; ISO format "YYYY-MM-DD" recommended)
comment: "" # Free-text notes about the subject (e.g., health status, treatment history)
Slice:
protocol: "" # Protocol identifier or description used for slice preparation
person: "" # Name or initials of the person who prepared the slice
date: "" # Date of slice preparation (ISO format "YYYY-MM-DD" recommended)
solution: "" # Cutting solution/composition used during slicing (e.g., ACSF recipe)
brainLocation: "" # Target brain region from which the slice was taken (e.g., "V1 layer 2/3")
cuttingThickness: "" # Physical slice thickness with units (e.g., "300 um")
generated: "" # Identifier or reference to the generated Slice object in the pipeline (e.g., UUID or file ID)
hemisphere: "" # Brain hemisphere of origin (e.g., "left", "right", "unknown")
slicingPlane: "" # Anatomical plane of section (e.g., "sagittal", "coronal", "horizontal")
slicingAngle: "" # Any deviation angle from the canonical slicing plane (e.g., "15 degrees from coronal")
comment: "" # Free-text notes on slicing conditions or observations
PatchedSlice:
protocol: "" # Protocol identifier or description used for patch-clamp recording
person: "" # Name or initials of the person who performed the patch-clamp
date: "" # Date of recording (ISO format "YYYY-MM-DD" recommended)
name: "" # Name or ID of the patched slice (e.g., slice label on rig)
brainLocation: "" # Recorded region within the slice (e.g., "V1 L2/3", "CA1 stratum pyramidale")
bathSolution: "" # Bath/recording solution and composition used during recording
temperature: "" # Bath temperature during recording with units (e.g., "32 C")
recordingType: "" # Type of recording (e.g., "whole-cell current clamp", "voltage clamp", "cell-attached")
intracellularSolution: "" # Composition or identifier of the internal pipette solution
generated: "" # Identifier or reference to the generated PatchedSlice object in the pipeline
comment: "" # Free-text notes about recording quality, issues, or deviations from protocol
FixedStainedSlice:
protocol: "" # Protocol identifier or description for fixation and staining
person: "" # Name or initials of the person who performed fixation/staining
date: "" # Date of fixation/staining (ISO format "YYYY-MM-DD" recommended)
name: "" # Name or ID of the fixed/stained slice (e.g., histology label)
comment: "" # Free-text notes on fixation, staining quality, or protocol variations
ImagedSlice:
protocol: "" # Protocol identifier or description for imaging (e.g., microscope settings, modality)
person: "" # Name or initials of the person who acquired the images
date: "" # Date of imaging (ISO format "YYYY-MM-DD" recommended)
name: "" # Name or ID of the imaged slice or image stack
generated: "" # Identifier or reference to the generated ImagedSlice data (e.g., image file or dataset ID)
comment: "" # Free-text notes on imaging conditions or quality (e.g., Z-step, objective, artifacts)
LabeledCell:
name: "" # Name or ID of the labeled cell (e.g., cell ID from recording)
brainLocation: "" # Brain region assignment for the labeled cell (e.g., "V1 L2/3", "S1 L4")
coordinatesInBrainAtlas: # 3D coordinates of the cell in a reference brain atlas
rostrocaudal: "" # Rostrocaudal (anterior-posterior) coordinate with units or atlas units
lateral: "" # Medial-lateral coordinate with units or atlas units
dorsal: "" # Dorsal-ventral coordinate with units or atlas units
locationInSlice: "" # Cell position within the slice (e.g., depth from pia, distance from landmark)
putativeMType: "" # Putative morphological type based on preliminary assessment (e.g., "L2/3 IT", "basket cell")
generated: "" # Identifier or reference to the generated LabeledCell object in the pipeline
comment: "" # Free-text notes on labeling quality, ambiguity, or classification rationale
ReconstructedCell:
protocol: "" # Protocol identifier or description for morphological reconstruction and tracing
person: "" # Name or initials of the person who performed the reconstruction
date: "" # Date of reconstruction (ISO format "YYYY-MM-DD" recommended)
name: "" # Name or ID of the reconstructed cell (e.g., reconstruction file or cell label)
mType: "" # Final assigned morphological cell type (e.g., standardized M-type classification)
axonProjection: "" # Description of axonal projection pattern (e.g., "local", "callosal", target regions)
compressionCorrection: "" # Description or factor for correction of tissue compression during slice preparation
shrinkageCorrection: "" # Description or factor for correction of tissue shrinkage during histology
geometryCorrected: "" # Flag or description indicating whether geometry was corrected (e.g., "yes", "no", method)
comment: "" # Free-text notes about reconstruction quality, uncertainties, or additional details

Copilot uses AI. Check for mistakes.
14 changes: 14 additions & 0 deletions templates/yaml/slice.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Brain Slice Preparation Template

Slice:
protocol: ""
person: ""
date: ""
solution: ""
brainLocation: ""
cuttingThickness: "" # e.g., "300um"
generated: ""
hemisphere: "" # "left" or "right"
slicingPlane: "" # e.g., "sagittal", "coronal", "horizontal"
slicingAngle: ""
Comment on lines +4 to +13

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The slice.yaml and patched_slice.yaml templates lack the detailed descriptive comments that subject.yaml provides. For consistency and better usability, consider adding descriptive comments above each field (similar to subject.yaml) explaining what each field represents, expected formats, and providing examples.

Suggested change
protocol: ""
person: ""
date: ""
solution: ""
brainLocation: ""
cuttingThickness: "" # e.g., "300um"
generated: ""
hemisphere: "" # "left" or "right"
slicingPlane: "" # e.g., "sagittal", "coronal", "horizontal"
slicingAngle: ""
# Name or identifier of the slicing protocol used.
# Example: "Standard_Adult_Mouse_Hippocampus_v1"
protocol: ""
# Name or identifier of the person who prepared the slice.
# Example: "Dr. Jane Doe" or "tech_123"
person: ""
# Date when the slice was prepared, in ISO 8601 format (YYYY-MM-DD).
# Example: "2024-03-15"
date: ""
# Name or description of the slicing solution / ACSF used during cutting.
# Example: "Ice-cold sucrose ACSF" or "NMDG-ACSF (composition in protocol)"
solution: ""
# Brain region and location from which the slice was prepared.
# Include species/structure and, if relevant, coordinates or level.
# Example: "Mouse hippocampus, dorsal, ~-2.0 mm from bregma"
brainLocation: ""
# Physical thickness of the slice including units.
# Example: "300 um" or "250 µm"
cuttingThickness: ""
# Timestamp or date-time string indicating when this slice record was generated.
# Use ISO 8601 format where possible.
# Example: "2024-03-15T10:32:00Z"
generated: ""
# Cerebral hemisphere from which the tissue was taken.
# Allowed values: "left", "right".
# Example: "left"
hemisphere: ""
# Orientation of the slice relative to standard anatomical planes.
# Common values: "sagittal", "coronal", "horizontal".
# Example: "coronal"
slicingPlane: ""
# Angle of the slice relative to the principal plane, in degrees.
# Use 0 for no tilt; positive values indicate the deviation and
# optionally specify the reference axis in free text.
# Example: "15" or "15 degrees from coronal plane"
slicingAngle: ""
# Free-text notes about the slice preparation (conditions, issues, remarks).
# Example: "Minor tearing near CA1; temperature 33–34°C throughout."

Copilot uses AI. Check for mistakes.
comment: ""
28 changes: 28 additions & 0 deletions templates/yaml/subject.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Subject/Animal Metadata Template
# Fill in the fields below with your experimental data
# Remove or leave blank any fields that don't apply to your experiment

Subject:
# Unique identifier for this subject (required)
id: ""

# Species name (e.g., "Mus musculus") or taxonomy ID (e.g., "NCBITaxon:10090")
species: ""

# Genetic strain (e.g., "C57BL/6", "Wistar")
strain: ""

# Biological sex ("Male" or "Female")
sex: ""

# Age of subject (e.g., "P21", "8 weeks", "3 months")
age: ""

# Weight with unit (e.g., "25g", "250g")
animal_weight: ""

# Date of experiment in ISO format (YYYY-MM-DD)
date: ""

# Additional notes or comments
comment: ""
Loading