chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:12:01 +08:00
commit 0fc0c0bb15
1490 changed files with 604780 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
name: PR Skill Scan
on:
pull_request:
paths:
- "skills/**"
- "scan_skills.py"
- "scan_pr_skills.py"
- "pyproject.toml"
- "uv.lock"
- ".github/workflows/pr-skill-scan.yml"
permissions:
contents: read
pull-requests: write
concurrency:
group: pr-skill-scan-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
scan:
name: Scan changed skills
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout PR
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Detect changed skills
id: changed
run: |
set -euo pipefail
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
echo "Base: $BASE_SHA"
echo "Head: $HEAD_SHA"
# Files added/copied/modified/renamed under skills/<skill>/...
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMR "$BASE_SHA" "$HEAD_SHA" -- 'skills/**' || true)
echo "Changed files under skills/:"
echo "$CHANGED_FILES"
# Derive unique top-level skill directories and keep only those that still exist with a SKILL.md
SKILL_DIRS=$(echo "$CHANGED_FILES" \
| awk -F/ 'NF>=2 && $1=="skills" {print $1 "/" $2}' \
| sort -u)
EXISTING=""
for d in $SKILL_DIRS; do
if [ -f "$d/SKILL.md" ]; then
EXISTING="$EXISTING $d"
fi
done
EXISTING=$(echo "$EXISTING" | xargs || true)
echo "Skill dirs to scan: '$EXISTING'"
echo "skill_dirs=$EXISTING" >> "$GITHUB_OUTPUT"
- name: Set up uv
if: steps.changed.outputs.skill_dirs != ''
uses: astral-sh/setup-uv@v8.0.0
with:
enable-cache: true
cache-dependency-glob: uv.lock
python-version: "3.13"
- name: Install dependencies
if: steps.changed.outputs.skill_dirs != ''
run: uv sync --python 3.13
- name: Run scanner on changed skills
if: steps.changed.outputs.skill_dirs != ''
id: scan
env:
SKILL_SCANNER_LLM_API_KEY: ${{ secrets.SKILL_SCANNER_LLM_API_KEY }}
SKILL_SCANNER_LLM_MODEL: ${{ vars.SKILL_SCANNER_LLM_MODEL || 'claude-sonnet-4-6' }}
run: |
uv run python scan_pr_skills.py \
--output pr_scan_comment.md \
--fail-on HIGH \
${{ steps.changed.outputs.skill_dirs }}
- name: Prepare no-op comment
if: steps.changed.outputs.skill_dirs == ''
run: |
cat > pr_scan_comment.md <<'EOF'
<!-- skill-security-scan -->
## 🛡️ Skill Security Scan
No skill directories (with a `SKILL.md`) were changed in this PR — nothing to scan.
EOF
- name: Upload scan comment as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: pr-skill-scan-comment
path: pr_scan_comment.md
if-no-files-found: ignore
- name: Post or update PR comment
if: always() && hashFiles('pr_scan_comment.md') != ''
uses: marocchino/sticky-pull-request-comment@v2
with:
header: skill-security-scan
path: pr_scan_comment.md
+101
View File
@@ -0,0 +1,101 @@
name: Create Release
on:
push:
branches:
- main
paths:
- 'pyproject.toml'
workflow_dispatch:
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0 # Fetch all history for release notes
- name: Extract version from pyproject.toml
id: get_version
run: |
VERSION=$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
echo "Extracted version: $VERSION"
- name: Check if tag already exists
id: check_tag
run: |
if git rev-parse "v${{ steps.get_version.outputs.version }}" >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Tag v${{ steps.get_version.outputs.version }} already exists"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Tag v${{ steps.get_version.outputs.version }} does not exist"
fi
- name: Get previous tag
id: previous_tag
if: steps.check_tag.outputs.exists == 'false'
run: |
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then
echo "previous_tag=" >> $GITHUB_OUTPUT
echo "No previous tag found"
else
echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
echo "Previous tag: $PREVIOUS_TAG"
fi
- name: Generate release notes
id: release_notes
if: steps.check_tag.outputs.exists == 'false'
run: |
PREVIOUS_TAG="${{ steps.previous_tag.outputs.previous_tag }}"
# Start release notes
cat > release_notes.md << 'EOF'
## What's Changed
EOF
# Generate changelog from commits
if [ -n "$PREVIOUS_TAG" ]; then
echo "Changes since $PREVIOUS_TAG:" >> release_notes.md
echo "" >> release_notes.md
# Get commits with nice formatting
git log ${PREVIOUS_TAG}..HEAD --pretty=format:"* %s (%h)" --no-merges >> release_notes.md
else
echo "Initial release of Claude Scientific Skills" >> release_notes.md
echo "" >> release_notes.md
echo "This release includes:" >> release_notes.md
git log --pretty=format:"* %s (%h)" --no-merges --max-count=20 >> release_notes.md
fi
cat release_notes.md
- name: Create Release
if: steps.check_tag.outputs.exists == 'false'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.get_version.outputs.tag }}
name: v${{ steps.get_version.outputs.version }}
body_path: release_notes.md
draft: false
prerelease: false
generate_release_notes: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Skip release creation
if: steps.check_tag.outputs.exists == 'true'
run: |
echo "Release v${{ steps.get_version.outputs.version }} already exists. Skipping release creation."
+44
View File
@@ -0,0 +1,44 @@
name: Weekly Security Scan
on:
schedule:
- cron: "0 9 * * 1" # Every Monday at 09:00 UTC
workflow_dispatch: # Allow manual trigger
permissions:
contents: write
jobs:
scan:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.0.0
with:
enable-cache: true
cache-dependency-glob: uv.lock
python-version: "3.13"
- name: Install dependencies
run: uv sync --python 3.13
- name: Run security scan
env:
SKILL_SCANNER_LLM_API_KEY: ${{ secrets.SKILL_SCANNER_LLM_API_KEY }}
SKILL_SCANNER_LLM_MODEL: ${{ vars.SKILL_SCANNER_LLM_MODEL || 'claude-sonnet-4-6' }}
run: uv run python scan_skills.py
- name: Commit updated SECURITY.md
run: |
git diff --quiet SECURITY.md && exit 0
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git stash --include-untracked
git pull --rebase
git stash pop || true
git add SECURITY.md
git commit -m "chore: update security scan report [skip ci]"
git push
+21
View File
@@ -0,0 +1,21 @@
# OS
.DS_Store
# Python
.venv/
.python-version
__pycache__/
# Secrets
.env
# Project
temp/
research/
.agents/
AGENTS.md
skills-lock.json
uv.lock
+217
View File
@@ -0,0 +1,217 @@
# Contributing Skills
Thanks for helping improve Scientific Agent Skills. This guide explains how to add or update a skill in this repository while following the open [Agent Skills specification](https://agentskills.io/specification).
## Ways to Contribute
- Add a new scientific package, database, platform, workflow, or research method skill.
- Improve an existing skill with clearer instructions, current APIs, better examples, references, or scripts.
- Fix outdated examples, broken install steps, security issues, or documentation gaps.
- Report bugs or request new skills through GitHub Issues.
## Skill Location
All repository skills live under `skills/`:
```text
skills/
└── skill-name/
├── SKILL.md
├── references/
├── scripts/
└── assets/
```
Only `SKILL.md` is required. Use optional directories when they make the skill easier to maintain:
- `references/` for longer documentation that agents should read only when needed.
- `scripts/` for executable helpers, validators, or reusable workflow code.
- `assets/` for templates, static resources, or example data.
Keep references one level deep from `SKILL.md` where possible, and keep the main `SKILL.md` concise. The Agent Skills specification recommends keeping `SKILL.md` under 500 lines and using progressive disclosure for longer material.
## Required Skill Format
Every skill must be a directory containing a `SKILL.md` file with YAML frontmatter followed by Markdown instructions.
Use this minimum template:
```markdown
---
name: skill-name
description: Clear description of what the skill does and when an agent should use it.
metadata: {"version": "1.0", "skill-author": "Your Name"}
---
# Skill Title
## When to Use
Use this skill when...
## Workflow
1. ...
2. ...
## Examples
...
```
### Frontmatter Requirements
Follow the [Agent Skills specification](https://agentskills.io/specification) and this repository's conventions:
- `name` is required, must match the parent directory name, and must be 1-64 characters.
- `name` may contain only lowercase letters, numbers, and hyphens.
- `name` must not start or end with a hyphen and must not contain consecutive hyphens.
- `description` is required, non-empty, and must be at most 1024 characters.
- `description` should explain both what the skill does and when an agent should use it.
- `metadata.version` is required in this repository, even though `metadata` is optional in the upstream spec.
- Version values must be quoted numeric strings, such as `"1.0"` or `"1.1"`.
- **Write `metadata` as a single-line JSON object** (flow style), for example `metadata: {"version": "1.0", "skill-author": "K-Dense Inc."}`. This is valid YAML — so it parses identically in Claude Code, Cursor, Codex, Hermes, Pi, and any Agent Skills-compliant host — and it is the only form OpenClaw's line-based frontmatter reader can parse (a multi-line block `metadata:` is silently dropped there). Do not use a nested `metadata:` block.
Optional frontmatter fields from the specification may be used when relevant:
- `license`: the license for the individual skill, if different or worth stating explicitly.
- `compatibility`: environment requirements such as Python version, system packages, agent host, or network access.
- `metadata`: additional metadata. Must be a single-line JSON object (see above). Common keys: `version` (required), `skill-author`, an optional `openclaw` block, and an optional `hermes` block (see below).
- `allowed-tools`: space-separated tool permissions for hosts that support this experimental field.
- `required_environment_variables`: top-level Hermes credential declarations (see below). Other hosts ignore it.
### OpenClaw gating (`metadata.openclaw`)
OpenClaw reads an optional `openclaw` object nested inside `metadata` for dependency gating, credential injection, and display. Because it lives under `metadata`, the Agent Skills spec permits it and other hosts ignore it. It is only needed for skills with external requirements (credentials, daemons, specific binaries) — most skills omit it entirely. Supported keys:
- `requires`: hard eligibility gates — `{"bins": [...]}` (all must be on `PATH`), `{"anyBins": [...]}` (at least one), `{"env": [...]}` (vars that must be set), `{"config": [...]}`. A failed gate hides the skill from the agent, so only gate on things the skill genuinely cannot run without.
- `primaryEnv`: the main credential variable; OpenClaw injects it from its config (`skills.entries.<name>.apiKey`).
- `envVars`: descriptive (non-gating) declarations — `[{"name": "X_API_KEY", "required": true, "description": "..."}]`. Declare every env var your scripts reference so ClawHub's security analysis does not flag a metadata mismatch.
- `os`: platform filter, e.g. `["darwin", "linux"]`.
- `emoji`, `homepage`: display only.
Example (an API-key skill that stays available even without the key set, so it gates nothing and only declares the credential):
```yaml
metadata: {"version": "1.0", "skill-author": "K-Dense Inc.", "openclaw": {"primaryEnv": "EXA_API_KEY", "envVars": [{"name": "EXA_API_KEY", "required": true, "description": "Exa search API key."}]}}
```
### Hermes compatibility (`required_environment_variables` and `metadata.hermes`)
[Hermes](https://hermes-agent.nousresearch.com/docs) is Agent Skills-compatible, so every skill in this repository already loads and runs there with no changes. Two optional fields make credentialed skills first-class on Hermes:
- **`required_environment_variables`** (top level): the credentials Hermes should prompt for. Write it as a single-line JSON array — `[{"name": "X_API_KEY", "prompt": "What it is", "required_for": "full functionality"}]`. This is the one Hermes-specific field that is *not* nested under `metadata`, because Hermes reads secrets at the top level. Writing it as single-line JSON keeps it valid YAML for every host and lets OpenClaw's line-based reader skip it cleanly; Claude Code, Cursor, and Codex ignore the unknown key. Mirror the same variables you declare in `metadata.openclaw.envVars`, using `required_for: "full functionality"` for required vars and `"optional features"` for optional ones.
- **`metadata.hermes`** (nested, spec-safe like `openclaw`): optional classification and gating — `tags`, `category`, `requires_toolsets`, `fallback_for_toolsets`. A failed `requires_toolsets` gate *hides* the skill, so only gate on a tool the skill genuinely cannot run without; prefer leaving it unset so the skill stays available.
Example (an API-key skill, declaring its credential for Hermes alongside the OpenClaw block):
```yaml
required_environment_variables: [{"name": "EXA_API_KEY", "prompt": "Exa search API key.", "required_for": "full functionality"}]
metadata: {"version": "1.0", "skill-author": "Exa", "openclaw": {"primaryEnv": "EXA_API_KEY", "envVars": [{"name": "EXA_API_KEY", "required": true, "description": "Exa search API key."}]}}
```
## Versioning
Every `SKILL.md` must include a quoted `version` inside the single-line `metadata` object:
```yaml
metadata: {"version": "1.0"}
```
For a new skill, start at `"1.0"`.
When updating an existing skill, increment `metadata.version` in the same pull request:
- Use a minor bump for normal improvements, for example `"1.0"` to `"1.1"`.
- Use a major bump only for a breaking change or substantial redesign, for example `"1.9"` to `"2.0"`.
## Writing a Good Skill
Good skills are specific, practical, and easy for an agent to apply.
- Write the `description` in third person with useful trigger terms.
- Include concrete workflows, commands, and examples instead of broad background explanations.
- Prefer current official APIs, docs, and installation instructions.
- Document required Python packages, system dependencies, credentials, or network access.
- Include scientific best practices, caveats, and validation checks where they matter.
- Move long API details, tables, and extended examples into `references/`.
- Use scripts for fragile or repetitive logic instead of asking the agent to recreate it every time.
- Avoid secrets, credentials, API keys, private URLs, and unpublished data.
## Adding a New Skill
1. Fork the repository and create a branch:
```bash
git checkout -b add-skill-name
```
2. Create a new directory under `skills/` whose name matches the skill name:
```text
skills/skill-name/
```
3. Add `SKILL.md` with valid frontmatter, including `metadata.version`.
4. Add supporting `references/`, `scripts/`, or `assets/` only when they are useful.
5. Test any commands, code examples, and scripts included in the skill.
6. Update related documentation if the new skill changes repository-level lists, examples, or setup guidance.
7. Run validation and security checks before opening a pull request.
## Updating an Existing Skill
1. Read the current `SKILL.md` and any supporting files.
2. Check upstream package, API, or platform documentation for current behavior.
3. Make the smallest useful change that fixes or improves the skill.
4. Increment `metadata.version`.
5. Test changed examples, commands, and scripts.
6. Note any behavior changes in the pull request description.
## Validation
Validate Agent Skills format with the reference validator:
```bash
skills-ref validate ./skills/skill-name
```
If `skills-ref` is not installed, follow the installation instructions from the [skills-ref reference library](https://github.com/agentskills/agentskills/tree/main/skills-ref).
Security-scan new or substantially changed skills:
```bash
uv pip install cisco-ai-skill-scanner
skill-scanner scan ./skills/skill-name --use-behavioral
```
A clean scan reduces review noise but does not replace manual review.
## Pull Request Checklist
Before submitting a pull request, confirm:
- The skill directory name and `name` frontmatter match exactly.
- `SKILL.md` has valid YAML frontmatter and Markdown body content.
- `metadata` is a single-line JSON object (not a multi-line block), so it parses on OpenClaw as well as Claude Code, Cursor, Codex, Hermes, and Pi.
- If the skill needs credentials, `required_environment_variables` is present as a single-line JSON array and mirrors the variables in `metadata.openclaw.envVars`.
- `metadata.version` exists and is quoted.
- Existing skills have a version bump when changed.
- The `description` clearly says what the skill does and when to use it.
- Examples and scripts have been tested or clearly marked as illustrative.
- No secrets, credentials, private data, or unsafe instructions are included.
- Relevant official documentation is linked where useful.
- Security scanner results are clean or explained in the pull request.
## Pull Request Process
1. Push your branch to your fork.
2. Open a pull request with a clear title, such as `Add scanpy workflow examples` or `Update astropy skill for current API`.
3. Describe what changed, why it matters, and how you tested it.
4. Link related issues, package documentation, release notes, or security findings.
5. Respond to review comments and update the skill as needed.
Thank you for helping make scientific computing more accessible to AI agents and researchers.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 K-Dense Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+804
View File
@@ -0,0 +1,804 @@
# Scientific Agent Skills
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md)
[![Version](https://img.shields.io/badge/Version-2.53.0-blue.svg)](pyproject.toml)
[![Skills](https://img.shields.io/badge/Skills-148-brightgreen.svg)](#-whats-included)
[![Databases](https://img.shields.io/badge/Databases-100%2B-orange.svg)](#-whats-included)
[![Agent Skills](https://img.shields.io/badge/Standard-Agent_Skills-blueviolet.svg)](https://agentskills.io/)
[![Security Scan](https://github.com/K-Dense-AI/scientific-agent-skills/actions/workflows/security-scan.yml/badge.svg)](https://github.com/K-Dense-AI/scientific-agent-skills/actions/workflows/security-scan.yml)
[![Works with](https://img.shields.io/badge/Works_with-Cursor_|_Claude_Code_|_Codex_|_Google_Antigravity-blue.svg)](#-getting-started)
[![X](https://img.shields.io/badge/Follow_on_X-%40k__dense__ai-000000?logo=x)](https://x.com/k_dense_ai)
[![LinkedIn](https://img.shields.io/badge/LinkedIn-K--Dense_Inc.-0A66C2?logo=linkedin)](https://www.linkedin.com/company/k-dense-inc)
[![YouTube](https://img.shields.io/badge/YouTube-K--Dense_Inc.-FF0000?logo=youtube)](https://www.youtube.com/@K-Dense-Inc)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=K-Dense-AI/scientific-agent-skills&type=date&legend=top-left)](https://www.star-history.com/#K-Dense-AI/scientific-agent-skills&type=date&legend=top-left)
> **🔔 Claude Scientific Skills is now Scientific Agent Skills.** Same skills, broader compatibility — now works with any AI agent that supports the open [Agent Skills](https://agentskills.io/) standard, not just Claude.
> **New: [K-Dense BYOK](https://github.com/K-Dense-AI/k-dense-byok)** — A free, open-source AI co-scientist that runs on your desktop, powered by Scientific Agent Skills. Bring your own API keys, pick from 40+ models, and get a full research workspace with web search, file handling, 100+ scientific databases, and access to all 148 skills in this repo. Your data stays on your computer, and you can optionally scale to cloud compute via [Modal](https://modal.com/) for heavy workloads. [Get started here.](https://github.com/K-Dense-AI/k-dense-byok)
> **Stay up to date:** Follow K-Dense on [X](https://x.com/k_dense_ai), [LinkedIn](https://www.linkedin.com/company/k-dense-inc), and [YouTube](https://www.youtube.com/@K-Dense-Inc) for new skills, release announcements, walkthroughs, research workflow demos, and examples you can use with your own AI agent.
A comprehensive collection of **148 ready-to-use scientific and research skills** (covering cancer genomics, drug-target binding, molecular dynamics, RNA velocity, geospatial science, time series forecasting, scientific ML resource discovery via Hugging Science, 78+ scientific databases, and more) for any AI agent that supports the open [Agent Skills](https://agentskills.io/) standard, created by [K-Dense](https://k-dense.ai). Works with **Cursor, Claude Code, Codex, Google Antigravity, and more**. Transform your AI agent into a research assistant capable of executing complex multi-step scientific workflows across biology, chemistry, medicine, and beyond.
> ⭐ **Help make AI for science easier to discover:** If Scientific Agent Skills saves you time, teaches your agent a workflow, or helps your lab move faster, please [star this repository](https://github.com/K-Dense-AI/scientific-agent-skills). A star is a public signal that these open, reusable research skills are worth maintaining: it helps scientists, engineers, and open-source contributors find the project, shows which agent-skill standards are gaining real adoption, and gives us a clear reason to keep expanding the collection for the community.
---
These skills enable your AI agent to seamlessly work with specialized scientific libraries, databases, and tools across multiple scientific domains. While the agent can use any Python package or API on its own, these explicitly defined skills provide curated documentation and examples that make it significantly stronger and more reliable for the workflows below:
- 🧬 Bioinformatics & Genomics - Sequence analysis, single-cell RNA-seq, gene regulatory networks, variant annotation, phylogenetic analysis
- 🧪 Cheminformatics & Drug Discovery - Molecular property prediction, virtual screening, ADMET analysis, molecular docking, lead optimization
- 🔬 Proteomics & Mass Spectrometry - LC-MS/MS processing, peptide identification, spectral matching, protein quantification
- 🏥 Clinical Research & Precision Medicine - Clinical trials, pharmacogenomics, variant interpretation, drug safety, clinical decision support, treatment planning
- 🧠 Healthcare AI & Clinical ML - EHR analysis, physiological signal processing, medical imaging, clinical prediction models
- 🖼️ Medical Imaging & Digital Pathology - DICOM processing, whole slide image analysis, computational pathology, radiology workflows
- 🤖 Machine Learning & AI - Deep learning, reinforcement learning, time series analysis, model interpretability, Bayesian methods
- 🔮 Materials Science & Chemistry - Crystal structure analysis, phase diagrams, metabolic modeling, computational chemistry
- 🌌 Physics & Astronomy - Astronomical data analysis, coordinate transformations, cosmological calculations, symbolic mathematics, physics computations
- ⚙️ Engineering & Simulation - Discrete-event simulation, multi-objective optimization, metabolic engineering, systems modeling, process optimization
- 📊 Data Analysis & Visualization - Statistical analysis, network analysis, time series, publication-quality figures, large-scale data processing, EDA
- 🌍 Geospatial Science & Remote Sensing - Satellite imagery processing, GIS analysis, spatial statistics, terrain analysis, machine learning for Earth observation
- 🧪 Laboratory Automation - Liquid handling protocols, lab equipment control, workflow automation, LIMS integration
- 📚 Scientific Communication - Literature review, peer review, scientific writing, document processing, posters, slides, schematics, citation management
- 🔬 Multi-omics & Systems Biology - Multi-modal data integration, pathway analysis, network biology, systems-level insights
- 🧬 Protein Engineering & Design - Protein language models, structure prediction, sequence design, function annotation
- 🧰 Agent Platforms & Infrastructure - Build on Pi with SDK, RPC, extensions, custom providers/models, packages, TUI components, and session tooling
- 🎓 Research Methodology - Hypothesis generation, scientific brainstorming, critical thinking, grant writing, scholar evaluation
**Transform your AI coding agent into an 'AI Scientist' on your desktop!**
> 🎬 **New to Scientific Agent Skills?** Watch our [Getting Started with Scientific Agent Skills](https://youtu.be/ZxbnDaD_FVg) video for a quick walkthrough.
---
## 📦 What's Included
This repository provides **148 scientific and research skills** organized into the following categories:
- **100+ Scientific & Financial Databases** - A unified database-lookup skill provides deterministic, provenance-rich access to 78 public databases (PubChem, ChEMBL, UniProt, COSMIC, ClinicalTrials.gov, FRED, USPTO, and more), plus dedicated skills for DepMap, Imaging Data Commons, PrimeKG, U.S. Treasury Fiscal Data, and Hugging Science (curated catalog of scientific datasets, models, and demos across 17 scientific domains on Hugging Face). Multi-database packages like BioServices (~40 bioinformatics services), BioPython (38 NCBI sub-databases via Entrez), and gget (20+ genomics databases) add further coverage
- **70+ Optimized Python Package Skills** - Explicitly defined skills for RDKit, Scanpy, PyTorch Lightning, scikit-learn, BioPython, pyzotero, BioServices, PennyLane, Qiskit, Molecular Dynamics (OpenMM/MDAnalysis), scVelo, TimesFM, and others — with curated documentation, examples, and best practices. Note: the agent can write code using *any* Python package, not just these; these skills simply provide stronger, more reliable performance for the packages listed
- **9 Scientific Integration Skills** - Explicitly defined skills for Benchling, DNAnexus, LatchBio, OMERO, Protocols.io, Open Notebook, Ginkgo Cloud Lab, LabArchives, and Opentrons. Again, the agent is not limited to these — any API or platform reachable from Python is fair game; these skills are the optimized, pre-documented paths
- **30+ Analysis & Communication Tools** - Literature review, scientific writing, peer review, document processing, Paperzilla, Exa Search, posters, slides, schematics, infographics, Mermaid diagrams, and more
- **10+ Research & Clinical Tools** - Hypothesis generation, grant writing, clinical decision support, treatment plans, BIDS, regulatory compliance, scenario analysis, and workflow-derived skill drafting with Autoskill
Each skill includes:
- ✅ Comprehensive documentation (`SKILL.md`)
- ✅ Practical code examples
- ✅ Use cases and best practices
- ✅ Integration guides
- ✅ Reference materials
---
## 📋 Table of Contents
- [What's Included](#-whats-included)
- [Why Use This?](#-why-use-this)
- [Getting Started](#-getting-started)
- [Security Disclaimer](#%EF%B8%8F-security-disclaimer)
- [Support Open Source](#%EF%B8%8F-support-the-open-source-community)
- [Prerequisites](#%EF%B8%8F-prerequisites)
- [Quick Examples](#-quick-examples)
- [Use Cases](#-use-cases)
- [Available Skills](#-available-skills)
- [Contributing](#-contributing)
- [Troubleshooting](#-troubleshooting)
- [FAQ](#-faq)
- [Support](#-support)
- [Citation](#-citation)
- [License](#-license)
---
## 🚀 Why Use This?
### ⚡ **Accelerate Your Research**
- **Save Days of Work** - Skip API documentation research and integration setup
- **Production-Ready Code** - Tested, validated examples following scientific best practices
- **Multi-Step Workflows** - Execute complex pipelines with a single prompt
### 🎯 **Comprehensive Coverage**
- **148 Skills** - Extensive coverage across all major scientific domains
- **100+ Databases** - Unified access to 78+ databases via database-lookup, plus dedicated data access skills and multi-database packages like BioServices, BioPython, and gget
- **70+ Optimized Python Package Skills** - RDKit, Scanpy, PyTorch Lightning, scikit-learn, BioServices, PennyLane, Qiskit, Molecular Dynamics (OpenMM/MDAnalysis), scVelo, TimesFM, and others (the agent can use any Python package; these are the pre-documented, higher-performing paths)
### 🔧 **Easy Integration**
- **Simple Setup** - Copy skills to your skills directory and start working
- **Automatic Discovery** - Your agent automatically finds and uses relevant skills
- **Well Documented** - Each skill includes examples, use cases, and best practices
### 🌟 **Maintained & Supported**
- **Regular Updates** - Continuously maintained and expanded by K-Dense team
- **Community Driven** - Open source with active community contributions
- **Enterprise Ready** - Commercial support available for advanced needs
---
## 🎯 Getting Started
### Option 1: npx (all platforms)
Install Scientific Agent Skills with a single command:
```bash
npx skills add K-Dense-AI/scientific-agent-skills
```
This is the official standard approach for installing Agent Skills across **all platforms**, including **Claude Code**, **Claude Cowork**, **Codex**, **Gemini CLI**, **Google Antigravity**, **Cursor**, **OpenClaw**, **NVIDIA NemoClaw**, **Hermes**, **Pi**, and any other agent that supports the open [Agent Skills](https://agentskills.io/) standard.
### Option 2: GitHub CLI (`gh skill`)
If you use the [GitHub CLI](https://cli.github.com/) (v2.90.0+), you can install skills with [`gh skill`](https://github.blog/changelog/2026-04-16-manage-agent-skills-with-github-cli/):
```bash
# Browse and install interactively
gh skill install K-Dense-AI/scientific-agent-skills
# Install a specific skill directly
gh skill install K-Dense-AI/scientific-agent-skills scanpy
# Target a specific agent host
gh skill install K-Dense-AI/scientific-agent-skills --agent cursor
gh skill install K-Dense-AI/scientific-agent-skills --agent claude-code
gh skill install K-Dense-AI/scientific-agent-skills --agent codex
gh skill install K-Dense-AI/scientific-agent-skills --agent gemini
```
`gh skill` automatically installs to the correct directory for your agent host and records provenance metadata for supply chain integrity.
#### Version pinning
Pin to a specific release tag or commit SHA for reproducible installs:
```bash
# Pin to a release tag
gh skill install K-Dense-AI/scientific-agent-skills --pin v1.0.0
# Pin to a commit SHA
gh skill install K-Dense-AI/scientific-agent-skills --pin abc123def
```
#### Keeping skills up to date
```bash
# Check for updates interactively
gh skill update
# Update all installed skills
gh skill update --all
```
### Other Agent Skills hosts (OpenClaw, NemoClaw, Pi, Hermes, …)
You usually don't need anything host-specific. `npx skills add` (Option 1) installs into the shared `~/.agents/skills/` convention, and any compliant client that scans that directory — including **OpenClaw**, **NVIDIA NemoClaw** (an OpenClaw-based secure runtime), and **Pi** — discovers the skills automatically. Project-scoped installs land in `.agents/skills/` and work the same way. To install without the CLI, clone straight into either location:
```bash
git clone https://github.com/K-Dense-AI/scientific-agent-skills.git ~/.agents/skills/scientific-agent-skills # user-level
git clone https://github.com/K-Dense-AI/scientific-agent-skills.git .agents/skills/scientific-agent-skills # project-level
```
**Hermes** is the one host that uses its own registry instead of the shared directory, so add the repo as a tap:
```bash
hermes skills tap add K-Dense-AI/scientific-agent-skills
```
These skills stay portable across all of them: `metadata` is single-line JSON (so OpenClaw's line-based reader parses it), credentialed skills declare a top-level `required_environment_variables` field (so Hermes prompts for keys), and unknown fields are ignored everywhere else. Because 148 skills add up to a lot of standing context, consider installing a topical subset rather than the whole collection.
> **NemoClaw note:** NemoClaw runs agents inside NVIDIA OpenShell with default-deny outbound networking. Skills are discovered and loaded normally, but any skill that needs the network — package installs via `uv`, or API calls (Exa, Parallel, Benchling, NCBI, Materials Project, …) — only works once the operator pre-approves the relevant domains in the OpenShell TUI.
**That's it!** Your AI agent will automatically discover the skills and use them when relevant to your scientific tasks. You can also invoke any skill manually by mentioning the skill name in your prompt.
---
## ⚠️ Security Disclaimer
> **Skills can execute code and influence your coding agent's behavior. Review what you install.**
Agent Skills are powerful — they can instruct your AI agent to run arbitrary code, install packages, make network requests, and modify files on your system. A malicious or poorly written skill has the potential to steer your coding agent into harmful behavior.
We take security seriously. All contributions go through a review process, and we run LLM-based security scans (via [Cisco AI Defense Skill Scanner](https://github.com/cisco-ai-defense/skill-scanner)) on every skill in this repository. However, as a small team with a growing number of community contributions, we cannot guarantee that every skill has been exhaustively reviewed for all possible risks.
**It is ultimately your responsibility to review the skills you install and decide which ones to trust.**
We recommend the following:
- **Do not install everything at once.** Only install the skills you actually need for your work. While installing the full collection was reasonable when K-Dense created and maintained every skill, the repository now includes many community contributions that we may not have reviewed as thoroughly.
- **Read the `SKILL.md` before installing.** Each skill's documentation describes what it does, what packages it uses, and what external services it connects to. If something looks suspicious, don't install it.
- **Check the contribution history.** Skills authored by K-Dense (`K-Dense-AI`) have been through our internal review process. Community-contributed skills have been reviewed to the best of our ability, but with limited resources.
- **Run the security scanner yourself.** Before installing third-party skills, scan them locally:
```bash
uv pip install cisco-ai-skill-scanner
skill-scanner scan /path/to/skill --use-behavioral
```
- **Report anything suspicious.** If you find a skill that looks malicious or behaves unexpectedly, please [open an issue](https://github.com/K-Dense-AI/scientific-agent-skills/issues) immediately so we can investigate.
All skills are scanned on an approximately weekly basis, and [SECURITY.md](SECURITY.md) is updated with the latest results. We try to address security gaps as they arise.
---
## ❤️ Support the Open Source Community
Scientific Agent Skills is powered by **50+ incredible open source projects** maintained by dedicated developers and research communities worldwide. Projects like Biopython, Scanpy, RDKit, scikit-learn, PyTorch Lightning, and many others form the foundation of these skills.
**If you find value in this repository, please consider supporting the projects that make it possible:**
- ⭐ **Star their repositories** on GitHub
- 💰 **Sponsor maintainers** via GitHub Sponsors or NumFOCUS
- 📝 **Cite projects** in your publications
- 💻 **Contribute** code, docs, or bug reports
👉 **[View the full list of projects to support](docs/open-source-sponsors.md)**
---
## ⚙️ Prerequisites
- **Python**: 3.13+ for repository tooling; individual skill dependencies may support broader Python ranges
- **uv**: Python package manager (required for installing skill dependencies)
- **Client**: Any agent that supports the [Agent Skills](https://agentskills.io/) standard (Cursor, Claude Code, Gemini CLI, Codex, Google Antigravity, etc.)
- **System**: macOS, Linux, or Windows with WSL2
- **Dependencies**: Automatically handled by individual skills (check `SKILL.md` files for specific requirements)
### Installing uv
The skills use `uv` as the package manager for installing Python dependencies. Install it using the instructions for your operating system:
**macOS and Linux:**
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
**Windows:**
```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
**Alternative (via pip):**
```bash
pip install uv
```
After installation, verify it works by running:
```bash
uv --version
```
For more installation options and details, visit the [official uv documentation](https://docs.astral.sh/uv/).
---
## 💡 Quick Examples
Once you've installed the skills, you can ask your AI agent to execute complex multi-step scientific workflows. Here are some example prompts:
### 🧪 Drug Discovery Pipeline
**Goal**: Find novel EGFR inhibitors for lung cancer treatment
**Prompt**:
```
Use available skills you have access to whenever possible. Query ChEMBL for EGFR inhibitors (IC50 < 50nM), analyze structure-activity relationships
with RDKit, generate improved analogs with datamol, perform virtual screening with DiffDock
against AlphaFold EGFR structure, search PubMed for resistance mechanisms, check COSMIC for
mutations, and create visualizations and a comprehensive report.
```
**Skills Used**: database-lookup, rdkit, datamol, diffdock, paper-lookup, scientific-visualization
---
### 🔬 Single-Cell RNA-seq Analysis
**Goal**: Comprehensive analysis of 10X Genomics data with public data integration
**Prompt**:
```
Use available skills you have access to whenever possible. Load 10X dataset with Scanpy, perform QC and doublet removal, integrate with Cellxgene
Census data, identify cell types using NCBI Gene markers, run differential expression with
PyDESeq2, infer gene regulatory networks with Arboreto, enrich pathways via Reactome/KEGG,
and identify therapeutic targets with Open Targets.
```
**Skills Used**: scanpy, cellxgene-census, database-lookup, pydeseq2, arboreto
---
### 🧬 Multi-Omics Biomarker Discovery
**Goal**: Integrate RNA-seq, proteomics, and metabolomics to predict patient outcomes
**Prompt**:
```
Use available skills you have access to whenever possible. Analyze RNA-seq with PyDESeq2, process mass spec with pyOpenMS, integrate metabolites from
HMDB/Metabolomics Workbench, map proteins to pathways (UniProt/KEGG), find interactions via
STRING, correlate omics layers with statsmodels, build predictive model with scikit-learn,
and search ClinicalTrials.gov for relevant trials.
```
**Skills Used**: pydeseq2, pyopenms, database-lookup, statsmodels, scikit-learn
---
### 🎯 Virtual Screening Campaign
**Goal**: Discover allosteric modulators for protein-protein interactions
**Prompt**:
```
Use available skills you have access to whenever possible. Retrieve AlphaFold structures, identify interaction interface with BioPython, search ZINC
for allosteric candidates (MW 300-500, logP 2-4), filter with RDKit, dock with DiffDock,
rank with DeepChem, check PubChem suppliers, search USPTO patents, and optimize leads with
MedChem/molfeat.
```
**Skills Used**: database-lookup, biopython, rdkit, diffdock, deepchem, medchem, molfeat
---
### 🏥 Clinical Variant Interpretation
**Goal**: Analyze VCF file for hereditary cancer risk assessment
**Prompt**:
```
Use available skills you have access to whenever possible. Parse VCF with pysam, annotate variants with Ensembl VEP, query ClinVar for pathogenicity,
check COSMIC for cancer mutations, retrieve gene info from NCBI Gene, analyze protein impact
with UniProt, search PubMed for case reports, check ClinPGx for pharmacogenomics, generate
clinical report with document processing tools, and find matching trials on ClinicalTrials.gov.
```
**Skills Used**: pysam, database-lookup, paper-lookup, clinical-reports, docx, pdf
---
### 🌐 Systems Biology Network Analysis
**Goal**: Analyze gene regulatory networks from RNA-seq data
**Prompt**:
```
Use available skills you have access to whenever possible. Query NCBI Gene for annotations, retrieve sequences from UniProt, identify interactions via
STRING, map to Reactome/KEGG pathways, analyze topology with Torch Geometric, reconstruct
GRNs with Arboreto, assess druggability with Open Targets, model with PyMC, visualize
networks, and search GEO for similar patterns.
```
**Skills Used**: database-lookup, torch-geometric, arboreto, pymc, networkx, scientific-visualization
> 📖 **Want more examples?** Check out [docs/examples.md](docs/examples.md) for comprehensive workflow examples and detailed use cases across all scientific domains.
---
## 🔬 Use Cases
### 🧪 Drug Discovery & Medicinal Chemistry
- **Virtual Screening**: Screen millions of compounds from PubChem/ZINC against protein targets
- **Lead Optimization**: Analyze structure-activity relationships with RDKit, generate analogs with datamol
- **ADMET Prediction**: Predict absorption, distribution, metabolism, excretion, and toxicity with DeepChem
- **Molecular Docking**: Predict binding poses with DiffDock and rescore poses with affinity-oriented tools
- **Bioactivity Mining**: Query ChEMBL for known inhibitors and analyze SAR patterns
### 🧬 Bioinformatics & Genomics
- **Sequence Analysis**: Process DNA/RNA/protein sequences with BioPython and pysam
- **Single-Cell Analysis**: Analyze 10X Genomics data with Scanpy, identify cell types, infer GRNs with Arboreto
- **Variant Annotation**: Annotate VCF files with Ensembl VEP, query ClinVar for pathogenicity
- **Variant Database Management**: Build scalable VCF databases with TileDB-VCF for incremental sample addition, efficient population-scale queries, and compressed storage of genomic variant data
- **Gene Discovery**: Query NCBI Gene, UniProt, and Ensembl for comprehensive gene information
- **Network Analysis**: Identify protein-protein interactions via STRING, map to pathways (KEGG, Reactome)
### 🏥 Clinical Research & Precision Medicine
- **Clinical Trials**: Search ClinicalTrials.gov for relevant studies, analyze eligibility criteria
- **Variant Interpretation**: Annotate variants with ClinVar, COSMIC, and ClinPGx for pharmacogenomics
- **Drug Safety**: Query FDA databases for adverse events, drug interactions, and recalls
- **Precision Therapeutics**: Match patient variants to targeted therapies and clinical trials
### 🔬 Multi-Omics & Systems Biology
- **Multi-Omics Integration**: Combine RNA-seq, proteomics, and metabolomics data
- **Pathway Analysis**: Enrich differentially expressed genes in KEGG/Reactome pathways
- **Network Biology**: Reconstruct gene regulatory networks, identify hub genes
- **Biomarker Discovery**: Integrate multi-omics layers to predict patient outcomes
### 📊 Data Analysis & Visualization
- **Statistical Analysis**: Perform hypothesis testing, power analysis, and experimental design
- **Publication Figures**: Create publication-quality visualizations with matplotlib and seaborn
- **Network Visualization**: Visualize biological networks with NetworkX
- **Report Generation**: Generate comprehensive reports with the PDF, DOCX, PPTX, XLSX, MarkItDown, LiteParse, and clinical-reporting skills
### 🧪 Laboratory Automation
- **Protocol Design**: Create Opentrons protocols for automated liquid handling
- **LIMS Integration**: Integrate with Benchling and LabArchives for data management
- **Workflow Automation**: Automate multi-step laboratory workflows
---
## 📚 Available Skills
This repository contains **148 scientific and research skills** organized across multiple domains. Each skill provides comprehensive documentation, code examples, and best practices for working with scientific libraries, databases, and tools.
### Skill Categories
> **Note:** The Python package and integration skills listed below are *explicitly defined* skills — curated with documentation, examples, and best practices for stronger, more reliable performance. They are not a ceiling: the agent can install and use *any* Python package or call *any* API, even without a dedicated skill. The skills listed simply make common workflows faster and more dependable.
#### 🧬 **Bioinformatics & Genomics** (23 skills)
- RNA-seq pipelines: Bulk RNA-seq (end-to-end FASTQ -> counts -> DE -> enrichment orchestrator)
- Sequence analysis: BioPython, pysam, scikit-bio, BioServices
- Single-cell analysis: Scanpy, AnnData, scvi-tools, scVelo (RNA velocity), Arboreto, Cellxgene Census
- Genomic tools: gget, geniml, gtars, deepTools, FlowIO, Polars-Bio, Zarr, TileDB-VCF
- Differential expression: PyDESeq2
- Functional enrichment: Pathway Enrichment (ORA, GSEA/preranked, ssGSEA via gseapy + g:Profiler; GO, KEGG, Reactome, WikiPathways, MSigDB)
- Phylogenetics: ETE Toolkit, Phylogenetics (MAFFT, IQ-TREE 2, FastTree)
#### 🧪 **Cheminformatics & Drug Discovery** (10 skills)
- Molecular manipulation: RDKit, Datamol, Molfeat
- Deep learning: DeepChem, TorchDrug
- Docking & screening: DiffDock
- Molecular dynamics: OpenMM + MDAnalysis (MD simulation & trajectory analysis)
- Cloud quantum chemistry: Rowan (pKa, docking, cofolding)
- Drug-likeness: MedChem
- Benchmarks: PyTDC
#### 🔬 **Proteomics & Mass Spectrometry** (2 skills)
- Spectral processing: matchms, pyOpenMS
#### 🏥 **Clinical Research & Precision Medicine** (8 skills)
- Clinical databases: via Database Lookup (ClinicalTrials.gov, ClinVar, ClinPGx, COSMIC, FDA, cBioPortal, Monarch, and more)
- Cancer genomics: DepMap (cancer dependency scores, drug sensitivity)
- Cancer imaging: Imaging Data Commons (NCI radiology & pathology datasets via idc-index)
- Healthcare AI: PyHealth, NeuroKit2, Clinical Decision Support
- Clinical documentation: Clinical Reports, Treatment Plans
#### 🖼️ **Medical Imaging & Digital Pathology** (3 skills)
- DICOM processing: pydicom
- Whole slide imaging: histolab, PathML
#### 🧠 **Neuroscience & Electrophysiology** (2 skills)
- Data standards: BIDS (Brain Imaging Data Structure for neuroscience and biomedical datasets)
- Neural recordings: Neuropixels-Analysis (extracellular spikes, silicon probes, spike sorting)
#### 🤖 **Machine Learning & AI** (14 core skills)
- Deep learning: PyTorch Lightning, Transformers, Stable Baselines3, PufferLib
- Classical ML: scikit-learn, scikit-survival, SHAP
- Time series: aeon, TimesFM (Google's zero-shot foundation model for univariate forecasting)
- Bayesian methods: PyMC
- Optimization: PyMOO
- Graph ML: Torch Geometric
- Dimensionality reduction: UMAP-learn
- Statistical modeling: statsmodels
#### 🔮 **Materials Science, Chemistry & Physics** (7 skills)
- Materials: Pymatgen
- Metabolic modeling: COBRApy
- Astronomy: Astropy
- Quantum computing: Cirq, PennyLane, Qiskit, QuTiP
#### ⚙️ **Engineering & Simulation** (4 skills)
- Numerical computing: MATLAB/Octave
- Computational fluid dynamics: FluidSim
- Discrete-event simulation: SimPy
- Symbolic math: SymPy
#### 📊 **Data Analysis & Visualization** (21 skills)
- Visualization: Matplotlib, Seaborn, Scientific Visualization
- Geospatial analysis: GeoPandas, GeoMaster (remote sensing, GIS, satellite imagery, spatial ML, 500+ examples)
- Data processing: Dask, Polars, Vaex
- Network analysis: NetworkX
- Document processing: LiteParse (local PDF/document parsing with bounding boxes and OCR), MarkItDown, PDF, DOCX, PPTX, and XLSX
- Infographics: Infographics (AI-powered professional infographic creation)
- Diagrams: Markdown & Mermaid Writing (text-based diagrams as default documentation standard)
- Exploratory data analysis: EDA workflows
- Statistical analysis: Statistical Analysis workflows
- Experimental design: Experimental Design (randomization, blocking, factorial/fractional-factorial DOE, crossover, cluster, sequential designs; pyDOE3)
- Statistical power: Statistical Power (sample-size & power for t-tests, ANOVA, proportions, correlation, regression — closed-form plus simulation-based for GLMs, mixed models, and cluster designs)
#### 🧪 **Laboratory Automation** (6 skills)
- Liquid handling: PyLabRobot and Opentrons
- Cloud lab: Ginkgo Cloud Lab (protein expression & purification across cell-free/E. coli/Pichia, IVT RNA synthesis, thermal shift and Echo-MS assays, SPR onboarding, fluorescent pixel art via autonomous RAC infrastructure)
- Protocol management: Protocols.io
- LIMS integration: Benchling, LabArchives
#### 🔬 **Multi-omics & Systems Biology** (4 skills)
- Pathway analysis: via Database Lookup (KEGG, Reactome, STRING) and PrimeKG
- Multi-omics: HypoGeniC
- Data management: LaminDB
#### 🧬 **Protein Engineering & Design** (4 skills)
- Protein language models: ESM
- Glycoengineering: Glycoengineering (N/O-glycosylation prediction, therapeutic antibody optimization)
- Cloud laboratory platform: Adaptyv (automated protein testing and validation)
- Cloud structure & design platform: Tamarind (managed-GPU access to AlphaFold, Boltz, Chai, ESMFold, RFdiffusion, ProteinMPNN, BoltzGen, antibody/nanobody design, DiffDock/Vina docking, binding affinity, and MSA generation via REST API or MCP)
#### 📚 **Scientific Communication** (26 skills)
- Literature: Paper Lookup (PubMed, PMC, bioRxiv, medRxiv, arXiv, OpenAlex, Crossref, Semantic Scholar, CORE, Unpaywall), Literature Review, Paperzilla
- Advanced paper search: BGPT Paper Search (25+ structured fields per paper — methods, results, sample sizes, quality scores — from full text, not just abstracts)
- Web search: Parallel Web, Exa Search, and Research Lookup
- Research notebooks: Open Notebook (self-hosted NotebookLM alternative — PDFs, videos, audio, web pages; 16+ AI providers; multi-speaker podcast generation)
- Writing: Scientific Writing, Peer Review
- Document processing: LiteParse, PDF, DOCX, PPTX, XLSX, and MarkItDown
- Publishing and paper workflows: Venue Templates
- Presentations: Scientific Slides, LaTeX Posters, PPTX Posters
- Diagrams: Scientific Schematics, Markdown & Mermaid Writing
- Infographics: Infographics (10 types, 8 styles, colorblind-safe palettes)
- Citations: Citation Management, pyzotero
- Illustration: Generate Image (AI image generation with FLUX.2 Pro and Gemini 3 Pro (Nano Banana Pro))
#### 🔬 **Scientific Databases & Data Access** (6 skills → 100+ databases total)
> A unified database-lookup skill provides deterministic REST API access to 78 public databases across all domains, with retrieval contracts, pagination/count reconciliation, and endpoint provenance. Dedicated skills cover specialized data platforms. Multi-database packages like BioServices (~40 bioinformatics services), BioPython (38 NCBI sub-databases via Entrez), and gget (20+ genomics databases) add further coverage.
- Unified access: Database Lookup (78 databases spanning chemistry, genomics, clinical, pathways, patents, economics, and more — PubChem, ChEMBL, UniProt, PDB, AlphaFold, KEGG, Reactome, STRING, ClinVar, COSMIC, ClinicalTrials.gov, FDA, FRED, USPTO, SEC EDGAR, and dozens more — with auditable filters and provenance)
- Cancer genomics: DepMap (cancer cell line dependencies, drug sensitivity, gene effect profiles)
- Cancer imaging: Imaging Data Commons (NCI radiology & pathology datasets via idc-index)
- Knowledge graph: PrimeKG (precision medicine knowledge graph — genes, drugs, diseases, phenotypes)
- Fiscal data: U.S. Treasury Fiscal Data (national debt, Treasury statements, auctions, exchange rates)
- Scientific ML resource catalog: Hugging Science (curated index of datasets, models, blog posts, and interactive Spaces across 17 scientific domains — astronomy, biology, chemistry, climate, genomics, materials science, medicine, physics, scientific reasoning, and more — with usage patterns for `datasets`, `transformers`, and `gradio_client`)
#### 🔧 **Infrastructure & Platforms** (11 skills)
- Cloud compute: Modal
- GPU acceleration: Optimize for GPU (CuPy, Numba CUDA, Warp, cuDF, cuML, cuGraph, KvikIO, cuCIM, cuxfilter, cuVS, cuSpatial, RAFT)
- Genomics platforms: DNAnexus, LatchBio
- Workflow engines: Nextflow (build/run/debug Nextflow & nf-core pipelines — DSL2 modules, executors/containers, HPC/cloud scaling) and pacsomatic (operator toolkit for the nf-core/pacsomatic tumor-normal somatic variant-calling workflow)
- Microscopy: OMERO
- Automation: Opentrons
- Resource detection: Get Available Resources
- Workflow mining: Autoskill (local screenpipe-based repeated workflow detection and skill drafting)
- Agent platform development: Pi Agent (using Pi as a terminal coding harness and building on it with SDK, RPC/JSONL, extensions, custom providers/models, packages, TUI components, and session tooling)
#### 🎓 **Research Methodology & Planning** (12 skills)
- Ideation: Scientific Brainstorming, Hypothesis Generation
- Autonomous optimization: Arbor (Hypothesis Tree Refinement — iteratively improve a code/model/agent-harness/data artifact against a dev evaluator while a held-out test gate guards against overfitting)
- Critical analysis: Scientific Critical Thinking, Scholar Evaluation
- Scenario analysis: What-If Oracle (46 branch possibility exploration, contingency planning, decision stress-testing)
- Multi-perspective deliberation: Consciousness Council (diverse expert viewpoints, devil's advocate analysis)
- Cognitive profiling: DHDNA Profiler (extract thinking patterns and cognitive signatures from any text)
- Funding: Research Grants
- Discovery: Research Lookup, Paper Lookup (10 academic databases)
- Market analysis: Market Research Reports
#### ⚖️ **Regulatory & Standards** (1 skill)
- Medical device standards: ISO 13485 Certification
> 📖 **For complete details on all skills**, see [docs/skills.md](docs/skills.md)
> 💡 **Looking for practical examples?** Check out [docs/examples.md](docs/examples.md) for comprehensive workflow examples across all scientific domains.
---
## 🤝 Contributing
We welcome contributions to expand and improve this scientific skills repository!
For detailed instructions on adding or updating a skill, see [CONTRIBUTING.md](CONTRIBUTING.md). The guide covers repository structure, required `SKILL.md` frontmatter, Agent Skills specification requirements, versioning, validation, security scanning, and pull request expectations.
### Ways to Contribute
✨ **Add New Skills**
- Create skills for additional scientific packages or databases
- Add integrations for scientific platforms and tools
📚 **Improve Existing Skills**
- Enhance documentation with more examples and use cases
- Add new workflows and reference materials
- Improve code examples and scripts
- Fix bugs or update outdated information
🐛 **Report Issues**
- Submit bug reports with detailed reproduction steps
- Suggest improvements or new features
### How to Contribute
1. **Fork** the repository
2. **Create** a feature branch (`git checkout -b feature/amazing-skill`)
3. **Follow** [CONTRIBUTING.md](CONTRIBUTING.md) and the existing directory structure
4. **Ensure** all new skills include valid `SKILL.md` files with required frontmatter and `metadata.version`
5. **Test** your examples and workflows thoroughly
6. **Commit** your changes (`git commit -m 'Add amazing skill'`)
7. **Push** to your branch (`git push origin feature/amazing-skill`)
8. **Submit** a pull request with a clear description of your changes
### Contribution Guidelines
✅ **Adhere to the [Agent Skills Specification](https://agentskills.io/specification)** — Every skill must follow the official spec (valid `SKILL.md` frontmatter, naming conventions, directory structure)
✅ Include a quoted `metadata.version` value in every `SKILL.md`
✅ Increment `metadata.version` when updating an existing skill
✅ Maintain consistency with existing skill documentation format
✅ Ensure all code examples are tested and functional
✅ Follow scientific best practices in examples and workflows
✅ Update relevant documentation when adding new capabilities
✅ Provide clear comments and docstrings in code
✅ Include references to official documentation
### Security Scanning
All skills in this repository are security-scanned using [Cisco AI Defense Skill Scanner](https://github.com/cisco-ai-defense/skill-scanner), an open-source tool that detects prompt injection, data exfiltration, and malicious code patterns in Agent Skills.
If you are contributing a new skill, we recommend running the scanner locally before submitting a pull request:
```bash
uv pip install cisco-ai-skill-scanner
skill-scanner scan /path/to/your/skill --use-behavioral
```
> **Note:** A clean scan result reduces noise in review, but does not guarantee a skill is free of all risk. Contributed skills are also reviewed manually before merging.
### Recognition
Contributors are recognized in our community and may be featured in:
- Repository contributors list
- Special mentions in release notes
- K-Dense community highlights
Your contributions help make scientific computing more accessible and enable researchers to leverage AI tools more effectively!
### Support Open Source
This project builds on 50+ amazing open source projects. If you find value in these skills, please consider [supporting the projects we depend on](docs/open-source-sponsors.md).
---
## 🔧 Troubleshooting
### Common Issues
**Problem: Skills not loading**
- Verify skill folders are in the correct directory (see [Getting Started](#-getting-started))
- Each skill folder must contain a `SKILL.md` file
- Restart your agent/IDE after copying skills
- In Cursor, check Settings → Rules to confirm skills are discovered
**Problem: Missing Python dependencies**
- Solution: Check the specific `SKILL.md` file for required packages
- Install dependencies: `uv pip install package-name`
**Problem: API rate limits**
- Solution: Many databases have rate limits. Review the specific database documentation
- Consider implementing caching or batch requests
**Problem: Authentication errors**
- Solution: Some services require API keys. Check the `SKILL.md` for authentication setup
- Verify your credentials and permissions
**Problem: Outdated examples**
- Solution: Report the issue via GitHub Issues
- Check the official package documentation for updated syntax
**Problem: `gh skill install` or docs link to `scientific-skills/` fails (v2.43.0+)**
- As of v2.43.0, skills live under `skills/` (not `scientific-skills/`) to match the Agent Skills layout expected by GitHub CLI
- Update manual copy paths, bookmarks, and citations from `scientific-skills/<name>` to `skills/<name>`
- Re-run `gh skill install K-Dense-AI/scientific-agent-skills` after pulling the latest release
---
## ❓ FAQ
### General Questions
**Q: Is this free to use?**
A: Yes! This repository is MIT licensed. However, each individual skill has its own license specified in the `license` metadata field within its `SKILL.md` file—be sure to review and comply with those terms.
**Q: Why are all skills grouped together instead of separate packages?**
A: We believe good science in the age of AI is inherently interdisciplinary. Bundling all skills together makes it trivial for you (and your agent) to bridge across fields—e.g., combining genomics, cheminformatics, clinical data, and machine learning in one workflow—without worrying about which individual skills to install or wire together.
**Q: Can I use this for commercial projects?**
A: The repository itself is MIT licensed, which allows commercial use. However, individual skills may have different licenses—check the `license` field in each skill's `SKILL.md` file to ensure compliance with your intended use.
**Q: Do all skills have the same license?**
A: No. Each skill has its own license specified in the `license` metadata field within its `SKILL.md` file. These licenses may differ from the repository's MIT License. Users are responsible for reviewing and adhering to the license terms of each individual skill they use.
**Q: How often is this updated?**
A: We regularly update skills to reflect the latest versions of packages and APIs. Major updates are announced in release notes.
**Q: Can I use this with other AI models?**
A: The skills follow the open [Agent Skills](https://agentskills.io/) standard and work with any compatible agent, including Cursor, Claude Code, Codex, Google Antigravity, OpenClaw, NVIDIA NemoClaw, Hermes, and Pi.
### Installation & Setup
**Q: Do I need all the Python packages installed?**
A: No! Only install the packages you need. Each skill specifies its requirements in its `SKILL.md` file.
**Q: What if a skill doesn't work?**
A: First check the [Troubleshooting](#-troubleshooting) section. If the issue persists, file an issue on GitHub with detailed reproduction steps.
**Q: Do the skills work offline?**
A: Database skills require internet access to query APIs. Package skills work offline once Python dependencies are installed.
### Contributing
**Q: Can I contribute my own skills?**
A: Absolutely! We welcome contributions. See the [Contributing](#-contributing) section for guidelines and best practices.
**Q: How do I report bugs or suggest features?**
A: Open an issue on GitHub with a clear description. For bugs, include reproduction steps and expected vs actual behavior.
---
## 💬 Support
Need help? Here's how to get support:
- 📖 **Documentation**: Check the relevant `SKILL.md` and `references/` folders
- 🐛 **Bug Reports**: [Open an issue](https://github.com/K-Dense-AI/scientific-agent-skills/issues)
- 💡 **Feature Requests**: [Submit a feature request](https://github.com/K-Dense-AI/scientific-agent-skills/issues/new)
- 📣 **Updates and demos**: Follow [X](https://x.com/k_dense_ai), [LinkedIn](https://www.linkedin.com/company/k-dense-inc), and [YouTube](https://www.youtube.com/@K-Dense-Inc) to keep up with new skills, tutorials, and Scientific Agent Skills releases
- 💼 **Enterprise Support**: Contact [K-Dense](https://k-dense.ai/) for commercial support
---
## 📖 Citation
If you use Scientific Agent Skills in your research or project, please cite the overall collection and, when relevant, the individual skill or skills that materially supported your work.
The collection citation helps others find the repository, understand the broader skill ecosystem used in your workflow, and credit the maintenance effort behind Scientific Agent Skills. Individual skill citations give more precise credit for the specific package, database, or workflow guidance your agent used.
Recommended practice:
- Always cite **Scientific Agent Skills** using one of the formats below.
- Also cite each individual skill that directly contributed to your analysis, code, figures, reports, or research workflow.
- If a skill wraps or documents an external package, database, or platform, cite that upstream project too when your field's norms require it.
### Collection Citation
#### BibTeX
```bibtex
@software{scientific_agent_skills_2026,
author = {{K-Dense Inc.}},
title = {Scientific Agent Skills: A Comprehensive Collection of Scientific Tools for AI Agents},
year = {2026},
url = {https://github.com/K-Dense-AI/scientific-agent-skills},
note = {148 skills covering databases, packages, integrations, and analysis tools}
}
```
#### APA
```
K-Dense Inc. (2026). Scientific Agent Skills: A comprehensive collection of scientific tools for AI agents [Computer software]. https://github.com/K-Dense-AI/scientific-agent-skills
```
#### MLA
```
K-Dense Inc. Scientific Agent Skills: A Comprehensive Collection of Scientific Tools for AI Agents. 2026, github.com/K-Dense-AI/scientific-agent-skills.
```
#### Plain Text
```
Scientific Agent Skills by K-Dense Inc. (2026)
Available at: https://github.com/K-Dense-AI/scientific-agent-skills
```
### Individual Skill Citation
When citing a specific skill, include the skill name, version from `metadata.version` in that skill's `SKILL.md`, and the direct skill URL. For example:
```bibtex
@software{scientific_agent_skills_astropy_2026,
author = {{K-Dense Inc.}},
title = {Astropy Skill for Scientific Agent Skills},
year = {2026},
url = {https://github.com/K-Dense-AI/scientific-agent-skills/tree/main/skills/astropy},
note = {Version 1.0, part of Scientific Agent Skills}
}
```
Plain text format:
```text
Astropy skill for Scientific Agent Skills, version 1.0.
K-Dense Inc. (2026).
https://github.com/K-Dense-AI/scientific-agent-skills/tree/main/skills/astropy
```
We appreciate acknowledgment in publications, presentations, or projects that benefit from these skills.
---
## 📄 License
This project is licensed under the **MIT License**.
**Copyright © 2026 K-Dense Inc.** ([k-dense.ai](https://k-dense.ai/))
### Key Points:
- ✅ **Free for any use** (commercial and noncommercial)
- ✅ **Open source** - modify, distribute, and use freely
- ✅ **Permissive** - minimal restrictions on reuse
- ⚠️ **No warranty** - provided "as is" without warranty of any kind
See [LICENSE.md](LICENSE.md) for full terms.
### Individual Skill Licenses
> ⚠️ **Important**: Each skill has its own license specified in the `license` metadata field within its `SKILL.md` file. These licenses may differ from the repository's MIT License and may include additional terms or restrictions. **Users are responsible for reviewing and adhering to the license terms of each individual skill they use.**
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`K-Dense-AI/scientific-agent-skills`
- 原始仓库:https://github.com/K-Dense-AI/scientific-agent-skills
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+3767
View File
File diff suppressed because it is too large Load Diff
+3699
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 MiB

+186
View File
@@ -0,0 +1,186 @@
# Support the Open Source Projects We Depend On
Scientific Agent Skills is built on the shoulders of giants. The 148 skills in this repository leverage dozens of incredible open source projects created and maintained by dedicated developers and research communities around the world.
**If you find value in these skills, please consider supporting the underlying open source projects that make them possible.**
---
## How to Support Open Source
1. **Star repositories** on GitHub - It's free and helps projects gain visibility
2. **Sponsor maintainers** directly through GitHub Sponsors, Open Collective, or project-specific donation pages
3. **Contribute** code, documentation, or bug reports
4. **Cite** projects in your publications
5. **Share** projects with colleagues
---
## Featured Projects by Domain
### Bioinformatics & Genomics
| Project | Description | Links |
|---------|-------------|-------|
| **Biopython** | Computational molecular biology toolkit | [GitHub](https://github.com/biopython/biopython) - [Donate](https://numfocus.org/donate-to-biopython) |
| **Scanpy** | Single-cell analysis in Python | [GitHub](https://github.com/scverse/scanpy) - [scverse](https://scverse.org/) |
| **AnnData** | Annotated data matrices for single-cell | [GitHub](https://github.com/scverse/anndata) |
| **scvi-tools** | Deep learning for single-cell omics | [GitHub](https://github.com/scverse/scvi-tools) |
| **Arboreto** | Gene regulatory network inference | [GitHub](https://github.com/aertslab/arboreto) |
| **pysam** | SAM/BAM/VCF file interface | [GitHub](https://github.com/pysam-developers/pysam) |
| **scikit-bio** | Bioinformatics library | [GitHub](https://github.com/scikit-bio/scikit-bio) |
| **gget** | Gene and transcript info retrieval | [GitHub](https://github.com/pachterlab/gget) |
| **deepTools** | Tools for deep-sequencing data | [GitHub](https://github.com/deeptools/deepTools) |
| **ETE Toolkit** | Phylogenetic tree analysis | [GitHub](https://github.com/etetoolkit/ete) |
### Cheminformatics & Drug Discovery
| Project | Description | Links |
|---------|-------------|-------|
| **RDKit** | Cheminformatics toolkit | [GitHub](https://github.com/rdkit/rdkit) - [Donate](https://github.com/sponsors/rdkit) |
| **Datamol** | Molecular manipulation made easy | [GitHub](https://github.com/datamol-io/datamol) |
| **DeepChem** | Deep learning for chemistry | [GitHub](https://github.com/deepchem/deepchem) |
| **TorchDrug** | Drug discovery with PyTorch | [GitHub](https://github.com/DeepGraphLearning/torchdrug) |
| **molfeat** | Molecular featurization | [GitHub](https://github.com/datamol-io/molfeat) |
| **MedChem** | Medicinal chemistry filters | [GitHub](https://github.com/datamol-io/medchem) |
| **PyTDC** | Therapeutics Data Commons | [GitHub](https://github.com/mims-harvard/TDC) |
### Proteomics & Mass Spectrometry
| Project | Description | Links |
|---------|-------------|-------|
| **matchms** | Mass spectrometry data processing | [GitHub](https://github.com/matchms/matchms) |
| **pyOpenMS** | Mass spectrometry toolkit | [GitHub](https://github.com/OpenMS/OpenMS) |
### Machine Learning & AI
| Project | Description | Links |
|---------|-------------|-------|
| **PyTorch Lightning** | Deep learning framework | [GitHub](https://github.com/Lightning-AI/pytorch-lightning) - [Sponsor](https://github.com/sponsors/Lightning-AI) |
| **Transformers** | State-of-the-art NLP | [GitHub](https://github.com/huggingface/transformers) |
| **scikit-learn** | Machine learning in Python | [GitHub](https://github.com/scikit-learn/scikit-learn) - [Donate](https://numfocus.org/donate-to-scikit-learn) |
| **PyTorch Geometric** | Geometric deep learning | [GitHub](https://github.com/pyg-team/pytorch_geometric) |
| **PyMC** | Probabilistic programming | [GitHub](https://github.com/pymc-devs/pymc) - [Donate](https://numfocus.org/donate-to-pymc) |
| **SHAP** | Model interpretability | [GitHub](https://github.com/shap/shap) |
| **Stable Baselines3** | Reinforcement learning | [GitHub](https://github.com/DLR-RM/stable-baselines3) |
| **scikit-survival** | Survival analysis | [GitHub](https://github.com/sebp/scikit-survival) |
| **aeon** | Time series ML toolkit | [GitHub](https://github.com/aeon-toolkit/aeon) |
| **PyMOO** | Multi-objective optimization | [GitHub](https://github.com/anyoptimization/pymoo) |
| **UMAP** | Dimensionality reduction | [GitHub](https://github.com/lmcinnes/umap) |
### Data Science & Visualization
| Project | Description | Links |
|---------|-------------|-------|
| **Matplotlib** | Plotting library | [GitHub](https://github.com/matplotlib/matplotlib) - [Donate](https://numfocus.org/donate-to-matplotlib) |
| **Seaborn** | Statistical visualization | [GitHub](https://github.com/mwaskom/seaborn) |
| **Plotly** | Interactive visualizations | [GitHub](https://github.com/plotly/plotly.py) |
| **NetworkX** | Network analysis | [GitHub](https://github.com/networkx/networkx) - [Donate](https://numfocus.org/donate-to-networkx) |
| **SymPy** | Symbolic mathematics | [GitHub](https://github.com/sympy/sympy) - [Donate](https://numfocus.org/donate-to-sympy) |
| **statsmodels** | Statistical modeling | [GitHub](https://github.com/statsmodels/statsmodels) |
| **GeoPandas** | Geospatial data in Python | [GitHub](https://github.com/geopandas/geopandas) |
| **Polars** | Fast DataFrame library | [GitHub](https://github.com/pola-rs/polars) |
| **Dask** | Parallel computing | [GitHub](https://github.com/dask/dask) - [Donate](https://numfocus.org/donate-to-dask) |
| **Vaex** | Out-of-core DataFrames | [GitHub](https://github.com/vaexio/vaex) |
### Medical Imaging & Digital Pathology
| Project | Description | Links |
|---------|-------------|-------|
| **pydicom** | DICOM file handling | [GitHub](https://github.com/pydicom/pydicom) |
| **histolab** | Digital pathology preprocessing | [GitHub](https://github.com/histolab/histolab) |
| **PathML** | Pathology ML toolkit | [GitHub](https://github.com/Dana-Farber-AIOS/pathml) |
### Healthcare & Clinical
| Project | Description | Links |
|---------|-------------|-------|
| **PyHealth** | Healthcare AI toolkit | [GitHub](https://github.com/sunlabuiuc/PyHealth) |
| **NeuroKit2** | Neurophysiological signal processing | [GitHub](https://github.com/neuropsychology/NeuroKit) |
### Materials Science & Physics
| Project | Description | Links |
|---------|-------------|-------|
| **Pymatgen** | Materials analysis | [GitHub](https://github.com/materialsproject/pymatgen) |
| **COBRApy** | Metabolic modeling | [GitHub](https://github.com/opencobra/cobrapy) |
| **Astropy** | Astronomy library | [GitHub](https://github.com/astropy/astropy) - [Donate](https://numfocus.org/donate-to-astropy) |
### Quantum Computing
| Project | Description | Links |
|---------|-------------|-------|
| **Qiskit** | IBM quantum computing SDK | [GitHub](https://github.com/Qiskit/qiskit) |
| **Cirq** | Google quantum computing | [GitHub](https://github.com/quantumlib/Cirq) |
| **PennyLane** | Quantum ML library | [GitHub](https://github.com/PennyLaneAI/pennylane) |
| **QuTiP** | Quantum toolbox in Python | [GitHub](https://github.com/qutip/qutip) |
### Simulation & Engineering
| Project | Description | Links |
|---------|-------------|-------|
| **SimPy** | Discrete-event simulation | [GitHub](https://github.com/TeamSim/SimPy) |
| **FluidSim** | CFD framework | [GitHub](https://github.com/fluiddyn/fluidsim) |
### Laboratory & Automation
| Project | Description | Links |
|---------|-------------|-------|
| **PyLabRobot** | Lab automation control | [GitHub](https://github.com/PyLabRobot/pylabrobot) |
### Protein Engineering
| Project | Description | Links |
|---------|-------------|-------|
| **ESM** | Evolutionary scale modeling | [GitHub](https://github.com/Biohub/esm) |
### Data Formats & I/O
| Project | Description | Links |
|---------|-------------|-------|
| **Zarr** | Chunked array storage | [GitHub](https://github.com/zarr-developers/zarr-python) |
| **FlowIO** | Flow cytometry I/O | [GitHub](https://github.com/whitews/FlowIO) |
---
## NumFOCUS-Sponsored Projects
Many of the projects above are sponsored by [NumFOCUS](https://numfocus.org/), a nonprofit supporting open source scientific computing. Consider [donating to NumFOCUS](https://numfocus.org/donate) to support the broader ecosystem.
**NumFOCUS-sponsored projects in this collection:**
- Biopython
- scikit-learn
- Matplotlib
- NetworkX
- SymPy
- Dask
- Astropy
- PyMC
---
## scverse Ecosystem
The [scverse](https://scverse.org/) consortium maintains foundational tools for single-cell omics:
- Scanpy
- AnnData
- scvi-tools
- And more
Consider supporting their mission to advance single-cell research.
---
## A Note from K-Dense
At K-Dense, we believe in giving back to the communities that make our work possible. We encourage all users of Scientific Agent Skills to:
1. **Acknowledge** these projects when you use them in research
2. **Contribute** back improvements when you can
3. **Support** maintainers financially if you derive commercial value
The open source scientific Python ecosystem is a shared resource. Let's keep it thriving together.
---
*This list is not exhaustive. Many other excellent open source projects power the skills in this repository. If you notice a project that should be listed here, please open a PR!*
+222
View File
@@ -0,0 +1,222 @@
# Scientific Skills
## Scientific Databases & Data Access
- **Database Lookup** - Deterministically query 78 public scientific, biomedical, materials science, regulatory, finance, and demographics databases through documented REST APIs. Adds retrieval contracts, endpoint provenance, pagination/count reconciliation, rate-limit guidance, and untrusted-response handling for reproducible lookups of compounds, drugs, proteins, genes, pathways, variants, clinical trials, patents, SEC filings, economic indicators, structures, astronomical objects, earthquakes, weather, and related database-backed scientific facts
- **DepMap** - Query the Cancer Dependency Map (DepMap) for cancer cell line gene dependency scores (CRISPR Chronos), drug sensitivity data, and gene effect profiles. Use for identifying cancer-specific vulnerabilities, synthetic lethal interactions, and validating oncology drug targets
- **Imaging Data Commons** - Query and download public cancer imaging data from NCI Imaging Data Commons using idc-index. Use for accessing large-scale radiology (CT, MR, PET) and pathology datasets for AI training or research. No authentication required. Query by metadata, visualize in browser, check licenses
- **PrimeKG** - Query the Precision Medicine Knowledge Graph (PrimeKG) for multiscale biological data including genes, drugs, diseases, phenotypes, and more. Integrates 20+ biomedical resources into a single knowledge graph for drug repurposing, disease mechanism exploration, and target identification
- **U.S. Treasury Fiscal Data** - Free, open REST API from the U.S. Department of the Treasury providing 54 datasets and 179 data tables covering federal fiscal data. No API key required. Access national debt (Debt to the Penny back to 1993, Historical Debt back to 1790), Daily Treasury Statements (TGA balances, deposits/withdrawals), Monthly Treasury Statements (federal budget receipts and outlays), Treasury securities auctions data (bills, notes, bonds, TIPS, FRNs since 1979), average interest rates on Treasury securities, Treasury reporting exchange rates (quarterly for 170+ currencies), I Bond and savings bond rates, TIPS/CPI data, and more. Supports filtering, sorting, pagination, and CSV/XML/JSON output formats
- **Hugging Science** - Curated, LLM-friendly catalog of scientific datasets, models, blog posts, and interactive Spaces hosted on Hugging Face, spanning 17 scientific domains (astronomy, benchmark, biology, biotechnology, chemistry, climate, conservation, earth-science, ecology, energy, engineering, genomics, materials-science, mathematics, medicine, physics, scientific-reasoning). Discovery happens via huggingscience.co (with `llms.txt`, `llms-full.txt`, and per-topic markdown files designed for agent consumption); usage goes through standard Hugging Face APIs. Includes a bundled `fetch_catalog.py` script for filtered access by topic, type (datasets/models/blogs/spaces), or free-text search, plus reference guides for loading datasets via the `datasets` library (with streaming for billion-token corpora), running models via `transformers` or the Inference API/Providers (handling `trust_remote_code` requirements for custom architectures like Evo-2), and calling Spaces via `gradio_client` (with a worked BoltzGen example for protein binder design). Authenticates gated resources via `HF_TOKEN` from `.env`. Use cases: discovering the right dataset/model for a scientific ML task without trawling the broader Hub, fine-tuning on curated scientific data, citing methodology blogs from dataset/model authors, running interactive scientific demos (binder design, theorem proving, weather modeling) without local GPU setup, and bridging from "I need a model for protein/genome/molecule/climate/materials/astronomy" to working code
## Scientific Integrations
### Laboratory Information Management Systems (LIMS) & R&D Platforms
- **Benchling Integration** - Toolkit for integrating with Benchling's R&D platform, providing programmatic access to laboratory data management including registry entities (DNA sequences, proteins), inventory systems (samples, containers, locations), electronic lab notebooks (entries, protocols), workflows (tasks, automation), and data exports using Python SDK and REST API
### Cloud Platforms for Genomics & Biomedical Data
- **DNAnexus Integration** - Comprehensive toolkit for working with the DNAnexus cloud platform for genomics and biomedical data analysis. Covers building and deploying apps/applets (Python/Bash), managing data objects (files, records, databases), running analyses and workflows, using the dxpy Python SDK, and configuring app metadata and dependencies (dxapp.json setup, system packages, Docker, assets). Enables processing of FASTQ/BAM/VCF files, bioinformatics pipelines, job execution, workflow orchestration, and platform operations including project management and permissions
### Laboratory Automation
- **Opentrons Integration** - Toolkit for creating, editing, and debugging Opentrons Python Protocol API v2 protocols for laboratory automation using Flex and OT-2 robots. Enables automated liquid handling, pipetting workflows, hardware module control (thermocycler, temperature, magnetic, heater-shaker, absorbance plate reader), labware management, and complex protocol development for biological and chemical experiments
- **Ginkgo Cloud Lab** - Submit and manage protocols on Ginkgo Bioworks Cloud Lab (cloud.ginkgo.bio), a web-based interface for autonomous lab execution on Reconfigurable Automation Carts (RACs). Covers the full catalog: protein expression and purification across cell-free, E. coli, and Pichia systems (HiBiT/luminescence, A280 yield, and LabChip purity/size tiers, plus minibinder screening), cell-free validation ($39) and 24-condition DoE optimization ($199), IVT mRNA/circRNA synthesis with qPCR, thermal shift / developability assays, Echo-MS enzyme detection and analyte method onboarding, SPR target onboarding for kinetics, and fluorescent pixel art ($25/plate, 7-color E. coli palette). Includes the EstiMate AI agent for custom protocol feasibility and pricing
### Electronic Lab Notebooks (ELN)
- **LabArchives Integration** - Toolkit for interacting with LabArchives Electronic Lab Notebook (ELN) REST API. Provides programmatic access to notebooks (backup, retrieval, management), entries (creation, comments, attachments), user authentication, site reports and analytics, and third-party integrations (Protocols.io, GraphPad Prism, SnapGene, Geneious, Jupyter, REDCap). Includes Python scripts for configuration setup, notebook operations, and entry management. Supports multi-regional API endpoints (US, UK, Australia) and OAuth authentication
- **Open Notebook** - Self-hosted, open-source alternative to Google NotebookLM for AI-powered research and document analysis. Organizes research materials into notebooks, ingests diverse content sources (PDFs, videos, audio, web pages, Office documents), generates AI-powered notes and summaries, creates multi-speaker podcasts from research, enables document chat with context-aware AI, and searches across materials with full-text and vector search. Supports 16+ AI providers including OpenAI, Anthropic, Google, Ollama, Groq, and Mistral with complete data privacy through self-hosting
### Workflow Platforms & Cloud Execution
- **LatchBio Integration** - Integration with the Latch platform for building, deploying, and executing bioinformatics workflows. Provides comprehensive support for creating serverless bioinformatics pipelines using Python decorators, deploying Nextflow/Snakemake pipelines, managing cloud data (LatchFile, LatchDir) and structured Registry (Projects, Tables, Records), configuring computational resources (CPU, GPU, memory, storage), and using pre-built Latch Verified workflows (RNA-seq, AlphaFold, DESeq2, single-cell analysis, CRISPR editing). Enables automatic containerization, UI generation, workflow versioning, and execution on scalable cloud infrastructure with comprehensive data management
- **Nextflow** - Build, run, and debug Nextflow data pipelines and nf-core workflows end to end. Covers writing and testing DSL2 modules/subworkflows (processes, channels, operators, nf-test), running community pipelines (nf-core/rnaseq, nf-core/sarek), authoring samplesheets and `nextflow.config`, configuring executors and containers (Docker, Singularity/Apptainer, Conda, Wave), scaling to HPC/SLURM or cloud (AWS Batch, Google Batch, Azure, Kubernetes), and debugging failed or `-resume` runs. Use for any reproducible scientific/bioinformatics workflow work and for authoring nf-core-compliant pipelines, modules, configs, and linting
- **pacsomatic** - Operator toolkit for running the nf-core/pacsomatic matched tumor-normal (somatic variant calling) workflow from BAM inputs. Validates run inputs, generates pacsomatic-compliant samplesheets, prepares reproducible Nextflow launch artifacts, runs locally or submits to schedulers (LSF/Slurm/PBS/SGE), and triages execution and startup failures. Use to prepare launch commands/scripts, perform dry-run checks, or troubleshoot pipeline and scheduler submission errors
### Microscopy & Bio-image Data
- **OMERO Integration** - Toolkit for interacting with OMERO microscopy data management systems using Python. Provides comprehensive access to microscopy images stored in OMERO servers, including dataset and screening data retrieval, pixel data analysis, annotation and metadata management, regions of interest (ROIs) creation and analysis, batch processing, OMERO.scripts development, and OMERO.tables for structured data storage. Essential for researchers working with high-content screening data, multi-dimensional microscopy datasets, or collaborative image repositories
### Protocol Management & Sharing
- **Protocols.io Integration** - Integration with protocols.io API for managing scientific protocols. Enables programmatic access to protocol discovery (search by keywords, DOI, category), protocol lifecycle management (create, update, publish with DOI), step-by-step procedure documentation, collaborative development with workspaces and discussions, file management (upload data, images, documents), experiment tracking and documentation, and data export. Supports OAuth authentication, protocol PDF generation, materials management, threaded comments, workspace permissions, and institutional protocol repositories. Essential for protocol standardization, reproducibility, lab knowledge management, and scientific collaboration
## Scientific Packages
### Bioinformatics & Genomics
- **AnnData** - Python package for handling annotated data matrices, specifically designed for single-cell genomics data. Provides efficient storage and manipulation of high-dimensional data with associated annotations (observations/cells and variables/genes). Key features include: HDF5-based h5ad file format for efficient I/O and compression, integration with pandas DataFrames for metadata, support for sparse matrices (scipy.sparse) for memory efficiency, layered data organization (X for main data matrix, obs for observation annotations, var for variable annotations, obsm/varm for multi-dimensional annotations, obsp/varp for pairwise matrices), and seamless integration with Scanpy, scvi-tools, and other single-cell analysis packages. Supports lazy loading, chunked operations, and conversion to/from other formats (CSV, HDF5, Zarr). Use cases: single-cell RNA-seq data management, multi-modal single-cell data (RNA+ATAC, CITE-seq), spatial transcriptomics, and any high-dimensional annotated data requiring efficient storage and manipulation
- **Arboreto** - Scalable Python GRN inference (Aerts Lab) using GRNBoost2 (gradient boosting) and GENIE3 (random forest) with Dask distributed execution. Accepts pandas DataFrames, dense NumPy arrays, or sparse CSC matrices (cells × genes). Features: local multi-core and cluster scheduling, TF-restricted search, `limit` for top-N links, and `diy()` for custom regressors. Core engine for pySCENIC (cisTarget pruning and regulon activity are downstream). Use for bulk or single-cell expression matrices when inferring TFtarget regulatory links from co-expression patterns
- **BioPython** - Comprehensive Python library for computational biology and bioinformatics providing tools for sequence manipulation, database access, and biological data analysis. Key features include: sequence objects (Seq, SeqRecord, SeqIO) for DNA/RNA/protein sequences with biological alphabet validation, file format parsers (FASTA, FASTQ, GenBank, EMBL, Swiss-Prot, PDB, SAM/BAM, VCF, GFF), NCBI database access (Entrez Programming Utilities for PubMed, GenBank, BLAST, taxonomy), BLAST integration (running searches, parsing results), sequence alignment (pairwise and multiple sequence alignment with Bio.Align), phylogenetics (tree construction and manipulation with Bio.Phylo), population genetics (Hardy-Weinberg, F-statistics), protein structure analysis (PDB parsing, structure calculations), and statistical analysis tools. Supports integration with NumPy, pandas, and other scientific Python libraries. Use cases: sequence analysis, database queries, phylogenetic analysis, sequence alignment, file format conversion, and general bioinformatics workflows
- **BioServices** - Python library providing unified programmatic access to 40+ biological web services and databases. Supports major bioinformatics resources including KEGG (pathway and compound data), UniProt (protein sequences and annotations), ChEBI (chemical entities), ChEMBL (bioactive molecules), Reactome (pathways), IntAct (protein interactions), BioModels (biological models), and many others. Features consistent API across different services, automatic result caching, error handling and retry logic, support for both REST and SOAP web services, and conversion of results to Python objects (dictionaries, lists, BioPython objects). Handles authentication, rate limiting, and API versioning. Use cases: automated data retrieval from multiple biological databases, building bioinformatics pipelines, database integration workflows, and programmatic access to biological web resources without manual web browsing
- **Bulk RNA-seq** - End-to-end orchestrator for bulk RNA-seq differential-expression studies, chaining the repo's skills into one defensible pipeline: raw FASTQ -> QC/trimming (FastQC, fastp/Trim Galore) -> alignment and quantification (STAR, Salmon, featureCounts) -> a gene-level counts matrix -> differential expression (PyDESeq2) -> pathway/GSEA enrichment (Pathway Enrichment) -> publication figures (scientific-visualization). Offers two interchangeable upstream paths with a decision guide — nf-core/rnaseq driven through the Nextflow skill, or standalone STAR/Salmon recipes — plus the glue no single tool provides: experimental-design and QC guidance (replication, batch/confounding, strandedness, mapping-rate and PCA gates), a quant->counts bridge script (build_counts_matrix.py: aggregates Salmon quant.sf via pytximport, selects the correct STAR ReadsPerGene strand column, or parses featureCounts into a PyDESeq2-ready counts.csv plus metadata template), and a samplesheet/design validator (validate_samplesheet.py). Emphasizes reproducibility (pinned pipeline/tool/reference versions, containers) and correct statistics (integer counts, gene-ID mapping to symbols, FDR control). Use cases: going from sequencing reads to differentially expressed genes and enriched pathways, running or configuring nf-core/rnaseq, STAR/Salmon quantification, building a counts matrix for DESeq2, and scoping a complete reproducible bulk RNA-seq analysis. For single-cell RNA-seq use Scanpy instead
- **Cellxgene Census** - Python package for querying and analyzing versioned public single-cell and spatial transcriptomics data from CZ CELLxGENE Discover. Provides access to the current stable LTS Census release with 217M+ total cells, 125M+ unique cells, and 1,845 datasets, with standardized annotations and metadata across human, mouse, marmoset, rhesus macaque, and chimpanzee. Key features include: efficient TileDB-SOMA access for scalable metadata and expression queries, AnnData and Scanpy integration, source H5AD URI/download helpers, precomputed summary counts, embeddings, spatial Census access, and TileDB-SOMA-ML guidance for PyTorch loaders. Enables subsetting datasets by organism, cell type, tissue, disease, assay, or other metadata before downloading, reducing transfer and memory requirements. Use cases: large-scale single-cell analysis, spatial transcriptomics access, cell-type discovery, cross-dataset comparisons, reference atlas construction, and exploratory analysis of public Census data
- **deepTools** - Comprehensive suite of Python tools for exploring and visualizing next-generation sequencing (NGS) data, particularly ChIP-seq, RNA-seq, and ATAC-seq experiments. Provides command-line tools and Python API for processing BAM and bigWig files. Key features include: quality control metrics (plotFingerprint, plotCorrelation), coverage track generation (bamCoverage for creating bigWig files), matrix generation for heatmaps (computeMatrix, plotHeatmap, plotProfile), comparative analysis (multiBigwigSummary, plotPCA), and efficient handling of large files. Supports normalization methods, binning options, and various visualization outputs. Designed for high-throughput analysis workflows and publication-quality figure generation. Use cases: ChIP-seq peak visualization, RNA-seq coverage analysis, ATAC-seq signal tracks, comparative genomics, and NGS data exploration
- **FlowIO** - Python library for reading and manipulating Flow Cytometry Standard (FCS) files, the standard format for flow cytometry data. Provides efficient parsing of FCS files (versions 2.0, 3.0, 3.1), access to event data (fluorescence intensities, scatter parameters), metadata extraction (keywords, parameters, acquisition settings), and conversion to pandas DataFrames or NumPy arrays. Features include: support for large FCS files, handling of multiple data segments, access to text segments and analysis segments, and integration with flow cytometry analysis workflows. Enables programmatic access to flow cytometry data for downstream analysis, visualization, and machine learning applications. Use cases: flow cytometry data analysis, high-throughput screening, immune cell profiling, and automated processing of FCS files
- **gget** - Command-line tool and Python package for efficient querying of genomic databases with a simple, unified interface. Provides fast access to Ensembl (gene information and sequences), UniProt, NCBI BLAST/BLAT and viral sequence data, PDB/AlphaFold structures, CELLxGENE Census, OpenTargets, cBioPortal, COSMIC, Enrichr, Bgee, and 8cubeDB mouse specificity/expression metrics. Features include: single-command queries without complex API setup, automatic result formatting, batch query support, integration with pandas DataFrames, and support for both command-line and Python API usage. Optimized for speed and ease of use, making database queries accessible to users without extensive bioinformatics experience. Use cases: quick gene lookups, sequence retrieval, viral dataset downloads, enrichment analysis, disease/drug associations, protein structure access, and rapid database queries in bioinformatics workflows
- **geniml** - Genomic interval machine learning toolkit providing unsupervised methods for building ML models on BED files. Key capabilities include Region2Vec (word2vec-style embeddings of genomic regions and region sets using tokenization and neural language modeling), BEDspace (joint embeddings of regions and metadata labels using StarSpace for cross-modal queries), scEmbed (Region2Vec applied to single-cell ATAC-seq data generating cell-level embeddings for clustering and annotation with scanpy integration), consensus peak building (four statistical methods CC/CCF/ML/HMM for creating reference universes from BED collections), and comprehensive utilities (BBClient for BED caching, BEDshift for genomic randomization preserving context, evaluation metrics for embedding quality, Text2BedNN for neural search backends). Part of BEDbase ecosystem. Supports Python API and CLI workflows, pre-trained models on Hugging Face, and integration with gtars for tokenization. Use cases: region similarity searches, dimension reduction of chromatin accessibility data, scATAC-seq clustering and cell-type annotation, metadata-aware genomic queries, universe construction for standardized references, and any ML task requiring genomic region feature vectors
- **gtars** - High-performance Rust toolkit for genomic interval analysis providing specialized tools for overlap detection using IGD (Integrated Genome Database) indexing, coverage track generation (uniwig module for WIG/BigWig formats), genomic tokenization for machine learning applications (TreeTokenizer for deep learning models), reference sequence management (refget protocol compliance), fragment processing for single-cell genomics (barcode-based splitting and cluster analysis), and fragment scoring against reference datasets. Offers Python bindings with NumPy integration, command-line tools (gtars-cli), and Rust library. Key modules include: tokenizers (convert genomic regions to ML tokens), overlaprs (efficient overlap computation), uniwig (ATAC-seq/ChIP-seq/RNA-seq coverage profiles), refget (GA4GH-compliant sequence digests), bbcache (BEDbase.org integration), scoring (fragment enrichment metrics), and fragsplit (single-cell fragment manipulation). Supports parallel processing, memory-mapped files, streaming for large datasets, and serves as foundation for geniml genomic ML package. Ideal for genomic ML preprocessing, regulatory element analysis, variant annotation, chromatin accessibility profiling, and computational genomics workflows
- **Polars-Bio** - High-performance genomic interval operations and bioinformatics file I/O on Polars DataFrames. Provides overlap, nearest, merge, coverage, complement, and subtract operations for BED/VCF/BAM/GFF intervals. Streaming and cloud-native architecture for large-scale genomic analyses, serving as a faster alternative to bioframe
- **pysam** - Read, write, and manipulate genomic data files (SAM/BAM/CRAM alignments, VCF/BCF variants, FASTA/FASTQ sequences) with pileup analysis, coverage calculations, and bioinformatics workflows
- **PyDESeq2** - Python implementation of the DESeq2 differential gene expression analysis method for bulk RNA-seq data. Provides statistical methods for determining differential expression between experimental conditions using negative binomial generalized linear models. Key features include: size factor estimation for library size normalization, dispersion estimation and shrinkage, hypothesis testing with Wald test or likelihood ratio test, multiple testing correction (Benjamini-Hochberg FDR), results filtering and ranking, and integration with pandas DataFrames. Handles complex experimental designs, batch effects, and replicates. Produces fold-change estimates, p-values, and adjusted p-values for each gene. Use cases: identifying differentially expressed genes between conditions, RNA-seq experiment analysis, biomarker discovery, and gene expression studies requiring rigorous statistical analysis
- **Pathway Enrichment** - Pathway and gene-set enrichment analysis on gene lists or ranked gene data, with result interpretation. Supports over-representation analysis (ORA via Enrichr/Fisher's exact/hypergeometric), preranked and standard Gene Set Enrichment Analysis (GSEA), and single-sample scoring (ssGSEA/GSVA) using gseapy and the official g:Profiler client. Covers gene-set libraries (GO Biological Process/Molecular Function/Cellular Component, KEGG, Reactome, WikiPathways, and MSigDB collections including Hallmark, C2 canonical pathways, C5 ontology, and C7 immune signatures), gene-ID mapping (Ensembl/Entrez to symbols via Biomart, g:Convert, or mygene) and organism handling, choice of the statistical background universe, multiple-testing correction (Benjamini-Hochberg FDR vs g:Profiler g:SCS vs Bonferroni), redundancy reduction (enrichment maps, leading-edge genes, term clustering), and publication-ready tables plus dotplots/bar plots/GSEA running-score plots. Includes a CLI helper (run_enrichment.py) that runs ORA or preranked GSEA end-to-end — automatically building the ranking metric from a DESeq2 results table — and writes a results table and dotplot. Cross-references PyDESeq2 and Scanpy upstream (sources of differentially expressed genes and cluster markers) and database-lookup/gget for gene-ID mapping and Reactome/KEGG/STRING APIs. Use cases: functional interpretation of differentially expressed genes, CRISPR-screen hits, and single-cell cluster markers; GO/KEGG/Reactome/WikiPathways enrichment; preranked GSEA from DESeq2 statistics; pathway activity scoring per sample or cell; and building defensible, reproducible enrichment analyses that avoid common pitfalls (gene-ID/organism mismatch, wrong background, thresholding before GSEA)
- **Scanpy** - Comprehensive Python toolkit for single-cell RNA-seq data analysis built on AnnData (scanpy 1.12.x; Python 3.12+). Provides end-to-end workflows for preprocessing (quality control, scrublet doublet detection, normalization, log transformation), dimensionality reduction (PCA, UMAP, t-SNE), Leiden clustering, marker gene identification, pseudobulk aggregation via `get.aggregate()`, trajectory inference (PAGA, diffusion maps), and visualization. Key features include: efficient handling of large datasets using sparse matrices and experimental Dask out-of-core support, integration with scvi-tools for advanced analysis, batch correction methods (ComBat), and publication-quality plotting. Optional GPU acceleration via rapids-singlecell. Use cases: single-cell RNA-seq analysis, cell-type identification, exploratory cluster markers, pseudobulk DE workflows (with pydeseq2), trajectory analysis, and comprehensive single-cell genomics workflows
- **scVelo** - RNA velocity analysis for estimating cell state transitions from unspliced/spliced mRNA dynamics. Infers trajectory directions, computes latent time, and identifies driver genes in single-cell RNA-seq data. Complements Scanpy/scVI-tools for trajectory inference, enabling the study of cellular differentiation dynamics and lineage decisions at single-cell resolution
- **scvi-tools** - Probabilistic deep learning models for single-cell omics analysis. PyTorch-based framework providing variational autoencoders (VAEs) for dimensionality reduction, batch correction, differential expression, and data integration across modalities. Includes 30+ models: scVI/scANVI (RNA-seq integration and cell type annotation), totalVI/totalANVI (CITE-seq protein+RNA), MultiVI (multiome RNA+ATAC integration), PeakVI (ATAC-seq analysis), DestVI/Stereoscope/Tangram (spatial transcriptomics deconvolution), MethylVI (methylation), CytoVI (flow/mass cytometry), VeloVI (RNA velocity), contrastiveVI (perturbation studies), and Solo (doublet detection). Supports seamless integration with Scanpy/AnnData ecosystem, GPU acceleration, reference mapping (scArches), and probabilistic differential expression with uncertainty quantification
- **scikit-bio** - Python library for bioinformatics providing data structures, algorithms, and parsers for biological sequence analysis. Built on NumPy, SciPy, and pandas. Key features include: sequence objects (DNA, RNA, protein sequences) with biological alphabet validation, sequence alignment algorithms (local, global, semiglobal), phylogenetic tree manipulation, diversity metrics (alpha diversity, beta diversity, phylogenetic diversity), distance metrics for sequences and communities, file format parsers (FASTA, FASTQ, QIIME formats, Newick), and statistical analysis tools. Provides scikit-learn compatible transformers for machine learning workflows. Supports efficient processing of large sequence datasets. Use cases: sequence analysis, microbial ecology (16S rRNA analysis), metagenomics, phylogenetic analysis, and bioinformatics research requiring sequence manipulation and diversity calculations
- **TileDB-VCF** - High-performance C++ library with Python and CLI interfaces for efficient storage and retrieval of genomic variant-call data using TileDB multidimensional sparse array technology. Enables scalable VCF/BCF ingestion with incremental sample addition, compressed storage, parallel queries across genomic regions and samples, and export capabilities for population genomics workflows. Key features include: memory-efficient queries, cloud storage integration (S3, Azure, GCS), and CLI tools for dataset creation, sample ingestion, data export, and statistics. Supports building variant databases for large cohorts, population-scale genomics studies, and association analysis. Use cases: population genomics databases, cohort studies, variant discovery workflows, genomic data warehousing, and scaling to enterprise-level analysis with TileDB-Cloud platform
- **Zarr** - Python library (Zarr-Python 3.x) implementing chunked, compressed N-dimensional arrays for local disk and cloud object storage (S3, GCS via fsspec). Supports Zarr format 2 and 3, `zarr.codecs` compression (Blosc, gzip, zstd), partial chunk reads, consolidated metadata, sharding, and integration with NumPy, Dask, and Xarray. Use for out-of-core arrays, cloud-native pipelines, and large scientific datasets (genomics, imaging, climate). Skill: `zarr-python`
### Data Management & Infrastructure
- **LaminDB** - Open-source lineage-native lakehouse for biology that makes datasets and models queryable, traceable, validated, reproducible, and FAIR. Covers LaminDB 2.x setup, artifact and collection registration, feature-based query/search, lineage tracking with `ln.track()`/`ln.finish()` and `@ln.flow()`/`@ln.step()`, schema validation for DataFrame/AnnData/SpatialData/TileDB-SOMA, and ontology-backed annotation with Bionty 2.x. Includes current guidance for projects, branches, spaces, storage backends (local, S3, GCS, S3-compatible, Hugging Face), SQLite/Postgres deployment, safe credential handling, external-data validation, and integrations with Nextflow/nf-lamin, Snakemake, Redun, W&B, MLflow, Lightning, scVI-tools, DuckDB, and Vitessce.
- **Modal** - Serverless cloud platform for running Python code with minimal configuration, specialized for AI/ML workloads and scientific computing. Execute functions on powerful GPUs (T4, L4, A10, A100, L40S, H100, H200, B200, B200+), scale automatically from zero to thousands of containers, and pay only for compute used. Key features include: declarative container image building with uv (recommended)/pip/apt package management, automatic autoscaling with configurable limits and buffer containers, GPU acceleration with multi-GPU support (up to 8 GPUs per container, up to 1,536 GB VRAM), persistent storage via Volumes (v1 and v2) for model weights and datasets, secret management for API keys and credentials, scheduled jobs with cron expressions, web endpoints for deploying serverless APIs (FastAPI, ASGI, WSGI, WebSockets), parallel execution with `.map()` for batch processing, input concurrency and dynamic batching for I/O-bound workloads, Sandboxes for isolated execution of untrusted or agent-generated code with network (CIDR) restrictions, and resource configuration (CPU cores, memory, ephemeral disk up to 3 TiB). Supports custom Docker images, Micromamba/Conda environments, integration with Hugging Face/Weights & Biases, and distributed multi-GPU training. Free tier includes $30/month credits. Use cases: ML model deployment and inference (LLMs, image generation, speech, embeddings), GPU-accelerated training and fine-tuning, batch processing large datasets in parallel, scheduled compute-intensive jobs, serverless API deployment with autoscaling, protein folding and computational biology, scientific computing requiring distributed compute or specialized hardware, and data pipeline automation
- **Optimize for GPU** - GPU-accelerate Python code using the NVIDIA RAPIDS ecosystem and related libraries for 10x1000x speedups on suitable workloads. Covers 12 GPU libraries with decision framework for choosing the right tool: CuPy (drop-in NumPy/SciPy replacement for array operations, FFT, linear algebra), Numba CUDA (custom GPU kernels with fine-grained thread/block/shared-memory control), Warp (JIT-compiled simulation kernels with built-in spatial types for physics, mesh ray casting, differentiable programming, and robotics), cuDF (drop-in pandas replacement for dataframe ETL, groupby, joins), cuML (drop-in scikit-learn replacement for classification, regression, clustering, dimensionality reduction, preprocessing), cuGraph (drop-in NetworkX replacement for PageRank, centrality, community detection, shortest paths), KvikIO (GPUDirect Storage for high-performance file IO bypassing CPU memory, S3/HTTP direct-to-GPU reads, Zarr GPU backend), cuxfilter (GPU-accelerated interactive cross-filtering dashboards with Bokeh, Datashader, and Deck.gl), cuCIM (drop-in scikit-image replacement for image filtering, morphology, segmentation, plus fast whole-slide image reading for digital pathology), cuVS (GPU-accelerated vector/similarity search with CAGRA, IVF-Flat, IVF-PQ for RAG and recommender systems), cuSpatial (GPU-accelerated GeoPandas replacement for spatial joins, point-in-polygon, trajectory analysis), and RAFT/pylibraft (sparse eigensolvers, device memory management, multi-GPU communication). All libraries interoperate via CUDA Array Interface for zero-copy data sharing. Includes optimization workflow (profile first, assess GPU suitability, start with drop-in replacements, minimize host-device transfers), code transformation patterns for each library, memory management principles, and common pitfalls. Use cases: accelerating NumPy/pandas/scikit-learn/NetworkX/scikit-image/GeoPandas/Faiss workloads, physics simulation, differentiable rendering, particle systems, vector search for RAG pipelines, GPUDirect Storage file IO, interactive data exploration dashboards, geospatial analysis, medical imaging, and sparse eigenvalue problems
### Cheminformatics & Drug Discovery
- **Datamol** - Python library for molecular manipulation and featurization built on RDKit with enhanced workflows and performance optimizations. Provides utilities for molecular I/O (reading/writing SMILES, SDF, MOL files), molecular standardization and sanitization, molecular transformations (tautomer enumeration, stereoisomer generation), molecular featurization (descriptors, fingerprints, graph representations), parallel processing for large datasets, and integration with machine learning pipelines. Features include: optimized RDKit operations, caching for repeated computations, molecular filtering and preprocessing, and seamless integration with pandas DataFrames. Designed for drug discovery and cheminformatics workflows requiring efficient processing of large compound libraries. Use cases: molecular preprocessing for ML models, compound library management, molecular similarity searches, and cheminformatics data pipelines
- **DeepChem** - Molecular machine learning library (PyPI 2.8.0; Python 3.73.11) with TensorFlow, PyTorch, and JAX backends. Graph neural networks (GCN, GAT, MPNN, AttentiveFP), 30+ MoleculeNet benchmarks, diverse featurizers (fingerprints, descriptors, graphs), and pretrained models (ChemBERTa, GROVER, MolFormer). Install backend-specific extras (`deepchem[torch]`, `[tensorflow]`, `[jax]`). Use for ADMET/toxicity prediction, materials properties, and transfer learning on small datasets
- **DiffDock** - Diffusion-based molecular docking method for predicting protein-ligand binding poses with DiffDock-L confidence scores. Uses diffusion models to generate diverse, high-quality poses without exhaustive search. Key features include: fast GPU inference compared to traditional docking methods, generation of multiple diverse poses, confidence scoring for pose quality, batch CSV input for screening campaigns, and support for protein PDB files or ESMFold sequence inputs with SMILES/SDF/MOL2 ligands. DiffDock does not directly predict binding affinity; combine generated poses with GNINA, MM/GBSA, or free-energy workflows when affinity ranking is required. Use cases: virtual screening, lead optimization, binding pose prediction, structure-based drug design, and initial pose generation for downstream refinement
- **MedChem** - Python library for molecular filtering and prioritization in drug discovery (datamol-io). Apply literature-derived drug-likeness rules (Lipinski, Veber, CNS, lead-like), named structural alert catalogs (PAINS, Brenk, NIBR), complexity thresholds against ZINC-derived percentiles, chemical group detection, and a custom query language for multi-criteria filtering. Returns pandas DataFrames from rule/structural filters and boolean masks from the functional API. Integrates with datamol/RDKit and supports parallel processing. Use cases: compound library triage, hit-to-lead filtering, PAINS deprioritization, screening-deck curation, and lead optimization workflows
- **Molfeat** - Comprehensive Python library providing 100+ molecular featurizers for converting molecules into numerical representations suitable for machine learning. Includes molecular fingerprints (ECFP, MACCS, RDKit, Pharmacophore), molecular descriptors (2D/3D descriptors, constitutional, topological, electronic), graph-based representations (molecular graphs, line graphs), and pre-trained models (MolBERT, ChemBERTa, Uni-Mol embeddings). Features unified API across different featurizer types, caching for performance, parallel processing, and integration with popular ML frameworks (scikit-learn, PyTorch, TensorFlow). Supports both traditional cheminformatics descriptors and modern learned representations. Use cases: molecular property prediction, virtual screening, molecular similarity searches, and preparing molecular data for machine learning models
- **PyTDC** - Python library providing access to Therapeutics Data Commons (TDC), a collection of curated datasets and benchmarks for drug discovery and development. Includes datasets for ADMET prediction (absorption, distribution, metabolism, excretion, toxicity), drug-target interactions, drug-drug interactions, drug response prediction, molecular generation, and retrosynthesis. Features standardized data formats, data loaders with automatic preprocessing, benchmark tasks with evaluation metrics, leaderboards for model comparison, and integration with popular ML frameworks. Provides both single-molecule and drug-pair datasets, covering various stages of drug discovery from target identification to clinical outcomes. Use cases: benchmarking ML models for drug discovery, ADMET prediction model development, drug-target interaction prediction, and drug discovery research
- **RDKit** - Open-source cheminformatics toolkit for molecular informatics and drug discovery. Provides comprehensive functionality for molecular I/O (reading/writing SMILES, SDF, MOL, PDB files), molecular descriptors (200+ 2D and 3D descriptors), molecular fingerprints (Morgan, RDKit, MACCS, topological torsions), SMARTS pattern matching for substructure searches, molecular alignment and 3D coordinate generation, pharmacophore perception, reaction handling, and molecular drawing. Features high-performance C++ core with Python bindings, support for large molecule sets, and extensive documentation. Widely used in pharmaceutical industry and academic research. Use cases: molecular property calculation, virtual screening, molecular similarity searches, substructure matching, molecular visualization, and general cheminformatics workflows
- **Rowan** - Cloud-based quantum chemistry platform with Python API for computational chemistry workflows. Provides access to 45+ chemistry calculations including pKa prediction, redox potentials, solubility, conformer searching, geometry optimization, protein-ligand docking (AutoDock Vina), and AI-powered protein cofolding (Chai-1, Boltz-1/2). Supports DFT, semiempirical (GFN-xTB), and neural network potential methods (AIMNet2, Egret). Key features include: automatic cloud resource allocation, unified API for diverse computational methods, RDKit-native interface for seamless cheminformatics integration, workflow organization with folders and projects, batch processing, and web interface for visualization. Requires API key from labs.rowansci.com. Use cases: molecular property prediction, structure-based drug design, virtual screening campaigns, protein-ligand binding prediction, conformational analysis, and automated computational chemistry pipelines
- **TorchDrug** - PyTorch-based machine learning platform for drug discovery with 40+ datasets, 20+ GNN models for molecular property prediction, protein modeling, knowledge graph reasoning, molecular generation, and retrosynthesis planning
### Proteomics & Mass Spectrometry
- **matchms** - Processing and similarity matching of mass spectrometry data with 40+ filters, spectral library matching (Cosine, Modified Cosine, Neutral Losses), metadata harmonization, molecular fingerprint comparison, and support for multiple file formats (MGF, MSP, mzML, JSON)
- **pyOpenMS** - Comprehensive mass spectrometry data analysis for proteomics and metabolomics (LC-MS/MS processing, peptide identification, feature detection, quantification, chemical calculations, and integration with search engines like Comet, Mascot, MSGF+)
### Medical Imaging & Digital Pathology
- **histolab** - Digital pathology toolkit for whole slide image (WSI) processing and analysis. Provides automated tissue detection, tile extraction for deep learning pipelines, and preprocessing for gigapixel histopathology images. Key features include: multi-format WSI support (SVS, TIFF, NDPI), three tile extraction strategies (RandomTiler for sampling, GridTiler for complete coverage, ScoreTiler for quality-driven selection), automated tissue masks with customizable filters, built-in scorers (NucleiScorer, CellularityScorer), Macenko and Reinhard stain normalization (0.6.0+), pyramidal image handling, visualization tools (thumbnails, mask overlays, tile previews), and H&E stain decomposition. Supports multiple tissue sections, artifact removal, pen annotation exclusion, and reproducible extraction with seeding. Requires Python 3.83.11, OpenSlide, and Linux or macOS. Use cases: creating training datasets for computational pathology, extracting informative tiles for tumor classification, whole-slide tissue characterization, quality assessment of histology samples, automated nuclei density analysis, and preprocessing for digital pathology deep learning workflows
- **PathML** - Comprehensive computational pathology toolkit for whole slide image analysis, tissue segmentation, and machine learning on pathology data. Provides end-to-end workflows for digital pathology research including data loading, preprocessing, feature extraction, and model deployment
- **pydicom** - Pure Python package for working with DICOM (Digital Imaging and Communications in Medicine) files. Provides comprehensive support for reading, writing, and manipulating medical imaging data from CT, MRI, X-ray, ultrasound, PET scans and other modalities. Key features include: pixel data extraction and manipulation with automatic decompression (JPEG/JPEG 2000/RLE), metadata access and modification with 1000+ standardized DICOM tags, image format conversion (PNG/JPEG/TIFF), anonymization tools for removing Protected Health Information (PHI), windowing and display transformations (VOI LUT application), multi-frame and 3D volume processing, DICOM sequence handling, and support for multiple transfer syntaxes. Use cases: medical image analysis, PACS system integration, radiology workflows, research data processing, DICOM anonymization, format conversion, image preprocessing for machine learning, multi-slice volume reconstruction, and clinical imaging pipelines
### Healthcare AI & Clinical Machine Learning
- **NeuroKit2** - Comprehensive biosignal processing toolkit for analyzing physiological data including ECG, EEG, EDA, RSP, PPG, EMG, and EOG signals. Use this skill when processing cardiovascular signals, brain activity, electrodermal responses, respiratory patterns, muscle activity, or eye movements. Key features include: automated signal processing pipelines (cleaning, peak detection, delineation, quality assessment), heart rate variability analysis across time/frequency/nonlinear domains (SDNN, RMSSD, LF/HF, DFA, entropy measures), EEG analysis (frequency band power, microstates, source localization), autonomic nervous system assessment (sympathetic indices, respiratory sinus arrhythmia), comprehensive complexity measures (25+ entropy types, 15+ fractal dimensions, Lyapunov exponents), event-related and interval-related analysis modes, epoch creation and averaging for stimulus-locked responses, multi-signal integration with unified workflows, and extensive signal processing utilities (filtering, decomposition, peak correction, spectral analysis). Includes modular reference documentation across 12 specialized domains. Use cases: heart rate variability for cardiovascular health assessment, EEG microstates for consciousness studies, electrodermal activity for emotion research, respiratory variability analysis, psychophysiology experiments, affective computing, stress monitoring, sleep staging, autonomic dysfunction assessment, biofeedback applications, and multi-modal physiological signal integration for comprehensive human state monitoring
- **PyHealth** - Comprehensive healthcare AI toolkit for developing, testing, and deploying machine learning models with clinical data. Provides specialized tools for electronic health records (EHR), physiological signals, medical imaging, and clinical text analysis. Key features include: 10+ healthcare datasets (MIMIC-III/IV, eICU, OMOP, sleep EEG, COVID-19 CXR), 20+ predefined clinical prediction tasks (mortality, hospital readmission, length of stay, drug recommendation, sleep staging, EEG analysis), 33+ models (Logistic Regression, MLP, CNN, RNN, Transformer, GNN, plus healthcare-specific models like RETAIN, SafeDrug, GAMENet, StageNet), comprehensive data processing (sequence processors, signal processors, medical code translation between ICD-9/10, NDC, RxNorm, ATC systems), training/evaluation utilities (Trainer class, fairness metrics, calibration, uncertainty quantification), and interpretability tools (attention visualization, SHAP, ChEFER). 3x faster than pandas for healthcare data processing. Use cases: ICU mortality prediction, hospital readmission risk assessment, safe medication recommendation with drug-drug interaction constraints, sleep disorder diagnosis from EEG signals, medical code standardization and translation, clinical text to ICD coding, length of stay estimation, and any clinical ML application requiring interpretability, fairness assessment, and calibrated predictions for healthcare deployment
### Clinical Documentation & Decision Support
- **Clinical Decision Support** - Generate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings. Includes patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Features GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration (genomic alterations, gene expression signatures, IHC markers), and regulatory compliance. Use cases: pharmaceutical cohort reporting, clinical guideline development, comparative effectiveness analyses, treatment algorithm creation, and evidence synthesis for drug development
- **Clinical Reports** - Write comprehensive clinical reports following established guidelines and standards. Covers case reports (CARE guidelines), diagnostic reports (radiology, pathology, laboratory), clinical trial reports (ICH-E3, SAE, CSR), and patient documentation (SOAP notes, H&P, discharge summaries). Includes templates, regulatory compliance (HIPAA, FDA, ICH-GCP), and validation tools. Use cases: journal case reports, diagnostic findings documentation, clinical trial reporting, patient progress notes, and regulatory submissions
- **Treatment Plans** - Generate concise (3-4 page), focused medical treatment plans in LaTeX/PDF format for all clinical specialties. Supports general medical treatment, rehabilitation therapy, mental health care, chronic disease management, perioperative care, and pain management. Features SMART goal frameworks, evidence-based interventions, HIPAA compliance, and professional formatting. Use cases: individualized patient care plans, rehabilitation programs, psychiatric treatment plans, surgical care pathways, and pain management protocols
### Neuroscience & Electrophysiology
- **BIDS** - Brain Imaging Data Structure (BIDS) standard for organizing and describing neuroscience and biomedical research datasets. While originating for MRI, BIDS now covers 11 modalities: imaging (MRI structural/functional/diffusion/perfusion, PET, microscopy), electrophysiology (EEG, MEG, iEEG, EMG), and other data (NIRS, motion capture, behavioral, MR spectroscopy), with active BEPs extending to microelectrode electrophysiology (Neuropixels), stimuli, and more. Covers the BIDS directory hierarchy, file naming conventions with entities (subject, session, task, acquisition, run, etc.), and JSON sidecar metadata. Key features include: dataset creation and validation workflows, querying BIDS datasets with PyBIDS (BIDSLayout), DICOM-to-BIDS conversion using HeuDiConv (ReproIn turnkey, map-into-reproin, and custom heuristic modes), dcm2bids (config-file-based), and BIDScoin (GUI-based), metadata inheritance and sidecar management, events files for task fMRI, participants and scans TSV files, BIDS derivatives conventions for preprocessed data and analysis outputs, BIDS-Apps interface (fMRIPrep, MRIQC, QSIPrep), machine-readable BIDS schema (bids_schema.json) and BEP listing (beps.yml) with update script, and .bidsignore configuration. Includes detailed reference documentation for the complete BIDS specification entity table (35 entities in schema ordering), required and recommended metadata fields for every modality, standard template spaces, and conversion tool workflows with examples. Use cases: organizing neuroscience data for sharing and analysis, validating BIDS compliance before repository submission (OpenNeuro, DANDI), converting DICOM scanner data to BIDS format, creating BIDS-compliant derivatives, querying datasets programmatically, and preparing data for BIDS-Apps processing pipelines
- **Neuropixels-Analysis** - Comprehensive toolkit for analyzing Neuropixels high-density neural recordings using SpikeInterface, Allen Institute, and International Brain Laboratory (IBL) best practices. Supports the full workflow from raw data to publication-ready curated units. Key features include: data loading from SpikeGLX, Open Ephys, and NWB formats, preprocessing pipelines (highpass filtering, phase shift correction for Neuropixels 1.0, bad channel detection, common average referencing), motion/drift estimation and correction (nonrigid_fast_and_accurate, nonrigid_accurate, and DREDge presets), spike sorting integration (Kilosort4 GPU, SpykingCircus2, Tridesclous2, Mountainsort5 CPU), comprehensive postprocessing (waveform extraction, template computation, spike amplitudes, correlograms, unit locations), quality metrics computation (SNR, ISI violations, presence ratio, amplitude cutoff, drift metrics), automated curation using Allen Institute and IBL criteria with configurable thresholds, model-based curation with pretrained UnitRefine classifiers (noise/neural and SUA/MUA) loaded from Hugging Face via spikeinterface.curation, AI-assisted visual curation for uncertain units using vision-language models, and export to Phy for manual review or NWB for sharing. Supports Neuropixels 1.0 (960 electrodes, 384 channels) and Neuropixels 2.0 (single and 4-shank configurations). Use cases: extracellular electrophysiology analysis, spike sorting from silicon probes, neural population recordings, systems neuroscience research, unit quality assessment, publication-ready neural data processing, and integration of AI-assisted curation for borderline units
### Protein Engineering & Design
- **Adaptyv** - Cloud laboratory platform for automated protein testing and validation. Submit protein sequences via API or web interface and receive experimental results in approximately 21 days. Supports multiple assay types including binding assays (biolayer interferometry for protein-target interactions, KD/kon/koff measurements), expression testing (quantify protein expression levels in E. coli, mammalian, yeast, or insect cells), thermostability measurements (DSF and CD for Tm determination and thermal stability profiling), and enzyme activity assays (kinetic parameters, substrate specificity, inhibitor testing). Includes computational optimization tools for pre-screening sequences: NetSolP/SoluProt for solubility prediction, SolubleMPNN for sequence redesign to improve expression, ESM for sequence likelihood scoring, ipTM (AlphaFold-Multimer) for interface stability assessment, and pSAE for aggregation risk quantification. Platform features automated workflows from expression through purification to assay execution with quality control, webhook notifications for experiment completion, batch submission support for high-throughput screening, and comprehensive results with kinetic parameters, confidence metrics, and raw data access. Use cases: antibody affinity maturation, therapeutic protein developability assessment, enzyme engineering and optimization, protein stability improvement, AI-driven protein design validation, library screening for expression and function, lead optimization with experimental feedback, and integration of computational design with wet-lab validation in iterative design-build-test-learn cycles
- **ESM (Evolutionary Scale Modeling)** - Protein language models from EvolutionaryScale/Biohub for protein design, structure prediction, and representation learning. Covers current `esm` SDK workflows for local ESM3/ESMC open models, Forge-hosted ESM3 and ESMC inference with `ESM_API_KEY`, Biohub-hosted ESMC embeddings, and ESMFold2 all-atom structure prediction. Use cases: novel protein design, sequence/structure co-design, protein embeddings, function annotation, variant generation, and directed evolution workflows
- **Glycoengineering** - Analyze and engineer protein glycosylation. Scan sequences for N-glycosylation sequons (N-X-S/T), predict O-glycosylation hotspots, and access curated glycoengineering tools (NetOGlyc, GlycoShield, GlycoWorkbench). Use for glycoprotein engineering, therapeutic antibody optimization, and vaccine design
- **Molecular Dynamics** - Run and analyze molecular dynamics simulations with OpenMM and MDAnalysis. Set up protein/small molecule systems, define force fields, run energy minimization and production MD, analyze trajectories (RMSD, RMSF, contact maps, free energy surfaces). Use for structural biology, drug binding studies, and biophysics research
- **Tamarind** - Run a large catalog of open-source molecular design and structural biology tools on the Tamarind Bio managed-GPU cloud via its REST API (`x-api-key` header) or MCP server — no local GPUs required. Covers structure prediction (AlphaFold, Boltz-2, Chai-1, ESMFold2), protein/binder/de novo design (RFdiffusion, ProteinMPNN/LigandMPNN, BoltzGen, BindCraft), antibody and nanobody design, humanization and developability, protein-ligand docking (DiffDock, AutoDock Vina) and binding-affinity prediction, MSA generation, and molecular dynamics — all through one uniform job API with single and batch submission and tool chaining (design → fold → score). Discovers tools and schemas live (`GET /tools`, MCP `getAvailableTools`/`getJobSchema`) and reads the key from `TAMARIND_API_KEY`. There is no official Python SDK — use plain `requests` or the MCP server. Use cases: cloud structure prediction and protein design without provisioning hardware, high-throughput batch characterization of sequences/designs, and chaining design-fold-score pipelines
### Machine Learning & Deep Learning
- **aeon** - Comprehensive scikit-learn compatible Python toolkit for time series machine learning providing state-of-the-art algorithms across 7 domains: classification (13 algorithm categories including ROCKET variants, deep learning with InceptionTime/ResNet/FCN, distance-based with DTW/ERP/LCSS, shapelet-based, dictionary methods like BOSS/WEASEL, and hybrid ensembles HIVECOTE), regression (9 categories mirroring classification approaches), clustering (k-means/k-medoids with temporal distances, deep learning autoencoders, spectral methods), forecasting (ARIMA, ETS, Theta, Threshold Autoregressive, TCN, DeepAR), anomaly detection (STOMP/MERLIN matrix profile, clustering-based CBLOF/KMeans, isolation methods, copula-based COPOD), segmentation (ClaSP, FLUSS, HMM, binary segmentation), and similarity search (MASS algorithm, STOMP motif discovery, approximate nearest neighbors). Includes 40+ distance metrics (elastic: DTW/DDTW/WDTW/Shape-DTW, edit-based: ERP/EDR/LCSS/TWE/MSM, lock-step: Euclidean/Manhattan), extensive transformations (ROCKET/MiniRocket/MultiRocket for features, Catch22/TSFresh for statistics, SAX/PAA for symbolic representation, shapelet transforms, wavelets, matrix profile), 20+ deep learning architectures (FCN, ResNet, InceptionTime, TCN, autoencoders with attention mechanisms), comprehensive benchmarking tools (UCR/UEA archives with 100+ datasets, published results repository, statistical testing), and performance-optimized implementations using numba. Features progressive model complexity from fast baselines (MiniRocket: <1 second training, 0.95+ accuracy on many benchmarks) to state-of-the-art ensembles (HIVECOTE V2), GPU acceleration support, and extensive visualization utilities. Use cases: physiological signal classification (ECG, EEG), industrial sensor monitoring, financial forecasting, change point detection, pattern discovery, activity recognition from wearables, predictive maintenance, climate time series analysis, and any sequential data requiring specialized temporal modeling beyond standard ML
- **Cirq** - Google quantum computing framework for designing, simulating, and running quantum circuits. Best suited for targeting Google Quantum AI hardware, designing noise-aware circuits, and running quantum characterization experiments. Provides low-level circuit design, noise modeling, and hardware-specific optimizations. For IBM hardware use Qiskit; for quantum ML with autodiff use PennyLane; for physics simulations use QuTiP
- **PufferLib** - High-performance reinforcement learning library achieving 1M-4M steps/second through optimized vectorization, native multi-agent support, and efficient PPO training (PuffeRL). Use this skill for RL training on any environment (Gymnasium, PettingZoo, Atari, Procgen), creating custom PufferEnv environments, developing policies (CNN, LSTM, multi-input architectures), optimizing parallel simulation performance, or scaling multi-agent systems. Includes Ocean suite (20+ environments), seamless framework integration with automatic space flattening, zero-copy vectorization with shared memory buffers, distributed training support, and comprehensive reference guides for training workflows, environment development, vectorization optimization, policy architectures, and third-party integrations
- **PyMC** - Comprehensive Python library for Bayesian statistical modeling and probabilistic programming. Provides intuitive syntax for building probabilistic models, advanced MCMC sampling algorithms (NUTS, nutpie, Metropolis-Hastings, Slice sampling), variational inference methods (ADVI, SVGD), Gaussian processes, time series distributions, and model comparison tools (WAIC, LOO). Features include automatic differentiation via PyTensor, compiled backends such as Numba/JAX, parallel sampling, model diagnostics and convergence checking, PyMC 6 data-container prediction workflows, and integration with ArviZ for visualization and analysis. Supports hierarchical models, mixture models, survival analysis, and custom distributions. Use cases: Bayesian data analysis, uncertainty quantification, A/B testing, time series forecasting, hierarchical modeling, and probabilistic machine learning
- **PyMOO** - Python framework for multi-objective optimization using evolutionary algorithms. Provides implementations of state-of-the-art algorithms including NSGA-II, NSGA-III, MOEA/D, SPEA2, and reference-point based methods. Features include: support for constrained and unconstrained optimization, multiple problem types (continuous, discrete, mixed-variable), performance indicators (hypervolume, IGD, GD), visualization tools (Pareto front plots, convergence plots), and parallel evaluation support. Supports custom problem definitions, algorithm configuration, and result analysis. Designed for engineering design, parameter optimization, and any problem requiring optimization of multiple conflicting objectives simultaneously. Use cases: multi-objective optimization problems, Pareto-optimal solution finding, engineering design optimization, and research in evolutionary computation
- **PyTorch Lightning** - Deep learning framework (lightning 2.6+) that organizes PyTorch code to eliminate boilerplate while maintaining full flexibility. Automates training workflows (epoch/batch iteration, optimizer steps, gradient management, checkpointing), supports multi-GPU/TPU training with DDP/FSDP/DeepSpeed strategies, includes LightningModule for model organization, Trainer for automation, LightningDataModule for data pipelines, callbacks for extensibility, and integrations with TensorBoard, Wandb, and MLflow for experiment tracking
- **PennyLane** - Cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Enables building and training quantum circuits with automatic differentiation, seamless integration with PyTorch/JAX/NumPy, and device-independent execution across simulators and quantum hardware (IBM, Amazon Braket, Google, Rigetti, IonQ). Key features include: quantum circuit construction with QNodes (quantum functions with automatic differentiation), 100+ quantum gates and operations (Pauli, Hadamard, rotation, controlled gates), circuit templates and layers for common ansatze (StronglyEntanglingLayers, BasicEntanglerLayers, UCCSD for chemistry), gradient computation methods (parameter-shift rule for hardware, backpropagation for simulators, adjoint differentiation), quantum chemistry module (molecular Hamiltonian construction, VQE for ground state energy, differentiable Hartree-Fock solver), ML framework integration (TorchLayer for PyTorch models, JAX transformations, TensorFlow deprecated), built-in optimizers (Adam, GradientDescent, QNG, Rotosolve), measurement types (expectation values, probabilities, samples, state vectors), device ecosystem (default.qubit simulator, lightning.qubit for performance, hardware plugins for IBM/Braket/Cirq/Rigetti/IonQ), and Catalyst for just-in-time compilation with adaptive circuits. Supports variational quantum algorithms (VQE, QAOA), quantum neural networks, hybrid quantum-classical models, data encoding strategies (angle, amplitude, IQP embeddings), and pulse-level programming. Use cases: variational quantum eigensolver for molecular simulations, quantum circuit machine learning with gradient-based optimization, hybrid quantum-classical neural networks, quantum chemistry calculations with differentiable workflows, quantum algorithm prototyping with hardware-agnostic code, quantum machine learning research with automatic differentiation, and deploying quantum circuits across multiple quantum computing platforms
- **Qiskit** - World's most popular open-source quantum computing framework for building, optimizing, and executing quantum circuits with 13M+ downloads and 74% developer preference. Provides comprehensive tools for quantum algorithm development including circuit construction with 100+ quantum gates (Pauli, Hadamard, CNOT, rotation gates, controlled gates), circuit transpilation with 83x faster optimization than competitors producing circuits with 29% fewer two-qubit gates, primitives for execution (Sampler for bitstring measurements and probability distributions, Estimator for expectation values and observables), visualization tools (circuit diagrams in matplotlib/LaTeX, result histograms, Bloch sphere, state visualizations), backend-agnostic execution (local simulators including StatevectorSampler and Aer, IBM Quantum hardware with 100+ qubit systems, IonQ trapped ion, Amazon Braket multi-provider), session and batch modes for iterative and parallel workloads, error mitigation with configurable resilience levels (readout error correction, ZNE, PEC reducing sampling overhead by 100x), four-step patterns workflow (Map classical problems to quantum circuits, Optimize through transpilation, Execute with primitives, Post-process results), algorithm libraries including Qiskit Nature for quantum chemistry (molecular Hamiltonians, VQE for ground states, UCCSD ansatz, multiple fermion-to-qubit mappings), Qiskit Optimization for combinatorial problems (QAOA, portfolio optimization, MaxCut), and Qiskit Machine Learning (quantum kernels, VQC, QNN), support for Python/C/Rust with modular architecture, parameterized circuits for variational algorithms, quantum Fourier transform, Grover search, Shor's algorithm, pulse-level control, IBM Quantum Runtime for cloud execution with job management and queuing, and comprehensive documentation with textbook and tutorials. Use cases: variational quantum eigensolver for molecular ground state energy, QAOA for combinatorial optimization problems, quantum chemistry simulations with multiple ansatze and mappings, quantum machine learning with kernel methods and neural networks, hybrid quantum-classical algorithms, quantum algorithm research and prototyping across multiple hardware platforms, quantum circuit optimization and benchmarking, quantum error mitigation and characterization, quantum information science experiments, and production quantum computing workflows on real quantum hardware
- **QuTiP** - Quantum Toolbox in Python for simulating and analyzing quantum mechanical systems. Provides comprehensive tools for both closed (unitary) and open (dissipative) quantum systems including quantum states (kets, bras, density matrices, Fock states, coherent states), quantum operators (creation/annihilation operators, Pauli matrices, angular momentum operators, quantum gates), time evolution solvers (Schrödinger equation with sesolve, Lindblad master equation with mesolve, quantum trajectories with Monte Carlo mcsolve, Bloch-Redfield brmesolve, Floquet methods for periodic Hamiltonians), analysis tools (expectation values, entropy measures, fidelity, concurrence, correlation functions, steady state calculations), visualization (Bloch sphere with animations, Wigner functions, Q-functions, Fock distributions, matrix histograms), and advanced methods (Hierarchical Equations of Motion for non-Markovian dynamics, permutational invariance for identical particles, stochastic solvers, superoperators). Supports tensor products for composite systems, partial traces, time-dependent Hamiltonians, multiple dissipation channels, and parallel processing. Includes extensive documentation, tutorials, and examples. Use cases: quantum optics simulations (cavity QED, photon statistics), quantum computing (gate operations, circuit dynamics), open quantum systems (decoherence, dissipation), quantum information theory (entanglement dynamics, quantum channels), condensed matter physics (spin chains, many-body systems), and general quantum mechanics research and education
- **scikit-learn** - Industry-standard Python library for classical machine learning (current stable **1.8.0**, Python 3.113.14, free-threaded CPython wheels). Provides comprehensive supervised learning (classification: Logistic Regression, SVM, Decision Trees, Random Forests with 17+ variants, Gradient Boosting with XGBoost-compatible HistGradientBoosting, Naive Bayes, KNN, Neural Networks/MLP; regression: Linear, Ridge, Lasso, ElasticNet, SVR, ensemble methods), unsupervised learning (clustering: K-Means, DBSCAN, native HDBSCAN since 1.3, OPTICS, Agglomerative/Hierarchical, Spectral, Gaussian Mixture Models, BIRCH, MeanShift; dimensionality reduction: PCA, Kernel PCA, t-SNE, Isomap, LLE, MDS, ClassicalMDS, NMF, TruncatedSVD, FastICA, LDA; outlier detection: IsolationForest, LocalOutlierFactor, OneClassSVM), data preprocessing (scaling: StandardScaler, MinMaxScaler, RobustScaler; encoding: OneHotEncoder, OrdinalEncoder, LabelEncoder; imputation: SimpleImputer, KNNImputer, IterativeImputer; feature engineering: PolynomialFeatures, KBinsDiscretizer, text vectorization with CountVectorizer/TfidfVectorizer), model evaluation (cross-validation: KFold, StratifiedKFold, TimeSeriesSplit, GroupKFold; hyperparameter tuning: GridSearchCV, RandomizedSearchCV, HalvingGridSearchCV; metrics: 30+ evaluation metrics for classification/regression/clustering including accuracy, precision, recall, F1, ROC-AUC, MSE, RMSE via root_mean_squared_error, R², silhouette score), and Pipeline/ColumnTransformer for production-ready workflows. Features consistent API (fit/predict/transform), extensive documentation, integration with NumPy/pandas/SciPy, joblib persistence, and scikit-learn-compatible ecosystem (XGBoost, LightGBM, CatBoost, imbalanced-learn, UMAP via umap-learn). Optimized implementations using Cython/OpenMP for performance. Use cases: predictive modeling, customer segmentation, anomaly detection, feature engineering, model selection/validation, text classification, image classification (with feature extraction), time series forecasting (with preprocessing), medical diagnosis, fraud detection, recommendation systems, and any tabular data ML task requiring interpretable models or established algorithms
- **scikit-survival** - Survival analysis and time-to-event modeling with censored data. Built on scikit-learn, provides Cox proportional hazards models (CoxPHSurvivalAnalysis, CoxnetSurvivalAnalysis with elastic net regularization), ensemble methods (Random Survival Forests, Gradient Boosting), Survival Support Vector Machines (linear and kernel), non-parametric estimators (Kaplan-Meier, Nelson-Aalen), competing risks analysis, and specialized evaluation metrics (concordance index, time-dependent AUC, Brier score). Handles right-censored data, integrates with scikit-learn pipelines, and supports feature selection and hyperparameter tuning via cross-validation
- **SHAP** - Model interpretability and explainability using Shapley values from game theory. Provides unified approach to explain any ML model with TreeExplainer (fast exact explanations for XGBoost/LightGBM/Random Forest), DeepExplainer (TensorFlow/PyTorch neural networks), KernelExplainer (model-agnostic), and LinearExplainer. Includes comprehensive visualizations (waterfall plots for individual predictions, beeswarm plots for global importance, scatter plots for feature relationships, bar/force/heatmap plots), supports model debugging, fairness analysis, feature engineering guidance, and production deployment
- **Stable Baselines3** - PyTorch-based reinforcement learning library (current stable **2.8.0**, Python 3.10+, PyTorch >= 2.3). Provides reliable implementations of RL algorithms (PPO, SAC, DQN, TD3, DDPG, A2C, HER, RecurrentPPO via SB3-Contrib). Use this skill for training RL agents on standard or custom Gymnasium environments, implementing callbacks for monitoring and control, using vectorized environments for parallel training, creating custom environments with proper Gymnasium API implementation, and integrating with deep RL workflows. Includes comprehensive training templates, evaluation utilities, algorithm selection guidance (on-policy vs off-policy, continuous vs discrete actions), support for multi-input policies (dict observations), goal-conditioned learning with HER, and integration with TensorBoard for experiment tracking
- **statsmodels** - Statistical modeling and econometrics (OLS, GLM, logit/probit, ARIMA, time series forecasting, hypothesis testing, diagnostics)
- **TimesFM Forecasting** - Zero-shot time series forecasting with Google's TimesFM foundation model. Use for any univariate time series (sales, sensors, energy, vitals, weather) without training a custom model. Supports CSV/DataFrame/array inputs with point forecasts and prediction intervals. Includes a preflight system checker script to verify RAM/GPU before first use
- **Torch Geometric** - PyTorch Geometric (PyG 2.7.x) for graph neural networks on PyTorch 2.6+. Covers `Data`/`HeteroData`, 60+ conv layers (GCN, GAT, GraphSAGE, GIN), node/link/graph classification, heterogeneous graphs, neighbor sampling (`NeighborLoader`, `LinkNeighborLoader`), OGB and built-in datasets, custom dataset loading, GNN explainability, and scaling via DDP, Lightning, and `torch.compile`
- **Transformers** - Hugging Face Transformers 5.x (PyTorch) for NLP, computer vision, audio, and multimodal tasks. Provides 1M+ pre-trained Hub models via pipelines (text-classification, NER, QA, summarization, translation, text-generation, image-classification, object-detection, ASR, VQA), Trainer API fine-tuning with mixed precision and distributed training, flexible text generation (greedy, beam search, sampling), and Auto classes for architecture selection (BERT, GPT, T5, ViT, BART, etc.)
- **UMAP-learn** - Python implementation of Uniform Manifold Approximation and Projection (UMAP) for dimensionality reduction and manifold learning. Provides fast, scalable nonlinear dimensionality reduction that preserves both local and global structure of high-dimensional data. Key features include: support for both supervised and unsupervised dimensionality reduction, ability to handle mixed data types, integration with scikit-learn API, and efficient implementation using numba for performance. Produces low-dimensional embeddings (typically 2D or 3D) suitable for visualization and downstream analysis. Often outperforms t-SNE in preserving global structure while maintaining local neighborhoods. Use cases: data visualization, feature extraction, preprocessing for machine learning, single-cell data analysis, and exploratory data analysis of high-dimensional datasets
### Materials Science & Chemistry
- **Astropy** - Comprehensive Python library for astronomy and astrophysics providing core functionality for astronomical research and data analysis. Includes coordinate system transformations (ICRS, Galactic, FK5, AltAz), physical units and quantities with automatic dimensional consistency, FITS file operations (reading, writing, manipulating headers and data), cosmological calculations (luminosity distance, lookback time, Hubble parameter, Planck/WMAP models), precise time handling across multiple time scales (UTC, TAI, TT, TDB) and formats (JD, MJD, ISO), table operations with unit support (FITS, CSV, HDF5, VOTable), WCS transformations between pixel and world coordinates, astronomical constants, modeling framework, visualization tools, and statistical functions. Use for celestial coordinate transformations, unit conversions, FITS image/table processing, cosmological distance calculations, barycentric time corrections, catalog cross-matching, and astronomical data analysis
- **COBRApy** - Python package for constraint-based reconstruction and analysis (COBRA) of metabolic networks. Provides tools for building, manipulating, and analyzing genome-scale metabolic models (GEMs). Key features include: flux balance analysis (FBA) for predicting optimal metabolic fluxes, flux variability analysis (FVA), gene knockout simulations, pathway analysis, model validation, and integration with other COBRA Toolbox formats (SBML, JSON). Supports various optimization objectives (biomass production, ATP production, metabolite production), constraint handling (reaction bounds, gene-protein-reaction associations), and model comparison. Includes utilities for model construction, gap filling, and model refinement. Use cases: metabolic engineering, systems biology, biotechnology applications, understanding cellular metabolism, and predicting metabolic phenotypes
- **Pymatgen** - Python Materials Genomics (pymatgen) library for materials science computation and analysis. Provides comprehensive tools for crystal structure manipulation, phase diagram construction, electronic structure analysis, and materials property calculations. Key features include: structure objects with symmetry analysis, space group determination, structure matching and comparison, phase diagram generation from formation energies, band structure and density of states analysis, defect calculations, surface and interface analysis, and integration with DFT codes (VASP, Quantum ESPRESSO, ABINIT). Supports Materials Project database integration, structure file I/O (CIF, POSCAR, VASP), and high-throughput materials screening workflows. Use cases: materials discovery, crystal structure analysis, phase stability prediction, electronic structure calculations, and computational materials science research
### Engineering & Simulation
- **MATLAB/Octave** - Numerical computing environment for matrix operations, data analysis, visualization, and scientific computing. MATLAB is commercial software optimized for matrix operations, while GNU Octave is a free open-source alternative with high compatibility. Key features include: matrix operations (creation, manipulation, linear algebra), comprehensive mathematics (eigenvalues, SVD, FFT, ODEs, optimization, statistics), 2D/3D visualization (plot, surf, contour, with extensive customization), data import/export (CSV, Excel, MAT files, images), programming constructs (functions, scripts, control flow, OOP), signal processing (FFT, filtering, convolution), and Python integration (calling Python from MATLAB and vice versa). Supports vectorized operations for performance, anonymous functions, tables for mixed data types, and cell arrays for heterogeneous data. GNU Octave provides compatibility with most MATLAB scripts with minor differences (comments with #, block terminators like endif, compound operators like +=). Scripts can be executed via `matlab -nodisplay -r "run('script.m'); exit;"` or `octave script.m`. Use cases: numerical simulations, signal processing, image processing, control systems, statistical analysis, algorithm prototyping, data visualization, and any scientific computing task requiring matrix operations or numerical methods
- **FluidSim** - Object-oriented Python framework for high-performance computational fluid dynamics (CFD) simulations using pseudospectral methods with FFT. Provides solvers for periodic-domain equations including 2D/3D incompressible Navier-Stokes equations (with/without stratification), shallow water equations, and Föppl-von Kármán elastic plate equations. Key features include: Pythran/Transonic compilation for performance comparable to Fortran/C++, MPI parallelization for large-scale simulations, hierarchical parameter configuration with type safety, comprehensive output management (physical fields in HDF5, spatial means, energy/enstrophy spectra, spectral energy budgets), custom forcing mechanisms (time-correlated random forcing, proportional forcing, script-defined forcing), flexible initial conditions (noise, vortex, dipole, Taylor-Green, from file, in-script), online and offline visualization, and integration with ParaView/VisIt for 3D visualization. Supports workflow features including simulation restart/continuation, parametric studies with batch execution, cluster submission integration, and adaptive CFL-based time stepping. Use cases: 2D/3D turbulence studies with energy cascade analysis, stratified oceanic and atmospheric flows with buoyancy effects, geophysical flows with rotation (Coriolis effects), vortex dynamics and fundamental fluid mechanics research, high-resolution direct numerical simulation (DNS), parametric studies exploring parameter spaces, validation studies (Taylor-Green vortex), and any periodic-domain fluid dynamics research requiring HPC-grade performance with Python flexibility
- **SimPy** - Process-based discrete-event simulation framework for modeling systems with processes, queues, and resource contention (manufacturing, service operations, network traffic, logistics). Supports generator-based process definition, multiple resource types (Resource, PriorityResource, PreemptiveResource, Container, Store), event-driven scheduling, process interaction mechanisms (signaling, interruption, parallel/sequential execution), real-time simulation synchronized with wall-clock time, and comprehensive monitoring capabilities for utilization, wait times, and queue statistics
- **SymPy** - Symbolic mathematics in Python for exact computation using mathematical symbols rather than numerical approximations. Provides comprehensive support for symbolic algebra (simplification, expansion, factorization), calculus (derivatives, integrals, limits, series), equation solving (algebraic, differential, systems of equations), matrices and linear algebra (eigenvalues, decompositions, solving linear systems), physics (classical mechanics with Lagrangian/Hamiltonian formulations, quantum mechanics, vector analysis, units), number theory (primes, factorization, modular arithmetic, Diophantine equations), geometry (2D/3D analytic geometry), combinatorics (permutations, combinations, partitions, group theory), logic and sets, statistics (probability distributions, random variables), special functions (gamma, Bessel, orthogonal polynomials), and code generation (lambdify to NumPy/SciPy functions, C/Fortran code generation, LaTeX output for documentation). Emphasizes exact arithmetic using rational numbers and symbolic representations, supports assumptions for improved simplification (positive, real, integer), integrates seamlessly with NumPy/SciPy through lambdify for fast numerical evaluation, and enables symbolic-to-numeric pipelines for scientific computing workflows
### Data Analysis & Visualization
- **Dask** - Parallel and distributed computing for larger-than-RAM pandas/NumPy workflows (dask 2026.x; Python 3.10+). Provides lazy DataFrames (expression-based query planning since 2025.1), Arrays, Bags, delayed tasks, and Futures via dask.distributed. Scales from multi-core laptops to clusters with a diagnostic dashboard. PyArrow 16+ required for DataFrame I/O; cloud paths need s3fs/gcsfs. Use for parallel file processing, out-of-core ETL, and scaling existing pandas/NumPy code. For single-machine out-of-core analytics without a cluster, consider vaex; for in-memory speed use polars
- **GeoMaster** - Comprehensive geospatial science skill covering remote sensing, GIS, spatial analysis, machine learning for earth observation, and 30+ scientific domains. Supports satellite imagery processing (Sentinel, Landsat, MODIS, SAR, hyperspectral), vector and raster data operations, spatial statistics, point cloud processing, network analysis, cloud-native workflows (STAC, COG, Planetary Computer), and 8 programming languages (Python, R, Julia, JavaScript, C++, Java, Go, Rust) with 500+ code examples. Use for remote sensing workflows, GIS analysis, spatial ML, Earth observation data processing, terrain analysis, hydrological modeling, marine spatial analysis, atmospheric science, and any geospatial computation task
- **GeoPandas** - Python library extending pandas for working with geospatial vector data including shapefiles, GeoJSON, and GeoPackage files. Provides GeoDataFrame and GeoSeries data structures combining geometric data with tabular attributes for spatial analysis. Key features include: reading/writing spatial file formats (Shapefile, GeoJSON, GeoPackage, PostGIS, Parquet) with Arrow acceleration for 2-4x faster I/O, geometric operations (buffer, simplify, centroid, convex hull, affine transformations) through Shapely integration, spatial analysis (spatial joins with predicates like intersects/contains/within, nearest neighbor joins, overlay operations for union/intersection/difference, dissolve for aggregation, clipping), coordinate reference system (CRS) management (setting CRS, reprojecting between coordinate systems, UTM estimation), and visualization (static choropleth maps with matplotlib, interactive maps with folium, multi-layer mapping, classification schemes with mapclassify). Supports spatial indexing for performance, filtering during read operations (bbox, mask, SQL WHERE), and integration with cartopy for cartographic projections. Use cases: spatial data manipulation, buffer analysis, spatial joins between datasets, dissolving boundaries, calculating areas/distances in projected CRS, reprojecting coordinate systems, creating choropleth maps, converting between spatial file formats, PostGIS database integration, and geospatial data analysis workflows
- **Matplotlib** - Comprehensive Python plotting library for creating publication-quality static, animated, and interactive visualizations. Provides extensive customization options for creating figures, subplots, axes, and annotations. Key features include: support for multiple plot types (line, scatter, bar, histogram, contour, 3D, and many more), extensive customization (colors, fonts, styles, layouts), multiple backends (PNG, PDF, SVG, interactive backends), LaTeX integration for mathematical notation, and integration with NumPy and pandas. Includes specialized modules (pyplot for MATLAB-like interface, artist layer for fine-grained control, backend layer for rendering). Supports complex multi-panel figures, color maps, legends, and annotations. Use cases: scientific figure creation, data visualization, exploratory data analysis, publication graphics, and any application requiring high-quality plots
- **NetworkX** - Comprehensive toolkit for creating, analyzing, and visualizing complex networks and graphs. Supports four graph types (Graph, DiGraph, MultiGraph, MultiDiGraph) with nodes as any hashable objects and rich edge attributes. Provides 100+ algorithms including shortest paths (Dijkstra, Bellman-Ford, A*), centrality measures (degree, betweenness, closeness, eigenvector, PageRank), clustering (coefficients, triangles, transitivity), community detection (modularity-based, label propagation, Girvan-Newman), connectivity analysis (components, cuts, flows), tree algorithms (MST, spanning trees), matching, graph coloring, isomorphism, and traversal (DFS, BFS). Includes 50+ graph generators for classic (complete, cycle, wheel), random (Erdős-Rényi, Barabási-Albert, Watts-Strogatz, stochastic block model), lattice (grid, hexagonal, hypercube), and specialized networks. Supports I/O across formats (edge lists, GraphML, GML, JSON, Pajek, GEXF, DOT) with Pandas/NumPy/SciPy integration. Visualization capabilities include 8+ layout algorithms (spring/force-directed, circular, spectral, Kamada-Kawai), customizable node/edge appearance, interactive visualizations with Plotly/PyVis, and publication-quality figure generation. Use cases: social network analysis, biological networks (protein-protein interactions, gene regulatory networks, metabolic pathways), transportation systems, citation networks, knowledge graphs, web structure analysis, infrastructure networks, and any domain involving pairwise relationships requiring structural analysis or graph-based modeling
- **Polars** - High-performance DataFrame library written in Rust with Python bindings for fast data manipulation, ETL, analytics, and pandas migration. Provides expression-based transformations, lazy query optimization, automatic parallel execution, streaming out-of-core processing, Arrow interoperability, and optional GPU execution. Supports common data sources and formats including CSV, Parquet, JSON/NDJSON, Excel, Arrow IPC, cloud object storage, databases, and BigQuery. Use cases: large-scale data processing, memory-conscious analytical pipelines, feature engineering, and high-performance DataFrame workflows
- **Seaborn** - Statistical data visualization with dataset-oriented interface, automatic confidence intervals, publication-quality themes, colorblind-safe palettes, and comprehensive support for exploratory analysis, distribution comparisons, correlation matrices, regression plots, and multi-panel figures
- **Vaex** - High-performance Python library for lazy, out-of-core DataFrames to process and visualize tabular datasets larger than available RAM. Processes over a billion rows per second through memory-mapped files (HDF5, Apache Arrow), lazy evaluation, and virtual columns (zero memory overhead). Provides instant file opening, efficient aggregations across billions of rows, interactive visualizations without sampling, machine learning pipelines with transformers (scalers, encoders, PCA), and seamless integration with pandas/NumPy/Arrow. Includes comprehensive ML framework (vaex.ml) with feature scaling, categorical encoding, dimensionality reduction, and integration with scikit-learn/XGBoost/LightGBM/CatBoost. Supports distributed computing via Dask, asynchronous operations, and state management for production deployment. Use cases: processing gigabyte to terabyte datasets, fast statistical aggregations on massive data, visualizing billion-row datasets, ML pipelines on big data, converting between data formats, and working with astronomical, financial, or scientific large-scale datasets
### Phylogenetics & Evolutionary Biology
- **ETE Toolkit** - Python library for phylogenetic tree manipulation, visualization, and analysis. Provides comprehensive tools for working with phylogenetic trees including tree construction, manipulation (pruning, collapsing, rooting), tree comparison (Robinson-Foulds distance, tree reconciliation), annotation (node colors, labels, branch styles), and publication-quality visualization. Key features include: support for multiple tree formats (Newick, Nexus, PhyloXML), integration with phylogenetic software (PhyML, RAxML, FastTree), tree annotation with metadata, interactive tree visualization, and export to various image formats (PNG, PDF, SVG). Supports species trees, gene trees, and reconciliation analysis. Use cases: phylogenetic analysis, tree visualization, evolutionary biology research, comparative genomics, and teaching phylogenetics
- **Phylogenetics** - Build and analyze phylogenetic trees using MAFFT (multiple alignment), IQ-TREE 2 (maximum likelihood), and FastTree (fast NJ/ML). Visualize with ETE3 or FigTree. Use for evolutionary analysis, microbial genomics, viral phylodynamics, protein family analysis, and molecular clock studies
### Multi-omics & AI Agent Frameworks
- **Pi Agent** - Build with and use Pi, the minimal terminal coding harness. Covers installing and configuring Pi, authenticating providers, adding custom models and providers, creating Pi skills/extensions/packages/themes/prompt templates, embedding Pi through the Node/TypeScript SDK, integrating over RPC or JSON event streams, parsing session JSONL files, and building custom TUI components. Use cases: building custom agent UIs, exposing Pi from another process or language, wiring private model gateways, packaging reusable Pi workflows, and helping agents operate Pi as a platform rather than only a CLI
- **HypoGeniC** - Automated hypothesis generation and testing using large language models to accelerate scientific discovery. Provides three frameworks: HypoGeniC (data-driven hypothesis generation from observational data), HypoRefine (synergistic approach combining literature insights with empirical patterns through an agentic system), and Union methods (mechanistic combination of literature and data-driven hypotheses). Features iterative refinement that improves hypotheses by learning from challenging examples, Redis caching for API cost reduction, and customizable YAML-based prompt templates. Includes command-line tools for generation (hypogenic_generation) and testing (hypogenic_inference). Research applications have demonstrated 14.19% accuracy improvement in AI-content detection and 7.44% in deception detection. Use cases: deception detection in reviews, AI-generated content identification, mental stress detection, exploratory research without existing literature, hypothesis-driven analysis in novel domains, and systematic exploration of competing explanations
### Autonomous Research & Optimization Frameworks
- **Arbor** - Autonomous Optimization (AO) via Hypothesis Tree Refinement (HTR), adapted from the Arbor framework (Jin et al., 2026; RUC-NLPIR/Arbor). Iteratively improves a real artifact — a codebase, model-training recipe, agent/search harness, data-synthesis pipeline, or prompt — against a measurable objective over many experiment cycles, without overfitting to the feedback signal. The agent acts as a long-lived **coordinator** that maintains a persistent **hypothesis tree** (each node binds a falsifiable hypothesis, a distilled reusable insight, and a pointer to the artifact version that realizes it) and runs the six-step loop Observe → Ideate → Select → Dispatch → Backpropagate → Decide; short-lived **executor** subagents each test one hypothesis in an isolated git worktree and return a dev score, factual result, distilled insight, and branch reference. A **held-out merge gate** promotes a candidate to the best artifact only when its improvement transfers to a test evaluator the search never optimized against, separating exploratory dev gains from verified progress. Insights are propagated up the tree so local findings become direction-level constraints and global priors, and falsified branches are pruned with their reasons recorded as negative constraints. Ships a `tree.py` state manager (init/observe/add-node/set-evidence/propagate/prune/merge/status/validate) that keeps the tree consistent and auditable, plus reference docs on the HTR methodology, the executor brief, the final-report template, and how to run the upstream `arbor` CLI instead. Use cases: "improve my model's eval score in fewer steps", optimizing an agent or search harness for higher pass rate/accuracy, tuning a data-generation pipeline judged by downstream model behavior, MLE-bench / Kaggle-style "improve the submission" tasks, and any long-horizon "make this artifact better and don't just memorize the dev set" workflow that needs structured, branching exploration with an audit trail
### Scientific Communication & Publishing
- **BGPT Paper Search** - Search scientific papers and retrieve structured experimental data extracted from full-text studies via the BGPT MCP server. Returns 25+ fields per paper including methods, results, sample sizes, quality scores, and conclusions. Use for literature reviews, evidence synthesis, and finding experimental details not available in abstracts alone
- **pyzotero** - Python client for the Zotero Web API v3. Programmatically manage Zotero reference libraries: retrieve, create, update, and delete items, collections, tags, and attachments. Export citations as BibTeX, CSL-JSON, and formatted bibliography HTML. Supports user and group libraries, local mode for offline access, paginated retrieval with `everything()`, full-text content indexing, saved search management, and file upload/download. Optional CLI and built-in MCP server (pyzotero 1.12+) for searching local Zotero 7 libraries including full-text PDF search and Semantic Scholar integration. Use cases: building research automation pipelines that integrate with Zotero, bulk importing references, exporting bibliographies programmatically, managing large reference collections, syncing library metadata, enriching bibliographic data, and connecting LLM agents to a local Zotero library.
- **Citation Management** - Comprehensive citation management for academic research. Search Google Scholar and PubMed for papers, extract accurate metadata from multiple sources (CrossRef, PubMed, arXiv), validate citations, and generate properly formatted BibTeX entries. Features include converting DOIs, PMIDs, or arXiv IDs to BibTeX, cleaning and formatting bibliography files, finding highly cited papers, checking for duplicates, and ensuring consistent citation formatting. Use cases: building bibliographies for manuscripts, verifying citation accuracy, citation deduplication, and maintaining reference databases
- **Generate Image** - AI-powered image generation and editing for scientific illustrations, schematics, and visualizations using OpenRouter's image generation models. Supports multiple models including google/gemini-3-pro-image-preview (high quality, recommended default) and black-forest-labs/flux.2-pro (fast, high quality). Key features include: text-to-image generation from detailed prompts, image editing capabilities (modify existing images with natural language instructions), automatic base64 encoding/decoding, PNG output with configurable paths, and comprehensive error handling. Requires OpenRouter API key (via .env file or environment variable). Use cases: generating scientific diagrams and illustrations, creating publication-quality figures, editing existing images (changing colors, adding elements, removing backgrounds), producing schematics for papers and presentations, visualizing experimental setups, creating graphical abstracts, and generating conceptual illustrations for scientific communication
- **Infographics** - Create professional infographics using Nano Banana Pro AI with smart iterative refinement. Uses Gemini 3 Pro for quality review. Integrates research-lookup and web search for accurate data. Supports 10 infographic types, 8 industry styles, and colorblind-safe palettes
- **LaTeX Posters** - Create professional research posters in LaTeX using beamerposter, tikzposter, or baposter. Support for conference presentations, academic posters, and scientific communication with layout design, color schemes, multi-column formats, figure integration, and poster-specific best practices. Features compliance with conference size requirements (A0, A1, 36×48"), complex multi-column layouts, and integration of figures, tables, equations, and citations. Use cases: conference poster sessions, thesis defenses, symposia presentations, and research group templates
- **Market Research Reports** - Generate comprehensive market research reports (50+ pages) in the style of top consulting firms (McKinsey, BCG, Gartner). Features professional LaTeX formatting, extensive visual generation, deep integration with research-lookup for data gathering, and multi-framework strategic analysis including Porter's Five Forces, PESTLE, SWOT, TAM/SAM/SOM, and BCG Matrix. Use cases: investment decisions, strategic planning, competitive landscape analysis, market sizing, and market entry evaluation
- **PPTX Posters** - Create professional research posters using PowerPoint/HTML formats for researchers who prefer WYSIWYG tools over LaTeX. Features design principles, layout templates, quality checklists, and export guidance for poster sessions. Use cases: conference posters when LaTeX is not preferred, quick poster creation, and collaborative poster design
- **Scientific Schematics** - Create publication-quality scientific diagrams using Nano Banana Pro AI with smart iterative refinement. Uses Gemini 3 Pro for quality review with document-type-specific thresholds (journal: 8.5/10, conference: 8.0/10, poster: 7.0/10). Specializes in neural network architectures, system diagrams, flowcharts, biological pathways, and complex scientific visualizations. Features natural language input, automatic quality assessment, and publication-ready output. Use cases: creating figures for papers, generating workflow diagrams, visualizing experimental designs, and producing graphical abstracts
- **Scientific Slides** - Build slide decks and presentations for research talks using PowerPoint and LaTeX Beamer. Features slide structure, design templates, timing guidance, and visual validation. Emphasizes visual engagement with minimal text, research-backed content with proper citations, and story-driven narrative. Use cases: conference presentations, academic seminars, thesis defenses, grant pitches, and professional talks
- **Venue Templates** - Access comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). Provides ready-to-use templates and detailed specifications for successful academic submissions. Use cases: manuscript preparation, conference papers, research posters, and grant proposals with venue-specific formatting
### Document Processing & Conversion
- **DOCX** - Create, read, edit, and manipulate Word documents (.docx files). Supports professional documents with formatting like tables of contents, headings, page numbers, letterheads, find-and-replace, tracked changes, comments, and image insertion. Use for reports, memos, letters, templates, and any Word document deliverable
- **MarkItDown** - Python utility for converting 20+ file formats to Markdown optimized for LLM processing. Converts Office documents (PDF, DOCX, PPTX, XLSX), images with OCR, audio with transcription, web content (HTML, YouTube transcripts, EPUB), and structured data (CSV, JSON, XML) while preserving document structure (headings, lists, tables, hyperlinks). Key features include: Azure Document Intelligence integration for enhanced PDF table extraction, LLM-powered image descriptions using GPT-4o, batch processing with ZIP archive support, modular installation for specific formats, streaming approach without temporary files, and plugin system for custom converters. Supports Python 3.10+. Use cases: preparing documents for RAG systems, extracting text from PDFs and Office files, transcribing audio to text, performing OCR on images and scanned documents, converting YouTube videos to searchable text, processing HTML and EPUB books, converting structured data to readable format, document analysis pipelines, and LLM training data preparation
- **LiteParse** - Fast, local-first document parser (Rust core, Python/Node bindings) for layout-aware text extraction with bounding boxes—no cloud API required. Parses PDFs natively; converts Office and image formats via LibreOffice/ImageMagick when installed. Outputs layout-preserved plain text or structured JSON with per-page `text_items` (coordinates, font metadata, OCR confidence). Built-in Tesseract OCR with optional HTTP OCR servers (EasyOCR/PaddleOCR-compatible API), page subsets, encrypted PDFs, stdin/bytes parsing, and PNG page screenshots for multimodal agents. CLI (`lit parse`, `lit batch-parse`, `lit screenshot`) and Python API (`LiteParse`, `search_items`). Targets liteparse 2.0.0, Python 3.10+. Use when you need spatial grounding for RAG, batch literature ingestion, or agent vision—not for Markdown (MarkItDown) or PDF merge/split/forms (pdf skill)
- **Markdown & Mermaid Writing** - Comprehensive markdown and Mermaid diagram writing skill for creating scientific documents, reports, analyses, and visualizations. Establishes text-based diagrams as the default documentation standard with full style guides, 24 diagram type references, and 9 document templates
- **PDF** - Read, create, combine, split, rotate, watermark, encrypt/decrypt, and manipulate PDF files. Supports extracting text/tables, filling forms, OCR on scanned PDFs, image extraction, and format conversion
- **PPTX** - Create, read, edit, and manipulate PowerPoint presentations (.pptx files). Use for slide decks, pitch decks, extracting text from presentations, editing existing slides, combining or splitting presentations, and working with templates, layouts, speaker notes, and comments
- **XLSX** - Spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization
### Laboratory Automation & Equipment Control
- **PyLabRobot** - Hardware-agnostic, pure Python SDK for automated and autonomous laboratories. Provides unified interface for controlling liquid handling robots (Hamilton STAR/STARlet, Opentrons OT-2, Tecan EVO), plate readers (BMG CLARIOstar), heater shakers, incubators, centrifuges, pumps, and scales. Key features include: modular resource management system for plates, tips, and containers with hierarchical deck layouts and JSON serialization; comprehensive liquid handling operations (aspirate, dispense, transfer, serial dilutions, plate replication) with automatic tip and volume tracking; backend abstraction enabling hardware-agnostic protocols that work across different robots; ChatterboxBackend for protocol simulation and testing without hardware; browser-based visualizer for real-time 3D deck state visualization; cross-platform support (Windows, macOS, Linux, Raspberry Pi); and integration capabilities for multi-device workflows combining liquid handlers, analytical equipment, and material handling devices. Use cases: automated sample preparation, high-throughput screening, serial dilution protocols, plate reading workflows, laboratory protocol development and validation, robotic liquid handling automation, and reproducible laboratory automation with state tracking and persistence
### Tool Discovery & Computational Resources
- **Autoskill** - Observe the user's screen via the local screenpipe daemon, detect repeated research workflows, match them against existing scientific-agent-skills, and draft new skills (or composition recipes that chain existing ones) for patterns not yet covered. Use when the user asks to analyze their recent work and propose skills based on what they actually do. Requires the screenpipe daemon running locally on port 3030; all detection runs locally and only redacted cluster summaries reach the LLM
- **Get Available Resources** - Detect available computational resources and generate strategic recommendations for scientific computing tasks at the start of any computationally intensive scientific task. Automatically identifies CPU capabilities, GPU availability (NVIDIA CUDA, AMD ROCm, Apple Silicon Metal), memory constraints, and disk space. Creates JSON file with resource information and recommendations for parallel processing (joblib, multiprocessing), out-of-core computing (Dask, Zarr), GPU acceleration (PyTorch, JAX), or memory-efficient strategies. Use cases: determining optimal computational approaches before data analysis, model training, or large file operations
### Research Methodology & Proposal Writing
- **Paperzilla** - Chat with your agent about Paperzilla projects, recommendations, and canonical papers. Use for recent project recommendations, recommendation triage, canonical paper details, markdown-based summaries, relevance-to-my-research discussions, recommendation feedback, JSON export, and Atom feed URLs
- **Paper Lookup** - Search 10 academic paper databases via their REST APIs to find research papers, preprints, and scholarly articles. Covers biomedical literature (PubMed, PMC full text), preprint servers (bioRxiv, medRxiv, arXiv), multidisciplinary indexes (OpenAlex, Crossref, Semantic Scholar), open access aggregators (CORE, Unpaywall). Use for searching research papers, finding citations, looking up articles by DOI or PMID, retrieving abstracts or full text, checking open access availability, exploring citation graphs, and systematic literature searches
- **Research Grants** - Write competitive research proposals for NSF, NIH, DOE, DARPA, and Taiwan NSTC. Features agency-specific formatting, review criteria understanding, budget preparation, broader impacts statements, significance narratives, innovation sections, and compliance with submission requirements (including PAPPG 24-1 and current NIH salary-cap guidance). Covers project descriptions, specific aims, technical narratives, milestone plans, budget justifications, and biosketches. Optional figures via the scientific-schematics skill. Use cases: federal grant applications, resubmissions with reviewer response, multi-institutional collaborations, and preliminary data sections
- **Research Lookup** - Look up current research information using Perplexity's Sonar Pro Search or Sonar Reasoning Pro models through OpenRouter. Intelligently selects models based on query complexity. Provides access to current academic literature, recent studies, technical documentation, and general research information with proper citations. Use cases: finding latest research, literature verification, gathering background research, finding citation sources, and staying current with emerging trends
- **Scholar Evaluation** - Apply the ScholarEval framework to systematically evaluate scholarly and research work. Provides structured evaluation methodology based on peer-reviewed research assessment criteria for analyzing academic papers, research proposals, literature reviews, and scholarly writing across multiple quality dimensions. Use cases: evaluating research papers for quality and rigor, assessing methodology design, scoring data analysis approaches, benchmarking research quality, and assessing publication readiness
### Regulatory & Standards Compliance
- **ISO 13485 Certification** - Comprehensive toolkit for preparing ISO 13485:2016 certification documentation for medical device Quality Management Systems. Provides gap analysis of existing documentation, templates for all mandatory documents, compliance checklists, and step-by-step documentation creation. Covers 31 required procedures including Quality Manuals, Medical Device Files, and work instructions. Use cases: starting ISO 13485 certification process, conducting gap analysis, creating or updating QMS documentation, preparing for certification audits, transitioning from FDA QSR to QMSR, and harmonizing with EU MDR requirements
## Scientific Thinking & Analysis
### Analysis & Methodology
- **Experimental Design** - Design studies before data collection: choosing a design, randomization (simple, block, stratified, cluster), blocking and stratification, factorial and fractional-factorial designs (DOE), screening (Plackett-Burman), response-surface designs (central composite, Box-Behnken), Latin hypercube, crossover/repeated-measures/split-plot/Latin-square, sequential and adaptive designs, and avoiding pseudoreplication. Includes seeded allocation-schedule and DOE-matrix generators (numpy, pandas, pyDOE3). For sample size see Statistical Power; for analysis see Statistical Analysis
- **Exploratory Data Analysis** - Comprehensive EDA toolkit with automated statistics, visualizations, and insights for any tabular dataset
- **Hypothesis Generation** - Structured frameworks for generating and evaluating scientific hypotheses
- **Literature Review** - Systematic literature search and review toolkit with support for multiple scientific databases (PubMed, bioRxiv, Google Scholar), citation management with multiple citation styles (APA, AMA, Vancouver, Chicago, IEEE, Nature, Science), citation verification and deduplication, search strategies (Boolean operators, MeSH terms, field tags), PDF report generation with formatted references, and comprehensive templates for conducting systematic reviews following PRISMA guidelines
- **Peer Review** - Comprehensive toolkit for conducting high-quality scientific peer review with structured evaluation of methodology, statistics, reproducibility, ethics, and presentation across all scientific disciplines
- **Scientific Brainstorming** - Conversational brainstorming partner for generating novel research ideas, exploring connections, challenging assumptions, and developing creative approaches through structured ideation workflows
- **Scientific Critical Thinking** - Tools and approaches for rigorous scientific reasoning and evaluation
- **Scientific Visualization** - Best practices and templates for creating publication-quality scientific figures with matplotlib and seaborn, including statistical plots with automatic confidence intervals, colorblind-safe palettes, multi-panel figures, heatmaps, and journal-specific formatting
- **Scientific Writing** - Comprehensive toolkit for writing, structuring, and formatting scientific research papers using IMRAD format, multiple citation styles (APA, AMA, Vancouver, Chicago, IEEE), reporting guidelines (CONSORT, STROBE, PRISMA), effective figures and tables, field-specific terminology, venue-specific structure expectations, and core writing principles for clarity, conciseness, and accuracy across all scientific disciplines
- **Statistical Analysis** - Guided hypothesis testing, assumption checks, effect sizes, power analysis, and APA reporting (Pingouin, SciPy, statsmodels, PyMC). For dedicated model APIs see statsmodels and pymc skills; for in-depth sample-size/power planning see Statistical Power; for designing the study see Experimental Design
- **Statistical Power** - Sample-size and statistical power analysis for planning studies: required n, minimum detectable effect (MDE), and power curves for t-tests, ANOVA, proportions, correlation, chi-square, and regression via a unified closed-form interface, plus a Monte Carlo harness for designs with no formula (logistic/Poisson regression, mixed models, cluster-randomized trials, survival). Covers effect-size choice (SESOI), and adjustments for multiplicity, dropout, and clustering (statsmodels, scipy, pingouin, numpy). For laying out the design see Experimental Design; for post-collection analysis see Statistical Analysis
### Decision & Scenario Analysis
- **Consciousness Council** - Run a multi-perspective Mind Council deliberation on any question, decision, or creative challenge. Use for diverse viewpoints, tough decisions, devil's advocate analysis, exploring problems from multiple angles, or when facing dilemmas, trade-offs, and complex choices with no obvious answer
- **DHDNA Profiler** - Extract cognitive patterns and thinking fingerprints from any text. Analyze how someone thinks, understand cognitive style, profile writing or speech patterns, compare thinking styles between people, and gain deeper insight into an author's reasoning patterns, decision-making style, or cognitive signature
- **What-If Oracle** - Run structured What-If scenario analysis with 46 branch possibility exploration (best, likely, worst, wild card). Use for speculative what-if questions, strategic forks, contingency planning, and stress-testing decisions before committing
### Web Search & Information Retrieval
- **Exa Search** - Web search and URL content extraction via the Exa API. Use for high-quality web search tuned for scientific and technical content, scholarly filtering via `category="research paper"` plus academic domain allowlists, and batch URL extraction
- **Parallel Web** - Search the web, extract URL content, and run deep research using the Parallel Chat API and Extract API. Use for web searches, research queries, and general information gathering with synthesized summaries and citations
+16
View File
@@ -0,0 +1,16 @@
[project]
name = "scientific-agent-skills"
version = "2.53.0"
description = "A set of ready to use Agent Skills for research, science, engineering, analysis, finance and writing."
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"cisco-ai-skill-scanner>=2.0.8",
"firecrawl-py>=4.9.0",
"python-dotenv>=1.0.0",
]
[dependency-groups]
dev = [
"skills-ref @ git+https://github.com/agentskills/agentskills.git#subdirectory=skills-ref",
]
+244
View File
@@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""Scan a subset of skills (those changed in a pull request) and emit a PR comment.
Usage:
python scan_pr_skills.py [--output FILE] [--fail-on SEVERITY] SKILL_DIR ...
Each ``SKILL_DIR`` should be a directory containing a ``SKILL.md``. Directories
without a ``SKILL.md`` (e.g. deleted skills) are skipped gracefully.
The script writes a markdown report intended to be posted as a sticky comment
on the pull request. It exits non-zero when any scanned skill has a finding at
``--fail-on`` severity or higher (default: ``CRITICAL``).
"""
from __future__ import annotations
import argparse
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from dotenv import load_dotenv
from skill_scanner.core.loader import SkillLoadError
from skill_scanner.core.models import Report
from scan_skills import build_scanner, severity_badge
load_dotenv()
SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "SAFE"]
COMMENT_MARKER = "<!-- skill-security-scan -->"
def _sev_str(obj) -> str:
sev = getattr(obj, "max_severity", None) or getattr(obj, "severity", None)
if sev is None:
return "SAFE"
return sev.value if hasattr(sev, "value") else str(sev)
def scan_skill_dirs(scanner, skill_dirs: list[Path]) -> Report:
report = Report()
loaded_skills = []
for skill_dir in skill_dirs:
name = skill_dir.name
print(f" Scanning {name} ...", end="", flush=True)
t0 = time.time()
try:
skill = scanner.loader.load_skill(skill_dir)
result = scanner._scan_single_skill(skill, skill_dir)
report.add_scan_result(result)
loaded_skills.append(skill)
elapsed = time.time() - t0
n = len(result.findings)
tag = severity_badge(_sev_str(result))
print(f" {tag}{n} finding{'s' if n != 1 else ''} ({elapsed:.1f}s)")
except SkillLoadError as exc:
elapsed = time.time() - t0
print(f" ⚠️ SKIP ({exc}) ({elapsed:.1f}s)")
report.skills_skipped.append({"skill": str(skill_dir), "reason": str(exc)})
except Exception as exc: # pragma: no cover - defensive
elapsed = time.time() - t0
print(f" ❌ ERROR ({exc}) ({elapsed:.1f}s)")
report.skills_skipped.append({"skill": str(skill_dir), "reason": str(exc)})
if len(loaded_skills) > 1:
print("\n Running cross-skill overlap analysis ...", end="", flush=True)
t0 = time.time()
try:
from skill_scanner.core.analyzers.cross_skill_scanner import CrossSkillScanner
overlap = scanner._check_description_overlap(loaded_skills) or []
cross = CrossSkillScanner().analyze_skill_set(loaded_skills) or []
all_cross = [*overlap, *cross]
if scanner.policy.disabled_rules:
all_cross = [f for f in all_cross if f.rule_id not in scanner.policy.disabled_rules]
if all_cross:
scanner._apply_severity_overrides(all_cross)
report.add_cross_skill_findings(all_cross)
elapsed = time.time() - t0
print(f" {len(all_cross)} finding{'s' if len(all_cross) != 1 else ''} ({elapsed:.1f}s)")
except Exception as exc: # pragma: no cover - defensive
print(f" error: {exc}")
return report
def _loc(finding) -> str | None:
if not finding.file_path:
return None
loc = finding.file_path
if finding.line_number:
loc += f":{finding.line_number}"
return loc
def format_comment(report: Report, scanned_dirs: list[Path]) -> str:
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
lines: list[str] = [COMMENT_MARKER, "## 🛡️ Skill Security Scan", ""]
lines.append(f"_Generated at {now}_")
lines.append("")
if not scanned_dirs:
lines.append("No skill directories with a `SKILL.md` were changed in this PR — nothing to scan.")
return "\n".join(lines)
lines.append(f"**Skills scanned:** {report.total_skills_scanned} ")
lines.append(f"**Total findings:** {report.total_findings} ")
lines.append(
f"**Critical:** {report.critical_count} | "
f"**High:** {report.high_count} | "
f"**Safe:** {report.safe_count}/{report.total_skills_scanned}"
)
lines.append("")
if report.scan_results:
lines.append("| Skill | Severity | Findings | Safe |")
lines.append("|-------|----------|----------|------|")
sorted_results = sorted(
report.scan_results,
key=lambda r: SEVERITY_ORDER.index(_sev_str(r)),
)
for result in sorted_results:
sev = _sev_str(result)
safe = "" if result.is_safe else ""
lines.append(
f"| `{result.skill_name}` | {severity_badge(sev)} | {len(result.findings)} | {safe} |"
)
lines.append("")
flagged = [r for r in sorted_results if r.findings]
if flagged:
lines.append("### Findings")
lines.append("")
for result in flagged:
sev = _sev_str(result)
n = len(result.findings)
lines.append(
f"<details><summary><code>{result.skill_name}</code> — "
f"{severity_badge(sev)} ({n} finding{'s' if n != 1 else ''})</summary>"
)
lines.append("")
for finding in result.findings:
fsev = _sev_str(finding)
lines.append(
f"- **{severity_badge(fsev)}** `{finding.rule_id}` — {finding.title}"
)
if finding.description:
lines.append(f" > {finding.description}")
loc = _loc(finding)
if loc:
lines.append(f" > File: `{loc}`")
if finding.remediation:
lines.append(f" > **Remediation:** {finding.remediation}")
lines.append("")
lines.append("</details>")
lines.append("")
if report.skills_skipped:
lines.append("### Skipped")
lines.append("")
for entry in report.skills_skipped:
lines.append(f"- `{entry['skill']}`: {entry['reason']}")
lines.append("")
return "\n".join(lines)
def _should_block(report: Report, fail_on: str) -> tuple[bool, str | None]:
if fail_on == "NEVER":
return False, None
blocking_ranks = SEVERITY_ORDER[: SEVERITY_ORDER.index(fail_on) + 1]
for result in report.scan_results:
sev = _sev_str(result)
if sev in blocking_ranks:
return True, f"{sev} finding(s) in {result.skill_name}"
return False, None
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("skill_dirs", nargs="*", help="Skill directories to scan")
parser.add_argument(
"--output",
default="pr_scan_comment.md",
help="Path to write the PR comment markdown (default: pr_scan_comment.md)",
)
parser.add_argument(
"--fail-on",
default="CRITICAL",
choices=["CRITICAL", "HIGH", "MEDIUM", "LOW", "NEVER"],
help="Exit non-zero if any scanned skill has a finding at this severity or higher",
)
args = parser.parse_args()
scan_targets: list[Path] = []
for raw in args.skill_dirs:
p = Path(raw)
if not p.is_dir():
print(f" SKIP {raw} (not a directory)")
continue
if not (p / "SKILL.md").exists():
print(f" SKIP {raw} (no SKILL.md)")
continue
scan_targets.append(p)
if not scan_targets:
print("No skill directories to scan — writing no-op comment.")
md = format_comment(Report(), [])
Path(args.output).write_text(md)
return 0
print("Building scanner (LLM + behavioral + trigger + balanced policy)...")
scanner = build_scanner()
print(f"Analyzers: {scanner.list_analyzers()}\n")
print(f"Scanning {len(scan_targets)} skill(s):")
for d in scan_targets:
print(f" - {d}")
print()
report = scan_skill_dirs(scanner, scan_targets)
print(
f"\nResults: {report.total_skills_scanned} skills, {report.total_findings} findings "
f"(Critical: {report.critical_count} High: {report.high_count} Safe: {report.safe_count})"
)
md = format_comment(report, scan_targets)
Path(args.output).write_text(md)
print(f"Comment written to {args.output}")
blocked, reason = _should_block(report, args.fail_on)
if blocked:
print(f"\n❌ Blocking: {reason} (--fail-on {args.fail_on})")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+224
View File
@@ -0,0 +1,224 @@
#!/usr/bin/env python3
"""Scan all skills for security issues and produce SECURITY.md."""
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from dotenv import load_dotenv
from skill_scanner import SkillScanner
from skill_scanner.core.analyzers import (
BehavioralAnalyzer,
LLMAnalyzer,
TriggerAnalyzer,
)
from skill_scanner.core.loader import SkillLoadError
from skill_scanner.core.models import Report
from skill_scanner.core.scan_policy import ScanPolicy
load_dotenv()
SKILLS_DIR = "skills"
OUTPUT_FILE = "SECURITY.md"
def build_scanner() -> SkillScanner:
policy = ScanPolicy.from_preset("balanced")
policy.llm_analysis.max_instruction_body_chars = 75_000
policy.llm_analysis.max_referenced_file_chars = 75_000
policy.llm_analysis.max_code_file_chars = 75_000
policy.llm_analysis.max_total_prompt_chars = 500_000
llm_model = os.getenv("SKILL_SCANNER_LLM_MODEL", "anthropic/claude-sonnet-4-6")
llm_key = os.getenv("SKILL_SCANNER_LLM_API_KEY")
scanner = SkillScanner(
analyzers=[
BehavioralAnalyzer(),
TriggerAnalyzer(),
LLMAnalyzer(model=llm_model, api_key=llm_key, policy=policy),
],
policy=policy,
)
return scanner
def severity_badge(sev: str) -> str:
icons = {
"CRITICAL": "🔴",
"HIGH": "🟠",
"MEDIUM": "🟡",
"LOW": "🔵",
"INFO": "",
"SAFE": "🟢",
}
return f"{icons.get(sev, '')} {sev}"
def generate_report(report) -> str:
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
lines: list[str] = []
lines.append("# Security Scan Report")
lines.append("")
lines.append(f"**Generated:** {now} ")
lines.append(f"**Skills scanned:** {report.total_skills_scanned} ")
lines.append(f"**Total findings:** {report.total_findings} ")
lines.append(f"**Critical:** {report.critical_count} | **High:** {report.high_count} | **Safe skills:** {report.safe_count}/{report.total_skills_scanned}")
lines.append("")
# Summary table
lines.append("## Summary")
lines.append("")
lines.append("| Skill | Severity | Findings | Safe | Duration |")
lines.append("|-------|----------|----------|------|----------|")
sorted_results = sorted(
report.scan_results,
key=lambda r: ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "SAFE"].index(
r.max_severity.value if hasattr(r.max_severity, "value") else str(r.max_severity)
),
)
for result in sorted_results:
sev = result.max_severity.value if hasattr(result.max_severity, "value") else str(result.max_severity)
safe = "" if result.is_safe else ""
duration = f"{result.scan_duration_seconds:.1f}s"
lines.append(f"| {result.skill_name} | {severity_badge(sev)} | {len(result.findings)} | {safe} | {duration} |")
lines.append("")
# Per-skill details (only for skills with findings)
flagged = [r for r in sorted_results if r.findings]
if flagged:
lines.append("## Detailed Findings")
lines.append("")
for result in flagged:
sev = result.max_severity.value if hasattr(result.max_severity, "value") else str(result.max_severity)
lines.append(f"### {result.skill_name}{severity_badge(sev)}")
lines.append("")
for finding in result.findings:
fsev = finding.severity.value if hasattr(finding.severity, "value") else str(finding.severity)
lines.append(f"- **{severity_badge(fsev)}** `{finding.rule_id}` — {finding.title}")
if finding.description:
lines.append(f" > {finding.description}")
if finding.file_path:
loc = finding.file_path
if finding.line_number:
loc += f":{finding.line_number}"
lines.append(f" > File: `{loc}`")
if finding.remediation:
lines.append(f" > **Remediation:** {finding.remediation}")
lines.append("")
else:
lines.append("## Detailed Findings")
lines.append("")
lines.append("No findings to report — all skills passed.")
lines.append("")
return "\n".join(lines)
def scan_with_progress(scanner: SkillScanner, skills_dir: str) -> Report:
"""Run scan_directory logic with per-skill progress output."""
base = Path(skills_dir)
if not base.exists():
raise FileNotFoundError(f"Directory does not exist: {base}")
skill_dirs = sorted(
{p.parent for p in base.rglob("SKILL.md")},
key=lambda p: p.name,
)
total = len(skill_dirs)
if total == 0:
print(" No skills found.")
return Report()
print(f" Found {total} skills to scan\n")
report = Report()
loaded_skills = []
scan_start = time.time()
width = len(str(total))
longest_name = 0
for i, skill_dir in enumerate(skill_dirs, 1):
name = skill_dir.name
longest_name = max(longest_name, len(name))
counter = f"[{i:>{width}}/{total}]"
print(f" {counter} {name} ...", end="", flush=True)
t0 = time.time()
try:
skill = scanner.loader.load_skill(skill_dir)
result = scanner._scan_single_skill(skill, skill_dir)
report.add_scan_result(result)
loaded_skills.append(skill)
elapsed = time.time() - t0
sev = result.max_severity.value if hasattr(result.max_severity, "value") else str(result.max_severity)
tag = severity_badge(sev)
n_findings = len(result.findings)
detail = f"{n_findings} finding{'s' if n_findings != 1 else ''}" if n_findings else ""
print(f"\r {counter} {name:{longest_name}} {tag:18} {detail:20} ({elapsed:.1f}s)")
except SkillLoadError as e:
elapsed = time.time() - t0
print(f"\r {counter} {name:{longest_name}} ⚠️ SKIP ({e}) ({elapsed:.1f}s)")
report.skills_skipped.append({"skill": str(skill_dir), "reason": str(e)})
except Exception as e:
elapsed = time.time() - t0
print(f"\r {counter} {name:{longest_name}} ❌ ERROR ({e}) ({elapsed:.1f}s)")
report.skills_skipped.append({"skill": str(skill_dir), "reason": str(e)})
wall = time.time() - scan_start
if len(loaded_skills) > 1:
print("\n Running cross-skill overlap analysis ...", end="", flush=True)
t0 = time.time()
try:
overlap = scanner._check_description_overlap(loaded_skills)
from skill_scanner.core.analyzers.cross_skill_scanner import CrossSkillScanner
cross = CrossSkillScanner().analyze_skill_set(loaded_skills)
all_cross = [*list(overlap or []), *list(cross or [])]
if scanner.policy.disabled_rules:
all_cross = [f for f in all_cross if f.rule_id not in scanner.policy.disabled_rules]
if all_cross:
scanner._apply_severity_overrides(all_cross)
report.add_cross_skill_findings(all_cross)
elapsed = time.time() - t0
print(f" {len(all_cross)} finding{'s' if len(all_cross) != 1 else ''} ({elapsed:.1f}s)")
except Exception as e:
print(f" error: {e}")
print(f"\n Done in {wall:.1f}s")
return report
def main():
print("Building scanner (LLM + behavioral + trigger + balanced policy)...")
scanner = build_scanner()
print(f"Analyzers: {scanner.list_analyzers()}\n")
print(f"Scanning {SKILLS_DIR}/...")
report = scan_with_progress(scanner, SKILLS_DIR)
print(f"\nResults: {report.total_skills_scanned} skills, {report.total_findings} findings")
print(f" Critical: {report.critical_count} High: {report.high_count} Safe: {report.safe_count}")
md = generate_report(report)
with open(OUTPUT_FILE, "w") as f:
f.write(md)
print(f"\nReport written to {OUTPUT_FILE}")
if __name__ == "__main__":
main()
+239
View File
@@ -0,0 +1,239 @@
---
name: adaptyv
author: "K-Dense, Inc."
description: "How to use the Adaptyv Bio Foundry API and Python SDK for protein experiment design, submission, and results retrieval. Use this skill whenever the user mentions Adaptyv, Foundry API, protein binding assays, protein screening experiments, BLI/SPR assays, thermostability assays, or wants to submit protein sequences for experimental characterization. Also trigger when code imports `adaptyv`, `adaptyv_sdk`, or `FoundryClient`, or references `foundry-api-public.adaptyvbio.com`."
license: MIT
compatibility: Requires Python 3.10+, an Adaptyv Foundry account, and an API key from foundry.adaptyvbio.com. Install adaptyv-sdk from GitHub with uv pip install.
metadata: {"version": "1.2", "skill-author": "K-Dense Inc."}
---
# Adaptyv Bio Foundry API
Adaptyv Bio is a cloud lab that turns protein sequences into experimental data. Users submit amino acid sequences via API or UI; Adaptyv's automated lab runs assays (binding, thermostability, expression, fluorescence) and delivers results in ~21 days.
**Official docs:** [docs.adaptyvbio.com/api-reference](https://docs.adaptyvbio.com/api-reference) · [llms.txt index](https://docs.adaptyvbio.com/llms.txt) · [OpenAPI spec](https://foundry-api-public.adaptyvbio.com/api/v1/openapi.json)
## Quick Start
**Base URL:** `https://foundry-api-public.adaptyvbio.com/api/v1`
**Authentication:** Bearer token in the `Authorization` header. Tokens are obtained from [foundry.adaptyvbio.com](https://foundry.adaptyvbio.com/) sidebar.
When writing code, always read the API key from the environment variable `ADAPTYV_API_KEY` or from a `.env` file — never hardcode tokens. Check for a `.env` file in the project root first; if one exists, use a library like `python-dotenv` to load it.
The [official API docs](https://docs.adaptyvbio.com/api-reference/api-introduction) use `FOUNDRY_API_TOKEN` in curl examples; that is the same bearer token — prefer `ADAPTYV_API_KEY` in Python and new shell scripts for consistency with the SDK.
```bash
export ADAPTYV_API_KEY="abs0_..."
curl https://foundry-api-public.adaptyvbio.com/api/v1/targets?limit=3 \
-H "Authorization: Bearer $ADAPTYV_API_KEY"
```
Every request except `GET /openapi.json` requires authentication. Store tokens in environment variables or `.env` files — never commit them to source control.
## Python SDK
**Version note:** `adaptyv-sdk` **0.1.0** (beta) is not yet on PyPI — install from GitHub:
```bash
uv pip install "git+https://github.com/adaptyvbio/adaptyv-sdk.git"
```
In a project with `pyproject.toml`:
```bash
uv add "adaptyv-sdk @ git+https://github.com/adaptyvbio/adaptyv-sdk.git"
```
**Environment variables** (set in shell or `.env` file):
```bash
ADAPTYV_API_KEY=your_api_key
ADAPTYV_API_URL=https://foundry-api-public.adaptyvbio.com/api/v1
ADAPTYV_ORGANIZATION_ID=your_org_id # optional
```
The `@lab.experiment` decorator and `FoundryClient` both read `ADAPTYV_API_KEY` and `ADAPTYV_API_URL` from the environment when not passed explicitly.
### Decorator Pattern
```python
from adaptyv import lab
@lab.experiment(target="PD-L1", experiment_type="screening", method="bli")
def design_binders():
return {"design_a": "MVKVGVNG...", "design_b": "MKVLVAG..."}
result = design_binders()
print(f"Experiment: {result.experiment_url}")
```
### Client Pattern
```python
import os
from adaptyv import FoundryClient
client = FoundryClient(
api_key=os.environ["ADAPTYV_API_KEY"],
base_url=os.environ.get(
"ADAPTYV_API_URL",
"https://foundry-api-public.adaptyvbio.com/api/v1",
),
)
# Browse targets
targets = client.targets.list(search="EGFR", selfservice_only=True)
# Estimate cost
estimate = client.experiments.cost_estimate({
"experiment_spec": {
"experiment_type": "screening",
"method": "bli",
"target_id": "target-uuid",
"sequences": {"seq1": "EVQLVESGGGLVQ..."},
"n_replicates": 3
}
})
# Create and submit
exp = client.experiments.create({...})
client.experiments.submit(exp.experiment_id)
# Later: retrieve results
results = client.experiments.get_results(exp.experiment_id)
```
## Experiment Types
| Type | Method | Measures | Requires Target |
|---|---|---|---|
| `affinity` | `bli` or `spr` | KD, kon, koff kinetics | Yes |
| `screening` | `bli` or `spr` | Yes/no binding | Yes |
| `thermostability` | — | Melting temperature (Tm) | No |
| `expression` | — | Expression yield | No |
| `fluorescence` | — | Fluorescence intensity | No |
## Experiment Lifecycle
```
Draft → WaitingForConfirmation → QuoteSent → WaitingForMaterials → InQueue → InProduction → DataAnalysis → InReview → Done
```
| Status | Who Acts | Description |
|---|---|---|
| `Draft` | You | Editable, no cost commitment |
| `WaitingForConfirmation` | Adaptyv | Under review, quote being prepared |
| `QuoteSent` | You | Review and confirm the quote |
| `WaitingForMaterials` | Adaptyv | Gene fragments and target ordered |
| `InQueue` | Adaptyv | Materials arrived, queued for lab |
| `InProduction` | Adaptyv | Assay running |
| `DataAnalysis` | Adaptyv | Raw data processing and QC |
| `InReview` | Adaptyv | Final validation |
| `Done` | You | Results available |
| `Canceled` | Either | Experiment canceled |
The `results_status` field on an experiment tracks: `none`, `partial`, or `all`.
## Common Workflows
### 1. Submit a Binding Screen (Step by Step)
```python
# 1. Find a target
targets = client.targets.list(search="EGFR", selfservice_only=True)
target_id = targets.items[0].id
# 2. Preview cost
estimate = client.experiments.cost_estimate({
"experiment_spec": {
"experiment_type": "screening",
"method": "bli",
"target_id": target_id,
"sequences": {"seq1": "EVQLVESGGGLVQ...", "seq2": "MKVLVAG..."},
"n_replicates": 3
}
})
# 3. Create experiment (starts as Draft)
exp = client.experiments.create({
"name": "EGFR binder screen batch 1",
"experiment_spec": {
"experiment_type": "screening",
"method": "bli",
"target_id": target_id,
"sequences": {"seq1": "EVQLVESGGGLVQ...", "seq2": "MKVLVAG..."},
"n_replicates": 3
}
})
# 4. Submit for review
client.experiments.submit(exp.experiment_id)
# 5. Poll or use webhooks until Done
# 6. Retrieve results
results = client.experiments.get_results(exp.experiment_id)
```
### 2. Automated Pipeline (Skip Draft + Auto-Accept Quote)
```python
exp = client.experiments.create({
"name": "Auto pipeline run",
"experiment_spec": {...},
"skip_draft": True,
"auto_accept_quote": True,
"webhook_url": "https://my-server.com/webhook"
})
# Webhook fires on each status transition; poll or wait for Done
```
### 3. Using Webhooks
Pass `webhook_url` when creating an experiment. Adaptyv POSTs to that URL on every status transition with the experiment ID, previous status, and new status.
## Sequences
- Simple format: `{"seq1": "EVQLVESGGGLVQPGGSLRLSCAAS"}`
- Rich format: `{"seq1": {"aa_string": "EVQLVESGGGLVQ...", "control": false, "metadata": {"type": "scfv"}}}`
- Multi-chain: use colon separator — `"MVLS:EVQL"`
- Valid amino acids: A, C, D, E, F, G, H, I, K, L, M, N, P, Q, R, S, T, V, W, Y (case-insensitive, stored uppercase)
- Sequences can only be added to experiments in `Draft` status
## Filtering, Sorting, and Pagination
All list endpoints support pagination (`limit` 1-100, default 50; `offset`), search (free-text on name fields), and sorting.
**Filtering** uses s-expression syntax via the `filter` query parameter:
- Comparison: `eq(field,value)`, `neq`, `gt`, `gte`, `lt`, `lte`, `contains(field,substring)`
- Range/set: `between(field,lo,hi)`, `in(field,v1,v2,...)`
- Logic: `and(expr1,expr2,...)`, `or(...)`, `not(expr)`
- Null: `is_null(field)`, `is_not_null(field)`
- JSONB: `at(field,key)` — e.g., `eq(at(metadata,score),42)`
- Cast: `float()`, `int()`, `text()`, `timestamp()`, `date()`
**Sorting** uses `asc(field)` or `desc(field)`, comma-separated (max 8):
```
sort=desc(created_at),asc(name)
```
**Example:** `filter=and(gte(created_at,2026-01-01),eq(status,done))`
## Error Handling
All errors return:
```json
{
"error": "Human-readable description",
"request_id": "req_019462a4-b1c2-7def-8901-23456789abcd"
}
```
The `request_id` is also in the `x-request-id` response header — include it when contacting support.
## Token Management
Tokens use Biscuit-based cryptographic attenuation. You can create restricted tokens scoped by organization, resource type, actions (read/create/update), and expiry via `POST /tokens/attenuate`. Revoking a token (`POST /tokens/revoke`) revokes it and all its descendants.
## Detailed API Reference
For the full list of all 32 endpoints with request/response schemas, read `references/api-endpoints.md`.
+690
View File
@@ -0,0 +1,690 @@
# Adaptyv Bio Foundry API — Complete Endpoint Reference
Base URL: `https://foundry-api-public.adaptyvbio.com/api/v1`
OpenAPI spec: `GET /openapi.json`
## Table of Contents
- [Experiments](#experiments)
- [Sequences](#sequences)
- [Results](#results)
- [Targets](#targets)
- [Quotes](#quotes)
- [Tokens](#tokens)
- [Updates](#updates)
- [Feedback](#feedback)
---
## Experiments
### POST /experiments — Create experiment
Creates a new experiment. Starts in `Draft` status by default.
**Request body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Human-readable name |
| `experiment_spec` | ExperimentSpec | Yes | Experiment definition (see below) |
| `skip_draft` | boolean | No (default false) | Bypass Draft, go straight to WaitingForConfirmation |
| `auto_accept_quote` | boolean | No (default false) | Auto-accept quote and create invoice |
| `webhook_url` | string/null | No | URL for status-change POST notifications |
**ExperimentSpec:**
| Field | Type | Required | Description |
|---|---|---|---|
| `experiment_type` | string | Yes | `affinity`, `screening`, `thermostability`, `fluorescence`, or `expression` |
| `method` | string | Required for binding types | `bli` or `spr` |
| `target_id` | uuid | Required for binding types | Target UUID from catalog |
| `sequences` | object | Yes | Map of name → amino acid string or rich object |
| `n_replicates` | integer | Recommended (default 3) | Technical replicates (min 1) |
| `antigen_concentrations` | number[] | No (affinity only) | Defaults to `[1000.0, 316.2, 100.0, 31.6, 0.0]` nM |
| `parameters` | object | No | Experiment-specific settings |
**Field requirements by experiment type:**
| Field | Affinity | Screening | Thermostability | Fluorescence | Expression |
|---|---|---|---|---|---|
| `experiment_type` | required | required | required | required | required |
| `method` | required | required | — | — | — |
| `target_id` | required | required | — | — | — |
| `sequences` | required | required | required | required | required |
| `n_replicates` | recommended | recommended | optional | optional | optional |
| `antigen_concentrations` | optional | — | — | — | — |
**Response (201):**
| Field | Type | Description |
|---|---|---|
| `experiment_id` | string | UUID of new experiment |
| `error` | string/null | Error message if validation fails |
| `stripe_hosted_invoice_url` | string/null | Present when `auto_accept_quote` created an invoice |
| `stripe_invoice_id` | string/null | Stripe invoice ID |
**Status codes:** 201, 400, 401, 403, 404
---
### GET /experiments — List experiments
Lists experiments accessible to caller, sorted by creation date (newest first).
**Query params:** `limit`, `offset`, `filter`, `search`, `sort`
**Response item:**
| Field | Type | Description |
|---|---|---|
| `id` | uuid | Unique identifier |
| `code` | string | e.g., "EXP-2024-001" |
| `name` | string/null | Human-readable name |
| `status` | ExperimentStatus | Current lifecycle status |
| `experiment_type` | ExperimentType | affinity/screening/thermostability/fluorescence/expression |
| `results_status` | ResultsStatus | none/partial/all |
| `created_at` | datetime | ISO 8601 |
| `experiment_url` | string | URL to Foundry portal |
| `stripe_invoice_url` | string/null | Invoice URL |
| `stripe_quote_url` | string/null | Quote URL |
**Status codes:** 200, 401
---
### GET /experiments/{experiment_id} — Get experiment
Returns full metadata for a single experiment.
**Path param:** `experiment_id` (uuid)
**Response:**
| Field | Type | Description |
|---|---|---|
| `id` | uuid | Unique identifier |
| `code` | string | Experiment code |
| `status` | ExperimentStatus | Current status |
| `experiment_spec` | ExperimentSpec | Full experiment definition |
| `results_status` | ResultsStatus | none/partial/all |
| `created_at` | datetime | ISO 8601 |
| `experiment_url` | string | Portal URL |
| `costs` | object | Cost breakdown |
**Status codes:** 200, 401, 404, 500
---
### PATCH /experiments/{experiment_id} — Update experiment
Modify an existing experiment. Draft experiments allow full edits; after quote generation, only `name`, `description`, and `webhook_url` are editable.
**Path param:** `experiment_id` (uuid)
**Request body:** All fields optional — only provided fields are updated.
**Status codes:** 200, 400, 401, 404, 409
---
### POST /experiments/{experiment_id}/submit — Submit experiment
Submits a draft experiment for review. Advances from `Draft` to `WaitingForConfirmation`.
**Path param:** `experiment_id` (uuid)
**Response:**
| Field | Type | Description |
|---|---|---|
| `experiment_id` | string | Experiment UUID |
**Status codes:** 200, 401, 403, 404, 409, 500
---
### POST /experiments/cost-estimate — Estimate cost
Calculates cost without creating an experiment.
**Request body:**
```json
{
"experiment_spec": {
"experiment_type": "screening",
"method": "bli",
"target_id": "...",
"sequences": {"seq1": "MKTL..."},
"n_replicates": 3
}
}
```
**Response:**
| Field | Type | Description |
|---|---|---|
| `pricing_version` | string | e.g., "v1_2026-01-20" |
| `assay` | object | Per-type costs with base and replicate pricing |
| `materials` | object | Target material costs (binding experiments) |
| `total_cents` | integer | Sum in USD cents |
All prices exclude VAT; taxes calculated at invoicing. Targets without self-service pricing return incomplete estimates.
**Status codes:** 200, 400, 401
---
### GET /experiments/{experiment_id}/quote — Get quote
Returns quote metadata (totals, currency, status, expiration).
**Path param:** `experiment_id` (uuid)
**Response:**
| Field | Type | Description |
|---|---|---|
| `experiment_id` | string | Experiment UUID |
| `stripe_quote_url` | string | Stripe quote URL |
| `amount_total` | int64 | Total in smallest currency unit |
| `amount_subtotal` | int64 | Subtotal |
| `currency` | string | ISO currency code (e.g., "usd") |
| `status` | string | Quote status |
| `expires_at` | datetime/null | Expiration time |
**Status codes:** 200, 401, 403, 404, 500
---
### GET /experiments/{experiment_id}/quote/pdf — Get quote PDF
Returns the quote as a PDF file (`application/pdf`).
**Path param:** `experiment_id` (uuid)
**Status codes:** 200, 401, 403, 404, 500
---
### POST /experiments/{experiment_id}/quote/confirm — Accept quote (by experiment)
Accepts Stripe quote, creates draft invoice, transitions to `WaitingForMaterials`.
**Path param:** `experiment_id` (uuid)
**Request body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `purchase_order_number` | string/null | No | PO number for your records |
| `notes` | string/null | No | Reserved |
**Response:**
| Field | Type | Description |
|---|---|---|
| `id` | string | Quote ID |
| `status` | StripeQuoteStatus | New status |
| `hosted_invoice_url` | string/null | Stripe payment URL |
| `invoice_id` | string/null | Generated invoice ID |
**Status codes:** 200, 401, 403, 404, 409
---
### GET /experiments/{experiment_id}/invoice — Get invoice
Returns invoice metadata including hosted payment URL.
**Path param:** `experiment_id` (uuid)
**Status codes:** 200, 401, 403, 404, 500
---
### GET /experiments/{experiment_id}/results — List results for experiment
Returns all analysis results for a specific experiment.
**Path param:** `experiment_id` (uuid)
**Query params:** `limit`, `offset`, `filter`, `sort`
**Status codes:** 200, 400, 401, 403, 404
---
### GET /experiments/{experiment_id}/sequences — List sequences for experiment
Returns all sequences for a specific experiment, sorted newest first.
**Path param:** `experiment_id` (uuid)
**Query params:** `limit`, `offset`, `search`, `sort`
**Status codes:** 200, 400, 401, 403, 404
---
### GET /experiments/{experiment_id}/updates — List experiment updates
Returns updates for one experiment, oldest first. Types: `status_change`, `progress`, `error`.
**Path param:** `experiment_id` (uuid)
**Query params:** `limit`, `offset`, `filter`, `sort`
Filter example: `filter=eq(type,status_change)`
---
## Sequences
### GET /sequences — List sequences
Returns sequences from all experiments, sorted newest first.
**Query params:** `limit`, `offset`, `search`, `sort`, `experiment_id` (filter by experiment UUID)
**Response item:**
| Field | Type | Description |
|---|---|---|
| `id` | uuid | Unique identifier |
| `name` | string/null | Optional name |
| `aa_preview` | string/null | Truncated preview (first 50 chars) |
| `length` | int32 | Sequence length in amino acids |
| `experiment_id` | uuid | Parent experiment |
| `experiment_code` | string | Human-readable experiment code |
| `is_control` | boolean | Whether this is a control |
| `created_at` | datetime | Creation timestamp |
**Status codes:** 200, 401
---
### GET /sequences/{sequence_id} — Get sequence
Returns full details including complete amino acid string.
**Path param:** `sequence_id` (uuid)
**Response:**
| Field | Type | Description |
|---|---|---|
| `id` | uuid | Unique identifier |
| `aa_string` | string/null | Complete amino acid sequence |
| `length` | int32 | Length in amino acids |
| `is_control` | boolean | Control flag |
| `metadata` | object | Sequence-level annotations |
| `experiment` | object | Parent experiment reference |
| `created_at` | datetime | Creation timestamp |
**Status codes:** 200, 401, 403, 404, 500
---
### POST /sequences — Add sequences to experiment
Appends sequences to a **Draft** experiment identified by its human-readable code.
**Request body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `experiment_code` | string | Yes | e.g., "PROJ-001" |
| `sequences` | array | Yes | Array of sequence entries |
**Each sequence entry:**
| Field | Type | Required | Description |
|---|---|---|---|
| `aa_string` | string | Yes | Amino acid sequence |
| `name` | string | No | Human-readable name |
| `control` | boolean | No | Whether this is a control |
| `metadata` | object | No | Annotations |
**Response (201):**
| Field | Type | Description |
|---|---|---|
| `added_count` | int32 | Number of sequences added |
| `experiment_id` | string | Experiment UUID |
| `experiment_code` | string | Experiment code |
| `sequence_ids` | array | IDs of added sequences |
**Status codes:** 201, 400, 404, 409 (experiment not in Draft), 500
---
## Results
### GET /results — List results
Lists completed analysis results, sorted newest first. Results appear when `results_status` reaches `partial` or `all`.
**Query params:** `limit`, `offset`, `filter`, `search`, `sort`
**Response item:**
| Field | Type | Description |
|---|---|---|
| `id` | uuid | Result identifier |
| `title` | string | Human-readable title |
| `experiment_id` | uuid | Associated experiment |
| `result_type` | string | e.g., "affinity", "thermostability" |
| `summary` | array | Key results (type-specific, see below) |
| `metadata` | object | Extended metadata (e.g., instrument info) |
| `data_package_url` | string/null | Download URL for raw data package |
| `created_at` | datetime | When result was generated |
**AffinityResult summary fields:** `kd_mean`, `kd_std`, `kon_mean`, `kon_log_std`, `koff_mean`, `koff_std`, `replicates` (array with per-replicate `kd`, `kon`, `koff`, `binding_strength`, `kon_method`, `koff_method`, `replicate` index), `sequence`, `target_id`
**ThermostabilityResult summary fields:** Tm values and melting curves
**Status codes:** 200, 401
---
### GET /results/{result_id} — Get result
Returns detailed result data including full summary array.
**Path param:** `result_id` (uuid)
**Status codes:** 200, 401, 403, 404, 500
---
## Targets
### GET /targets — List targets
Lists validated antigens available for experiments.
**Query params:**
| Parameter | Type | Description |
|---|---|---|
| `limit` | int | Max items (1-100, default 50) |
| `offset` | int | Skip count |
| `search` | string | Free-text search on product name |
| `sort` | string | Sort expression |
| `selfservice_only` | boolean | Only targets with self-service pricing |
| `show_conjugated` | boolean | Include conjugated targets (default: unconjugated only) |
| `detailed` | boolean | Populate `details` block with enrichment data |
**Response item:**
| Field | Type | Description |
|---|---|---|
| `id` | uuid | Target UUID (use as `experiment_spec.target_id`) |
| `name` | string | Target name |
| `vendor_name` | string | Vendor name |
| `catalog_number` | string | Vendor catalog/SKU number |
| `url` | string | Target URL |
| `pricing` | object/null | Self-service pricing (null = custom quote required) |
| `details` | object/null | Enrichment data (gene names, structures, sequence, bioactivity) |
**Status codes:** 200, 401
---
### GET /targets/{target_id} — Get target
Returns catalog record for a single target.
**Path param:** `target_id` (uuid)
**Status codes:** 200, 400, 401, 403, 404, 500
---
### POST /targets/request-custom — Submit custom target request
Submit a new custom target for staff review. At least one of `sequence` or `pdb_id` must be provided.
**Request body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Display name |
| `product_id` | string | Yes | Must be unique within organization |
| `sequence` | string/null | At least one | Amino acid sequence |
| `pdb_id` | string/null | At least one | PDB identifier |
| `pdb_file` | string/null | No | PDB file content |
| `molecular_weight` | number/null | No | Weight in kDa |
| `note` | string/null | No | Additional notes |
**Status codes:** 201, 400, 401, 403, 500
---
### GET /targets/request-custom — List custom target requests
Returns custom target requests for your organization, sorted newest first.
**Query params:** `limit`, `offset`, `filter`, `sort`
Filter example: `filter=eq(status,pending_review)`
---
### GET /targets/request-custom/{request_id} — Get custom target request
**Path param:** `request_id` (uuid)
**Response:**
| Field | Type | Description |
|---|---|---|
| `id` | uuid | Request identifier |
| `name` | string | Target name |
| `product_id` | string | Your product ID |
| `status` | string | e.g., "pending_review" |
| `material_id` | string/null | Linked catalog ID if approved |
| `molecular_weight` | number/null | Weight in kDa |
| `note` | string/null | User notes |
| `created_at` | datetime | Created |
| `updated_at` | datetime | Last updated |
**Status codes:** 200, 401, 403, 404, 500
---
## Quotes
### GET /quotes — List quotes
Returns all quotes for caller's organization.
**Query params:** `limit`, `offset`, `filter`, `sort`
**Response item:**
| Field | Type | Description |
|---|---|---|
| `id` | string | Quote identifier |
| `quote_number` | string | Human-readable quote number |
| `organization_id` | uuid | Organization |
| `amount_cents` | int | Amount in cents |
| `currency` | string | ISO 4217 code |
| `status` | StripeQuoteStatus | Quote status |
| `valid_until` | datetime | Expiration |
| `created_at` | datetime | Creation timestamp |
---
### GET /quotes/{quote_id} — Get quote
Returns full quote document with itemized pricing.
**Path param:** `quote_id` (string, e.g., "qt_1Abc2DefGhi")
**Response:**
| Field | Type | Description |
|---|---|---|
| `id` | string | Quote identifier |
| `quote_number` | string | Reference number |
| `organization_id` | uuid | Organization |
| `organization_name` | string | Organization name |
| `line_items` | array | Itemized pricing |
| `subtotal_cents` | int | Subtotal in cents |
| `tax_cents` | int | Tax in cents |
| `total_cents` | int | Total in cents |
| `currency` | string | ISO 4217 |
| `status` | StripeQuoteStatus | Current status |
| `valid_until` | datetime | Expiration |
| `notes` | string | Special pricing info |
| `terms_and_conditions` | string | Terms |
| `stripe_quote_url` | string | Stripe URL |
| `created_at` | datetime | Created |
**Status codes:** 200, 401, 403, 404, 500
---
### POST /quotes/{quote_id}/confirm — Accept quote
Finalizes quote, creates draft invoice, advances experiment to `WaitingForMaterials`.
**Path param:** `quote_id` (string)
**Request body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `purchase_order_number` | string/null | No | PO number |
| `notes` | string/null | No | Reserved |
**Response:** `id`, `status`, `hosted_invoice_url`, `invoice_id`
**Status codes:** 200, 403, 404, 409, 500
---
### POST /quotes/{quote_id}/reject — Reject quote
Cancels quote; linked experiment reverts to `Draft`.
**Path param:** `quote_id` (string)
**Request body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `reason` | QuoteRejectionReason | Yes | Primary reason |
| `feedback` | string/null | No | Additional feedback |
**Response:** `id`, `status` (canceled)
**Status codes:** 200, 403, 404, 409, 500
---
## Tokens
### GET /tokens — List tokens
Returns all tokens (root and attenuated) the caller owns.
**Query params:** `limit`, `offset`
**Response item:**
| Field | Type | Description |
|---|---|---|
| `id` | string | Token identifier |
| `name` | string | Human-readable label |
| `kind` | string | "root" or "attenuated" |
| `created_at` | datetime | Created |
| `expires_at` | datetime/null | Expiration (null = no expiry) |
| `revoked_at` | datetime/null | Revocation timestamp |
| `parent_token_id` | string/null | Parent (null for root) |
| `root_token_id` | string/null | Root of derivation tree |
| `attenuation_spec` | object/null | Restrictions (null for root) |
---
### POST /tokens/attenuate — Attenuate token
Creates a restricted version of an existing token using Biscuit cryptographic attenuation.
**Request body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `token` | string | Yes | Existing token (`abs0_{slug}{biscuit_base64}`) |
| `attenuation` | AttenuationSpec | Yes | Restrictions to apply |
| `name` | string | Yes | Human-readable label |
| `attenuated_parent_token_id` | uuid/null | No | Parent ID for chained attenuation |
**Restriction types:** Organization, Resource (experiments/results), Action (read/create/update), Expiry
**Response (201):** `id` (database ID), `token` (new attenuated token string)
**Status codes:** 201, 400, 401, 403
---
### POST /tokens/revoke — Revoke token and lineage
Revokes the calling token's root and all attenuated descendants. Idempotent.
**Response:**
| Field | Type | Description |
|---|---|---|
| `token_id` | string | Root token ID revoked |
| `revoked_at` | datetime | Revocation timestamp |
| `children_revoked` | int64 | Child tokens newly revoked |
**Status codes:** 200, 403, 404
---
## Updates
### GET /updates — List updates
Returns the experiment update feed (newest first): status changes, progress, errors.
**Query params:** `limit`, `offset`, `filter`, `sort`
**Filter examples:**
- `filter=eq(experiment_id,<uuid>)`
- `filter=in(experiment_id,uuid1,uuid2)`
- `filter=eq(type,status_change)`
**Response item:**
| Field | Type | Description |
|---|---|---|
| `id` | string | Update identifier |
| `experiment_id` | uuid | Associated experiment |
| `experiment_code` | string | Human-readable code |
| `name` | string | Update description |
| `timestamp` | datetime | When the update occurred |
---
## Feedback
### POST /feedback/submit — Submit feedback
For bug reports, feature requests, or general feedback.
**Request body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `request_uuid` | uuid | Yes | UUID from the problematic API request |
| `feedback_type` | FeedbackType | Yes | `feature_request`, `feedback`, or `bug_report` |
| `title` | string/null | No | Short title |
| `json_body` | object/null | At least one | Structured error details |
| `human_note` | string/null | At least one | Free-form description |
**Response (201):** `reference` (feedback reference), `message` (confirmation)
**Status codes:** 201, 400, 401, 500
+400
View File
@@ -0,0 +1,400 @@
---
name: aeon
description: This skill should be used for time series machine learning tasks including classification, regression, clustering, forecasting, anomaly detection, segmentation, and similarity search. Use when working with temporal data, sequential patterns, or time-indexed observations requiring specialized algorithms beyond standard ML approaches. Particularly suited for univariate and multivariate time series analysis with scikit-learn compatible APIs.
license: BSD-3-Clause license
allowed-tools: Read Write Edit Bash
compatibility: Requires Python 3.10+ and the aeon package (uv pip install). Optional aeon[all_extras] for deep learning and extended dependencies.
metadata: {"version": "1.0", "skill-author": "K-Dense Inc."}
---
# Aeon Time Series Machine Learning
## Overview
Aeon is a scikit-learn compatible Python toolkit for time series machine learning ([aeon-toolkit.org](https://www.aeon-toolkit.org/)). It provides algorithms across classification, regression, clustering, forecasting, anomaly detection, segmentation, similarity search, distances, transformations, benchmarking, and visualization — with a consistent estimator API.
**Version note:** Examples target **aeon 1.x** (stable docs: v1.4.0, March 2026). The v1.0 release reworked forecasting and transformations; import paths differ from aeon 0.x/sktime-era code.
## When to Use This Skill
Apply this skill when:
- Classifying or predicting from time series data
- Detecting anomalies or change points in temporal sequences
- Clustering similar time series patterns
- Forecasting future values
- Finding repeated patterns (motifs) or unusual subsequences (discords)
- Comparing time series with specialized distance metrics
- Extracting features from temporal data
## Installation
Requires **Python 3.10+** (3.11+ recommended). Pin a 1.x release for reproducibility:
```bash
uv pip install "aeon>=1.4,<2"
```
For deep learning forecasters/classifiers and other optional estimators:
```bash
uv pip install "aeon[all_extras]>=1.4,<2"
```
On zsh, quote the extras: `uv pip install "aeon[all_extras]>=1.4,<2"`.
### Experimental modules
Upstream treats **forecasting**, **anomaly_detection**, **segmentation**, **similarity_search**, and **visualisation** as experimental — interfaces may change between minor releases. Prefer stable modules (classification, regression, clustering, distances, transformations) for production pipelines unless you need these tasks.
## Core Capabilities
### 1. Time Series Classification
Categorize time series into predefined classes. See `references/classification.md` for complete algorithm catalog.
**Quick Start:**
```python
from aeon.classification.convolution_based import RocketClassifier
from aeon.datasets import load_classification
# Load data
X_train, y_train = load_classification("GunPoint", split="train")
X_test, y_test = load_classification("GunPoint", split="test")
# Train classifier
clf = RocketClassifier(n_kernels=10000)
clf.fit(X_train, y_train)
accuracy = clf.score(X_test, y_test)
```
**Algorithm Selection:**
- **Speed + Performance**: `MiniRocketClassifier`, `Arsenal`
- **Maximum Accuracy**: `HIVECOTEV2`, `InceptionTimeClassifier`
- **Interpretability**: `ShapeletTransformClassifier`, `Catch22Classifier`
- **Small Datasets**: `KNeighborsTimeSeriesClassifier` with DTW distance
### 2. Time Series Regression
Predict continuous values from time series. See `references/regression.md` for algorithms.
**Quick Start:**
```python
from aeon.regression.convolution_based import RocketRegressor
from aeon.datasets import load_regression
X_train, y_train = load_regression("Covid3Month", split="train")
X_test, y_test = load_regression("Covid3Month", split="test")
reg = RocketRegressor()
reg.fit(X_train, y_train)
predictions = reg.predict(X_test)
```
### 3. Time Series Clustering
Group similar time series without labels. See `references/clustering.md` for methods.
**Quick Start:**
```python
from aeon.clustering import TimeSeriesKMeans
clusterer = TimeSeriesKMeans(
n_clusters=3,
distance="dtw",
averaging_method="ba"
)
labels = clusterer.fit_predict(X_train)
centers = clusterer.cluster_centers_
```
### 4. Forecasting
Predict future time series values (experimental module in aeon 1.x). See `references/forecasting.md` for forecasters.
**Quick Start:**
```python
import numpy as np
from aeon.forecasting import NaiveForecaster
from aeon.forecasting.stats import ARIMA
y_train = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0])
# Set horizon in the constructor; predict passes the series to forecast from
naive = NaiveForecaster(strategy="last", horizon=5)
naive.fit(y_train)
y_pred = naive.predict(y_train)
# ARIMA uses p/d/q (not order=); multi-step via iterative_forecast
arima = ARIMA(p=1, d=1, q=1)
arima.fit(y_train)
y_pred = arima.iterative_forecast(y_train, prediction_horizon=5)
```
### 5. Anomaly Detection
Identify unusual patterns or outliers. See `references/anomaly_detection.md` for detectors.
**Quick Start:**
```python
from aeon.anomaly_detection import STOMP
detector = STOMP(window_size=50)
anomaly_scores = detector.fit_predict(y)
# Higher scores indicate anomalies
threshold = np.percentile(anomaly_scores, 95)
anomalies = anomaly_scores > threshold
```
### 6. Segmentation
Partition time series into regions with change points. See `references/segmentation.md`.
**Quick Start:**
```python
from aeon.segmentation import ClaSPSegmenter
segmenter = ClaSPSegmenter()
change_points = segmenter.fit_predict(y)
```
### 7. Similarity Search
Find similar patterns within or across time series. See `references/similarity_search.md`.
**Quick Start:**
```python
from aeon.similarity_search import StompMotif
# Find recurring patterns
motif_finder = StompMotif(window_size=50, k=3)
motifs = motif_finder.fit_predict(y)
```
## Feature Extraction and Transformations
Transform time series for feature engineering. See `references/transformations.md`.
**ROCKET Features:**
```python
from aeon.transformations.collection.convolution_based import RocketTransformer
rocket = RocketTransformer()
X_features = rocket.fit_transform(X_train)
# Use features with any sklearn classifier
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier()
clf.fit(X_features, y_train)
```
**Statistical Features:**
```python
from aeon.transformations.collection.feature_based import Catch22
catch22 = Catch22()
X_features = catch22.fit_transform(X_train)
```
**Preprocessing:**
```python
from aeon.transformations.collection import MinMaxScaler, Normalizer
scaler = Normalizer() # Z-normalization
X_normalized = scaler.fit_transform(X_train)
```
## Distance Metrics
Specialized temporal distance measures. See `references/distances.md` for complete catalog.
**Usage:**
```python
from aeon.distances import dtw_distance, dtw_pairwise_distance
# Single distance
distance = dtw_distance(x, y, window=0.1)
# Pairwise distances
distance_matrix = dtw_pairwise_distance(X_train)
# Use with classifiers
from aeon.classification.distance_based import KNeighborsTimeSeriesClassifier
clf = KNeighborsTimeSeriesClassifier(
n_neighbors=5,
distance="dtw",
distance_params={"window": 0.2}
)
```
**Available Distances:**
- **Elastic**: DTW, DDTW, WDTW, ERP, EDR, LCSS, TWE, MSM
- **Lock-step**: Euclidean, Manhattan, Minkowski
- **Shape-based**: Shape DTW, SBD
## Deep Learning Networks
Neural architectures for time series. See `references/networks.md`.
**Architectures:**
- Convolutional: `FCNClassifier`, `ResNetClassifier`, `InceptionTimeClassifier`
- Recurrent: `RecurrentNetwork`, `TCNNetwork`
- Autoencoders: `AEFCNClusterer`, `AEResNetClusterer`
**Usage:**
```python
from aeon.classification.deep_learning import InceptionTimeClassifier
clf = InceptionTimeClassifier(n_epochs=100, batch_size=32)
clf.fit(X_train, y_train)
predictions = clf.predict(X_test)
```
## Datasets and Benchmarking
Load standard benchmarks and evaluate performance. See `references/datasets_benchmarking.md`.
**Load Datasets:**
```python
from aeon.datasets import load_classification, load_gunpoint, load_regression
# Classification (generic loader or dataset-specific helper)
X_train, y_train = load_classification("GunPoint", split="train")
X_train, y_train = load_gunpoint(split="train") # same UCR dataset
# Regression
X_train, y_train = load_regression("Covid3Month", split="train")
```
**Benchmarking:**
```python
from aeon.benchmarking import get_estimator_results
# Compare with published results
published = get_estimator_results("ROCKET", "GunPoint")
```
## Common Workflows
### Classification Pipeline
```python
from aeon.transformations.collection import Normalizer
from aeon.classification.convolution_based import RocketClassifier
from sklearn.pipeline import Pipeline
pipeline = Pipeline([
('normalize', Normalizer()),
('classify', RocketClassifier())
])
pipeline.fit(X_train, y_train)
accuracy = pipeline.score(X_test, y_test)
```
### Feature Extraction + Traditional ML
```python
from aeon.transformations.collection import RocketTransformer
from sklearn.ensemble import GradientBoostingClassifier
# Extract features
rocket = RocketTransformer()
X_train_features = rocket.fit_transform(X_train)
X_test_features = rocket.transform(X_test)
# Train traditional ML
clf = GradientBoostingClassifier()
clf.fit(X_train_features, y_train)
predictions = clf.predict(X_test_features)
```
### Anomaly Detection with Visualization
```python
from aeon.anomaly_detection import STOMP
import matplotlib.pyplot as plt
detector = STOMP(window_size=50)
scores = detector.fit_predict(y)
plt.figure(figsize=(15, 5))
plt.subplot(2, 1, 1)
plt.plot(y, label='Time Series')
plt.subplot(2, 1, 2)
plt.plot(scores, label='Anomaly Scores', color='red')
plt.axhline(np.percentile(scores, 95), color='k', linestyle='--')
plt.show()
```
## Best Practices
### Data Preparation
1. **Normalize**: Most algorithms benefit from z-normalization
```python
from aeon.transformations.collection import Normalizer
normalizer = Normalizer()
X_train = normalizer.fit_transform(X_train)
X_test = normalizer.transform(X_test)
```
2. **Handle Missing Values**: Impute before analysis
```python
from aeon.transformations.collection import SimpleImputer
imputer = SimpleImputer(strategy='mean')
X_train = imputer.fit_transform(X_train)
```
3. **Check Data Format**: Collections use `(n_cases, n_channels, n_timepoints)`; single series use `(n_channels, n_timepoints)` (see [data format](https://www.aeon-toolkit.org/en/stable/api_reference/data_format.html))
### Model Selection
1. **Start Simple**: Begin with ROCKET variants before deep learning
2. **Use Validation**: Split training data for hyperparameter tuning
3. **Compare Baselines**: Test against simple methods (1-NN Euclidean, Naive)
4. **Consider Resources**: ROCKET for speed, deep learning if GPU available
### Algorithm Selection Guide
**For Fast Prototyping:**
- Classification: `MiniRocketClassifier`
- Regression: `MiniRocketRegressor`
- Clustering: `TimeSeriesKMeans` with Euclidean
**For Maximum Accuracy:**
- Classification: `HIVECOTEV2`, `InceptionTimeClassifier`
- Regression: `InceptionTimeRegressor`
- Forecasting: `AutoARIMA`, `AutoETS`, `TCNForecaster` (requires `[all_extras]` for deep learning)
**For Interpretability:**
- Classification: `ShapeletTransformClassifier`, `Catch22Classifier`
- Features: `Catch22`, `TSFresh`
**For Small Datasets:**
- Distance-based: `KNeighborsTimeSeriesClassifier` with DTW
- Avoid: Deep learning (requires large data)
## Reference Documentation
Detailed information available in `references/`:
- `classification.md` - All classification algorithms
- `regression.md` - Regression methods
- `clustering.md` - Clustering algorithms
- `forecasting.md` - Forecasting approaches
- `anomaly_detection.md` - Anomaly detection methods
- `segmentation.md` - Segmentation algorithms
- `similarity_search.md` - Pattern matching and motif discovery
- `transformations.md` - Feature extraction and preprocessing
- `distances.md` - Time series distance metrics
- `networks.md` - Deep learning architectures
- `datasets_benchmarking.md` - Data loading and evaluation tools
## Additional Resources
- Documentation: https://www.aeon-toolkit.org/
- GitHub: https://github.com/aeon-toolkit/aeon
- Examples: https://www.aeon-toolkit.org/en/stable/examples.html
- API Reference: https://www.aeon-toolkit.org/en/stable/api_reference.html
+154
View File
@@ -0,0 +1,154 @@
# Anomaly Detection
Aeon provides anomaly detection methods for identifying unusual patterns in time series at both series and collection levels.
## Collection Anomaly Detectors
Detect anomalous time series within a collection:
- `ClassificationAdapter` - Adapts classifiers for anomaly detection
- Train on normal data, flag outliers during prediction
- **Use when**: Have labeled normal data, want classification-based approach
- `OutlierDetectionAdapter` - Wraps sklearn outlier detectors
- Works with IsolationForest, LOF, OneClassSVM
- **Use when**: Want to use sklearn anomaly detectors on collections
## Series Anomaly Detectors
Detect anomalous points or subsequences within a single time series.
### Distance-Based Methods
Use similarity metrics to identify anomalies:
- `CBLOF` - Cluster-Based Local Outlier Factor
- Clusters data, identifies outliers based on cluster properties
- **Use when**: Anomalies form sparse clusters
- `KMeansAD` - K-means based anomaly detection
- Distance to nearest cluster center indicates anomaly
- **Use when**: Normal patterns cluster well
- `LeftSTAMPi` - Left STAMP incremental
- Matrix profile for online anomaly detection
- **Use when**: Streaming data, need online detection
- `STOMP` - Scalable Time series Ordered-search Matrix Profile
- Computes matrix profile for subsequence anomalies
- **Use when**: Discord discovery, motif detection
- `MERLIN` - Matrix profile-based method
- Efficient matrix profile computation
- **Use when**: Large time series, need scalability
- `LOF` - Local Outlier Factor adapted for time series
- Density-based outlier detection
- **Use when**: Anomalies in low-density regions
- `ROCKAD` - ROCKET-based semi-supervised detection
- Uses ROCKET features for anomaly identification
- **Use when**: Have some labeled data, want feature-based approach
### Distribution-Based Methods
Analyze statistical distributions:
- `COPOD` - Copula-Based Outlier Detection
- Models marginal and joint distributions
- **Use when**: Multi-dimensional time series, complex dependencies
- `DWT_MLEAD` - Discrete Wavelet Transform Multi-Level Anomaly Detection
- Decomposes series into frequency bands
- **Use when**: Anomalies at specific frequencies
### Isolation-Based Methods
Use isolation principles:
- `IsolationForest` - Random forest-based isolation
- Anomalies easier to isolate than normal points
- **Use when**: High-dimensional data, no assumptions about distribution
- `OneClassSVM` - Support vector machine for novelty detection
- Learns boundary around normal data
- **Use when**: Well-defined normal region, need robust boundary
- `STRAY` - Streaming Robust Anomaly Detection
- Robust to data distribution changes
- **Use when**: Streaming data, distribution shifts
### External Library Integration
- `PyODAdapter` - Bridges PyOD library to aeon
- Access 40+ PyOD anomaly detectors
- **Use when**: Need specific PyOD algorithm
## Quick Start
```python
from aeon.anomaly_detection import STOMP
import numpy as np
# Create time series with anomaly
y = np.concatenate([
np.sin(np.linspace(0, 10, 100)),
[5.0], # Anomaly spike
np.sin(np.linspace(10, 20, 100))
])
# Detect anomalies
detector = STOMP(window_size=10)
anomaly_scores = detector.fit_predict(y)
# Higher scores indicate more anomalous points
threshold = np.percentile(anomaly_scores, 95)
anomalies = anomaly_scores > threshold
```
## Point vs Subsequence Anomalies
- **Point anomalies**: Single unusual values
- Use: COPOD, DWT_MLEAD, IsolationForest
- **Subsequence anomalies** (discords): Unusual patterns
- Use: STOMP, LeftSTAMPi, MERLIN
- **Collective anomalies**: Groups of points forming unusual pattern
- Use: Matrix profile methods, clustering-based
## Evaluation Metrics
Specialized metrics for anomaly detection:
```python
from aeon.benchmarking.metrics.anomaly_detection import (
range_precision,
range_recall,
range_f_score,
roc_auc_score
)
# Range-based metrics account for window detection
precision = range_precision(y_true, y_pred, alpha=0.5)
recall = range_recall(y_true, y_pred, alpha=0.5)
f1 = range_f_score(y_true, y_pred, alpha=0.5)
```
## Algorithm Selection
- **Speed priority**: KMeansAD, IsolationForest
- **Accuracy priority**: STOMP, COPOD
- **Streaming data**: LeftSTAMPi, STRAY
- **Discord discovery**: STOMP, MERLIN
- **Multi-dimensional**: COPOD, PyODAdapter
- **Semi-supervised**: ROCKAD, OneClassSVM
- **No training data**: IsolationForest, STOMP
## Best Practices
1. **Normalize data**: Many methods sensitive to scale
2. **Choose window size**: For matrix profile methods, window size critical
3. **Set threshold**: Use percentile-based or domain-specific thresholds
4. **Validate results**: Visualize detections to verify meaningfulness
5. **Handle seasonality**: Detrend/deseasonalize before detection
+144
View File
@@ -0,0 +1,144 @@
# Time Series Classification
Aeon provides 13 categories of time series classifiers with scikit-learn compatible APIs.
## Convolution-Based Classifiers
Apply random convolutional transformations for efficient feature extraction:
- `Arsenal` - Ensemble of ROCKET classifiers with varied kernels
- `HydraClassifier` - Multi-resolution convolution with dilation
- `RocketClassifier` - Random convolution kernels with ridge regression
- `MiniRocketClassifier` - Simplified ROCKET variant for speed
- `MultiRocketClassifier` - Combines multiple ROCKET variants
**Use when**: Need fast, scalable classification with strong performance across diverse datasets.
## Deep Learning Classifiers
Neural network architectures optimized for temporal sequences:
- `FCNClassifier` - Fully convolutional network
- `ResNetClassifier` - Residual networks with skip connections
- `InceptionTimeClassifier` - Multi-scale inception modules
- `TimeCNNClassifier` - Standard CNN for time series
- `MLPClassifier` - Multi-layer perceptron baseline
- `EncoderClassifier` - Generic encoder wrapper
- `DisjointCNNClassifier` - Shapelet-focused architecture
**Use when**: Large datasets available, need end-to-end learning, or complex temporal patterns.
## Dictionary-Based Classifiers
Transform time series into symbolic representations:
- `BOSSEnsemble` - Bag-of-SFA-Symbols with ensemble voting
- `TemporalDictionaryEnsemble` - Multiple dictionary methods combined
- `WEASEL` - Word ExtrAction for time SEries cLassification
- `MrSEQLClassifier` - Multiple symbolic sequence learning
**Use when**: Need interpretable models, sparse patterns, or symbolic reasoning.
## Distance-Based Classifiers
Leverage specialized time series distance metrics:
- `KNeighborsTimeSeriesClassifier` - k-NN with temporal distances (DTW, LCSS, ERP, etc.)
- `ElasticEnsemble` - Combines multiple elastic distance measures
- `ProximityForest` - Tree ensemble using distance-based splits
**Use when**: Small datasets, need similarity-based classification, or interpretable decisions.
## Feature-Based Classifiers
Extract statistical and signature features before classification:
- `Catch22Classifier` - 22 canonical time-series characteristics
- `TSFreshClassifier` - Automated feature extraction via tsfresh
- `SignatureClassifier` - Path signature transformations
- `SummaryClassifier` - Summary statistics extraction
- `FreshPRINCEClassifier` - Combines multiple feature extractors
**Use when**: Need interpretable features, domain expertise available, or feature engineering approach.
## Interval-Based Classifiers
Extract features from random or supervised intervals:
- `CanonicalIntervalForestClassifier` - Random interval features with decision trees
- `DrCIFClassifier` - Diverse Representation CIF with catch22 features
- `TimeSeriesForestClassifier` - Random intervals with summary statistics
- `RandomIntervalClassifier` - Simple interval-based approach
- `RandomIntervalSpectralEnsembleClassifier` - Spectral features from intervals
- `SupervisedTimeSeriesForest` - Supervised interval selection
**Use when**: Discriminative patterns occur in specific time windows.
## Shapelet-Based Classifiers
Identify discriminative subsequences (shapelets):
- `ShapeletTransformClassifier` - Discovers and uses discriminative shapelets
- `LearningShapeletClassifier` - Learns shapelets via gradient descent
- `SASTClassifier` - Scalable approximate shapelet transform
- `RDSTClassifier` - Random dilated shapelet transform
**Use when**: Need interpretable discriminative patterns or phase-invariant features.
## Hybrid Classifiers
Combine multiple classification paradigms:
- `HIVECOTEV1` - Hierarchical Vote Collective of Transformation-based Ensembles (version 1)
- `HIVECOTEV2` - Enhanced version with updated components
**Use when**: Maximum accuracy required, computational resources available.
## Early Classification
Make predictions before observing entire time series:
- `TEASER` - Two-tier Early and Accurate Series Classifier
- `ProbabilityThresholdEarlyClassifier` - Prediction when confidence exceeds threshold
**Use when**: Real-time decisions needed, or observations have cost.
## Ordinal Classification
Handle ordered class labels:
- `OrdinalTDE` - Temporal dictionary ensemble for ordinal outputs
**Use when**: Classes have natural ordering (e.g., severity levels).
## Composition Tools
Build custom pipelines and ensembles:
- `ClassifierPipeline` - Chain transformers with classifiers
- `WeightedEnsembleClassifier` - Weighted combination of classifiers
- `SklearnClassifierWrapper` - Adapt sklearn classifiers for time series
## Quick Start
```python
from aeon.classification.convolution_based import RocketClassifier
from aeon.datasets import load_classification
# Load data
X_train, y_train = load_classification("GunPoint", split="train")
X_test, y_test = load_classification("GunPoint", split="test")
# Train and predict
clf = RocketClassifier()
clf.fit(X_train, y_train)
accuracy = clf.score(X_test, y_test)
```
## Algorithm Selection
- **Speed priority**: MiniRocketClassifier, Arsenal
- **Accuracy priority**: HIVECOTEV2, InceptionTimeClassifier
- **Interpretability**: ShapeletTransformClassifier, Catch22Classifier
- **Small data**: KNeighborsTimeSeriesClassifier, Distance-based methods
- **Large data**: Deep learning classifiers, ROCKET variants
+123
View File
@@ -0,0 +1,123 @@
# Time Series Clustering
Aeon provides clustering algorithms adapted for temporal data with specialized distance metrics and averaging methods.
## Partitioning Algorithms
Standard k-means/k-medoids adapted for time series:
- `TimeSeriesKMeans` - K-means with temporal distance metrics (DTW, Euclidean, etc.)
- `TimeSeriesKMedoids` - Uses actual time series as cluster centers
- `TimeSeriesKShape` - Shape-based clustering algorithm
- `TimeSeriesKernelKMeans` - Kernel-based variant for nonlinear patterns
**Use when**: Known number of clusters, spherical cluster shapes expected.
## Large Dataset Methods
Efficient clustering for large collections:
- `TimeSeriesCLARA` - Clustering Large Applications with sampling
- `TimeSeriesCLARANS` - Randomized search variant of CLARA
**Use when**: Dataset too large for standard k-medoids, need scalability.
## Elastic Distance Clustering
Specialized for alignment-based similarity:
- `KASBA` - K-means with shift-invariant elastic averaging
- `ElasticSOM` - Self-organizing map using elastic distances
**Use when**: Time series have temporal shifts or warping.
## Spectral Methods
Graph-based clustering:
- `KSpectralCentroid` - Spectral clustering with centroid computation
**Use when**: Non-convex cluster shapes, need graph-based approach.
## Deep Learning Clustering
Neural network-based clustering with auto-encoders:
- `AEFCNClusterer` - Fully convolutional auto-encoder
- `AEResNetClusterer` - Residual network auto-encoder
- `AEDCNNClusterer` - Dilated CNN auto-encoder
- `AEDRNNClusterer` - Dilated RNN auto-encoder
- `AEBiGRUClusterer` - Bidirectional GRU auto-encoder
- `AEAttentionBiGRUClusterer` - Attention-enhanced BiGRU auto-encoder
**Use when**: Large datasets, need learned representations, or complex patterns.
## Feature-Based Clustering
Transform to feature space before clustering:
- `Catch22Clusterer` - Clusters on 22 canonical features
- `SummaryClusterer` - Uses summary statistics
- `TSFreshClusterer` - Automated tsfresh features
**Use when**: Raw time series not informative, need interpretable features.
## Composition
Build custom clustering pipelines:
- `ClustererPipeline` - Chain transformers with clusterers
## Averaging Methods
Compute cluster centers for time series:
- `mean_average` - Arithmetic mean
- `ba_average` - Barycentric averaging with DTW
- `kasba_average` - Shift-invariant averaging
- `shift_invariant_average` - General shift-invariant method
**Use when**: Need representative cluster centers for visualization or initialization.
## Quick Start
```python
from aeon.clustering import TimeSeriesKMeans
from aeon.datasets import load_classification
# Load data (using classification data for clustering)
X_train, _ = load_classification("GunPoint", split="train")
# Cluster time series
clusterer = TimeSeriesKMeans(
n_clusters=3,
distance="dtw", # Use DTW distance
averaging_method="ba" # Barycentric averaging
)
labels = clusterer.fit_predict(X_train)
centers = clusterer.cluster_centers_
```
## Algorithm Selection
- **Speed priority**: TimeSeriesKMeans with Euclidean distance
- **Temporal alignment**: KASBA, TimeSeriesKMeans with DTW
- **Large datasets**: TimeSeriesCLARA, TimeSeriesCLARANS
- **Complex patterns**: Deep learning clusterers
- **Interpretability**: Catch22Clusterer, SummaryClusterer
- **Non-convex clusters**: KSpectralCentroid
## Distance Metrics
Compatible distance metrics include:
- Euclidean, Manhattan, Minkowski (lock-step)
- DTW, DDTW, WDTW (elastic with alignment)
- ERP, EDR, LCSS (edit-based)
- MSM, TWE (specialized elastic)
## Evaluation
Use clustering metrics from sklearn or aeon benchmarking:
- Silhouette score
- Davies-Bouldin index
- Calinski-Harabasz index
@@ -0,0 +1,392 @@
# Datasets and Benchmarking
Aeon provides comprehensive tools for loading datasets and benchmarking time series algorithms.
From **aeon 1.4** onward, most classification and regression archives are hosted on **Zenodo** (including the relaunched Multiverse multivariate classification archive). Loaders download on first use; cache location follows aeon defaults.
## Dataset Loading
### Task-Specific Loaders
**Classification Datasets**:
```python
from aeon.datasets import load_classification
# Load train/test split (or use load_gunpoint for this benchmark)
from aeon.datasets import load_gunpoint
X_train, y_train = load_classification("GunPoint", split="train")
X_test, y_test = load_classification("GunPoint", split="test")
# X_train, y_train = load_gunpoint(split="train")
# Load entire dataset
X, y = load_classification("GunPoint")
```
**Regression Datasets**:
```python
from aeon.datasets import load_regression
X_train, y_train = load_regression("Covid3Month", split="train")
X_test, y_test = load_regression("Covid3Month", split="test")
# Bulk download
from aeon.datasets import download_all_regression
download_all_regression() # Downloads Monash TSER archive
```
**Forecasting Datasets**:
```python
from aeon.datasets import load_forecasting
# Load from forecastingdata.org
y, X = load_forecasting("airline", return_X_y=True)
```
**Anomaly Detection Datasets**:
```python
from aeon.datasets import load_anomaly_detection
X, y = load_anomaly_detection("NAB_realKnownCause")
```
### File Format Loaders
**Load from .ts files**:
```python
from aeon.datasets import load_from_ts_file
X, y = load_from_ts_file("path/to/data.ts")
```
**Load from .tsf files**:
```python
from aeon.datasets import load_from_tsf_file
df, metadata = load_from_tsf_file("path/to/data.tsf")
```
**Load from ARFF files**:
```python
from aeon.datasets import load_from_arff_file
X, y = load_from_arff_file("path/to/data.arff")
```
**Load from TSV files**:
```python
from aeon.datasets import load_from_tsv_file
data = load_from_tsv_file("path/to/data.tsv")
```
**Load TimeEval CSV**:
```python
from aeon.datasets import load_from_timeeval_csv_file
X, y = load_from_timeeval_csv_file("path/to/timeeval.csv")
```
### Writing Datasets
**Write to .ts format**:
```python
from aeon.datasets import write_to_ts_file
write_to_ts_file(X, "output.ts", y=y, problem_name="MyDataset")
```
**Write to ARFF format**:
```python
from aeon.datasets import write_to_arff_file
write_to_arff_file(X, "output.arff", y=y)
```
## Built-in Datasets
Aeon includes several benchmark datasets for quick testing:
### Classification
- `ArrowHead` - Shape classification
- `GunPoint` - Gesture recognition
- `ItalyPowerDemand` - Energy demand
- `BasicMotions` - Motion classification
- And 100+ more from UCR/UEA archives
### Regression
- `Covid3Month` - COVID forecasting
- Various datasets from Monash TSER archive
### Segmentation
- Time series segmentation datasets
- Human activity data
- Sensor data collections
### Special Collections
- `RehabPile` - Rehabilitation data (classification & regression)
## Dataset Metadata
Get information about datasets:
```python
from aeon.datasets import get_dataset_meta_data
metadata = get_dataset_meta_data("GunPoint")
print(metadata)
# {'n_train': 50, 'n_test': 150, 'length': 150, 'n_classes': 2, ...}
```
## Benchmarking Tools
### Loading Published Results
Access pre-computed benchmark results:
```python
from aeon.benchmarking import get_estimator_results
# Get results for specific algorithm on dataset
results = get_estimator_results(
estimator_name="ROCKET",
dataset_name="GunPoint"
)
# Get all available estimators for a dataset
estimators = get_available_estimators("GunPoint")
```
### Resampling Strategies
Create reproducible train/test splits:
```python
from aeon.benchmarking import stratified_resample
# Stratified resampling maintaining class distribution
X_train, X_test, y_train, y_test = stratified_resample(
X, y,
random_state=42,
test_size=0.3
)
```
### Performance Metrics
Specialized metrics for time series tasks:
**Anomaly Detection Metrics**:
```python
from aeon.benchmarking.metrics.anomaly_detection import (
range_precision,
range_recall,
range_f_score,
range_roc_auc_score
)
# Range-based metrics for window detection
precision = range_precision(y_true, y_pred, alpha=0.5)
recall = range_recall(y_true, y_pred, alpha=0.5)
f1 = range_f_score(y_true, y_pred, alpha=0.5)
auc = range_roc_auc_score(y_true, y_scores)
```
**Clustering Metrics**:
```python
from aeon.benchmarking.metrics.clustering import clustering_accuracy
# Clustering accuracy with label matching
accuracy = clustering_accuracy(y_true, y_pred)
```
**Segmentation Metrics**:
```python
from aeon.benchmarking.metrics.segmentation import (
count_error,
hausdorff_error
)
# Number of change points difference
count_err = count_error(y_true, y_pred)
# Maximum distance between predicted and true change points
hausdorff_err = hausdorff_error(y_true, y_pred)
```
### Statistical Testing
Post-hoc analysis for algorithm comparison:
```python
from aeon.benchmarking import (
nemenyi_test,
wilcoxon_test
)
# Nemenyi test for multiple algorithms
results = nemenyi_test(scores_matrix, alpha=0.05)
# Pairwise Wilcoxon signed-rank test
stat, p_value = wilcoxon_test(scores_alg1, scores_alg2)
```
## Benchmark Collections
### UCR/UEA Time Series Archives
Access to comprehensive benchmark repositories:
```python
# Classification: 112 univariate + 30 multivariate datasets
X_train, y_train = load_classification("Chinatown", split="train")
# Automatically downloads from timeseriesclassification.com
```
### Monash Forecasting Archive
```python
# Load forecasting datasets
y = load_forecasting("nn5_daily", return_X_y=False)
```
### Published Benchmark Results
Pre-computed results from major competitions:
- 2017 Univariate Bake-off
- 2021 Multivariate Classification
- 2023 Univariate Bake-off
## Workflow Example
Complete benchmarking workflow:
```python
from aeon.datasets import load_classification
from aeon.classification.convolution_based import RocketClassifier
from aeon.benchmarking import get_estimator_results
from sklearn.metrics import accuracy_score
import numpy as np
# Load dataset
dataset_name = "GunPoint"
X_train, y_train = load_classification(dataset_name, split="train")
X_test, y_test = load_classification(dataset_name, split="test")
# Train model
clf = RocketClassifier(n_kernels=10000, random_state=42)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
# Evaluate
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.4f}")
# Compare with published results
published = get_estimator_results("ROCKET", dataset_name)
print(f"Published ROCKET accuracy: {published['accuracy']:.4f}")
```
## Best Practices
### 1. Use Standard Splits
For reproducibility, use provided train/test splits:
```python
# Good: Use standard splits
X_train, y_train = load_classification("GunPoint", split="train")
X_test, y_test = load_classification("GunPoint", split="test")
# Avoid: Creating custom splits
X, y = load_classification("GunPoint")
X_train, X_test, y_train, y_test = train_test_split(X, y)
```
### 2. Set Random Seeds
Ensure reproducibility:
```python
clf = RocketClassifier(random_state=42)
results = stratified_resample(X, y, random_state=42)
```
### 3. Report Multiple Metrics
Don't rely on single metric:
```python
from sklearn.metrics import accuracy_score, f1_score, precision_score
accuracy = accuracy_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred, average='weighted')
precision = precision_score(y_test, y_pred, average='weighted')
```
### 4. Cross-Validation
For robust evaluation on small datasets:
```python
from sklearn.model_selection import cross_val_score
scores = cross_val_score(
clf, X_train, y_train,
cv=5,
scoring='accuracy'
)
print(f"CV Accuracy: {scores.mean():.4f} (+/- {scores.std():.4f})")
```
### 5. Compare Against Baselines
Always compare with simple baselines:
```python
from aeon.classification.distance_based import KNeighborsTimeSeriesClassifier
# Simple baseline: 1-NN with Euclidean distance
baseline = KNeighborsTimeSeriesClassifier(n_neighbors=1, distance="euclidean")
baseline.fit(X_train, y_train)
baseline_acc = baseline.score(X_test, y_test)
print(f"Baseline: {baseline_acc:.4f}")
print(f"Your model: {accuracy:.4f}")
```
### 6. Statistical Significance
Test if improvements are statistically significant:
```python
from aeon.benchmarking import wilcoxon_test
# Run on multiple datasets
accuracies_alg1 = [0.85, 0.92, 0.78, 0.88]
accuracies_alg2 = [0.83, 0.90, 0.76, 0.86]
stat, p_value = wilcoxon_test(accuracies_alg1, accuracies_alg2)
if p_value < 0.05:
print("Difference is statistically significant")
```
## Dataset Discovery
Find datasets matching criteria:
```python
# List all available classification datasets
from aeon.datasets import get_available_datasets
datasets = get_available_datasets("classification")
print(f"Found {len(datasets)} classification datasets")
# Filter by properties
univariate_datasets = [
d for d in datasets
if get_dataset_meta_data(d)['n_channels'] == 1
]
```
+256
View File
@@ -0,0 +1,256 @@
# Distance Metrics
Aeon provides specialized distance functions for measuring similarity between time series, compatible with both aeon and scikit-learn estimators.
## Distance Categories
### Elastic Distances
Allow flexible temporal alignment between series:
**Dynamic Time Warping Family:**
- `dtw` - Classic Dynamic Time Warping
- `ddtw` - Derivative DTW (compares derivatives)
- `wdtw` - Weighted DTW (penalizes warping by location)
- `wddtw` - Weighted Derivative DTW
- `shape_dtw` - Shape-based DTW
**Edit-Based:**
- `erp` - Edit distance with Real Penalty
- `edr` - Edit Distance on Real sequences
- `lcss` - Longest Common SubSequence
- `twe` - Time Warp Edit distance
**Specialized:**
- `msm` - Move-Split-Merge distance
- `adtw` - Amerced DTW
- `sbd` - Shape-Based Distance
**Use when**: Time series may have temporal shifts, speed variations, or phase differences.
### Lock-Step Distances
Compare time series point-by-point without alignment:
- `euclidean` - Euclidean distance (L2 norm)
- `manhattan` - Manhattan distance (L1 norm)
- `minkowski` - Generalized Minkowski distance (Lp norm)
- `squared` - Squared Euclidean distance
**Use when**: Series already aligned, need computational speed, or no temporal warping expected.
## Usage Patterns
### Computing Single Distance
```python
from aeon.distances import dtw_distance
# Distance between two time series
distance = dtw_distance(x, y)
# With window constraint (Sakoe-Chiba band)
distance = dtw_distance(x, y, window=0.1)
```
### Pairwise Distance Matrix
```python
from aeon.distances import dtw_pairwise_distance
# All pairwise distances in collection
X = [series1, series2, series3, series4]
distance_matrix = dtw_pairwise_distance(X)
# Cross-collection distances
distance_matrix = dtw_pairwise_distance(X_train, X_test)
```
### Cost Matrix and Alignment Path
```python
from aeon.distances import dtw_cost_matrix, dtw_alignment_path
# Get full cost matrix
cost_matrix = dtw_cost_matrix(x, y)
# Get optimal alignment path
path = dtw_alignment_path(x, y)
# Returns indices: [(0,0), (1,1), (2,1), (2,2), ...]
```
### Using with Estimators
```python
from aeon.classification.distance_based import KNeighborsTimeSeriesClassifier
# Use DTW distance in classifier
clf = KNeighborsTimeSeriesClassifier(
n_neighbors=5,
distance="dtw",
distance_params={"window": 0.2}
)
clf.fit(X_train, y_train)
```
## Distance Parameters
### Window Constraints
Limit warping path deviation (improves speed and prevents pathological warping):
```python
# Sakoe-Chiba band: window as fraction of series length
dtw_distance(x, y, window=0.1) # Allow 10% deviation
# Itakura parallelogram: slopes constrain path
dtw_distance(x, y, itakura_max_slope=2.0)
```
### Normalization
Control whether to z-normalize series before distance computation:
```python
# Most elastic distances support normalization
distance = dtw_distance(x, y, normalize=True)
```
### Distance-Specific Parameters
```python
# ERP: penalty for gaps
distance = erp_distance(x, y, g=0.5)
# TWE: stiffness and penalty parameters
distance = twe_distance(x, y, nu=0.001, lmbda=1.0)
# LCSS: epsilon threshold for matching
distance = lcss_distance(x, y, epsilon=0.5)
```
## Algorithm Selection
### By Use Case:
**Temporal misalignment**: DTW, DDTW, WDTW
**Speed variations**: DTW with window constraint
**Shape similarity**: Shape DTW, SBD
**Edit operations**: ERP, EDR, LCSS
**Derivative matching**: DDTW
**Computational speed**: Euclidean, Manhattan
**Outlier robustness**: Manhattan, LCSS
### By Computational Cost:
**Fastest**: Euclidean (O(n))
**Fast**: Constrained DTW (O(nw) where w is window)
**Medium**: Full DTW (O(n²))
**Slower**: Complex elastic distances (ERP, TWE, MSM)
## Quick Reference Table
| Distance | Alignment | Speed | Robustness | Interpretability |
|----------|-----------|-------|------------|------------------|
| Euclidean | Lock-step | Very Fast | Low | High |
| DTW | Elastic | Medium | Medium | Medium |
| DDTW | Elastic | Medium | High | Medium |
| WDTW | Elastic | Medium | Medium | Medium |
| ERP | Edit-based | Slow | High | Low |
| LCSS | Edit-based | Slow | Very High | Low |
| Shape DTW | Elastic | Medium | Medium | High |
## Best Practices
### 1. Normalization
Most distances sensitive to scale; normalize when appropriate:
```python
from aeon.transformations.collection import Normalizer
normalizer = Normalizer()
X_normalized = normalizer.fit_transform(X)
```
### 2. Window Constraints
For DTW variants, use window constraints for speed and better generalization:
```python
# Start with 10-20% window
distance = dtw_distance(x, y, window=0.1)
```
### 3. Series Length
- Equal-length required: Most lock-step distances
- Unequal-length supported: Elastic distances (DTW, ERP, etc.)
### 4. Multivariate Series
Most distances support multivariate time series:
```python
# x.shape = (n_channels, n_timepoints)
distance = dtw_distance(x_multivariate, y_multivariate)
```
### 5. Performance Optimization
- Use numba-compiled implementations (default in aeon)
- Consider lock-step distances if alignment not needed
- Use windowed DTW instead of full DTW
- Precompute distance matrices for repeated use
### 6. Choosing the Right Distance
```python
# Quick decision tree:
if series_aligned:
use_distance = "euclidean"
elif need_speed:
use_distance = "dtw" # with window constraint
elif temporal_shifts_expected:
use_distance = "dtw" or "shape_dtw"
elif outliers_present:
use_distance = "lcss" or "manhattan"
elif derivatives_matter:
use_distance = "ddtw" or "wddtw"
```
## Integration with scikit-learn
Aeon distances work with sklearn estimators:
```python
from sklearn.neighbors import KNeighborsClassifier
from aeon.distances import dtw_pairwise_distance
# Precompute distance matrix
X_train_distances = dtw_pairwise_distance(X_train)
# Use with sklearn
clf = KNeighborsClassifier(metric='precomputed')
clf.fit(X_train_distances, y_train)
```
## Available Distance Functions
Get list of all available distances:
```python
from aeon.distances import get_distance_function_names
print(get_distance_function_names())
# ['dtw', 'ddtw', 'wdtw', 'euclidean', 'erp', 'edr', ...]
```
Retrieve specific distance function:
```python
from aeon.distances import get_distance_function
distance_func = get_distance_function("dtw")
result = distance_func(x, y, window=0.1)
```
+109
View File
@@ -0,0 +1,109 @@
# Time Series Forecasting
The `aeon.forecasting` module provides forecasters for univariate and multivariate series. In aeon **1.x**, forecasting was rebuilt on array-native `BaseForecaster` estimators (replacing the old sktime-style `fh` API). The module is marked **experimental** — expect API evolution between releases.
Import paths (aeon 1.4+):
- `from aeon.forecasting import NaiveForecaster, RegressionForecaster`
- `from aeon.forecasting.stats import ARIMA, AutoARIMA, ETS, AutoETS, Theta, TAR, AutoTAR, TVP`
- `from aeon.forecasting.deep_learning import TCNForecaster, DeepARForecaster`
List all forecasters: `aeon.utils.discovery.all_estimators(type_filter="forecaster")`.
## Naive and Baseline Methods
- `NaiveForecaster``strategy` in `"last"`, `"mean"`, `"seasonal_last"`; set `horizon` and `seasonal_period` in the constructor
- **Use when**: Establishing baselines or simple patterns
## Statistical Models
- `ARIMA` / `AutoARIMA``p`, `d`, `q` orders (not `order=(p,d,q)`); supports exogenous variables via `exog`
- `ETS` / `AutoETS` — exponential smoothing (native implementations in aeon 1.4+)
- `Theta` — classical Theta method
- `TAR` / `AutoTAR` — threshold autoregressive models for regime switching
- `TVP` — time-varying parameter (Kalman-style) models
## Deep Learning Forecasters
Requires `aeon[all_extras]` (PyTorch stack):
- `TCNForecaster` — temporal convolutional network
- `DeepARForecaster` — probabilistic RNN forecaster (replaces legacy `DeepARNetwork` naming)
## Regression-Based Forecasting
- `RegressionForecaster` — sliding `window` over history, `horizon` steps ahead, any sklearn/aeon regressor
## Quick Start
```python
import numpy as np
from aeon.forecasting import NaiveForecaster
from aeon.forecasting.stats import ARIMA, AutoETS
y = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0])
# Naive — horizon is a constructor argument; predict(y) forecasts from series y
naive = NaiveForecaster(strategy="last", horizon=3)
naive.fit(y)
pred_naive = naive.predict(y)
# ARIMA — one-step by default; multi-step via iterative_forecast
arima = ARIMA(p=1, d=1, q=1)
arima.fit(y)
pred_arima = arima.iterative_forecast(y, prediction_horizon=3)
# Auto model selection
auto_ets = AutoETS(horizon=3)
auto_ets.fit(y)
pred_ets = auto_ets.predict(y)
```
## Forecasting Horizon
In aeon 1.x, set `horizon` on the estimator (number of steps ahead). `predict(y)` returns the forecast `horizon` steps beyond the end of `y`.
Multi-step strategies:
- **`iterative_forecast(y, prediction_horizon)`** — reuse one fitted model, feed predictions back (ARIMA, many stats models)
- **`direct_forecast(y, prediction_horizon)`** — refit per horizon (requires `capability:horizon` tag; e.g. `RegressionForecaster`)
- **`NaiveForecaster`** — set `horizon>1` directly when `strategy` supports it
There is no `ForecastingHorizon` / `fh=[1,2,3]` API in aeon 1.x.
## Model Selection
- **Baseline**: `NaiveForecaster(strategy="seasonal_last", seasonal_period=12, horizon=h)`
- **Linear / stationary**: `ARIMA`, `AutoARIMA`
- **Trend + seasonality**: `ETS`, `AutoETS`
- **Regime changes**: `TAR`, `AutoTAR`
- **Complex patterns**: `TCNForecaster`, `RegressionForecaster` with aeon regressors
- **Probabilistic**: `DeepARForecaster`
## Evaluation Metrics
Use scikit-learn or standard numpy metrics on hold-out forecasts:
```python
from sklearn.metrics import mean_absolute_error, mean_squared_error
mae = mean_absolute_error(y_true, y_pred)
mse = mean_squared_error(y_true, y_pred)
```
## Exogenous Variables
Pass aligned exogenous arrays as `exog` (not `X`):
```python
forecaster.fit(y_train, exog=exog_train)
y_pred = forecaster.predict(y_test, exog=exog_test)
```
## Base Classes
- `BaseForecaster``horizon`, `axis`, `fit`, `predict`, `forecast`
- `DirectForecastingMixin` / `IterativeForecastingMixin` — multi-step helpers
- `BaseDeepForecaster` — deep learning forecasters
Extend `BaseForecaster` for custom forecasters.
+289
View File
@@ -0,0 +1,289 @@
# Deep Learning Networks
Aeon provides neural network architectures specifically designed for time series tasks. These networks serve as building blocks for classification, regression, clustering, and forecasting.
## Core Network Architectures
### Convolutional Networks
**FCNNetwork** - Fully Convolutional Network
- Three convolutional blocks with batch normalization
- Global average pooling for dimensionality reduction
- **Use when**: Need simple yet effective CNN baseline
**ResNetNetwork** - Residual Network
- Residual blocks with skip connections
- Prevents vanishing gradients in deep networks
- **Use when**: Deep networks needed, training stability important
**InceptionNetwork** - Inception Modules
- Multi-scale feature extraction with parallel convolutions
- Different kernel sizes capture patterns at various scales
- **Use when**: Patterns exist at multiple temporal scales
**TimeCNNNetwork** - Standard CNN
- Basic convolutional architecture
- **Use when**: Simple CNN sufficient, interpretability valued
**DisjointCNNNetwork** - Separate Pathways
- Disjoint convolutional pathways
- **Use when**: Different feature extraction strategies needed
**DCNNNetwork** - Dilated CNN
- Dilated convolutions for large receptive fields
- **Use when**: Long-range dependencies without many layers
### Recurrent Networks
**RecurrentNetwork** - RNN/LSTM/GRU
- Configurable cell type (RNN, LSTM, GRU)
- Sequential modeling of temporal dependencies
- **Use when**: Sequential dependencies critical, variable-length series
### Temporal Convolutional Network
**TCNNetwork** - Temporal Convolutional Network
- Dilated causal convolutions
- Large receptive field without recurrence
- **Use when**: Long sequences, need parallelizable architecture
### Multi-Layer Perceptron
**MLPNetwork** - Basic Feedforward
- Simple fully-connected layers
- Flattens time series before processing
- **Use when**: Baseline needed, computational limits, or simple patterns
## Encoder-Based Architectures
Networks designed for representation learning and clustering.
### Autoencoder Variants
**EncoderNetwork** - Generic Encoder
- Flexible encoder structure
- **Use when**: Custom encoding needed
**AEFCNNetwork** - FCN-based Autoencoder
- Fully convolutional encoder-decoder
- **Use when**: Need convolutional representation learning
**AEResNetNetwork** - ResNet Autoencoder
- Residual blocks in encoder-decoder
- **Use when**: Deep autoencoding with skip connections
**AEDCNNNetwork** - Dilated CNN Autoencoder
- Dilated convolutions for compression
- **Use when**: Need large receptive field in autoencoder
**AEDRNNNetwork** - Dilated RNN Autoencoder
- Dilated recurrent connections
- **Use when**: Sequential patterns with long-range dependencies
**AEBiGRUNetwork** - Bidirectional GRU
- Bidirectional recurrent encoding
- **Use when**: Context from both directions helpful
**AEAttentionBiGRUNetwork** - Attention + BiGRU
- Attention mechanism on BiGRU outputs
- **Use when**: Need to focus on important time steps
## Specialized Architectures
**LITENetwork** - Lightweight Inception Time Ensemble
- Efficient inception-based architecture
- LITEMV variant for multivariate series
- **Use when**: Need efficiency with strong performance
**DeepARForecaster** - Probabilistic forecasting (use via `aeon.forecasting.deep_learning`)
- Autoregressive RNN for forecasting
- Produces probabilistic predictions
- **Use when**: Need forecast uncertainty quantification
## Usage with Estimators
Networks are typically used within estimators, not directly:
```python
from aeon.classification.deep_learning import FCNClassifier
from aeon.regression.deep_learning import ResNetRegressor
from aeon.clustering.deep_learning import AEFCNClusterer
# Classification with FCN
clf = FCNClassifier(n_epochs=100, batch_size=16)
clf.fit(X_train, y_train)
# Regression with ResNet
reg = ResNetRegressor(n_epochs=100)
reg.fit(X_train, y_train)
# Clustering with autoencoder
clusterer = AEFCNClusterer(n_clusters=3, n_epochs=100)
labels = clusterer.fit_predict(X_train)
```
## Custom Network Configuration
Many networks accept configuration parameters:
```python
# Configure FCN layers
clf = FCNClassifier(
n_epochs=200,
batch_size=32,
kernel_size=[7, 5, 3], # Kernel sizes for each layer
n_filters=[128, 256, 128], # Filters per layer
learning_rate=0.001
)
```
## Base Classes
- `BaseDeepLearningNetwork` - Abstract base for all networks
- `BaseDeepRegressor` - Base for deep regression
- `BaseDeepClassifier` - Base for deep classification
- `BaseDeepForecaster` - Base for deep forecasting
Extend these to implement custom architectures.
## Training Considerations
### Hyperparameters
Key hyperparameters to tune:
- `n_epochs` - Training iterations (50-200 typical)
- `batch_size` - Samples per batch (16-64 typical)
- `learning_rate` - Step size (0.0001-0.01)
- Network-specific: layers, filters, kernel sizes
### Callbacks
Many networks support callbacks for training monitoring:
```python
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
clf = FCNClassifier(
n_epochs=200,
callbacks=[
EarlyStopping(patience=20, restore_best_weights=True),
ReduceLROnPlateau(patience=10, factor=0.5)
]
)
```
### GPU Acceleration
Deep learning networks benefit from GPU:
```python
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0' # Use first GPU
# Networks automatically use GPU if available
clf = InceptionTimeClassifier(n_epochs=100)
clf.fit(X_train, y_train)
```
## Architecture Selection
### By Task:
**Classification**: InceptionNetwork, ResNetNetwork, FCNNetwork
**Regression**: InceptionNetwork, ResNetNetwork, TCNNetwork
**Forecasting**: TCNForecaster, DeepARForecaster, RecurrentNetwork
**Clustering**: AEFCNNetwork, AEResNetNetwork, AEAttentionBiGRUNetwork
### By Data Characteristics:
**Long sequences**: TCNNetwork, DCNNNetwork (dilated convolutions)
**Short sequences**: MLPNetwork, FCNNetwork
**Multivariate**: InceptionNetwork, FCNNetwork, LITENetwork
**Variable length**: RecurrentNetwork with masking
**Multi-scale patterns**: InceptionNetwork
### By Computational Resources:
**Limited compute**: MLPNetwork, LITENetwork
**Moderate compute**: FCNNetwork, TimeCNNNetwork
**High compute available**: InceptionNetwork, ResNetNetwork
**GPU available**: Any deep network (major speedup)
## Best Practices
### 1. Data Preparation
Normalize input data:
```python
from aeon.transformations.collection import Normalizer
normalizer = Normalizer()
X_train_norm = normalizer.fit_transform(X_train)
X_test_norm = normalizer.transform(X_test)
```
### 2. Training/Validation Split
Use validation set for early stopping:
```python
from sklearn.model_selection import train_test_split
X_train_fit, X_val, y_train_fit, y_val = train_test_split(
X_train, y_train, test_size=0.2, stratify=y_train
)
clf = FCNClassifier(n_epochs=200)
clf.fit(X_train_fit, y_train_fit, validation_data=(X_val, y_val))
```
### 3. Start Simple
Begin with simpler architectures before complex ones:
1. Try MLPNetwork or FCNNetwork first
2. If insufficient, try ResNetNetwork or InceptionNetwork
3. Consider ensembles if single models insufficient
### 4. Hyperparameter Tuning
Use grid search or random search:
```python
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_epochs': [100, 200],
'batch_size': [16, 32],
'learning_rate': [0.001, 0.0001]
}
clf = FCNClassifier()
grid = GridSearchCV(clf, param_grid, cv=3)
grid.fit(X_train, y_train)
```
### 5. Regularization
Prevent overfitting:
- Use dropout (if network supports)
- Early stopping
- Data augmentation (if available)
- Reduce model complexity
### 6. Reproducibility
Set random seeds:
```python
import numpy as np
import random
import tensorflow as tf
seed = 42
np.random.seed(seed)
random.seed(seed)
tf.random.set_seed(seed)
```
+118
View File
@@ -0,0 +1,118 @@
# Time Series Regression
Aeon provides time series regressors across 9 categories for predicting continuous values from temporal sequences.
## Convolution-Based Regressors
Apply convolutional kernels for feature extraction:
- `HydraRegressor` - Multi-resolution dilated convolutions
- `RocketRegressor` - Random convolutional kernels
- `MiniRocketRegressor` - Simplified ROCKET for speed
- `MultiRocketRegressor` - Combined ROCKET variants
- `MultiRocketHydraRegressor` - Merges ROCKET and Hydra approaches
**Use when**: Need fast regression with strong baseline performance.
## Deep Learning Regressors
Neural architectures for end-to-end temporal regression:
- `FCNRegressor` - Fully convolutional network
- `ResNetRegressor` - Residual blocks with skip connections
- `InceptionTimeRegressor` - Multi-scale inception modules
- `TimeCNNRegressor` - Standard CNN architecture
- `RecurrentRegressor` - RNN/LSTM/GRU variants
- `MLPRegressor` - Multi-layer perceptron
- `EncoderRegressor` - Generic encoder wrapper
- `LITERegressor` - Lightweight inception time ensemble
- `DisjointCNNRegressor` - Specialized CNN architecture
**Use when**: Large datasets, complex patterns, or need feature learning.
## Distance-Based Regressors
k-nearest neighbors with temporal distance metrics:
- `KNeighborsTimeSeriesRegressor` - k-NN with DTW, LCSS, ERP, or other distances
**Use when**: Small datasets, local similarity patterns, or interpretable predictions.
## Feature-Based Regressors
Extract statistical features before regression:
- `Catch22Regressor` - 22 canonical time-series characteristics
- `FreshPRINCERegressor` - Pipeline combining multiple feature extractors
- `SummaryRegressor` - Summary statistics features
- `TSFreshRegressor` - Automated tsfresh feature extraction
**Use when**: Need interpretable features or domain-specific feature engineering.
## Hybrid Regressors
Combine multiple approaches:
- `RISTRegressor` - Randomized Interval-Shapelet Transformation
**Use when**: Benefit from combining interval and shapelet methods.
## Interval-Based Regressors
Extract features from time intervals:
- `CanonicalIntervalForestRegressor` - Random intervals with decision trees
- `DrCIFRegressor` - Diverse Representation CIF
- `TimeSeriesForestRegressor` - Random interval ensemble
- `RandomIntervalRegressor` - Simple interval-based approach
- `RandomIntervalSpectralEnsembleRegressor` - Spectral interval features
- `QUANTRegressor` - Quantile-based interval features
**Use when**: Predictive patterns occur in specific time windows.
## Shapelet-Based Regressors
Use discriminative subsequences for prediction:
- `RDSTRegressor` - Random Dilated Shapelet Transform
**Use when**: Need phase-invariant discriminative patterns.
## Composition Tools
Build custom regression pipelines:
- `RegressorPipeline` - Chain transformers with regressors
- `RegressorEnsemble` - Weighted ensemble with learnable weights
- `SklearnRegressorWrapper` - Adapt sklearn regressors for time series
## Utilities
- `DummyRegressor` - Baseline strategies (mean, median)
- `BaseRegressor` - Abstract base for custom regressors
- `BaseDeepRegressor` - Base for deep learning regressors
## Quick Start
```python
from aeon.regression.convolution_based import RocketRegressor
from aeon.datasets import load_regression
# Load data
X_train, y_train = load_regression("Covid3Month", split="train")
X_test, y_test = load_regression("Covid3Month", split="test")
# Train and predict
reg = RocketRegressor()
reg.fit(X_train, y_train)
predictions = reg.predict(X_test)
```
## Algorithm Selection
- **Speed priority**: MiniRocketRegressor
- **Accuracy priority**: InceptionTimeRegressor, MultiRocketHydraRegressor
- **Interpretability**: Catch22Regressor, SummaryRegressor
- **Small data**: KNeighborsTimeSeriesRegressor
- **Large data**: Deep learning regressors, ROCKET variants
- **Interval patterns**: DrCIFRegressor, CanonicalIntervalForestRegressor
+163
View File
@@ -0,0 +1,163 @@
# Time Series Segmentation
Aeon provides algorithms to partition time series into regions with distinct characteristics, identifying change points and boundaries.
## Segmentation Algorithms
### Binary Segmentation
- `BinSegmenter` - Recursive binary segmentation
- Iteratively splits series at most significant change points
- Parameters: `n_segments`, `cost_function`
- **Use when**: Known number of segments, hierarchical structure
### Classification-Based
- `ClaSPSegmenter` - Classification Score Profile
- Uses classification performance to identify boundaries
- Discovers segments where classification distinguishes neighbors
- **Use when**: Segments have different temporal patterns
### Fast Pattern-Based
- `FLUSSSegmenter` - Fast Low-cost Unipotent Semantic Segmentation
- Efficient semantic segmentation using arc crossings
- Based on matrix profile
- **Use when**: Large time series, need speed and pattern discovery
### Information Theory
- `InformationGainSegmenter` - Information gain maximization
- Finds boundaries maximizing information gain
- **Use when**: Statistical differences between segments
### Gaussian Modeling
- `GreedyGaussianSegmenter` - Greedy Gaussian approximation
- Models segments as Gaussian distributions
- Incrementally adds change points
- **Use when**: Segments follow Gaussian distributions
### Hierarchical Agglomerative
- `EAggloSegmenter` - Bottom-up merging approach
- Estimates change points via agglomeration
- **Use when**: Want hierarchical segmentation structure
### Hidden Markov Models
- `HMMSegmenter` - HMM with Viterbi decoding
- Probabilistic state-based segmentation
- **Use when**: Segments represent hidden states
### Dimensionality-Based
- `HidalgoSegmenter` - Heterogeneous Intrinsic Dimensionality Algorithm
- Detects changes in local dimensionality
- **Use when**: Dimensionality shifts between segments
### Baseline
- `RandomSegmenter` - Random change point generation
- **Use when**: Need null hypothesis baseline
## Quick Start
```python
from aeon.segmentation import ClaSPSegmenter
import numpy as np
# Create time series with regime changes
y = np.concatenate([
np.sin(np.linspace(0, 10, 100)), # Segment 1
np.cos(np.linspace(0, 10, 100)), # Segment 2
np.sin(2 * np.linspace(0, 10, 100)) # Segment 3
])
# Segment the series
segmenter = ClaSPSegmenter()
change_points = segmenter.fit_predict(y)
print(f"Detected change points: {change_points}")
```
## Output Format
Segmenters return change point indices:
```python
# change_points = [100, 200] # Boundaries between segments
# This divides series into: [0:100], [100:200], [200:end]
```
## Algorithm Selection
- **Speed priority**: FLUSSSegmenter, BinSegmenter
- **Accuracy priority**: ClaSPSegmenter, HMMSegmenter
- **Known segment count**: BinSegmenter with n_segments parameter
- **Unknown segment count**: ClaSPSegmenter, InformationGainSegmenter
- **Pattern changes**: FLUSSSegmenter, ClaSPSegmenter
- **Statistical changes**: InformationGainSegmenter, GreedyGaussianSegmenter
- **State transitions**: HMMSegmenter
## Common Use Cases
### Regime Change Detection
Identify when time series behavior fundamentally changes:
```python
from aeon.segmentation import InformationGainSegmenter
segmenter = InformationGainSegmenter(k=3) # Up to 3 change points
change_points = segmenter.fit_predict(stock_prices)
```
### Activity Segmentation
Segment sensor data into activities:
```python
from aeon.segmentation import ClaSPSegmenter
segmenter = ClaSPSegmenter()
boundaries = segmenter.fit_predict(accelerometer_data)
```
### Seasonal Boundary Detection
Find season transitions in time series:
```python
from aeon.segmentation import HMMSegmenter
segmenter = HMMSegmenter(n_states=4) # 4 seasons
segments = segmenter.fit_predict(temperature_data)
```
## Evaluation Metrics
Use segmentation quality metrics:
```python
from aeon.benchmarking.metrics.segmentation import (
count_error,
hausdorff_error
)
# Count error: difference in number of change points
count_err = count_error(y_true, y_pred)
# Hausdorff: maximum distance between predicted and true points
hausdorff_err = hausdorff_error(y_true, y_pred)
```
## Best Practices
1. **Normalize data**: Ensures change detection not dominated by scale
2. **Choose appropriate metric**: Different algorithms optimize different criteria
3. **Validate segments**: Visualize to verify meaningful boundaries
4. **Handle noise**: Consider smoothing before segmentation
5. **Domain knowledge**: Use expected segment count if known
6. **Parameter tuning**: Adjust sensitivity parameters (thresholds, penalties)
## Visualization
```python
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 4))
plt.plot(y, label='Time Series')
for cp in change_points:
plt.axvline(cp, color='r', linestyle='--', label='Change Point')
plt.legend()
plt.show()
```
+187
View File
@@ -0,0 +1,187 @@
# Similarity Search
Aeon provides tools for finding similar patterns within and across time series, including subsequence search, motif discovery, and approximate nearest neighbors.
## Subsequence Nearest Neighbors (SNN)
Find most similar subsequences within a time series.
### MASS Algorithm
- `MassSNN` - Mueen's Algorithm for Similarity Search
- Fast normalized cross-correlation for similarity
- Computes distance profile efficiently
- **Use when**: Need exact nearest neighbor distances, large series
### STOMP-Based Motif Discovery
- `StompMotif` - Discovers recurring patterns (motifs)
- Finds top-k most similar subsequence pairs
- Based on matrix profile computation
- **Use when**: Want to discover repeated patterns
### Brute Force Baseline
- `DummySNN` - Exhaustive distance computation
- Computes all pairwise distances
- **Use when**: Small series, need exact baseline
## Collection-Level Search
Find similar time series across collections.
### Approximate Nearest Neighbors (ANN)
- `RandomProjectionIndexANN` - Locality-sensitive hashing
- Uses random projections with cosine similarity
- Builds index for fast approximate search
- **Use when**: Large collection, speed more important than exactness
## Quick Start: Motif Discovery
```python
from aeon.similarity_search import StompMotif
import numpy as np
# Create time series with repeated patterns
pattern = np.sin(np.linspace(0, 2*np.pi, 50))
y = np.concatenate([
pattern + np.random.normal(0, 0.1, 50),
np.random.normal(0, 1, 100),
pattern + np.random.normal(0, 0.1, 50),
np.random.normal(0, 1, 100)
])
# Find top-3 motifs
motif_finder = StompMotif(window_size=50, k=3)
motifs = motif_finder.fit_predict(y)
# motifs contains indices of motif occurrences
for i, (idx1, idx2) in enumerate(motifs):
print(f"Motif {i+1} at positions {idx1} and {idx2}")
```
## Quick Start: Subsequence Search
```python
from aeon.similarity_search import MassSNN
import numpy as np
# Time series to search within
y = np.sin(np.linspace(0, 20, 500))
# Query subsequence
query = np.sin(np.linspace(0, 2, 50))
# Find nearest subsequences
searcher = MassSNN()
distances = searcher.fit_transform(y, query)
# Find best match
best_match_idx = np.argmin(distances)
print(f"Best match at index {best_match_idx}")
```
## Quick Start: Approximate NN on Collections
```python
from aeon.similarity_search import RandomProjectionIndexANN
from aeon.datasets import load_classification
# Load time series collection
X_train, _ = load_classification("GunPoint", split="train")
# Build index
ann = RandomProjectionIndexANN(n_projections=8, n_bits=4)
ann.fit(X_train)
# Find approximate nearest neighbors
query = X_train[0]
neighbors, distances = ann.kneighbors(query, k=5)
```
## Matrix Profile
The matrix profile is a fundamental data structure for many similarity search tasks:
- **Distance Profile**: Distances from a query to all subsequences
- **Matrix Profile**: Minimum distance for each subsequence to any other
- **Motif**: Pair of subsequences with minimum distance
- **Discord**: Subsequence with maximum minimum distance (anomaly)
```python
from aeon.similarity_search import StompMotif
# Compute matrix profile and find motifs/discords
mp = StompMotif(window_size=50)
mp.fit(y)
# Access matrix profile
profile = mp.matrix_profile_
profile_indices = mp.matrix_profile_index_
# Find discords (anomalies)
discord_idx = np.argmax(profile)
```
## Algorithm Selection
- **Exact subsequence search**: MassSNN
- **Motif discovery**: StompMotif
- **Anomaly detection**: Matrix profile (see anomaly_detection.md)
- **Fast approximate search**: RandomProjectionIndexANN
- **Small data**: DummySNN for exact results
## Use Cases
### Pattern Matching
Find where a pattern occurs in a long series:
```python
# Find heartbeat pattern in ECG data
searcher = MassSNN()
distances = searcher.fit_transform(ecg_data, heartbeat_pattern)
occurrences = np.where(distances < threshold)[0]
```
### Motif Discovery
Identify recurring patterns:
```python
# Find repeated behavioral patterns
motif_finder = StompMotif(window_size=100, k=5)
motifs = motif_finder.fit_predict(activity_data)
```
### Time Series Retrieval
Find similar time series in database:
```python
# Build searchable index
ann = RandomProjectionIndexANN()
ann.fit(time_series_database)
# Query for similar series
neighbors = ann.kneighbors(query_series, k=10)
```
## Best Practices
1. **Window size**: Critical parameter for subsequence methods
- Too small: Captures noise
- Too large: Misses fine-grained patterns
- Rule of thumb: 10-20% of series length
2. **Normalization**: Most methods assume z-normalized subsequences
- Handles amplitude variations
- Focus on shape similarity
3. **Distance metrics**: Different metrics for different needs
- Euclidean: Fast, shape-based
- DTW: Handles temporal warping
- Cosine: Scale-invariant
4. **Exclusion zone**: For motif discovery, exclude trivial matches
- Typically set to 0.5-1.0 × window_size
- Prevents finding overlapping occurrences
5. **Performance**:
- MASS is O(n log n) vs O(n²) brute force
- ANN trades accuracy for speed
- GPU acceleration available for some methods
+246
View File
@@ -0,0 +1,246 @@
# Transformations
Aeon provides extensive transformation capabilities for preprocessing, feature extraction, and representation learning from time series data.
## Transformation Types
Aeon distinguishes between:
- **CollectionTransformers**: Transform multiple time series (collections)
- **SeriesTransformers**: Transform individual time series
## Collection Transformers
### Convolution-Based Feature Extraction
Fast, scalable feature generation using random kernels:
- `RocketTransformer` - Random convolutional kernels
- `MiniRocketTransformer` - Simplified ROCKET for speed
- `MultiRocketTransformer` - Enhanced ROCKET variant
- `HydraTransformer` - Multi-resolution dilated convolutions
- `MultiRocketHydraTransformer` - Combines ROCKET and Hydra
- `ROCKETGPU` - GPU-accelerated variant
**Use when**: Need fast, scalable features for any ML algorithm, strong baseline performance.
### Statistical Feature Extraction
Domain-agnostic features based on time series characteristics:
- `Catch22` - 22 canonical time-series characteristics
- `TSFresh` - Comprehensive automated feature extraction (100+ features)
- `TSFreshRelevant` - Feature extraction with relevance filtering
- `SevenNumberSummary` - Descriptive statistics (mean, std, quantiles)
**Use when**: Need interpretable features, domain-agnostic approach, or feeding traditional ML.
### Dictionary-Based Representations
Symbolic approximations for discrete representations:
- `SAX` - Symbolic Aggregate approXimation
- `PAA` - Piecewise Aggregate Approximation
- `SFA` - Symbolic Fourier Approximation
- `SFAFast` - Optimized SFA
- `SFAWhole` - SFA on entire series (no windowing)
- `BORF` - Bag-of-Receptive-Fields
**Use when**: Need discrete/symbolic representation, dimensionality reduction, interpretability.
### Shapelet-Based Features
Discriminative subsequence extraction:
- `RandomShapeletTransform` - Random discriminative shapelets
- `RandomDilatedShapeletTransform` - Dilated shapelets for multi-scale
- `SAST` - Scalable And Accurate Subsequence Transform
- `RSAST` - Randomized SAST
**Use when**: Need interpretable discriminative patterns, phase-invariant features.
### Interval-Based Features
Statistical summaries from time intervals:
- `RandomIntervals` - Features from random intervals
- `SupervisedIntervals` - Supervised interval selection
- `QUANTTransformer` - Quantile-based interval features
**Use when**: Predictive patterns localized to specific windows.
### Preprocessing Transformations
Data preparation and normalization:
- `MinMaxScaler` - Scale to [0, 1] range
- `Normalizer` - Z-normalization (zero mean, unit variance)
- `Centerer` - Center to zero mean
- `SimpleImputer` - Fill missing values
- `DownsampleTransformer` - Reduce temporal resolution
- `Tabularizer` - Convert time series to tabular format
**Use when**: Need standardization, missing value handling, format conversion.
### Specialized Transformations
Advanced analysis methods:
- `MatrixProfile` - Computes distance profiles for pattern discovery
- `DWTTransformer` - Discrete Wavelet Transform
- `AutocorrelationFunctionTransformer` - ACF computation
- `Dobin` - Distance-based Outlier BasIs using Neighbors
- `SignatureTransformer` - Path signature methods
- `PLATransformer` - Piecewise Linear Approximation
### Class Imbalance Handling
- `ADASYN` - Adaptive Synthetic Sampling
- `SMOTE` - Synthetic Minority Over-sampling
- `OHIT` - Over-sampling with Highly Imbalanced Time series
**Use when**: Classification with imbalanced classes.
### Pipeline Composition
- `CollectionTransformerPipeline` - Chain multiple transformers
## Series Transformers
Transform individual time series (e.g., for preprocessing in forecasting).
### Statistical Analysis
- `AutoCorrelationSeriesTransformer` - Autocorrelation
- `StatsModelsACF` - ACF using statsmodels
- `StatsModelsPACF` - Partial autocorrelation
### Smoothing and Filtering
- `ExponentialSmoothing` - Exponentially weighted moving average
- `MovingAverage` - Simple or weighted moving average
- `SavitzkyGolayFilter` - Polynomial smoothing
- `GaussianFilter` - Gaussian kernel smoothing
- `BKFilter` - Baxter-King bandpass filter
- `DiscreteFourierApproximation` - Fourier-based filtering
**Use when**: Need noise reduction, trend extraction, or frequency filtering.
### Dimensionality Reduction
- `PCASeriesTransformer` - Principal component analysis
- `PlASeriesTransformer` - Piecewise Linear Approximation
### Transformations
- `BoxCoxTransformer` - Variance stabilization
- `LogTransformer` - Logarithmic scaling
- `ClaSPTransformer` - Classification Score Profile
### Pipeline Composition
- `SeriesTransformerPipeline` - Chain series transformers
## Quick Start: Feature Extraction
```python
from aeon.transformations.collection.convolution_based import RocketTransformer
from aeon.classification.sklearn import RotationForest
from aeon.datasets import load_classification
# Load data
X_train, y_train = load_classification("GunPoint", split="train")
X_test, y_test = load_classification("GunPoint", split="test")
# Extract ROCKET features
rocket = RocketTransformer()
X_train_features = rocket.fit_transform(X_train)
X_test_features = rocket.transform(X_test)
# Use with any sklearn classifier
clf = RotationForest()
clf.fit(X_train_features, y_train)
accuracy = clf.score(X_test_features, y_test)
```
## Quick Start: Preprocessing Pipeline
```python
from aeon.transformations.collection import (
MinMaxScaler,
SimpleImputer,
CollectionTransformerPipeline
)
# Build preprocessing pipeline
pipeline = CollectionTransformerPipeline([
('imputer', SimpleImputer(strategy='mean')),
('scaler', MinMaxScaler())
])
X_transformed = pipeline.fit_transform(X_train)
```
## Quick Start: Series Smoothing
```python
from aeon.transformations.series import MovingAverage
# Smooth individual time series
smoother = MovingAverage(window_size=5)
y_smoothed = smoother.fit_transform(y)
```
## Algorithm Selection
### For Feature Extraction:
- **Speed + Performance**: MiniRocketTransformer
- **Interpretability**: Catch22, TSFresh
- **Dimensionality reduction**: PAA, SAX, PCA
- **Discriminative patterns**: Shapelet transforms
- **Comprehensive features**: TSFresh (with longer runtime)
### For Preprocessing:
- **Normalization**: Normalizer, MinMaxScaler
- **Smoothing**: MovingAverage, SavitzkyGolayFilter
- **Missing values**: SimpleImputer
- **Frequency analysis**: DWTTransformer, Fourier methods
### For Symbolic Representation:
- **Fast approximation**: PAA
- **Alphabet-based**: SAX
- **Frequency-based**: SFA, SFAFast
## Best Practices
1. **Fit on training data only**: Avoid data leakage
```python
transformer.fit(X_train)
X_train_tf = transformer.transform(X_train)
X_test_tf = transformer.transform(X_test)
```
2. **Pipeline composition**: Chain transformers for complex workflows
```python
pipeline = CollectionTransformerPipeline([
('imputer', SimpleImputer()),
('scaler', Normalizer()),
('features', RocketTransformer())
])
```
3. **Feature selection**: TSFresh can generate many features; consider selection
```python
from sklearn.feature_selection import SelectKBest
selector = SelectKBest(k=100)
X_selected = selector.fit_transform(X_features, y)
```
4. **Memory considerations**: Some transformers memory-intensive on large datasets
- Use MiniRocket instead of ROCKET for speed
- Consider downsampling for very long series
- Use ROCKETGPU for GPU acceleration
5. **Domain knowledge**: Choose transformations matching domain:
- Periodic data: Fourier-based methods
- Noisy data: Smoothing filters
- Spike detection: Wavelet transforms
+429
View File
@@ -0,0 +1,429 @@
---
name: anndata
description: Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
license: BSD-3-Clause license
allowed-tools: Read Write Edit Bash
compatibility: Requires Python 3.11+ and uv. Examples target AnnData 0.12.16, with experimental APIs clearly marked where used.
metadata: {"version": "1.1", "skill-author": "K-Dense Inc."}
---
# AnnData
## Overview
AnnData is a Python package for handling annotated data matrices, storing experimental measurements (X) alongside observation metadata (obs), variable metadata (var), and multi-dimensional annotations (obsm, varm, obsp, varp, uns). Originally designed for single-cell genomics through Scanpy, it now serves as a general-purpose framework for any annotated data requiring efficient storage, manipulation, and analysis.
## When to Use This Skill
Use this skill when:
- Creating, reading, or writing AnnData objects
- Working with h5ad, zarr, or other genomics data formats
- Performing single-cell RNA-seq analysis
- Managing large datasets with sparse matrices or backed mode
- Concatenating multiple datasets or experimental batches
- Subsetting, filtering, or transforming annotated data
- Integrating with scanpy, scvi-tools, or other scverse ecosystem tools
## Installation
Requires Python 3.11+. Current stable release: 0.12.16 (released 2026-05-18).
```bash
uv pip install "anndata==0.12.16"
# Lazy I/O and dask-backed operations
uv pip install "anndata[dask,lazy]==0.12.16"
# Development / docs (contributors)
uv pip install "anndata[dev,test,doc]==0.12.16"
```
Use unpinned installs only when intentionally tracking the latest compatible release.
Current API notes:
- Use `anndata.io` for non-native `read_*` and `write_*` helpers. Top-level `anndata.read_h5ad` and `anndata.read_zarr` remain supported.
- Avoid deprecated APIs: `ad.read`, `AnnData.concatenate()`, `AnnData.*_keys()`, and `anndata.__version__`. Prefer `ad.read_h5ad`, `ad.concat`, mapping `.keys()`, and `importlib.metadata.version("anndata")`.
- Treat `anndata.experimental` APIs as useful but unstable. Prefer them for large-data workflows only when their current caveats are acceptable.
## Quick Start
### Creating an AnnData object
```python
import anndata as ad
import numpy as np
import pandas as pd
# Minimal creation
X = np.random.rand(100, 2000) # 100 cells × 2000 genes
adata = ad.AnnData(X)
# With metadata
obs = pd.DataFrame({
'cell_type': ['T cell', 'B cell'] * 50,
'sample': ['A', 'B'] * 50
}, index=[f'cell_{i}' for i in range(100)])
var = pd.DataFrame({
'gene_name': [f'Gene_{i}' for i in range(2000)]
}, index=[f'ENSG{i:05d}' for i in range(2000)])
adata = ad.AnnData(X=X, obs=obs, var=var)
```
### Reading data
```python
# Native formats (read_h5ad/read_zarr remain at top-level)
adata = ad.read_h5ad('data.h5ad')
adata = ad.read_h5ad('large_data.h5ad', backed='r') # lazy load for large files
adata = ad.read_zarr('data.zarr')
# Other formats: prefer anndata.io (top-level imports are deprecated)
from anndata.io import read_csv, read_loom, read_mtx
adata = read_csv('data.csv')
adata = read_loom('data.loom')
# 10X Genomics: use scanpy (not anndata) — see scanpy skill
import scanpy as sc
adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')
adata = sc.read_10x_mtx('filtered_feature_bc_matrix/')
```
### Writing data
```python
# Write h5ad file
adata.write_h5ad('output.h5ad')
# Write with compression
adata.write_h5ad('output.h5ad', compression='gzip')
# Write other formats
adata.write_zarr('output.zarr')
adata.write_csvs('output_dir/')
```
### Basic operations
```python
# Subset by conditions
t_cells = adata[adata.obs['cell_type'] == 'T cell']
# Subset by indices
subset = adata[0:50, 0:100]
# Add metadata
adata.obs['quality_score'] = np.random.rand(adata.n_obs)
adata.var['highly_variable'] = np.random.rand(adata.n_vars) > 0.8
# Access dimensions
print(f"{adata.n_obs} observations × {adata.n_vars} variables")
```
## Core Capabilities
### 1. Data Structure
Understand the AnnData object structure including X, obs, var, layers, obsm, varm, obsp, varp, uns, and raw components.
**See**: `references/data_structure.md` for comprehensive information on:
- Core components (X, obs, var, layers, obsm, varm, obsp, varp, uns, raw)
- Creating AnnData objects from various sources
- Accessing and manipulating data components
- Memory-efficient practices
### 2. Input/Output Operations
Read and write data in various formats with support for compression, backed mode, and cloud storage.
**See**: `references/io_operations.md` for details on:
- Native formats (h5ad, zarr)
- Alternative formats (CSV, MTX, Loom, 10X, Excel)
- Backed mode for large datasets
- Remote data access
- Format conversion
- Performance optimization
Common commands:
```python
from anndata.io import read_mtx
# Read/write h5ad
adata = ad.read_h5ad('data.h5ad', backed='r')
adata.write_h5ad('output.h5ad', compression='gzip')
# 10X Genomics (via scanpy)
import scanpy as sc
adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')
# Read MTX format
adata = read_mtx('matrix.mtx').T
```
### 3. Concatenation
Combine multiple AnnData objects along observations or variables with flexible join strategies.
**See**: `references/concatenation.md` for comprehensive coverage of:
- Basic concatenation (axis=0 for observations, axis=1 for variables)
- Join types (inner, outer)
- Merge strategies (same, unique, first, only)
- Tracking data sources with labels
- Lazy concatenation (AnnCollection)
- On-disk concatenation for large datasets
Common commands:
```python
# Concatenate observations (combine samples)
adata = ad.concat(
[adata1, adata2, adata3],
axis=0,
join='inner',
label='batch',
keys=['batch1', 'batch2', 'batch3']
)
# Concatenate variables (combine modalities)
adata = ad.concat([adata_rna, adata_protein], axis=1)
# Lazy collection over backed AnnData objects (experimental)
from anndata.experimental import AnnCollection
backed_adatas = [
ad.read_h5ad(path, backed='r')
for path in ['data1.h5ad', 'data2.h5ad']
]
collection = AnnCollection(
backed_adatas,
join_obs='outer',
join_vars='inner',
label='dataset'
)
```
### 4. Data Manipulation
Transform, subset, filter, and reorganize data efficiently.
**See**: `references/manipulation.md` for detailed guidance on:
- Subsetting (by indices, names, boolean masks, metadata conditions)
- Transposition
- Copying (full copies vs views)
- Renaming (observations, variables, categories)
- Type conversions (strings to categoricals, sparse/dense)
- Adding/removing data components
- Reordering
- Quality control filtering
Common commands:
```python
# Subset by metadata
filtered = adata[adata.obs['quality_score'] > 0.8]
hv_genes = adata[:, adata.var['highly_variable']]
# Transpose
adata_T = adata.T
# Copy vs view
view = adata[0:100, :] # View (lightweight reference)
copy = adata[0:100, :].copy() # Independent copy
# Convert strings to categoricals
adata.strings_to_categoricals()
```
### 5. Best Practices
Follow recommended patterns for memory efficiency, performance, and reproducibility.
**See**: `references/best_practices.md` for guidelines on:
- Memory management (sparse matrices, categoricals, backed mode)
- Views vs copies
- Data storage optimization
- Performance optimization
- Working with raw data
- Metadata management
- Reproducibility
- Error handling
- Integration with other tools
- Common pitfalls and solutions
Key recommendations:
```python
# Use sparse matrices for sparse data
from scipy.sparse import csr_matrix
adata.X = csr_matrix(adata.X)
# Convert strings to categoricals
adata.strings_to_categoricals()
# Use backed mode for large files
adata = ad.read_h5ad('large.h5ad', backed='r')
# Store raw before filtering
adata.raw = adata.copy()
adata = adata[:, adata.var['highly_variable']]
```
## Integration with Scverse Ecosystem
AnnData serves as the foundational data structure for the scverse ecosystem:
### Scanpy (Single-cell analysis)
```python
import scanpy as sc
# Preprocessing
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
# Dimensionality reduction
sc.pp.pca(adata, n_comps=50)
sc.pp.neighbors(adata, n_neighbors=15)
sc.tl.umap(adata)
sc.tl.leiden(adata)
# Visualization
sc.pl.umap(adata, color=['cell_type', 'leiden'])
```
### Muon (Multimodal data)
```python
import muon as mu
# Combine RNA and protein data
mdata = mu.MuData({'rna': adata_rna, 'protein': adata_protein})
```
### PyTorch integration
```python
from anndata.experimental import AnnLoader
# Create DataLoader for deep learning
dataloader = AnnLoader(adata, batch_size=128, shuffle=True)
for batch in dataloader:
X = batch.X
# Train model
```
## Common Workflows
### Single-cell RNA-seq analysis
```python
import anndata as ad
import scanpy as sc
# 1. Load data (10X via scanpy; anndata handles h5ad/zarr natively)
adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')
# 2. Quality control
adata.obs['n_genes'] = (adata.X > 0).sum(axis=1)
adata.obs['n_counts'] = adata.X.sum(axis=1)
adata = adata[adata.obs['n_genes'] > 200]
adata = adata[adata.obs['n_counts'] < 50000]
# 3. Store raw
adata.raw = adata.copy()
# 4. Normalize and filter
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
adata = adata[:, adata.var['highly_variable']]
# 5. Save processed data
adata.write_h5ad('processed.h5ad')
```
### Batch integration
```python
# Load multiple batches
adata1 = ad.read_h5ad('batch1.h5ad')
adata2 = ad.read_h5ad('batch2.h5ad')
adata3 = ad.read_h5ad('batch3.h5ad')
# Concatenate with batch labels
adata = ad.concat(
[adata1, adata2, adata3],
label='batch',
keys=['batch1', 'batch2', 'batch3'],
join='inner'
)
# Apply batch correction
import scanpy as sc
sc.pp.combat(adata, key='batch')
# Continue analysis
sc.pp.pca(adata)
sc.pp.neighbors(adata)
sc.tl.umap(adata)
```
### Working with large datasets
```python
# Open in backed mode
adata = ad.read_h5ad('100GB_dataset.h5ad', backed='r')
# Filter based on metadata (no data loading)
high_quality = adata[adata.obs['quality_score'] > 0.8]
# Load filtered subset
adata_subset = high_quality.to_memory()
# Process subset
process(adata_subset)
# Or process in chunks
chunk_size = 1000
for i in range(0, adata.n_obs, chunk_size):
chunk = adata[i:i+chunk_size, :].to_memory()
process(chunk)
```
## Troubleshooting
### Out of memory errors
Use backed mode or convert to sparse matrices:
```python
# Backed mode
adata = ad.read_h5ad('file.h5ad', backed='r')
# Sparse matrices
from scipy.sparse import csr_matrix
adata.X = csr_matrix(adata.X)
```
### Slow file reading
Use compression and appropriate formats:
```python
# Optimize for storage
adata.strings_to_categoricals()
adata.write_h5ad('file.h5ad', compression='gzip')
# Use Zarr for cloud storage; v3 writes are opt-in in anndata 0.12
import anndata as ad
ad.settings.zarr_write_format = 3
ad.settings.auto_shard_zarr_v3 = True # experimental; independent of zarr_write_format
adata.write_zarr('file.zarr', chunks=(1000, 1000))
```
### Index alignment issues
Always align external data on index:
```python
# Wrong
adata.obs['new_col'] = external_data['values']
# Correct
adata.obs['new_col'] = external_data.set_index('cell_id').loc[adata.obs_names, 'values']
```
## Additional Resources
- **Official documentation**: https://anndata.readthedocs.io/
- **Scanpy tutorials**: https://scanpy.readthedocs.io/
- **Scverse ecosystem**: https://scverse.org/
- **GitHub repository**: https://github.com/scverse/anndata
+532
View File
@@ -0,0 +1,532 @@
# Best Practices
Guidelines for efficient and effective use of AnnData.
## Memory Management
### Use sparse matrices for sparse data
```python
import numpy as np
from scipy.sparse import csr_matrix, csc_matrix
import anndata as ad
# Check data sparsity
data = np.random.rand(1000, 2000)
sparsity = 1 - np.count_nonzero(data) / data.size
print(f"Sparsity: {sparsity:.2%}")
# Convert to sparse if >50% zeros (anndata 0.12+ requires csr or csc)
if sparsity > 0.5:
adata = ad.AnnData(X=csr_matrix(data))
else:
adata = ad.AnnData(X=data)
# Benefits: 10-100x memory reduction for sparse genomics data
```
### Convert strings to categoricals
```python
# Inefficient: string columns use lots of memory
adata.obs['cell_type'] = ['Type_A', 'Type_B', 'Type_C'] * 333 + ['Type_A']
# Efficient: convert to categorical
adata.obs['cell_type'] = adata.obs['cell_type'].astype('category')
# Convert all string columns
adata.strings_to_categoricals()
# Benefits: 10-50x memory reduction for repeated strings
```
### Use backed mode for large datasets
```python
# Don't load entire dataset into memory
adata = ad.read_h5ad('large_dataset.h5ad', backed='r')
# Work with metadata
filtered = adata[adata.obs['quality'] > 0.8]
# Load only filtered subset
adata_subset = filtered.to_memory()
# Benefits: Work with datasets larger than RAM
```
## Views vs Copies
### Understanding views
```python
# Subsetting creates a view by default
subset = adata[0:100, :]
print(subset.is_view) # True
# Views don't copy data (memory efficient)
# But modifications can affect original
# Check if object is a view
if adata.is_view:
adata = adata.copy() # Make independent
```
### When to use views
```python
# Good: Read-only operations on subsets
mean_expr = adata[adata.obs['cell_type'] == 'T cell'].X.mean()
# Good: Temporary analysis
temp_subset = adata[:100, :]
result = analyze(temp_subset.X)
```
### When to use copies
```python
# Create independent copy for modifications
adata_filtered = adata[keep_cells, :].copy()
# Safe to modify without affecting original
adata_filtered.obs['new_column'] = values
# Always copy when:
# - Storing subset for later use
# - Modifying subset data
# - Passing to function that modifies data
```
## Data Storage Best Practices
### Choose the right format
**H5AD (HDF5) - Default choice**
```python
adata.write_h5ad('data.h5ad', compression='gzip')
```
- Fast random access
- Supports backed mode
- Good compression
- Best for: Most use cases
**Zarr - Cloud and parallel access**
```python
import anndata
# Default is Zarr v2; opt into v3 for cloud workflows (anndata 0.12+)
anndata.settings.zarr_write_format = 3
anndata.settings.auto_shard_zarr_v3 = True # experimental; independent of zarr_write_format
adata.write_zarr('data.zarr', chunks=(100, 100))
```
- Excellent for cloud storage (S3, GCS)
- Supports parallel I/O and opt-in Zarr v3 sharding (0.12+)
- Good compression
- Best for: Large datasets, cloud workflows, parallel processing
**CSV - Interoperability**
```python
adata.write_csvs('output_dir/')
```
- Human readable
- Compatible with all tools
- Large file sizes, slow
- Best for: Sharing with non-Python tools, small datasets
### Optimize file size
```python
# Before saving, optimize:
# 1. Convert to sparse if appropriate
from scipy.sparse import csr_matrix, issparse
if not issparse(adata.X):
density = np.count_nonzero(adata.X) / adata.X.size
if density < 0.5:
adata.X = csr_matrix(adata.X)
# 2. Convert strings to categoricals
adata.strings_to_categoricals()
# 3. Use compression
adata.write_h5ad('data.h5ad', compression='gzip', compression_opts=9)
# Typical results: 5-20x file size reduction
```
## Backed Mode Strategies
### Read-only analysis
```python
# Open in read-only backed mode
adata = ad.read_h5ad('data.h5ad', backed='r')
# Perform filtering without loading data
high_quality = adata[adata.obs['quality_score'] > 0.8]
# Load only filtered data
adata_filtered = high_quality.to_memory()
```
### Read-write modifications
```python
# Open in read-write backed mode
adata = ad.read_h5ad('data.h5ad', backed='r+')
# Modify X (persisted to disk)
adata.X[0, 0] = 0
# Metadata changes are not persisted from backed mode; load and write a new file
adata_memory = adata.to_memory()
adata_memory.obs['new_annotation'] = values
adata_memory.write_h5ad('data_with_annotations.h5ad')
```
### Chunked processing
```python
# Process large dataset in chunks
adata = ad.read_h5ad('huge_dataset.h5ad', backed='r')
results = []
chunk_size = 1000
for i in range(0, adata.n_obs, chunk_size):
chunk = adata[i:i+chunk_size, :].to_memory()
result = process(chunk)
results.append(result)
final_result = combine(results)
```
## Performance Optimization
### Subsetting performance
```python
# Fast: Boolean indexing with arrays
mask = np.array(adata.obs['quality'] > 0.5)
subset = adata[mask, :]
# Slow: Boolean indexing with Series (creates view chain)
subset = adata[adata.obs['quality'] > 0.5, :]
# Fastest: Integer indices
indices = np.where(adata.obs['quality'] > 0.5)[0]
subset = adata[indices, :]
```
### Avoid repeated subsetting
```python
# Inefficient: Multiple subset operations
for cell_type in ['A', 'B', 'C']:
subset = adata[adata.obs['cell_type'] == cell_type]
process(subset)
# Efficient: Group and process
groups = adata.obs.groupby('cell_type').groups
for cell_type, indices in groups.items():
subset = adata[indices, :]
process(subset)
```
### Use chunked operations for large matrices
```python
# Process X in chunks
for chunk in adata.chunked_X(chunk_size=1000):
result = compute(chunk)
# More memory efficient than loading full X
```
## Working with Raw Data
### Store raw before filtering
```python
# Original data with all genes
adata = ad.AnnData(X=counts)
# Store raw before filtering
adata.raw = adata.copy()
# Filter to highly variable genes
adata = adata[:, adata.var['highly_variable']]
# Later: access original data
original_expression = adata.raw.X
all_genes = adata.raw.var_names
```
### When to use raw
```python
# Use raw for:
# - Differential expression on filtered genes
# - Visualization of specific genes not in filtered set
# - Accessing original counts after normalization
# Access raw data
if adata.raw is not None:
gene_expr = adata.raw[:, 'GENE_NAME'].X
else:
gene_expr = adata[:, 'GENE_NAME'].X
```
## Metadata Management
### Naming conventions
```python
# Consistent naming improves usability
# Observation metadata (obs):
# - cell_id, sample_id
# - cell_type, tissue, condition
# - n_genes, n_counts, percent_mito
# - cluster, leiden, louvain
# Variable metadata (var):
# - gene_id, gene_name
# - highly_variable, n_cells
# - mean_expression, dispersion
# Embeddings (obsm):
# - X_pca, X_umap, X_tsne
# - X_diffmap, X_draw_graph_fr
# Follow conventions from scanpy/scverse ecosystem
```
### Document metadata
```python
# Store metadata descriptions in uns
adata.uns['metadata_descriptions'] = {
'cell_type': 'Cell type annotation from automated clustering',
'quality_score': 'QC score from scrublet (0-1, higher is better)',
'batch': 'Experimental batch identifier'
}
# Store processing history
adata.uns['processing_steps'] = [
'Raw counts loaded from 10X',
'Filtered: n_genes > 200, n_counts < 50000',
'Normalized to 10000 counts per cell',
'Log transformed'
]
```
## Reproducibility
### Set random seeds
```python
import numpy as np
# Set seed for reproducible results
np.random.seed(42)
# Document in uns
adata.uns['random_seed'] = 42
```
### Store parameters
```python
# Store analysis parameters in uns
adata.uns['pca'] = {
'n_comps': 50,
'svd_solver': 'arpack',
'random_state': 42
}
adata.uns['neighbors'] = {
'n_neighbors': 15,
'n_pcs': 50,
'metric': 'euclidean',
'method': 'umap'
}
```
### Version tracking
```python
import sys
from importlib.metadata import version
# Store package versions (anndata.__version__ deprecated in 0.12.3)
adata.uns['versions'] = {
'anndata': version('anndata'),
'scanpy': version('scanpy'),
'numpy': version('numpy'),
'python': sys.version,
}
```
## Error Handling
### Check data validity
```python
# Verify dimensions
assert adata.n_obs == len(adata.obs)
assert adata.n_vars == len(adata.var)
assert adata.X.shape == (adata.n_obs, adata.n_vars)
# Check for NaN values
has_nan = np.isnan(adata.X.data).any() if issparse(adata.X) else np.isnan(adata.X).any()
if has_nan:
print("Warning: Data contains NaN values")
# Check for negative values (if counts expected)
has_negative = (adata.X.data < 0).any() if issparse(adata.X) else (adata.X < 0).any()
if has_negative:
print("Warning: Data contains negative values")
```
### Validate metadata
```python
# Check for missing values
missing_obs = adata.obs.isnull().sum()
if missing_obs.any():
print("Missing values in obs:")
print(missing_obs[missing_obs > 0])
# Verify indices are unique
assert adata.obs_names.is_unique, "Observation names not unique"
assert adata.var_names.is_unique, "Variable names not unique"
# Check metadata alignment
assert len(adata.obs) == adata.n_obs
assert len(adata.var) == adata.n_vars
```
## Integration with Other Tools
### Scanpy integration
```python
import scanpy as sc
# AnnData is native format for scanpy
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_genes(adata, min_cells=3)
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata)
sc.pp.pca(adata)
sc.pp.neighbors(adata)
sc.tl.umap(adata)
```
### Pandas integration
```python
import pandas as pd
# Convert to DataFrame
df = adata.to_df()
# Create from DataFrame
adata = ad.AnnData(df)
# Work with metadata as DataFrames
adata.obs = adata.obs.merge(external_metadata, left_index=True, right_index=True)
```
### PyTorch integration
```python
from anndata.experimental import AnnLoader
# Create PyTorch DataLoader
dataloader = AnnLoader(adata, batch_size=128, shuffle=True)
# Iterate in training loop
for batch in dataloader:
X = batch.X
# Train model on batch
```
## Common Pitfalls
### Pitfall 1: Modifying views
```python
# Wrong: Modifying view can affect original
subset = adata[:100, :]
subset.X = new_data # May modify adata.X!
# Correct: Copy before modifying
subset = adata[:100, :].copy()
subset.X = new_data # Independent copy
```
### Pitfall 2: Index misalignment
```python
# Wrong: Assuming order matches
external_data = pd.read_csv('data.csv')
adata.obs['new_col'] = external_data['values'] # May misalign!
# Correct: Align on index
adata.obs['new_col'] = external_data.set_index('cell_id').loc[adata.obs_names, 'values']
```
### Pitfall 3: Mixing sparse and dense
```python
# Wrong: Converting sparse to dense uses huge memory
result = adata.X + 1 # Converts sparse to dense!
# Correct: Use sparse operations
from scipy.sparse import issparse
if issparse(adata.X):
result = adata.X.copy()
result.data += 1
```
### Pitfall 4: Not handling views
```python
# Wrong: Assuming subset is independent
subset = adata[mask, :]
del adata # subset may become invalid!
# Correct: Copy when needed
subset = adata[mask, :].copy()
del adata # subset remains valid
```
### Pitfall 5: Ignoring memory constraints
```python
# Wrong: Loading huge dataset into memory
adata = ad.read_h5ad('100GB_file.h5ad') # OOM error!
# Correct: Use backed mode
adata = ad.read_h5ad('100GB_file.h5ad', backed='r')
subset = adata[adata.obs['keep']].to_memory()
```
## Workflow Example
Complete best-practices workflow:
```python
import anndata as ad
import numpy as np
from scipy.sparse import csr_matrix, issparse
# 1. Load with backed mode if large
adata = ad.read_h5ad('data.h5ad', backed='r')
# 2. Quick metadata check without loading data
print(f"Dataset: {adata.n_obs} cells × {adata.n_vars} genes")
# 3. Filter based on metadata
high_quality = adata[adata.obs['quality_score'] > 0.8]
# 4. Load filtered subset to memory
adata = high_quality.to_memory()
# 5. Convert to optimal storage types (csr/csc sparse only since 0.12)
adata.strings_to_categoricals()
if not issparse(adata.X):
density = np.count_nonzero(adata.X) / adata.X.size
if density < 0.5:
adata.X = csr_matrix(adata.X)
# 6. Store raw before filtering genes
adata.raw = adata.copy()
# 7. Filter to highly variable genes
adata = adata[:, adata.var['highly_variable']].copy()
# 8. Document processing
adata.uns['processing'] = {
'filtered': 'quality_score > 0.8',
'n_hvg': adata.n_vars,
'date': '2025-11-03'
}
# 9. Save optimized
adata.write_h5ad('processed.h5ad', compression='gzip')
```
+399
View File
@@ -0,0 +1,399 @@
# Concatenating AnnData Objects
Combine multiple AnnData objects along either observations or variables axis.
## Basic Concatenation
### Concatenate along observations (stack cells/samples)
```python
import anndata as ad
import numpy as np
# Create multiple AnnData objects
adata1 = ad.AnnData(X=np.random.rand(100, 50))
adata2 = ad.AnnData(X=np.random.rand(150, 50))
adata3 = ad.AnnData(X=np.random.rand(200, 50))
# Concatenate along observations (axis=0, default)
adata_combined = ad.concat([adata1, adata2, adata3], axis=0)
print(adata_combined.shape) # (450, 50)
```
### Concatenate along variables (stack genes/features)
```python
# Create objects with same observations, different variables
adata1 = ad.AnnData(X=np.random.rand(100, 50))
adata2 = ad.AnnData(X=np.random.rand(100, 30))
adata3 = ad.AnnData(X=np.random.rand(100, 70))
# Concatenate along variables (axis=1)
adata_combined = ad.concat([adata1, adata2, adata3], axis=1)
print(adata_combined.shape) # (100, 150)
```
## Join Types
### Inner join (intersection)
Keep only variables/observations present in all objects.
```python
import pandas as pd
# Create objects with different variables
adata1 = ad.AnnData(
X=np.random.rand(100, 50),
var=pd.DataFrame(index=[f'Gene_{i}' for i in range(50)])
)
adata2 = ad.AnnData(
X=np.random.rand(150, 60),
var=pd.DataFrame(index=[f'Gene_{i}' for i in range(10, 70)])
)
# Inner join: only genes 10-49 are kept (overlap)
adata_inner = ad.concat([adata1, adata2], join='inner')
print(adata_inner.n_vars) # 40 genes (overlap)
```
### Outer join (union)
Keep all variables/observations, filling missing values.
```python
# Outer join: all genes are kept
adata_outer = ad.concat([adata1, adata2], join='outer')
print(adata_outer.n_vars) # 70 genes (union)
# Missing values are filled with appropriate defaults:
# - 0 for sparse matrices
# - NaN for dense matrices
```
### Fill values for outer joins
```python
# Specify fill value for missing data
adata_filled = ad.concat([adata1, adata2], join='outer', fill_value=0)
```
## Tracking Data Sources
### Add batch labels
```python
# Label which object each observation came from
adata_combined = ad.concat(
[adata1, adata2, adata3],
label='batch', # Column name for labels
keys=['batch1', 'batch2', 'batch3'] # Labels for each object
)
print(adata_combined.obs['batch'].value_counts())
# batch1 100
# batch2 150
# batch3 200
```
### Automatic batch labels
```python
# If keys not provided, uses integer indices
adata_combined = ad.concat(
[adata1, adata2, adata3],
label='dataset'
)
# dataset column contains: 0, 1, 2
```
## Merge Strategies
Control how metadata from different objects is combined using the `merge` parameter.
### merge=None (default for observations)
Exclude metadata on non-concatenation axis.
```python
# When concatenating observations, var metadata must match
adata1.var['gene_type'] = 'protein_coding'
adata2.var['gene_type'] = 'protein_coding'
# var is kept only if identical across all objects
adata_combined = ad.concat([adata1, adata2], merge=None)
```
### merge='same'
Keep metadata that is identical across all objects.
```python
adata1.var['chromosome'] = ['chr1'] * 25 + ['chr2'] * 25
adata2.var['chromosome'] = ['chr1'] * 25 + ['chr2'] * 25
adata1.var['type'] = 'protein_coding'
adata2.var['type'] = 'lncRNA' # Different
# 'chromosome' is kept (same), 'type' is excluded (different)
adata_combined = ad.concat([adata1, adata2], merge='same')
```
### merge='unique'
Keep metadata columns where each key has exactly one value.
```python
adata1.var['gene_id'] = [f'ENSG{i:05d}' for i in range(50)]
adata2.var['gene_id'] = [f'ENSG{i:05d}' for i in range(50)]
# gene_id is kept (unique values for each key)
adata_combined = ad.concat([adata1, adata2], merge='unique')
```
### merge='first'
Take values from the first object containing each key.
```python
adata1.var['description'] = ['Desc1'] * 50
adata2.var['description'] = ['Desc2'] * 50
# Uses descriptions from adata1
adata_combined = ad.concat([adata1, adata2], merge='first')
```
### merge='only'
Keep metadata that appears in only one object.
```python
adata1.var['adata1_specific'] = [1] * 50
adata2.var['adata2_specific'] = [2] * 50
# Both metadata columns are kept
adata_combined = ad.concat([adata1, adata2], merge='only')
```
## Handling Index Conflicts
### Make indices unique
```python
import pandas as pd
# Create objects with overlapping observation names
adata1 = ad.AnnData(
X=np.random.rand(3, 10),
obs=pd.DataFrame(index=['cell_1', 'cell_2', 'cell_3'])
)
adata2 = ad.AnnData(
X=np.random.rand(3, 10),
obs=pd.DataFrame(index=['cell_1', 'cell_2', 'cell_3'])
)
# Make indices unique by appending batch keys
adata_combined = ad.concat(
[adata1, adata2],
label='batch',
keys=['batch1', 'batch2'],
index_unique='_' # Separator for making indices unique
)
print(adata_combined.obs_names)
# ['cell_1_batch1', 'cell_2_batch1', 'cell_3_batch1',
# 'cell_1_batch2', 'cell_2_batch2', 'cell_3_batch2']
```
## Concatenating Layers
```python
# Objects with layers
adata1 = ad.AnnData(X=np.random.rand(100, 50))
adata1.layers['normalized'] = np.random.rand(100, 50)
adata1.layers['scaled'] = np.random.rand(100, 50)
adata2 = ad.AnnData(X=np.random.rand(150, 50))
adata2.layers['normalized'] = np.random.rand(150, 50)
adata2.layers['scaled'] = np.random.rand(150, 50)
# Layers are concatenated automatically if present in all objects
adata_combined = ad.concat([adata1, adata2])
print(adata_combined.layers.keys())
# dict_keys(['normalized', 'scaled'])
```
## Concatenating Multi-dimensional Annotations
### obsm/varm
```python
# Objects with embeddings
adata1.obsm['X_pca'] = np.random.rand(100, 50)
adata2.obsm['X_pca'] = np.random.rand(150, 50)
# obsm is concatenated along observation axis
adata_combined = ad.concat([adata1, adata2])
print(adata_combined.obsm['X_pca'].shape) # (250, 50)
```
### obsp/varp (pairwise annotations)
```python
from scipy.sparse import csr_matrix
# Pairwise matrices
adata1.obsp['connectivities'] = csr_matrix((100, 100))
adata2.obsp['connectivities'] = csr_matrix((150, 150))
# By default, obsp is NOT concatenated (set pairwise=True to include)
adata_combined = ad.concat([adata1, adata2])
# adata_combined.obsp is empty
# Include pairwise data (creates block diagonal matrix)
adata_combined = ad.concat([adata1, adata2], pairwise=True)
print(adata_combined.obsp['connectivities'].shape) # (250, 250)
```
## Concatenating uns (unstructured)
Unstructured metadata is merged recursively:
```python
adata1.uns['experiment'] = {'date': '2025-01-01', 'batch': 'A'}
adata2.uns['experiment'] = {'date': '2025-01-01', 'batch': 'B'}
# Using merge='unique' for uns
adata_combined = ad.concat([adata1, adata2], uns_merge='unique')
# 'date' is kept (same value), 'batch' might be excluded (different values)
```
## Lazy Concatenation (AnnCollection)
For very large datasets, use `AnnCollection` to lazily concatenate AnnData objects along the observation axis. This API is experimental; use backed AnnData objects when the inputs are stored in `.h5ad` files.
```python
import anndata as ad
from anndata.experimental import AnnCollection
files = ['data1.h5ad', 'data2.h5ad', 'data3.h5ad']
backed_adatas = [ad.read_h5ad(path, backed='r') for path in files]
collection = AnnCollection(
backed_adatas,
join_obs='outer',
join_vars='inner',
label='dataset',
keys=['dataset1', 'dataset2', 'dataset3']
)
# Access data lazily
print(collection.n_obs) # Total observations
print(collection.obs.head()) # Metadata loaded, not X
# Convert to regular AnnData when needed (loads all data)
adata = collection.to_adata()
```
### Working with AnnCollection
```python
# Subset without loading data
subset = collection[collection.obs['cell_type'] == 'T cell']
# Iterate through datasets
for adata in collection:
print(adata.shape)
# Access specific dataset
first_dataset = collection[0]
```
## Concatenation on Disk
For datasets too large for memory, concatenate directly on disk:
```python
import anndata as ad
from anndata.experimental import concat_on_disk
# Concatenate without loading into memory
concat_on_disk(
['data1.h5ad', 'data2.h5ad', 'data3.h5ad'],
'combined.h5ad',
join='outer'
)
# Load result in backed mode
adata = ad.read_h5ad('combined.h5ad', backed='r')
```
## Common Concatenation Patterns
### Combine technical replicates
```python
# Multiple runs of the same samples
replicates = [adata_run1, adata_run2, adata_run3]
adata_combined = ad.concat(
replicates,
label='technical_replicate',
keys=['rep1', 'rep2', 'rep3'],
join='inner' # Keep only genes measured in all runs
)
```
### Combine batches from experiment
```python
# Different experimental batches
batches = [adata_batch1, adata_batch2, adata_batch3]
adata_combined = ad.concat(
batches,
label='batch',
keys=['batch1', 'batch2', 'batch3'],
join='outer' # Keep all genes
)
# Later: apply batch correction
```
### Merge multi-modal data
```python
# Different measurement modalities (e.g., RNA + protein)
adata_rna = ad.AnnData(X=np.random.rand(100, 2000))
adata_protein = ad.AnnData(X=np.random.rand(100, 50))
# Concatenate along variables to combine modalities
adata_multimodal = ad.concat([adata_rna, adata_protein], axis=1)
# Add labels to distinguish modalities
adata_multimodal.var['modality'] = ['RNA'] * 2000 + ['protein'] * 50
```
## Best Practices
1. **Check compatibility before concatenating**
```python
# Verify shapes are compatible
print([adata.n_vars for adata in [adata1, adata2, adata3]])
# Check variable names match
print([set(adata.var_names) for adata in [adata1, adata2, adata3]])
```
2. **Use appropriate join type**
- `inner`: When you need the same features across all samples (most stringent)
- `outer`: When you want to preserve all features (most inclusive)
3. **Track data sources**
Always use `label` and `keys` to track which observations came from which dataset.
4. **Consider memory usage**
- For large datasets, use `AnnCollection` or `concat_on_disk`
- Consider backed mode for the result
5. **Handle batch effects**
Concatenation combines data but doesn't correct for batch effects. Apply batch correction after concatenation:
```python
# After concatenation, apply batch correction
import scanpy as sc
sc.pp.combat(adata_combined, key='batch')
```
6. **Validate results**
```python
# Check dimensions
print(adata_combined.shape)
# Check batch distribution
print(adata_combined.obs['batch'].value_counts())
# Verify metadata integrity
print(adata_combined.var.head())
print(adata_combined.obs.head())
```
+314
View File
@@ -0,0 +1,314 @@
# AnnData Object Structure
The AnnData object stores a data matrix with associated annotations, providing a flexible framework for managing experimental data and metadata.
## Core Components
### X (Data Matrix)
The primary data matrix with shape (n_obs, n_vars) storing experimental measurements.
```python
import anndata as ad
import numpy as np
# Create with dense array
adata = ad.AnnData(X=np.random.rand(100, 2000))
# Create with sparse matrix (recommended for large, sparse data)
from scipy.sparse import csr_matrix
sparse_data = csr_matrix(np.random.rand(100, 2000))
adata = ad.AnnData(X=sparse_data)
```
Access data:
```python
# Full matrix (caution with large datasets)
full_data = adata.X
# Single observation
obs_data = adata.X[0, :]
# Single variable across all observations
var_data = adata.X[:, 0]
```
### obs (Observation Annotations)
DataFrame storing metadata about observations (rows). Each row corresponds to one observation in X.
```python
import pandas as pd
# Create AnnData with observation metadata
obs_df = pd.DataFrame({
'cell_type': ['T cell', 'B cell', 'Monocyte'],
'treatment': ['control', 'treated', 'control'],
'timepoint': [0, 24, 24]
}, index=['cell_1', 'cell_2', 'cell_3'])
adata = ad.AnnData(X=np.random.rand(3, 100), obs=obs_df)
# Access observation metadata
print(adata.obs['cell_type'])
print(adata.obs.loc['cell_1'])
```
### var (Variable Annotations)
DataFrame storing metadata about variables (columns). Each row corresponds to one variable in X.
```python
# Create AnnData with variable metadata
var_df = pd.DataFrame({
'gene_name': ['ACTB', 'GAPDH', 'TP53'],
'chromosome': ['7', '12', '17'],
'highly_variable': [True, False, True]
}, index=['ENSG00001', 'ENSG00002', 'ENSG00003'])
adata = ad.AnnData(X=np.random.rand(100, 3), var=var_df)
# Access variable metadata
print(adata.var['gene_name'])
print(adata.var.loc['ENSG00001'])
```
### layers (Alternative Data Representations)
Dictionary storing alternative matrices with the same dimensions as X.
```python
# Store raw counts, normalized data, and scaled data
adata = ad.AnnData(X=np.random.rand(100, 2000))
adata.layers['raw_counts'] = np.random.randint(0, 100, (100, 2000))
adata.layers['normalized'] = adata.X / np.sum(adata.X, axis=1, keepdims=True)
adata.layers['scaled'] = (adata.X - adata.X.mean()) / adata.X.std()
# Access layers
raw_data = adata.layers['raw_counts']
normalized_data = adata.layers['normalized']
```
Common layer uses:
- `raw_counts`: Original count data before normalization
- `normalized`: Log-normalized or TPM values
- `scaled`: Z-scored values for analysis
- `imputed`: Data after imputation
### obsm (Multi-dimensional Observation Annotations)
Dictionary storing multi-dimensional arrays aligned to observations.
```python
# Store PCA coordinates and UMAP embeddings
adata.obsm['X_pca'] = np.random.rand(100, 50) # 50 principal components
adata.obsm['X_umap'] = np.random.rand(100, 2) # 2D UMAP coordinates
adata.obsm['X_tsne'] = np.random.rand(100, 2) # 2D t-SNE coordinates
# Access embeddings
pca_coords = adata.obsm['X_pca']
umap_coords = adata.obsm['X_umap']
```
Common obsm uses:
- `X_pca`: Principal component coordinates
- `X_umap`: UMAP embedding coordinates
- `X_tsne`: t-SNE embedding coordinates
- `X_diffmap`: Diffusion map coordinates
- `protein_expression`: Protein abundance measurements (CITE-seq)
### varm (Multi-dimensional Variable Annotations)
Dictionary storing multi-dimensional arrays aligned to variables.
```python
# Store PCA loadings
adata.varm['PCs'] = np.random.rand(2000, 50) # Loadings for 50 components
adata.varm['gene_modules'] = np.random.rand(2000, 10) # Gene module scores
# Access loadings
pc_loadings = adata.varm['PCs']
```
Common varm uses:
- `PCs`: Principal component loadings
- `gene_modules`: Gene co-expression module assignments
### obsp (Pairwise Observation Relationships)
Dictionary storing sparse matrices representing relationships between observations.
```python
from scipy.sparse import csr_matrix
# Store k-nearest neighbor graph
n_obs = 100
knn_graph = csr_matrix(np.random.rand(n_obs, n_obs) > 0.95)
adata.obsp['connectivities'] = knn_graph
adata.obsp['distances'] = csr_matrix(np.random.rand(n_obs, n_obs))
# Access graphs
knn_connections = adata.obsp['connectivities']
distances = adata.obsp['distances']
```
Common obsp uses:
- `connectivities`: Cell-cell neighborhood graph
- `distances`: Pairwise distances between cells
### varp (Pairwise Variable Relationships)
Dictionary storing sparse matrices representing relationships between variables.
```python
# Store gene-gene correlation matrix
n_vars = 2000
gene_corr = csr_matrix(np.random.rand(n_vars, n_vars) > 0.99)
adata.varp['correlations'] = gene_corr
# Access correlations
gene_correlations = adata.varp['correlations']
```
### uns (Unstructured Annotations)
Dictionary storing arbitrary unstructured metadata.
```python
# Store analysis parameters and results
adata.uns['experiment_date'] = '2025-11-03'
adata.uns['pca'] = {
'variance_ratio': [0.15, 0.10, 0.08],
'params': {'n_comps': 50}
}
adata.uns['neighbors'] = {
'params': {'n_neighbors': 15, 'method': 'umap'},
'connectivities_key': 'connectivities'
}
# Access unstructured data
exp_date = adata.uns['experiment_date']
pca_params = adata.uns['pca']['params']
```
Common uns uses:
- Analysis parameters and settings
- Color palettes for plotting
- Cluster information
- Tool-specific metadata
### raw (Original Data Snapshot)
Optional attribute preserving the original data matrix and variable annotations before filtering.
```python
# Create AnnData and store raw state
adata = ad.AnnData(X=np.random.rand(100, 5000))
adata.var['gene_name'] = [f'Gene_{i}' for i in range(5000)]
# Store raw state before filtering
adata.raw = adata.copy()
# Filter to highly variable genes
highly_variable_mask = np.random.rand(5000) > 0.5
adata = adata[:, highly_variable_mask]
# Access original data
original_matrix = adata.raw.X
original_var = adata.raw.var
```
## Object Properties
```python
# Dimensions
n_observations = adata.n_obs
n_variables = adata.n_vars
shape = adata.shape # (n_obs, n_vars)
# Index information
obs_names = adata.obs_names # Observation identifiers
var_names = adata.var_names # Variable identifiers
# Storage mode
is_view = adata.is_view # True if this is a view of another object
is_backed = adata.isbacked # True if backed by on-disk storage
filename = adata.filename # Path to backing file (if backed)
```
## Creating AnnData Objects
### From arrays and DataFrames
```python
import anndata as ad
import numpy as np
import pandas as pd
# Minimal creation
X = np.random.rand(100, 2000)
adata = ad.AnnData(X)
# With metadata
obs = pd.DataFrame({'cell_type': ['A', 'B'] * 50}, index=[f'cell_{i}' for i in range(100)])
var = pd.DataFrame({'gene_name': [f'Gene_{i}' for i in range(2000)]}, index=[f'ENSG{i:05d}' for i in range(2000)])
adata = ad.AnnData(X=X, obs=obs, var=var)
# With all components
adata = ad.AnnData(
X=X,
obs=obs,
var=var,
layers={'raw': np.random.randint(0, 100, (100, 2000))},
obsm={'X_pca': np.random.rand(100, 50)},
uns={'experiment': 'test'}
)
```
### From DataFrame
```python
# Create from pandas DataFrame (genes as columns, cells as rows)
df = pd.DataFrame(
np.random.rand(100, 50),
columns=[f'Gene_{i}' for i in range(50)],
index=[f'Cell_{i}' for i in range(100)]
)
adata = ad.AnnData(df)
```
## Data Access Patterns
### Vector extraction
```python
# Get observation annotation as array
cell_types = adata.obs_vector('cell_type')
# Get variable values across observations
gene_expression = adata.obs_vector('ACTB') # If ACTB is in var_names
# Get variable annotation as array
gene_names = adata.var_vector('gene_name')
```
### Subsetting
```python
# By index
subset = adata[0:10, 0:100] # First 10 obs, first 100 vars
# By name
subset = adata[['cell_1', 'cell_2'], ['ACTB', 'GAPDH']]
# By boolean mask
high_count_cells = adata.obs['total_counts'] > 1000
subset = adata[high_count_cells, :]
# By observation metadata
t_cells = adata[adata.obs['cell_type'] == 'T cell']
```
## Memory Considerations
The AnnData structure is designed for memory efficiency:
- Sparse matrices reduce memory for sparse data
- Views avoid copying data when possible
- Backed mode enables working with data larger than RAM
- Categorical annotations reduce memory for discrete values
```python
# Convert strings to categoricals (more memory efficient)
adata.obs['cell_type'] = adata.obs['cell_type'].astype('category')
adata.strings_to_categoricals()
# Check if object is a view (doesn't own data)
if adata.is_view:
adata = adata.copy() # Create independent copy
```
+466
View File
@@ -0,0 +1,466 @@
# Input/Output Operations
AnnData provides comprehensive I/O functionality for reading and writing data in various formats.
Since anndata 0.11, most `read_*` and `write_*` functions live in `anndata.io`. Top-level `read_h5ad` and `read_zarr` remain at `anndata` without deprecation warnings; other top-level imports still work but emit `FutureWarning`.
```python
import anndata as ad
from anndata.io import read_csv, read_mtx, read_loom, read_elem, write_elem
```
Avoid deprecated I/O aliases such as `ad.read`; use `ad.read_h5ad` or `anndata.io.read_h5ad` explicitly.
## Native Formats
### H5AD (HDF5-based)
The recommended native format for AnnData objects, providing efficient storage and fast access.
#### Writing H5AD files
```python
import anndata as ad
# Write to file
adata.write_h5ad('data.h5ad')
# Write with compression
adata.write_h5ad('data.h5ad', compression='gzip')
# Write with specific compression level (0-9, higher = more compression)
adata.write_h5ad('data.h5ad', compression='gzip', compression_opts=9)
```
#### Reading H5AD files
```python
# Read entire file into memory
adata = ad.read_h5ad('data.h5ad')
# Read in backed mode (lazy loading for large files)
adata = ad.read_h5ad('data.h5ad', backed='r') # Read-only
adata = ad.read_h5ad('data.h5ad', backed='r+') # Read-write for X
# Backed mode enables working with datasets larger than RAM
# Only accessed data is loaded into memory
# In backed mode, only X updates are persisted; write a new file for obs/var/uns changes.
```
#### Backed mode operations
```python
# Open in backed mode
adata = ad.read_h5ad('large_dataset.h5ad', backed='r')
# Access metadata without loading X into memory
print(adata.obs.head())
print(adata.var.head())
# Subset operations create views
subset = adata[:100, :500] # View, no data loaded
# Load specific data into memory
X_subset = subset.X[:] # Now loads this subset
# Convert entire backed object to memory
adata_memory = adata.to_memory()
```
### Zarr
Hierarchical array storage format, optimized for cloud storage and parallel I/O.
#### Writing Zarr
```python
# Write to Zarr store
adata.write_zarr('data.zarr')
# Write with specific chunks (important for performance)
adata.write_zarr('data.zarr', chunks=(100, 100))
```
#### Reading Zarr
```python
# Read Zarr store
adata = ad.read_zarr('data.zarr')
```
#### Zarr v3 (anndata 0.12+)
```python
import anndata
# Default writes Zarr v2; opt into v3 and optional auto-sharding
anndata.settings.zarr_write_format = 3
anndata.settings.auto_shard_zarr_v3 = True # experimental; independent of zarr_write_format
adata.write_zarr('data.zarr', chunks=(1000, 1000))
```
Zarr v3 writing is available in anndata 0.12, with structured-array exceptions and evolving performance guidance. Consolidated metadata is recommended for remote Zarr stores.
#### Remote Zarr access
Only open remote stores from trusted, expected locations. Prefer allowlisted HTTPS/S3/GCS paths or signed URLs, and avoid asking an agent to fetch arbitrary user-supplied URLs.
```python
import fsspec
# Access Zarr from an expected S3 location
store = fsspec.get_mapper('s3://bucket-name/data.zarr')
adata = ad.read_zarr(store)
# Access Zarr from a trusted HTTPS location
store = fsspec.get_mapper('https://example.com/data.zarr')
adata = ad.read_zarr(store)
```
## Alternative Input Formats
### CSV/TSV
```python
from anndata.io import read_csv
# Read CSV (genes as columns, cells as rows)
adata = read_csv('data.csv')
# Read with custom delimiter
adata = read_csv('data.tsv', delimiter='\t')
# Specify that first column is row names
adata = read_csv('data.csv', first_column_names=True)
```
### Excel
```python
from anndata.io import read_excel
# Read Excel file
adata = read_excel('data.xlsx')
# Read specific sheet
adata = read_excel('data.xlsx', sheet='Sheet1')
```
### Matrix Market (MTX)
Common format for sparse matrices in genomics.
```python
from anndata.io import read_mtx
# Read MTX with associated files
# Requires: matrix.mtx, genes.tsv, barcodes.tsv
adata = read_mtx('matrix.mtx')
# Read with custom gene and barcode files
adata = read_mtx(
'matrix.mtx',
var_names='genes.tsv',
obs_names='barcodes.tsv'
)
# Transpose if needed (MTX often has genes as rows)
adata = adata.T
```
### 10X Genomics formats
10X readers are provided by **scanpy**, not anndata. After loading, the result is a standard `AnnData` object.
```python
import scanpy as sc
# Read 10X h5 format
adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')
# Read 10X MTX directory
adata = sc.read_10x_mtx('filtered_feature_bc_matrix/')
# Specify genome if multiple present
adata = sc.read_10x_h5('data.h5', genome='GRCh38')
```
### Loom
```python
from anndata.io import read_loom
# Read Loom file
adata = read_loom('data.loom')
# Read with specific observation and variable annotations
adata = read_loom(
'data.loom',
obs_names='CellID',
var_names='Gene'
)
```
### Text files
```python
from anndata.io import read_text
# Read generic text file
adata = read_text('data.txt', delimiter='\t')
# Read with custom parameters
adata = read_text(
'data.txt',
delimiter=',',
first_column_names=True,
dtype='float32'
)
```
### UMI tools
```python
from anndata.io import read_umi_tools
# Read UMI tools format
adata = read_umi_tools('counts.tsv')
```
### HDF5 (generic)
```python
from anndata.io import read_hdf
# Read from HDF5 file (not h5ad format)
adata = read_hdf('data.h5', key='dataset')
```
## Alternative Output Formats
### CSV
```python
# Write to CSV files (creates multiple files)
adata.write_csvs('output_dir/')
# This creates:
# - output_dir/X.csv (expression matrix)
# - output_dir/obs.csv (observation annotations)
# - output_dir/var.csv (variable annotations)
# - output_dir/uns.csv (unstructured annotations, if possible)
# Skip certain components
adata.write_csvs('output_dir/', skip_data=True) # Skip X matrix
```
### Loom
```python
# Write to Loom format
adata.write_loom('output.loom')
```
## Reading Specific Elements
For fine-grained control, read specific elements from an open store:
```python
import h5py
from anndata.io import read_elem
# Read just observation annotations from an h5ad file
with h5py.File('data.h5ad', 'r') as f:
obs = read_elem(f['obs'])
layer = read_elem(f['layers/normalized'])
params = read_elem(f['uns/pca'])
```
## Writing Specific Elements
```python
from anndata.io import write_elem
import h5py
# Write element to existing file
with h5py.File('data.h5ad', 'a') as f:
write_elem(f, 'new_layer', adata.X.copy())
```
## Lazy Operations
For very large datasets, use lazy reading to avoid loading entire datasets. `read_lazy` is experimental and is designed for on-disk or in-cloud AnnData stores, including lazy `obs` and `var` access.
```python
from anndata.experimental import read_lazy
adata = read_lazy('large_data.zarr')
print(adata.obs.head()) # Does not require loading X
```
For element-level control, use `read_elem_lazy` on an open store:
```python
import h5py
from anndata.experimental import read_elem_lazy
# Lazy read from an open store (returns dask-backed array)
with h5py.File('large_data.h5ad', 'r') as f:
X_lazy = read_elem_lazy(f['X'])
subset = X_lazy[:100, :100].compute()
```
## Common I/O Patterns
### Convert between formats
```python
from anndata.io import read_mtx, read_csv
# MTX to H5AD
adata = read_mtx('matrix.mtx').T
adata.write_h5ad('data.h5ad')
# CSV to H5AD
adata = read_csv('data.csv')
adata.write_h5ad('data.h5ad')
# H5AD to Zarr
adata = ad.read_h5ad('data.h5ad')
adata.write_zarr('data.zarr')
```
### Load metadata without data
```python
# Backed mode allows inspecting metadata without loading X
adata = ad.read_h5ad('large_file.h5ad', backed='r')
print(f"Dataset contains {adata.n_obs} observations and {adata.n_vars} variables")
print(adata.obs.columns)
print(adata.var.columns)
# X is not loaded into memory
```
### Update backed data or write a new file
```python
# Open in read-write mode for X updates
adata = ad.read_h5ad('data.h5ad', backed='r+')
# X updates can be persisted in backed mode
adata.X[0, 0] = 0
# Metadata changes are not persisted from backed mode; write a new file instead
adata_memory = adata.to_memory()
adata_memory.obs['new_column'] = values
adata_memory.write_h5ad('data_with_metadata.h5ad')
```
### Download from a trusted URL
Validate remote sources before downloading. Prefer local files or vetted object-store paths over arbitrary URLs.
```python
import anndata as ad
import urllib.request
from urllib.parse import urlparse
url = 'https://example.org/datasets/reference.h5ad'
parsed = urlparse(url)
trusted_hosts = {'example.org'}
if parsed.scheme != 'https' or parsed.netloc not in trusted_hosts:
raise ValueError('Refusing to download from an untrusted host')
urllib.request.urlretrieve(url, 'reference.h5ad')
adata = ad.read_h5ad('reference.h5ad')
```
## Performance Tips
### Reading
- Use `backed='r'` for large files you only need to query
- Use `backed='r+'` only for `X` updates; write a new file for metadata changes
- H5AD format is generally fastest for random access
- Zarr is better for cloud storage and parallel access
- Consider compression for storage, but note it may slow down reading
### Writing
- Use compression for long-term storage: `compression='gzip'` or `compression='lzf'`
- LZF compression is faster but compresses less than GZIP
- For Zarr, tune chunk sizes based on access patterns:
- Larger chunks for sequential reads
- Smaller chunks for random access
- Convert string columns to categorical before writing (smaller files)
### Memory management
```python
# Convert strings to categoricals (reduces file size and memory)
adata.strings_to_categoricals()
adata.write_h5ad('data.h5ad')
# Use sparse matrices for sparse data
from scipy.sparse import csr_matrix
if isinstance(adata.X, np.ndarray):
density = np.count_nonzero(adata.X) / adata.X.size
if density < 0.5: # If more than 50% zeros
adata.X = csr_matrix(adata.X)
```
## Handling Large Datasets
### Strategy 1: Backed mode
```python
# Work with dataset larger than RAM
adata = ad.read_h5ad('100GB_file.h5ad', backed='r')
# Filter based on metadata (fast, no data loading)
filtered = adata[adata.obs['quality_score'] > 0.8]
# Load filtered subset into memory
adata_memory = filtered.to_memory()
```
### Strategy 2: Chunked processing
```python
# Process data in chunks
adata = ad.read_h5ad('large_file.h5ad', backed='r')
chunk_size = 1000
results = []
for i in range(0, adata.n_obs, chunk_size):
chunk = adata[i:i+chunk_size, :].to_memory()
# Process chunk
result = process(chunk)
results.append(result)
```
### Strategy 3: Use AnnCollection
```python
import anndata as ad
from anndata.experimental import AnnCollection
# Create backed objects, then lazily concatenate along observations
adatas = [
ad.read_h5ad(f'dataset_{i}.h5ad', backed='r')
for i in range(10)
]
collection = AnnCollection(
adatas,
join_obs='inner',
join_vars='inner'
)
# Process collection lazily
# Data is loaded only when accessed
```
## Common Issues and Solutions
### Issue: Out of memory when reading
**Solution**: Use backed mode or read in chunks
```python
adata = ad.read_h5ad('file.h5ad', backed='r')
```
### Issue: Slow reading from cloud storage
**Solution**: Use Zarr format with appropriate chunking
```python
adata.write_zarr('data.zarr', chunks=(1000, 1000))
```
### Issue: Large file sizes
**Solution**: Use compression and convert to sparse/categorical
```python
adata.strings_to_categoricals()
from scipy.sparse import csr_matrix
adata.X = csr_matrix(adata.X)
adata.write_h5ad('compressed.h5ad', compression='gzip')
```
### Issue: Cannot modify backed metadata
**Solution**: Load to memory and write a new file. Backed mode only persists updates to `X`.
```python
adata = adata.to_memory()
adata.obs['new_column'] = values
adata.write_h5ad('updated_file.h5ad')
```
+516
View File
@@ -0,0 +1,516 @@
# Data Manipulation
Operations for transforming, subsetting, and manipulating AnnData objects.
## Subsetting
### By indices
```python
import anndata as ad
import numpy as np
adata = ad.AnnData(X=np.random.rand(1000, 2000))
# Integer indices
subset = adata[0:100, 0:500] # First 100 obs, first 500 vars
# List of indices
obs_indices = [0, 10, 20, 30, 40]
var_indices = [0, 1, 2, 3, 4]
subset = adata[obs_indices, var_indices]
# Single observation or variable
single_obs = adata[0, :]
single_var = adata[:, 0]
```
### By names
```python
import pandas as pd
# Create with named indices
obs_names = [f'cell_{i}' for i in range(1000)]
var_names = [f'gene_{i}' for i in range(2000)]
adata = ad.AnnData(
X=np.random.rand(1000, 2000),
obs=pd.DataFrame(index=obs_names),
var=pd.DataFrame(index=var_names)
)
# Subset by observation names
subset = adata[['cell_0', 'cell_1', 'cell_2'], :]
# Subset by variable names
subset = adata[:, ['gene_0', 'gene_10', 'gene_20']]
# Both axes
subset = adata[['cell_0', 'cell_1'], ['gene_0', 'gene_1']]
```
### By boolean masks
```python
# Create boolean masks
high_count_obs = np.random.rand(1000) > 0.5
high_var_genes = np.random.rand(2000) > 0.7
# Subset using masks
subset = adata[high_count_obs, :]
subset = adata[:, high_var_genes]
subset = adata[high_count_obs, high_var_genes]
```
### By metadata conditions
```python
# Add metadata
adata.obs['cell_type'] = np.random.choice(['A', 'B', 'C'], 1000)
adata.obs['quality_score'] = np.random.rand(1000)
adata.var['highly_variable'] = np.random.rand(2000) > 0.8
# Filter by cell type
t_cells = adata[adata.obs['cell_type'] == 'A']
# Filter by multiple conditions
high_quality_a_cells = adata[
(adata.obs['cell_type'] == 'A') &
(adata.obs['quality_score'] > 0.7)
]
# Filter by variable metadata
hv_genes = adata[:, adata.var['highly_variable']]
# Complex conditions
filtered = adata[
(adata.obs['quality_score'] > 0.5) &
(adata.obs['cell_type'].isin(['A', 'B'])),
adata.var['highly_variable']
]
```
## Transposition
```python
# Transpose AnnData object (swap observations and variables)
adata_T = adata.T
# Shape changes
print(adata.shape) # (1000, 2000)
print(adata_T.shape) # (2000, 1000)
# obs and var are swapped
print(adata.obs.head()) # Observation metadata
print(adata_T.var.head()) # Same data, now as variable metadata
# Useful when data is in opposite orientation
# Common with some file formats where genes are rows
```
## Copying
### Full copy
```python
# Create independent copy
adata_copy = adata.copy()
# Modifications to copy don't affect original
adata_copy.obs['new_column'] = 1
print('new_column' in adata.obs.columns) # False
```
### Shallow copy
```python
# View (doesn't copy data, modifications affect original)
adata_view = adata[0:100, :]
# Check if object is a view
print(adata_view.is_view) # True
# Convert view to independent copy
adata_independent = adata_view.copy()
print(adata_independent.is_view) # False
```
## Renaming
### Rename observations and variables
```python
# Rename all observations
adata.obs_names = [f'new_cell_{i}' for i in range(adata.n_obs)]
# Rename all variables
adata.var_names = [f'new_gene_{i}' for i in range(adata.n_vars)]
# Make names unique (add suffix to duplicates)
adata.obs_names_make_unique()
adata.var_names_make_unique()
```
### Rename categories
```python
# Create categorical column
adata.obs['cell_type'] = pd.Categorical(['A', 'B', 'C'] * 333 + ['A'])
# Rename categories
adata.rename_categories('cell_type', ['Type_A', 'Type_B', 'Type_C'])
# Or using dictionary
adata.rename_categories('cell_type', {
'Type_A': 'T_cell',
'Type_B': 'B_cell',
'Type_C': 'Monocyte'
})
```
## Type Conversions
### Strings to categoricals
```python
# Convert string columns to categorical (more memory efficient)
adata.obs['cell_type'] = ['TypeA', 'TypeB'] * 500
adata.obs['tissue'] = ['brain', 'liver'] * 500
# Convert all string columns to categorical
adata.strings_to_categoricals()
print(adata.obs['cell_type'].dtype) # category
print(adata.obs['tissue'].dtype) # category
```
### Sparse to dense and vice versa
```python
from scipy.sparse import csr_matrix, issparse
# Dense to sparse
if not issparse(adata.X):
adata.X = csr_matrix(adata.X)
# Sparse to dense
if issparse(adata.X):
adata.X = adata.X.toarray()
# Convert layer
adata.layers['normalized'] = csr_matrix(adata.layers['normalized'])
```
## Chunked Operations
Process large datasets in chunks:
```python
# Iterate through data in chunks
chunk_size = 100
for chunk in adata.chunked_X(chunk_size):
# Process chunk
result = process_chunk(chunk)
```
## Extracting Vectors
### Get observation vectors
```python
# Get observation metadata as array
cell_types = adata.obs_vector('cell_type')
# Get gene expression across observations
actb_expression = adata.obs_vector('ACTB') # If ACTB in var_names
```
### Get variable vectors
```python
# Get variable metadata as array
gene_names = adata.var_vector('gene_name')
```
## Adding/Modifying Data
### Add observations
```python
# Create new observations
new_obs = ad.AnnData(X=np.random.rand(100, adata.n_vars))
new_obs.var_names = adata.var_names
# Concatenate with existing
adata_extended = ad.concat([adata, new_obs], axis=0)
```
### Add variables
```python
# Create new variables
new_vars = ad.AnnData(X=np.random.rand(adata.n_obs, 100))
new_vars.obs_names = adata.obs_names
# Concatenate with existing
adata_extended = ad.concat([adata, new_vars], axis=1)
```
### Add metadata columns
```python
# Add observation annotation
adata.obs['new_score'] = np.random.rand(adata.n_obs)
# Add variable annotation
adata.var['new_label'] = ['label'] * adata.n_vars
# Add from external data
external_data = pd.read_csv('metadata.csv', index_col=0)
adata.obs['external_info'] = external_data.loc[adata.obs_names, 'column']
```
### Add layers
```python
# Add new layer
adata.layers['raw_counts'] = np.random.randint(0, 100, adata.shape)
adata.layers['log_transformed'] = np.log1p(adata.X)
# Replace layer
adata.layers['normalized'] = new_normalized_data
```
### Add embeddings
```python
# Add PCA
adata.obsm['X_pca'] = np.random.rand(adata.n_obs, 50)
# Add UMAP
adata.obsm['X_umap'] = np.random.rand(adata.n_obs, 2)
# Add multiple embeddings
adata.obsm['X_tsne'] = np.random.rand(adata.n_obs, 2)
adata.obsm['X_diffmap'] = np.random.rand(adata.n_obs, 10)
```
### Add pairwise relationships
```python
from scipy.sparse import csr_matrix
# Add nearest neighbor graph
n_obs = adata.n_obs
knn_graph = csr_matrix(np.random.rand(n_obs, n_obs) > 0.95)
adata.obsp['connectivities'] = knn_graph
# Add distance matrix
adata.obsp['distances'] = csr_matrix(np.random.rand(n_obs, n_obs))
```
### Add unstructured data
```python
# Add analysis parameters
adata.uns['pca'] = {
'variance': [0.2, 0.15, 0.1],
'variance_ratio': [0.4, 0.3, 0.2],
'params': {'n_comps': 50}
}
# Add color schemes
adata.uns['cell_type_colors'] = ['#FF0000', '#00FF00', '#0000FF']
```
## Removing Data
### Remove observations or variables
```python
# Keep only specific observations
keep_obs = adata.obs['quality_score'] > 0.5
adata = adata[keep_obs, :]
# Remove specific variables
remove_vars = adata.var['low_count']
adata = adata[:, ~remove_vars]
```
### Remove metadata columns
```python
# Remove observation column
adata.obs.drop('unwanted_column', axis=1, inplace=True)
# Remove variable column
adata.var.drop('unwanted_column', axis=1, inplace=True)
```
### Remove layers
```python
# Remove specific layer
del adata.layers['unwanted_layer']
# Remove all layers
adata.layers = {}
```
### Remove embeddings
```python
# Remove specific embedding
del adata.obsm['X_tsne']
# Remove all embeddings
adata.obsm = {}
```
### Remove unstructured data
```python
# Remove specific key
del adata.uns['unwanted_key']
# Remove all unstructured data
adata.uns = {}
```
## Reordering
### Sort observations
```python
# Sort by observation metadata
adata = adata[adata.obs.sort_values('quality_score').index, :]
# Sort by observation names
adata = adata[sorted(adata.obs_names), :]
```
### Sort variables
```python
# Sort by variable metadata
adata = adata[:, adata.var.sort_values('gene_name').index]
# Sort by variable names
adata = adata[:, sorted(adata.var_names)]
```
### Reorder to match external list
```python
# Reorder observations to match external list
desired_order = ['cell_10', 'cell_5', 'cell_20', ...]
adata = adata[desired_order, :]
# Reorder variables
desired_genes = ['TP53', 'ACTB', 'GAPDH', ...]
adata = adata[:, desired_genes]
```
## Data Transformations
### Normalize
```python
# Total count normalization (CPM/TPM-like)
total_counts = adata.X.sum(axis=1)
adata.layers['normalized'] = adata.X / total_counts[:, np.newaxis] * 1e6
# Log transformation
adata.layers['log1p'] = np.log1p(adata.X)
# Z-score normalization
mean = adata.X.mean(axis=0)
std = adata.X.std(axis=0)
adata.layers['scaled'] = (adata.X - mean) / std
```
### Filter
```python
# Filter cells by total counts
total_counts = np.array(adata.X.sum(axis=1)).flatten()
adata.obs['total_counts'] = total_counts
adata = adata[adata.obs['total_counts'] > 1000, :]
# Filter genes by detection rate
detection_rate = (adata.X > 0).sum(axis=0) / adata.n_obs
adata.var['detection_rate'] = np.array(detection_rate).flatten()
adata = adata[:, adata.var['detection_rate'] > 0.01]
```
## Working with Views
Views are lightweight references to subsets of data that don't copy the underlying matrix:
```python
# Create view
view = adata[0:100, 0:500]
print(view.is_view) # True
# Views allow read access
data = view.X
# Modifying view data affects original
# (Be careful!)
# Convert view to independent copy
independent = view.copy()
# Force AnnData to be a copy, not a view
adata = adata.copy()
```
## Merging Metadata
```python
# Merge external metadata
external_metadata = pd.read_csv('additional_metadata.csv', index_col=0)
# Join metadata (inner join on index)
adata.obs = adata.obs.join(external_metadata)
# Left join (keep all adata observations)
adata.obs = adata.obs.merge(
external_metadata,
left_index=True,
right_index=True,
how='left'
)
```
## Common Manipulation Patterns
### Quality control filtering
```python
# Calculate QC metrics
adata.obs['n_genes'] = (adata.X > 0).sum(axis=1)
adata.obs['total_counts'] = adata.X.sum(axis=1)
adata.var['n_cells'] = (adata.X > 0).sum(axis=0)
# Filter low-quality cells
adata = adata[adata.obs['n_genes'] > 200, :]
adata = adata[adata.obs['total_counts'] < 50000, :]
# Filter rarely detected genes
adata = adata[:, adata.var['n_cells'] >= 3]
```
### Select highly variable genes
```python
# Mark highly variable genes
gene_variance = np.var(adata.X, axis=0)
adata.var['variance'] = np.array(gene_variance).flatten()
adata.var['highly_variable'] = adata.var['variance'] > np.percentile(gene_variance, 90)
# Subset to highly variable genes
adata_hvg = adata[:, adata.var['highly_variable']].copy()
```
### Downsample
```python
# Random sampling of observations
np.random.seed(42)
n_sample = 500
sample_indices = np.random.choice(adata.n_obs, n_sample, replace=False)
adata_downsampled = adata[sample_indices, :].copy()
# Stratified sampling by cell type
from sklearn.model_selection import train_test_split
train_idx, test_idx = train_test_split(
range(adata.n_obs),
test_size=0.2,
stratify=adata.obs['cell_type']
)
adata_train = adata[train_idx, :].copy()
adata_test = adata[test_idx, :].copy()
```
### Split train/test
```python
# Random train/test split
np.random.seed(42)
n_obs = adata.n_obs
train_size = int(0.8 * n_obs)
indices = np.random.permutation(n_obs)
train_indices = indices[:train_size]
test_indices = indices[train_size:]
adata_train = adata[train_indices, :].copy()
adata_test = adata[test_indices, :].copy()
```
+150
View File
@@ -0,0 +1,150 @@
---
name: arbor
description: Autonomously improve a real artifact (code, training recipe, agent harness, data pipeline, prompt) against an objective and an evaluator, using Hypothesis Tree Refinement (HTR) from the Arbor paper. Use this whenever someone wants to iteratively optimize something over many experiments without overfitting — e.g. "get my model's eval score up", "improve this agent/harness", "tune this pipeline", "beat the baseline on this benchmark", "run a search over approaches and keep the best", "do an MLE-bench / Kaggle-style optimization", or any long-horizon "make this artifact better and don't just memorize the dev set" task. Trigger it even when the user doesn't say "Arbor" or "hypothesis tree" but describes repeated experiment-and-evaluate loops, branching exploration of competing ideas, or worries about a dev/test gap. Runs Claude itself as the coordinator with subagent executors in isolated git worktrees; for the standalone `arbor` CLI tool see references/arbor-upstream.md.
allowed-tools: Read Write Edit Bash Agent
license: MIT license
metadata: {"version": "1.0", "skill-author": "K-Dense Inc."}
---
# Arbor — Autonomous Optimization via Hypothesis Tree Refinement
## Overview
This skill runs an **Autonomous Optimization (AO)** loop: starting from an existing artifact and a measurable objective, improve it through many rounds of experiment and evaluation — without step-by-step human supervision and without overfitting to the feedback signal. It's the right tool when the bottleneck isn't writing one good change, but *organizing dozens of trials* so that lessons accumulate instead of evaporating.
It implements **Hypothesis Tree Refinement (HTR)** from *Arbor* (Jin et al., 2026). The key idea: keep the research state in a persistent **hypothesis tree** rather than in conversation history. Each node binds a hypothesis, the distilled insight it produced, and a pointer to the artifact version that realizes it. You play the long-lived **coordinator** that owns this tree and decides where to search; short-lived **executor** subagents test one hypothesis each in isolated git worktrees and report back. A **held-out merge gate** admits a change only when it improves on a *test* evaluator the search never optimized against. This is what turns trial-and-error into cumulative, auditable research.
Use the `scripts/tree.py` state manager for all the bookkeeping (creating nodes, writing evidence, propagating insights, pruning, the merge gate, the Observe projection). It keeps the state consistent and frees you to spend judgment on what the evidence *means*.
## When to use this skill
Reach for Arbor when the task is **iterative improvement of a concrete artifact under an evaluator**:
- Model training: optimizer/architecture/recipe changes to lower loss or hit a target in fewer steps.
- Harness/agent engineering: raising pass rate or accuracy of an agent loop, search harness, or tool-use scaffold.
- Data synthesis: improving a generation/filtering pipeline judged by downstream model behavior.
- Benchmark optimization: MLE-bench / Kaggle-style "improve the submission" tasks.
- Prompt/system optimization where you can score outputs automatically.
The distinguishing signals: there's an **artifact you can modify**, an **objective**, a way to **score** candidates, and you expect to run **many experiments**. If the user only wants a single fix or a one-shot answer, this is overkill — just do the work directly. If they want open-ended ideation with no evaluator, use `hypothesis-generation` or `scientific-brainstorming` instead.
## The AO setup — pin this down first
Before any experiments, establish the task tuple `(M_0, O, E_dev, E_test)`. Getting this right matters more than any later decision, so confirm it explicitly:
- **M_0 — initial material**: the artifact to improve (a repo, a script, a config, a prompt). Make sure it's under git and currently runs.
- **O — objective**: the natural-language goal and the metric *direction* (maximize accuracy? minimize loss/steps?).
- **E_dev — development evaluator**: a command you can run freely during search to score a candidate. Fast, repeatable.
- **E_test — held-out test evaluator**: a *separate* evaluator (different seeds, different split, or a larger run) used only at the merge gate. It must not be used as a search oracle — that's the whole point.
If the user hasn't given you a clean dev/test split, **construct one and say so**. The dev/test separation is the mechanism that catches overfitting: a candidate that wins on dev but not on test isn't a success, it's a warning that you're exploiting the feedback signal. Without it, autonomous search reliably overfits.
Initialize the run:
```bash
python scripts/tree.py init \
--objective "Improve BrowseComp answer accuracy on the search harness" \
--dev-eval "python eval.py --split dev --n 50" \
--test-eval "python eval.py --split test --n 300" \
--material "." --metric-direction max --branching 3 --max-depth 2 --budget 12
```
`--branching` is how many sibling hypotheses you propose per parent; `--max-depth 2` keeps directions at depth 1 and concrete interventions at depth 2 (the paper's default); `--budget` is the number of coordinator cycles. Start small (1020 cycles) — structured search beats brute force, and you can extend if progress is still being made.
## The coordinator loop
You run repeated cycles of six steps. This is the heart of HTR; do not collapse it into ad-hoc editing. Run `python scripts/tree.py cycle` once per cycle to track the budget.
### 1. Observe
Begin every cycle by re-grounding in the tree, not in your memory of the conversation:
```bash
python scripts/tree.py observe
```
This prints the objective, global insights, the active frontier (selectable hypotheses), executed nodes with their evidence, pruned lessons (negative constraints), and the current best artifact. Treating the tree as the source of truth is what keeps you coherent over a long run, after context compression has thrown away the details.
### 2. Ideate
Pick a promising parent and propose a few child hypotheses under it. **Condition on the tree's evidence** — this is the difference between Arbor and random search:
- Validated insights are assumptions you can build on.
- Pruned nodes are dead ends to avoid.
- A "half-right" result is a *starting point for a sharper hypothesis*, not a reason to abandon the direction.
Each hypothesis should be a **falsifiable claim about how changing the artifact will move the metric**, not a vague intention. Depth-1 nodes are broad directions ("the search harness loses correct answers it already retrieved"); depth-2 nodes are concrete, executable interventions ("run K=5 independent rollouts and aggregate by evidence dossier instead of majority vote").
```bash
python scripts/tree.py add-node --parent n0 --hypothesis "Verification, not retrieval, is the bottleneck: candidates are found but discarded"
python scripts/tree.py add-node --parent n4 --hypothesis "Decompose the question into atomic constraints and verify each independently"
```
### 3. Select
Choose which pending leaves to run next. **Selection is not pure score-maximization** — pick a hypothesis because it has strong prior evidence, because it would resolve an ambiguity its siblings exposed, or because its failure would clarify an important assumption. Frontier control under delayed feedback rewards informative experiments, not just promising ones.
### 4. Dispatch
Run each selected hypothesis as an **executor subagent in an isolated worktree** (use the Agent tool with `isolation: "worktree"`, or have the executor create one with `git worktree add`). Isolation matters: parallel experiments must not clobber each other or the current best, and exploratory changes stay quarantined until they pass the merge gate.
Dispatch siblings **in parallel** (multiple Agent calls in one message) when they're independent — comparative evidence within one direction is exactly what makes later pruning and abstraction possible.
Give each executor a tight, **hypothesis-bound** brief. See `references/executor-brief.md` for the full template. The contract that makes HTR work: **the executor may not change the hypothesis when the metric stalls.** It repairs its own code and reruns, but `h_n` is fixed — otherwise the returned score is no longer evidence about the assigned node and the tree's semantics break. The executor returns exactly four things:
- **dev_score** — the dev evaluator result (for selection);
- **result** — a factual summary of what happened;
- **insight** — the distilled, reusable lesson (*why* the result supports, weakens, or bounds the hypothesis);
- **branch_ref** — the git branch/commit/worktree path holding the artifact.
Mark a node `running` before dispatch (`tree.py set-status --node n5 --status running`) so the Observe projection stays accurate.
### 5. Backpropagate
When an executor returns, write its report into the node, then **abstract the lesson upward**:
```bash
python scripts/tree.py set-evidence --node n5 --dev-score 70.0 \
--result "K=5 dossier aggregation recovers answers in minority rollouts" \
--insight "Correct answers often appear in a minority of rollouts; aggregation beats majority vote" \
--branch-ref "wt/n5"
python scripts/tree.py propagate --node n5 \
--insight "Candidate coverage, not verification, limits this direction" --to-root
```
This is the step that makes the tree more than a log. A leaf-level observation ("data-interface mismatch") should become a direction-level constraint and, if it generalizes, a global prior that shapes future ideation. **Insight propagation is the component that drives most of HTR's gains** — in the paper's MLE-Bench Lite ablation, a tree *without* insight feedback scored even lower than a flat experiment queue with no tree at all (54.5% vs. 63.6% any-medal, against 81.8% for the full system). Hierarchy alone isn't enough: the semantic memory is what matters. So spend real thought on the abstraction; don't just copy the leaf insight upward verbatim.
### 6. Decide
Decide what to do with the new evidence: keep expanding a direction, prune a falsified subtree, or attempt to merge a candidate.
- **Prune** dead ends, recording *why* — the reason becomes a negative constraint:
```bash
python scripts/tree.py prune --node n7 --reason "search-augmented judge overfits dev questions; no test transfer"
```
- **Merge gate** — promote a candidate to the new best **only if it improves on `E_test`**. Run the test evaluator in a *fresh* worktree (not the dev worktree, to avoid leakage), then:
```bash
python scripts/tree.py merge --node n5 --test-score 67.67 --branch-ref "wt/n5"
```
If the gate rejects it, that's informative: a high-dev / low-test candidate is evidence the direction may be exploiting the dev signal rather than producing a transferable improvement. Record that lesson; don't quietly promote it anyway.
Repeat until the budget is spent, the frontier is exhausted, or progress has clearly stalled.
## Finishing the run
When you stop, produce a short report (see `references/report-template.md`) covering:
- the final best artifact, its test score, and its delta over `M_0`;
- the tree (`python scripts/tree.py status`) as the audit trail of what was tried;
- the main hypothesis shifts — how task understanding deepened across the run (early nodes test broad mechanisms; later nodes find their limits; ancestor insights compress these into the constraints behind the final design);
- merged vs. explored: many nodes improve dev, far fewer pass the test gate — report that gap honestly rather than overstating dev wins.
Always leave `M_best` as a real, runnable artifact on a named branch, and tell the user how to check it out.
## Principles that make this work (not rote rules)
These come from the paper's analysis; understanding *why* matters more than following them mechanically.
- **The tree is the memory; conversation is not.** Over a long horizon your context gets compressed. Re-Observe each cycle so decisions rest on durable evidence, not a lossy summary.
- **Structured search, not more sampling.** Arbor's gains come from how the budget is *organized* — maintaining competing hypotheses, comparing siblings, carrying lessons forward — not from spending more tokens. Don't fan out aimlessly; each experiment should be conditioned on what the tree already knows.
- **Dev guides, test admits.** Use dev feedback freely to steer exploration, but never let a dev win into the final artifact without test confirmation. The dev/test disagreement is itself a signal worth reading.
- **Executors are hypothesis-bound.** Local engineering flexibility (edit, debug, rerun) is fine; silently changing the hypothesis to chase a better number is not — it destroys the meaning of the evidence.
- **Failures are constraints, not noise.** A falsified hypothesis tells you what the solution must avoid. Pruned-with-a-reason is more valuable than pruned-and-forgotten.
## Reference files
- `references/htr-methodology.md` — deeper explanation of HTR, the node structure, the six steps, and the paper's empirical lessons (ablations, transfer, cost). Read when you want the rationale behind a design choice.
- `references/executor-brief.md` — the template for the brief you hand each executor subagent.
- `references/report-template.md` — the final-report structure.
- `references/arbor-upstream.md` — how to install and run the standalone `arbor` CLI from RUC-NLPIR/Arbor instead of orchestrating it natively, and when to prefer each.
+91
View File
@@ -0,0 +1,91 @@
# Running the standalone Arbor CLI (upstream tool)
This skill normally runs HTR **natively** — Claude is the coordinator and
subagents are executors. That's the recommended path: no extra install, no
separate API keys, and you stay in the loop to read evidence between cycles.
But the paper's authors also ship a full implementation as a CLI. Use it instead
when the user explicitly wants to run *the published system* (e.g. to reproduce
paper results), wants Arbor to run fully unattended for many hours via its own
live dashboard, or wants its built-in report/web-UI tooling.
Source: https://github.com/RUC-NLPIR/Arbor
## Install
Requires Python ≥ 3.10 and Git.
```bash
git clone https://github.com/RUC-NLPIR/Arbor.git
cd Arbor
python -m venv .venv && source .venv/bin/activate
pip install -e .
arbor doctor # verify install, PATH, git, API keys
```
## Configure provider/model/keys
```bash
arbor setup # writes ~/.arbor/config.yaml (provider, model, base URL, keys)
```
Supported backends: Anthropic, OpenAI / OpenAI-compatible Responses API, and
LiteLLM (DeepSeek, Gemini, Qwen, vLLM, Ollama, local gateways). Keys can also be
set via environment variables.
## Run
1. Prepare a benchmark directory: an initial artifact under a **clean git repo**
plus an evaluation script (your `E_dev` / `E_test`).
2. Author a project `research_config.yaml` (task description, coordinator
settings — max cycles, depth, merge thresholds — executor max turns, UI mode).
See `examples/research_config.example.yaml` in the repo.
3. Start the interactive session:
```bash
arbor
```
Arbor runs an intake conversation, forms a Research Contract, then a live
dashboard takes over. Each experiment runs in an isolated git worktree;
verified improvements merge into a per-run trunk.
4. Outputs land in `.arbor/sessions/` with `REPORT.md`, the event log, and
results. Re-render a past session's report with `arbor report <session>`.
## Key CLI commands
| Command | Purpose |
|---|---|
| `arbor` | Start an interactive research session |
| `arbor setup` | Configure provider / model / keys |
| `arbor doctor` | Diagnose install, PATH, git, API keys |
| `arbor report <session>` | Re-render reports for a past session |
| `arbor version` | Print installed version |
## Codebase map (for the curious / for debugging)
The implementation lives under `src/` (a src-layout; the CLI installs as
`arbor`). The package directories are:
- `core/` — ReAct loop, tools, LLM providers, context management
- `coordinator/` — coordinator agent, the tree, orchestrator, coordinator tools
- `executor/` — executor agent and CLI
- `cli/` — intake, live dashboard, setup, doctor, config
- `events/` — typed event bus and payloads
- `report/`, `webui/` — report generation and read-only run monitor
- `search_agent/` — the minimal ReAct search harness (the `M_0` for the
BrowseComp / search-agent tasks)
- `plugins/` — domain plugins (e.g. `mle_kaggle.yaml`)
- `skills/` — on-demand markdown playbooks
(top-level `src/` also has `dashboard.py`, `run.py`, `review.py`.)
**Naming note:** the paper and this skill call the persistent state the
**hypothesis tree**; the tool's code and dashboard call the same structure the
**Idea Tree**. They are the same thing. The depth convention also matches the
native skill: root/depth 0 = objective + global insights, depth 1 = research
directions, depth 2+ = concrete tested methods.
## Native vs. upstream — quick guide
- **Native (this skill)**: best default. Lower setup, transparent, you read and
steer between cycles, reuses your existing Claude Code session and worktrees.
- **Upstream CLI**: choose for paper reproduction, long unattended runs with the
official dashboard, or when the user specifically asks for the `arbor` tool.
+68
View File
@@ -0,0 +1,68 @@
# Executor brief template
Each executor is a short-lived subagent that tests **one** hypothesis in an
isolated git worktree and returns structured evidence. Dispatch it with the
Agent tool (use `isolation: "worktree"` so it gets its own copy of the repo, or
instruct it to run `git worktree add` itself). Dispatch independent siblings in
parallel — multiple Agent calls in one message.
Fill in the bracketed parts. Keep the brief tight: the executor needs the
hypothesis, the context that lets it implement well, and a crisp contract for
what to return — nothing more.
---
```
You are an Arbor executor. Test ONE hypothesis in an isolated git worktree and
return structured evidence. Do not change the hypothesis — your job is to give
the coordinator clean evidence about THIS claim, even if it turns out false.
HYPOTHESIS (h_n):
[the falsifiable claim, e.g. "Aggregating K=5 independent rollouts by an
evidence dossier recovers correct answers that majority vote discards."]
CURRENT BEST ARTIFACT (M_best):
[path or git ref of the current best, e.g. branch `arbor/best` — start from this]
RELEVANT INSIGHTS FROM THE TREE (assume these; build on them, don't re-litigate):
[ancestor + sibling insights, e.g. "Verification is not the bottleneck;
candidate coverage is. Search-augmented judging overfits dev questions."]
OBJECTIVE & METRIC:
[O and direction, e.g. "Maximize BrowseComp answer accuracy."]
DEVELOPMENT EVALUATOR (E_dev) — run this to score your candidate:
[exact command, e.g. `python eval.py --split dev --n 50`]
WHAT TO DO:
1. Create/confirm an isolated worktree from M_best so you don't touch other
experiments or the current best.
2. Implement the MINIMAL change that realizes the hypothesis. You may edit,
debug, and rerun freely to get a working implementation — but keep the
change bound to this hypothesis. If the metric stalls, fix YOUR code; do
not pivot to a different idea.
3. Run E_dev and record the score. Run it more than once if it's noisy.
4. Commit the artifact on a clearly named branch.
RETURN EXACTLY THIS (your final message IS the data the coordinator reads):
- dev_score: <number from E_dev>
- result: <1-3 sentences of factual outcome — what the change did>
- insight: <the reusable lesson: WHY this result supports / weakens /
bounds the hypothesis. This is the most valuable output —
make it a constraint future experiments can use, not a restatement
of the score.>
- branch_ref: <git branch/commit/worktree path holding the artifact>
Do NOT run the held-out test evaluator — that is the coordinator's merge gate.
```
---
After the executor returns, the coordinator records it with:
```bash
python scripts/tree.py set-evidence --node <id> \
--dev-score <n> --result "..." --insight "..." --branch-ref "<ref>"
```
then abstracts the lesson upward with `tree.py propagate`.
+177
View File
@@ -0,0 +1,177 @@
# Hypothesis Tree Refinement (HTR) — methodology and evidence
Background reference for the `arbor` skill. Source: *Toward Generalist
Autonomous Research via Hypothesis-Tree Refinement* (Jin et al., 2026,
arXiv:2606.11926; code: github.com/RUC-NLPIR/Arbor). Read this when you want
the reasoning behind a design choice in the main loop.
## The problem: Autonomous Optimization (AO)
AO is the operational core of autonomous research. An agent starts from an
initial artifact and a research objective, then improves the artifact through
experimental feedback **without step-level human supervision**. Formally a task
is a tuple `P = (M_0, O, E_dev, E_test)`:
- `M_0` — mutable initial material (usually a codebase + its data).
- `O` — objective: what "better" means, as a metric direction over the
artifact's output.
- `E_dev` — development evaluator the agent may use freely during search.
- `E_test` — held-out test evaluator. Same objective, different evidence.
The goal is to return `M* = argmax over candidates of S_test(M')`, subject to
the constraint that hypotheses and implementation decisions are made **without
using `E_test` as an exploration oracle**. A candidate that exploits dev-split
idiosyncrasies may raise `S_dev` but is not a successful AO solution unless the
gain also transfers to `S_test`.
Why this is hard: feedback is delayed, experiments are expensive, and failed
attempts contain information that should guide later search. If an agent treats
each trial as an independent local attempt, it loses the structure of the
research process — what was tried, what evidence came back, how each result
reshapes the space of future hypotheses.
## The three design requirements
HTR is built to satisfy three requirements that ordinary agentic tool use does
not:
1. **Branching with coherence.** Multiple competing hypotheses can be plausible
at once, so exploration must branch — but unrestricted branching degenerates
into an unstructured log. The frontier must keep competing directions
organized, comparable, and actionable.
2. **Global strategy with local execution.** Strategic decisions depend on
evidence across the whole run; implementing one hypothesis is short-horizon
code editing. Separate the two so low-level traces don't obscure the global
state, and outcomes stay attributable to the hypotheses that produced them.
3. **Exploration with held-out admission.** Dev feedback guides search;
artifact-level progress is admitted only when it transfers beyond that
feedback. The system must distinguish exploratory dev improvement from
verified test improvement.
## The hypothesis tree as research state
A rooted tree `T = (V, E)`. Each node is a research unit `n = <h_n, iota_n, mu_n>`:
- **Hypothesis `h_n`** — a verifiable/falsifiable claim about how changing the
material improves the objective. Granularity tracks depth: nodes near the root
are broad directions; deeper nodes are concrete interventions an executor can
implement and evaluate. This organizes exploration as progressive refinement
rather than a flat sequence of independent trials.
- **Insight `iota_n`** — the reusable interpretation of evidence. For an
executed leaf: what was tried, what happened, and *why* the result supports,
weakens, or constrains the hypothesis. For an internal node: an abstraction
over its children's insights — the current understanding of that direction.
It is **not** an execution transcript; it is compact semantic memory for later
ideation and selection.
- **Metadata `mu_n`** — connects the semantic hypothesis to executable evidence:
node status, dev score, factual result, implementation reference (git branch
or commit), optional background. The material itself is **not** duplicated in
the tree — only references to external artifact states produced in isolated
worktrees. This keeps the state compact while every hypothesis stays grounded
in a verifiable implementation.
Internal nodes hold abstract directions and accumulated lessons; leaves hold
candidate interventions to dispatch. After a leaf executes, its score, result,
artifact ref, and insight are written back, and the insight is propagated upward
along the path to the root. Through this abstraction, local outcomes become
direction-level lessons and eventually a compact global understanding.
The tree therefore plays three roles at once: a **search frontier** (which
directions are active/validated/pruned), a **long-term memory** (reusable
evidence from successes *and* failures), and an **auditable record** (each
artifact change linked to the hypothesis and evidence that motivated it).
## The coordinatorexecutor split
- A persistent **coordinator** owns the shared tree and decides where to expand,
which evidence to trust, what to prune, and when to merge. It sees the whole
frontier but does not perform every low-level implementation step.
- Short-lived **executors** are invoked to test one hypothesis each. An executor
gets `h_n`, relevant ancestor insights, and the current best artifact; it
creates an isolated git worktree, implements the minimal change `h_n` requires,
evaluates on `E_dev`, repairs its own broken/inactive code, and returns
structured evidence.
The boundary is the point: exploratory code changes stay isolated until they
pass the merge gate, and the tree records only decision-relevant evidence
(scores, factual outcomes, artifact refs, distilled insights) rather than a raw
log of tool calls. This is how transient execution traces become persistent
research state.
### Executors are hypothesis-bound (and why)
An executor's local loop may involve many edits and reruns, but it stays bound
to the assigned hypothesis: `h_n` is fixed. If an executor were allowed to
change the hypothesis when the metric stalls, the returned score would no longer
be evidence about the assigned node, and ancestor insights built from it would
become impossible to interpret. Keeping executors hypothesis-bound preserves the
semantic meaning of every tree update while still allowing local engineering
flexibility.
## The six-step cycle (Algorithm 1, HTR)
Each coordinator cycle is a controlled mutation of the tree through a narrow
interface:
1. **Observe** — re-ground in a structured projection of the tree (frontier,
root/global insights, ancestor insights, current best). Makes the tree the
authoritative state after context compression, instead of relying on lossy
conversation history.
2. **Ideate** — under a chosen parent, propose `k` child hypotheses, each a
refinement/alternative/correction. Ideation is conditioned on tree evidence:
validated insights are assumptions to build on, pruned nodes are negative
constraints, recent reports suggest what's feasible or under-tested.
3. **Select** — choose pending nodes to execute. Balance expected utility
against the evidence already accumulated around ancestors and siblings. A
node may be selected because it has strong prior evidence, because its
siblings exposed an unresolved ambiguity, or because its failure would
clarify an important assumption. Selection is frontier control under partial,
delayed feedback — not raw score maximization.
4. **Dispatch** — selected hypotheses go to independent executors in fresh
worktrees. Parallel sibling execution yields comparative evidence within one
direction, which feeds later pruning and abstraction.
5. **Backpropagate** — write each executor's evidence into its leaf, then update
insights along the path to the root. The propagated signal is not just a
scalar: it includes causal attributions, applicability conditions, and
reusable lessons. A leaf-level data-interface mismatch can become a
direction-level constraint and then a global prior.
6. **Decide** — continue expanding a direction, prune a falsified subtree, or
attempt a merge. Promotion is guarded by the **held-out merge gate**: the
candidate is evaluated on `E_test` in a fresh worktree and merged into
`M_best` only if it improves under `O`. This separates exploratory success on
`E_dev` from verified artifact-level progress.
## Empirical lessons (use these to prioritize effort)
From the paper's experiments across six AO tasks (model training, harness
engineering, data synthesis) plus MLE-Bench Lite:
- **Insight feedback is the dominant component.** Ablating insight propagation
while *keeping* the tree caused a larger drop than removing the tree entirely
(on MLE-Bench Lite: full 81.82% any-medal vs. 54.54% w/o insight feedback vs.
63.64% w/o tree). Hierarchy alone is not enough — a tree without propagated
lessons organizes experiments syntactically but provides no semantic memory.
**Invest your judgment in the abstraction at Backpropagate**, not just in
generating more hypotheses.
- **Structured search, not a bigger budget.** Arbor used a comparable token
budget to single-trajectory baselines (~2043M tokens) yet got larger held-out
gains. The win is in how the budget is *organized*: maintaining competing
hypotheses, isolated execution, comparison, and an updated frontier.
- **The dev/test split exposes overfitting.** Across tasks, many nodes improved
dev but only a subset passed the test gate. On Terminal-Bench, the highest-dev
candidate was *not* the best on test. Always report the merged-vs-explored gap
honestly; a high-dev/low-test result is evidence of feedback exploitation.
- **Refinement deepens task understanding.** Early nodes test whether a broad
mechanism holds; later nodes localize where it stops working; ancestor
insights compress these into the constraints the final design must satisfy.
Successful proposals are usually *evidence-conditioned* responses to earlier
failures, not fresh guesses.
- **Lessons transfer.** A harness optimized only on one task's dev feedback
improved unrelated held-out tasks, indicating HTR discovers generally useful
design changes rather than fitting the source benchmark — when, and only when,
the merge gate is enforced.
- **What HTR does *not* fix.** Arbor is strongest at a sequence of concrete
refinements once a runnable solution exists. It is weaker when progress
requires a genuinely new high-level formulation only weakly connected to the
current tree — that still leans on good human task design (the choice of
`M_0`, evaluator, metric, and interface).
@@ -0,0 +1,39 @@
# Final report template
Produce this when the run ends (budget spent, frontier exhausted, or progress
stalled). The point is an honest, auditable account — not a victory lap. Keep it
concise and grounded in the tree.
```markdown
# Arbor run report: [objective]
## Result
- **Best artifact**: [git branch/ref of M_best, e.g. `arbor/best`]
- **Test score**: [S_test of M_best] vs. initial [S_test of M_0] → delta [Δ]
- **How to check it out**: `git checkout [ref]`
- One-line summary of the change that won.
## What was tried (audit trail)
[Paste `python scripts/tree.py status` — the tree shows every direction,
which were pruned, which merged, with dev/test scores.]
## How understanding evolved
2-4 bullets tracing the main hypothesis shifts: which early nodes tested broad
mechanisms, what they confirmed or ruled out, and how that reshaped later
hypotheses. The story should explain *why* the final design looks the way it
does — i.e. the constraints the run discovered.
## Dev vs. test (overfitting check)
- Nodes that improved **dev**: [count]
- Nodes that passed the **test merge gate**: [count]
- Comment on the gap: were there high-dev / low-test candidates? What did
rejecting them tell you? An honest gap here is more trustworthy than a clean
"everything worked".
## Open directions
What you'd explore with more budget, and any direction that seemed to need a
new high-level formulation rather than further refinement (HTR's known weak
spot — flag it for the human).
```
Always leave `M_best` as a real, runnable artifact on a named git branch.
+564
View File
@@ -0,0 +1,564 @@
#!/usr/bin/env python3
"""
tree.py — persistent hypothesis-tree state manager for Arbor-style
Hypothesis Tree Refinement (HTR).
The hypothesis tree is the durable research state for an Autonomous
Optimization (AO) run. This script owns the *mechanical* parts of that state
— creating nodes, writing back evidence, propagating insights up the tree,
pruning falsified branches, recording the held-out merge gate, and rendering
an "Observe" projection — so the coordinator (you, the model) can spend its
judgment on what the evidence *means* rather than on bookkeeping.
Division of labor:
- This script keeps the state consistent and auditable. It never decides
which hypothesis to try or whether one is good.
- The coordinator reads the projection (`observe`), forms hypotheses,
interprets executor reports, and calls the mutating commands to record
those decisions.
State lives in `.arbor/` under the run directory (default: current dir):
.arbor/tree.json — the hypothesis tree (nodes, edges, evidence, insights)
.arbor/run.json — run-level config: objective, evaluators, budget, M_best
Node fields mirror the paper's research unit n = <h, iota, mu>:
hypothesis (h) — the falsifiable claim this node tests
insight (iota) — distilled, reusable lesson (filled after execution)
metadata (mu) — status, dev_score, test_score, result, branch_ref, depth
Run `python tree.py --help` or `python tree.py <command> --help`.
"""
import argparse
import json
import os
import sys
import time
from pathlib import Path
# ----------------------------------------------------------------------------
# Storage helpers
# ----------------------------------------------------------------------------
VALID_STATUS = {"pending", "running", "executed", "merged", "pruned", "root"}
def _dir(run_dir):
return Path(run_dir) / ".arbor"
def _tree_path(run_dir):
return _dir(run_dir) / "tree.json"
def _run_path(run_dir):
return _dir(run_dir) / "run.json"
def _load(path, what):
if not path.exists():
sys.exit(
f"error: no {what} found at {path}. Run `tree.py init` first "
f"(from the run directory, or pass --run-dir)."
)
with open(path) as f:
return json.load(f)
def _save(path, data):
path.parent.mkdir(parents=True, exist_ok=True)
tmp = path.with_suffix(".tmp")
with open(tmp, "w") as f:
json.dump(data, f, indent=2)
tmp.replace(path)
def _load_tree(run_dir):
return _load(_tree_path(run_dir), "hypothesis tree")
def _load_run(run_dir):
return _load(_run_path(run_dir), "run config")
def _next_id(tree):
n = tree.get("_counter", 0) + 1
tree["_counter"] = n
return f"n{n}"
def _node(tree, node_id):
node = tree["nodes"].get(node_id)
if node is None:
sys.exit(f"error: no node with id '{node_id}'. Run `tree.py status` to list nodes.")
return node
def _children(tree, node_id):
return [nid for nid, n in tree["nodes"].items() if n.get("parent") == node_id]
def _ancestors(tree, node_id):
"""Path from node up to (and including) root, nearest first."""
path = []
cur = tree["nodes"][node_id].get("parent")
while cur is not None:
path.append(cur)
cur = tree["nodes"][cur].get("parent")
return path
def _depth(tree, node_id):
return len(_ancestors(tree, node_id))
def _stamp(node):
node["updated_at"] = int(time.time())
# ----------------------------------------------------------------------------
# Commands
# ----------------------------------------------------------------------------
def cmd_init(args):
d = _dir(args.run_dir)
if _tree_path(args.run_dir).exists() and not args.force:
sys.exit(
f"error: a tree already exists at {_tree_path(args.run_dir)}. "
f"Use --force to overwrite (this erases the current run)."
)
root = {
"id": "n0",
"parent": None,
"depth": 0,
"status": "root",
"hypothesis": args.objective,
"insight": "", # global insights accumulate here via backpropagation
"metadata": {"dev_score": None, "test_score": None, "result": "", "branch_ref": None},
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
tree = {"_counter": 0, "root": "n0", "nodes": {"n0": root}}
run = {
"objective": args.objective,
"metric_direction": args.metric_direction,
"dev_eval": args.dev_eval,
"test_eval": args.test_eval,
"material": args.material,
"branching": args.branching,
"max_depth": args.max_depth,
"budget_cycles": args.budget,
"cycles_used": 0,
"best_node": None, # node id of current M_best
"best_test_score": None, # held-out score of M_best
"best_branch_ref": None, # git ref / path of M_best artifact
"created_at": int(time.time()),
}
_save(_tree_path(args.run_dir), tree)
_save(_run_path(args.run_dir), run)
print(f"Initialized Arbor run in {d}")
print(f" objective : {args.objective}")
print(f" metric direction: {args.metric_direction} (higher-is-better after orienting)")
print(f" dev evaluator : {args.dev_eval}")
print(f" test evaluator : {args.test_eval}")
print(f" budget : {args.budget} cycles, branching {args.branching}, max depth {args.max_depth}")
print("\nNext: `tree.py observe` to read the state, then add direction nodes under n0.")
def cmd_add_node(args):
tree = _load_tree(args.run_dir)
parent = _node(tree, args.parent)
run = _load_run(args.run_dir)
nid = _next_id(tree)
depth = _depth(tree, args.parent) + 1
if depth > run["max_depth"]:
print(
f"warning: node depth {depth} exceeds max_depth {run['max_depth']}. "
f"Deep nodes should be concrete, executable interventions.",
file=sys.stderr,
)
node = {
"id": nid,
"parent": args.parent,
"depth": depth,
"status": "pending",
"hypothesis": args.hypothesis,
"insight": "",
"metadata": {"dev_score": None, "test_score": None, "result": "", "branch_ref": None},
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
tree["nodes"][nid] = node
_save(_tree_path(args.run_dir), tree)
kind = "direction" if depth == 1 else "intervention"
print(f"Added {kind} node {nid} (depth {depth}) under {args.parent}: {args.hypothesis}")
def cmd_set_status(args):
tree = _load_tree(args.run_dir)
node = _node(tree, args.node)
if args.status not in VALID_STATUS:
sys.exit(f"error: status must be one of {sorted(VALID_STATUS)}")
node["status"] = args.status
_stamp(node)
_save(_tree_path(args.run_dir), tree)
print(f"{args.node} -> status={args.status}")
def cmd_set_evidence(args):
"""Write an executor's report back into its node (the Backpropagate step,
leaf part). Insight propagation upward is a separate, deliberate call."""
tree = _load_tree(args.run_dir)
node = _node(tree, args.node)
meta = node["metadata"]
if args.dev_score is not None:
meta["dev_score"] = args.dev_score
if args.result is not None:
meta["result"] = args.result
if args.branch_ref is not None:
meta["branch_ref"] = args.branch_ref
if args.insight is not None:
node["insight"] = args.insight
node["status"] = args.status or "executed"
_stamp(node)
_save(_tree_path(args.run_dir), tree)
print(f"Wrote evidence to {args.node}: dev_score={meta['dev_score']} status={node['status']}")
if node["insight"]:
print(f" insight: {node['insight']}")
anc = _ancestors(tree, args.node)
if anc:
print(
"\nReminder: abstract this leaf insight upward. Decide what direction-level "
f"lesson it implies for ancestors {anc} and record it with "
f"`tree.py propagate --node {args.node} --insight \"...\"` (and update n0 "
"global insights if it generalizes)."
)
def cmd_propagate(args):
"""Backpropagate a distilled, direction-level lesson up the ancestor path.
The coordinator decides the abstracted wording; this appends it to the
chosen ancestor(s) so later ideation is conditioned on it. By default it
updates the immediate parent; --to-root also updates global insights."""
tree = _load_tree(args.run_dir)
_node(tree, args.node) # validate
targets = _ancestors(tree, args.node)
if not targets:
sys.exit("error: node has no ancestors (is it the root?).")
if not args.to_root:
targets = targets[:1] # immediate parent only
for tid in targets:
anc = tree["nodes"][tid]
existing = anc.get("insight", "")
line = f"[from {args.node}] {args.insight}"
anc["insight"] = (existing + "\n" + line).strip() if existing else line
_stamp(anc)
_save(_tree_path(args.run_dir), tree)
print(f"Propagated insight from {args.node} to ancestors {targets}")
def cmd_prune(args):
"""Mark a node (and its subtree) pruned. Pruned hypotheses become negative
constraints — record *why* so future ideation avoids the dead end."""
tree = _load_tree(args.run_dir)
_node(tree, args.node)
stack = [args.node]
pruned = []
while stack:
cur = stack.pop()
node = tree["nodes"][cur]
if node["status"] in ("merged", "root"):
continue
node["status"] = "pruned"
if cur == args.node and args.reason:
node["metadata"]["prune_reason"] = args.reason
_stamp(node)
pruned.append(cur)
stack.extend(_children(tree, cur))
_save(_tree_path(args.run_dir), tree)
print(f"Pruned {pruned}" + (f" — reason: {args.reason}" if args.reason else ""))
def cmd_merge(args):
"""Record a held-out merge gate decision. Only call this AFTER evaluating
the candidate on the TEST evaluator in a fresh worktree. Admitting a
candidate that only improved dev defeats the purpose of the split."""
tree = _load_tree(args.run_dir)
run = _load_run(args.run_dir)
node = _node(tree, args.node)
direction = run["metric_direction"]
prev = run["best_test_score"]
def better(new, old):
if old is None:
return True
return new > old if direction == "max" else new < old
improves = better(args.test_score, prev)
node["metadata"]["test_score"] = args.test_score
if improves:
node["status"] = "merged"
run["best_node"] = args.node
run["best_test_score"] = args.test_score
run["best_branch_ref"] = args.branch_ref or node["metadata"].get("branch_ref")
_stamp(node)
_save(_tree_path(args.run_dir), tree)
_save(_run_path(args.run_dir), run)
print(
f"MERGE GATE PASSED: {args.node} test={args.test_score} "
f"beats previous best={prev}. M_best is now {args.node} "
f"(ref: {run['best_branch_ref']})."
)
else:
_stamp(node)
_save(_tree_path(args.run_dir), tree)
print(
f"MERGE GATE REJECTED: {args.node} test={args.test_score} does not beat "
f"best={prev} (direction={direction}). This is informative, not a failure: "
"a high-dev / low-test gap means the candidate may be exploiting the dev "
"signal. Record that lesson and keep M_best unchanged."
)
def cmd_cycle(args):
"""Increment the cycle counter (call once per Observe->Decide pass)."""
run = _load_run(args.run_dir)
run["cycles_used"] += 1
_save(_run_path(args.run_dir), run)
left = run["budget_cycles"] - run["cycles_used"]
print(f"Cycle {run['cycles_used']}/{run['budget_cycles']} ({left} remaining).")
if left <= 0:
print("Budget exhausted — finish the run: do a final merge-gate check and report.")
# ----------------------------------------------------------------------------
# Read-only projections
# ----------------------------------------------------------------------------
def _fmt_score(node, run):
s = node["metadata"].get("dev_score")
t = node["metadata"].get("test_score")
bits = []
if s is not None:
bits.append(f"dev={s}")
if t is not None:
bits.append(f"test={t}")
return (" [" + " ".join(bits) + "]") if bits else ""
def cmd_observe(args):
"""The Observe step: a compact projection the coordinator re-grounds on at
the start of each cycle, so decisions come from the tree rather than from a
lossy conversation history."""
tree = _load_tree(args.run_dir)
run = _load_run(args.run_dir)
nodes = tree["nodes"]
root = nodes[tree["root"]]
print("=" * 72)
print("OBSERVE — current research state")
print("=" * 72)
print(f"Objective : {run['objective']}")
print(f"Metric direction: {run['metric_direction']}")
print(f"Dev evaluator : {run['dev_eval']}")
print(f"Test evaluator : {run['test_eval']}")
print(
f"Budget : cycle {run['cycles_used']}/{run['budget_cycles']}, "
f"branching {run['branching']}, max depth {run['max_depth']}"
)
print(
f"Current best : "
+ (
f"{run['best_node']} (test={run['best_test_score']}, ref={run['best_branch_ref']})"
if run["best_node"]
else "none yet — M_best is the initial material"
)
)
print("\n-- Global insights (root) --")
print(root["insight"].strip() if root["insight"].strip() else " (none yet)")
# Active frontier = pending/running leaves
frontier = [
n
for n in nodes.values()
if n["status"] in ("pending", "running") and not _children(tree, n["id"])
]
print("\n-- Active frontier (selectable hypotheses) --")
if not frontier:
print(" (empty — ideate new children under a promising node)")
for n in sorted(frontier, key=lambda x: x["id"]):
anc = _ancestors(tree, n["id"])
anc_ins = " | ".join(
nodes[a]["insight"].replace("\n", " ")[:80] for a in anc if nodes[a]["insight"].strip()
)
print(f" {n['id']} (depth {n['depth']}, {n['status']}): {n['hypothesis']}")
if anc_ins:
print(f" ancestor insights: {anc_ins}")
# Validated / executed leaves with evidence
executed = [n for n in nodes.values() if n["status"] in ("executed", "merged")]
print("\n-- Executed / merged nodes (evidence) --")
if not executed:
print(" (none yet)")
for n in sorted(executed, key=lambda x: x["id"]):
print(f" {n['id']} [{n['status']}]{_fmt_score(n, run)}: {n['hypothesis']}")
if n["insight"].strip():
print(f" insight: {n['insight'].splitlines()[0][:120]}")
# Pruned lessons (negative constraints)
pruned = [n for n in nodes.values() if n["status"] == "pruned"]
print("\n-- Pruned lessons (negative constraints — avoid these) --")
if not pruned:
print(" (none yet)")
for n in sorted(pruned, key=lambda x: x["id"]):
reason = n["metadata"].get("prune_reason", "")
print(f" {n['id']}: {n['hypothesis']}" + (f"{reason}" if reason else ""))
print("\n" + "=" * 72)
print(
"Next: Ideate children under a promising node, Select one or more frontier\n"
"leaves, Dispatch each to an executor subagent in an isolated worktree."
)
def cmd_status(args):
"""ASCII tree render — useful for reports and quick scans."""
tree = _load_tree(args.run_dir)
run = _load_run(args.run_dir)
nodes = tree["nodes"]
symbol = {
"root": "*",
"pending": "o",
"running": "~",
"executed": "=",
"merged": "V",
"pruned": "x",
}
def render(nid, prefix=""):
n = nodes[nid]
sym = symbol.get(n["status"], "?")
best = " <== M_best" if nid == run["best_node"] else ""
print(f"{prefix}[{sym}] {nid} {n['hypothesis'][:70]}{_fmt_score(n, run)}{best}")
kids = sorted(_children(tree, nid))
for i, c in enumerate(kids):
render(c, prefix + " ")
print(f"Run: {run['objective']}")
print(f"Cycle {run['cycles_used']}/{run['budget_cycles']} | legend: * root o pending ~ running = executed V merged x pruned\n")
render(tree["root"])
def cmd_validate(args):
"""Check invariants — catch a corrupted or inconsistent tree early."""
tree = _load_tree(args.run_dir)
run = _load_run(args.run_dir)
nodes = tree["nodes"]
problems = []
if tree["root"] not in nodes:
problems.append("root id not present in nodes")
for nid, n in nodes.items():
p = n.get("parent")
if p is not None and p not in nodes:
problems.append(f"{nid}: parent {p} missing")
if n["status"] not in VALID_STATUS:
problems.append(f"{nid}: invalid status {n['status']}")
if run["best_node"] and run["best_node"] not in nodes:
problems.append(f"best_node {run['best_node']} missing")
merged = [nid for nid, n in nodes.items() if n["status"] == "merged"]
if run["best_node"] and run["best_node"] not in merged and run["best_node"] != tree["root"]:
problems.append(f"best_node {run['best_node']} is not marked merged")
if problems:
print("INVALID:")
for p in problems:
print(f" - {p}")
sys.exit(1)
print(f"OK — {len(nodes)} nodes, root={tree['root']}, best={run['best_node']}")
# ----------------------------------------------------------------------------
# CLI
# ----------------------------------------------------------------------------
def build_parser():
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("--run-dir", default=".", help="Run directory holding .arbor/ (default: current dir)")
sub = p.add_subparsers(dest="command", required=True)
s = sub.add_parser("init", help="Initialize a new AO run / hypothesis tree")
s.add_argument("--objective", required=True, help="Natural-language research objective (root hypothesis)")
s.add_argument("--dev-eval", required=True, help="Command/description of the development evaluator")
s.add_argument("--test-eval", required=True, help="Command/description of the held-out test evaluator")
s.add_argument("--material", default="", help="Path/ref to the initial artifact M_0")
s.add_argument("--metric-direction", choices=["max", "min"], default="max", help="Is higher or lower the better score?")
s.add_argument("--branching", type=int, default=3, help="Max children proposed per parent (k)")
s.add_argument("--max-depth", type=int, default=2, help="Max tree depth (directions at 1, interventions at 2+)")
s.add_argument("--budget", type=int, default=20, help="Coordinator cycle budget B")
s.add_argument("--force", action="store_true", help="Overwrite an existing run")
s.set_defaults(func=cmd_init)
s = sub.add_parser("observe", help="Print the research-state projection (start of each cycle)")
s.set_defaults(func=cmd_observe)
s = sub.add_parser("add-node", help="Add a pending child hypothesis under a parent (Ideate)")
s.add_argument("--parent", required=True, help="Parent node id (n0 for a new research direction)")
s.add_argument("--hypothesis", required=True, help="Falsifiable claim this node tests")
s.set_defaults(func=cmd_add_node)
s = sub.add_parser("set-status", help="Set a node's status manually")
s.add_argument("--node", required=True)
s.add_argument("--status", required=True, help=f"One of {sorted(VALID_STATUS)}")
s.set_defaults(func=cmd_set_status)
s = sub.add_parser("set-evidence", help="Write an executor report into its node (Backpropagate, leaf)")
s.add_argument("--node", required=True)
s.add_argument("--dev-score", type=float, default=None, help="Dev evaluator score returned by the executor")
s.add_argument("--result", default=None, help="Factual result summary")
s.add_argument("--insight", default=None, help="Distilled, reusable lesson from this experiment")
s.add_argument("--branch-ref", default=None, help="Git branch/commit/worktree path of the artifact")
s.add_argument("--status", default=None, help="Override status (default: executed)")
s.set_defaults(func=cmd_set_evidence)
s = sub.add_parser("propagate", help="Abstract a leaf insight up to ancestors (Backpropagate, upward)")
s.add_argument("--node", required=True, help="The leaf the lesson came from")
s.add_argument("--insight", required=True, help="Direction-level abstraction of the lesson")
s.add_argument("--to-root", action="store_true", help="Also record as a global insight on the root")
s.set_defaults(func=cmd_propagate)
s = sub.add_parser("prune", help="Prune a falsified node and its subtree (Decide)")
s.add_argument("--node", required=True)
s.add_argument("--reason", default="", help="Why this direction is a dead end (becomes a negative constraint)")
s.set_defaults(func=cmd_prune)
s = sub.add_parser("merge", help="Record a held-out merge gate decision (Decide)")
s.add_argument("--node", required=True)
s.add_argument("--test-score", type=float, required=True, help="Score on the TEST evaluator in a fresh worktree")
s.add_argument("--branch-ref", default=None, help="Artifact ref to promote if it passes")
s.set_defaults(func=cmd_merge)
s = sub.add_parser("cycle", help="Increment the coordinator cycle counter")
s.set_defaults(func=cmd_cycle)
s = sub.add_parser("status", help="Render the tree as ASCII (for reports)")
s.set_defaults(func=cmd_status)
s = sub.add_parser("validate", help="Check tree invariants")
s.set_defaults(func=cmd_validate)
return p
def main():
args = build_parser().parse_args()
args.func(args)
if __name__ == "__main__":
main()
+265
View File
@@ -0,0 +1,265 @@
---
name: arboreto
description: Infer gene regulatory networks (GRNs) from gene expression data using scalable algorithms (GRNBoost2, GENIE3). Use when analyzing transcriptomics data (bulk RNA-seq, single-cell RNA-seq) to identify transcription factor-target gene relationships and regulatory interactions. Supports distributed computation for large-scale datasets.
license: BSD-3-Clause license
metadata: {"version": "1.0", "skill-author": "K-Dense Inc."}
---
# Arboreto
## Overview
Arboreto is a Python library from [Aerts Lab](https://github.com/aertslab/arboreto) for inferring gene regulatory networks (GRNs) from gene expression data. It parallelizes tree-based ensemble regression (GRNBoost2, GENIE3) with [Dask](https://distributed.dask.org/) across local cores or remote clusters.
**Core capability**: Identify which transcription factors (TFs) regulate which target genes based on expression patterns across observations (cells, samples, conditions).
**Upstream**: PyPI **0.1.6** (2021-02-09, latest). Docs: [arboreto.readthedocs.io](https://arboreto.readthedocs.io/en/latest/). Primary downstream consumer: [pySCENIC](https://github.com/aertslab/pySCENIC).
## Quick Start
Install arboreto:
```bash
uv pip install arboreto
```
Basic GRN inference:
```python
import pandas as pd
from arboreto.algo import grnboost2
if __name__ == '__main__':
# Load expression data (genes as columns)
expression_matrix = pd.read_csv('expression_data.tsv', sep='\t')
# Infer regulatory network
network = grnboost2(expression_data=expression_matrix)
# Save results (TF, target, importance)
network.to_csv('network.tsv', sep='\t', index=False, header=False)
```
**Critical**: Always use `if __name__ == '__main__':` guard because Dask spawns new processes.
## Core Capabilities
### 1. Basic GRN Inference
For standard GRN inference workflows including:
- Input data preparation (Pandas DataFrame or NumPy array)
- Running inference with GRNBoost2 or GENIE3
- Filtering by transcription factors
- Output format and interpretation
**See**: `references/basic_inference.md`
**Use the ready-to-run script**: `scripts/basic_grn_inference.py` for standard inference tasks:
```bash
python scripts/basic_grn_inference.py expression_data.tsv output_network.tsv --tf-file tfs.txt --seed 777 --limit 5000
```
### 2. Algorithm Selection
Arboreto provides two algorithms:
**GRNBoost2 (Recommended)**:
- Fast gradient boosting-based inference
- Optimized for large datasets (10k+ observations)
- Default choice for most analyses
**GENIE3**:
- Random Forest-based inference
- Original multiple regression approach
- Use for comparison or validation
Quick comparison:
```python
from arboreto.algo import grnboost2, genie3
# Fast, recommended
network_grnboost = grnboost2(expression_data=matrix)
# Classic algorithm
network_genie3 = genie3(expression_data=matrix)
```
**For detailed algorithm comparison, parameters, and selection guidance**: `references/algorithms.md`
### 3. Distributed Computing
Scale inference from local multi-core to cluster environments:
**Local (default)** - Uses all available cores automatically:
```python
network = grnboost2(expression_data=matrix)
```
**Custom local client** - Control resources:
```python
from distributed import LocalCluster, Client
local_cluster = LocalCluster(n_workers=10, memory_limit='8GB')
client = Client(local_cluster)
network = grnboost2(expression_data=matrix, client_or_address=client)
client.close()
local_cluster.close()
```
**Cluster computing** - Connect to remote Dask scheduler:
```python
from distributed import Client
client = Client('tcp://scheduler:8786')
network = grnboost2(expression_data=matrix, client_or_address=client)
```
**For cluster setup, performance optimization, and large-scale workflows**: `references/distributed_computing.md`
## Installation
```bash
uv pip install arboreto
```
Conda (Bioconda):
```bash
conda install -c bioconda arboreto
```
**Dependencies** (from upstream `requirements.txt`): `dask[complete]`, `distributed`, `numpy`, `pandas`, `scikit-learn`, `scipy`
**Input formats**: pandas DataFrame, dense `numpy.ndarray`, or sparse `scipy.sparse.csc_matrix` (rows = observations, columns = genes). For array/matrix inputs, pass `gene_names` explicitly.
## Common Use Cases
### Single-Cell RNA-seq Analysis
```python
import pandas as pd
from arboreto.algo import grnboost2
if __name__ == '__main__':
# Load single-cell expression matrix (cells x genes)
sc_data = pd.read_csv('scrna_counts.tsv', sep='\t')
# Infer cell-type-specific regulatory network
network = grnboost2(expression_data=sc_data, seed=42)
# Filter high-confidence links
high_confidence = network[network['importance'] > 0.5]
high_confidence.to_csv('grn_high_confidence.tsv', sep='\t', index=False)
```
### Bulk RNA-seq with TF Filtering
```python
from arboreto.utils import load_tf_names
from arboreto.algo import grnboost2
if __name__ == '__main__':
# Load data
expression_data = pd.read_csv('rnaseq_tpm.tsv', sep='\t')
tf_names = load_tf_names('human_tfs.txt')
# Infer with TF restriction
network = grnboost2(
expression_data=expression_data,
tf_names=tf_names,
seed=123
)
network.to_csv('tf_target_network.tsv', sep='\t', index=False)
```
### Comparative Analysis (Multiple Conditions)
```python
from arboreto.algo import grnboost2
if __name__ == '__main__':
# Infer networks for different conditions
conditions = ['control', 'treatment_24h', 'treatment_48h']
for condition in conditions:
data = pd.read_csv(f'{condition}_expression.tsv', sep='\t')
network = grnboost2(expression_data=data, seed=42)
network.to_csv(f'{condition}_network.tsv', sep='\t', index=False)
```
## Output Interpretation
Arboreto returns a DataFrame with regulatory links:
| Column | Description |
|--------|-------------|
| `TF` | Transcription factor (regulator) |
| `target` | Target gene |
| `importance` | Regulatory importance score (higher = stronger) |
**Filtering strategy**:
- `limit=N` at inference time (return top N links globally)
- Post-hoc importance threshold (e.g., > 0.5)
- Top links per target via `groupby('target')`
- Statistical significance testing (permutation tests, external tools)
## Integration with pySCENIC
Arboreto powers the GRN inference step in [pySCENIC](https://github.com/aertslab/pySCENIC). pySCENIC 0.11+ passes sparse expression matrices to `grnboost2` / `genie3`; pySCENIC 0.12+ defaults to `arboreto_with_multiprocessing.py` (no Dask) for compatibility — use standalone arboreto when you need Dask scaling.
```python
# Standalone: infer co-expression modules before pySCENIC cisTarget pruning
from arboreto.algo import grnboost2
network = grnboost2(expression_data=expression_df, tf_names=tf_list, limit=5000)
# Downstream: pySCENIC ctx pruning, regulon definition, AUCell (see pySCENIC docs)
```
Convert AnnData to a DataFrame for arboreto directly:
```python
expression_df = adata.to_df() # cells x genes
```
## Reproducibility
Always set a seed for reproducible results:
```python
network = grnboost2(expression_data=matrix, seed=777)
```
Run multiple seeds for robustness analysis:
```python
from distributed import LocalCluster, Client
if __name__ == '__main__':
client = Client(LocalCluster())
seeds = [42, 123, 777]
networks = []
for seed in seeds:
net = grnboost2(expression_data=matrix, client_or_address=client, seed=seed)
networks.append(net)
# Consensus: links recurring across runs (example: mean importance per TF-target pair)
import pandas as pd
combined = pd.concat(networks)
consensus = (
combined.groupby(['TF', 'target'], as_index=False)['importance']
.mean()
.query('importance > 0.5')
)
```
## Troubleshooting
**Memory errors**: Reduce dataset size by filtering low-variance genes or use distributed computing
**Slow performance**: Use GRNBoost2 instead of GENIE3, enable distributed client, filter TF list
**Dask errors**: Ensure `if __name__ == '__main__':` guard is present in scripts (required on Windows/macOS with spawn-based multiprocessing)
**Empty results**: Check data format (genes as columns), verify TF names match column names in the expression matrix
**Sparse data**: Use `scipy.sparse.csc_matrix` and pass matching `gene_names`; supported since arboreto 0.1.6 / pySCENIC 0.11
+152
View File
@@ -0,0 +1,152 @@
# GRN Inference Algorithms
Arboreto provides two high-level algorithms for gene regulatory network (GRN) inference, both based on the multiple regression approach.
## Algorithm Overview
Both algorithms follow the same inference strategy:
1. For each target gene in the dataset, train a regression model
2. Identify the most important features (potential regulators) from the model
3. Emit these features as candidate regulators with importance scores
The key difference is **computational efficiency** and the underlying regression method.
## GRNBoost2 (Recommended)
**Purpose**: Fast GRN inference for large-scale datasets using gradient boosting.
### When to Use
- **Large datasets**: Tens of thousands of observations (e.g., single-cell RNA-seq)
- **Time-constrained analysis**: Need faster results than GENIE3
- **Default choice**: GRNBoost2 is the flagship algorithm and recommended for most use cases
### Technical Details
- **Method**: Stochastic gradient boosting with early-stopping regularization
- **Performance**: Significantly faster than GENIE3 on large datasets
- **Output**: Same format as GENIE3 (TF-target-importance triplets)
### Usage
```python
from arboreto.algo import grnboost2
network = grnboost2(
expression_data=expression_matrix,
tf_names=tf_names,
seed=42,
limit=5000,
)
```
### Parameters (`grnboost2`)
```python
grnboost2(
expression_data, # DataFrame, ndarray, or scipy.sparse.csc_matrix
gene_names=None, # Required for ndarray/sparse inputs
tf_names='all', # TF list, None/'all' → all genes as regulators
client_or_address='local', # 'local', scheduler address, or Dask Client
early_stop_window_length=25, # Early-stopping window (GRNBoost2 only)
limit=None, # Return top N links globally
seed=None, # Random seed; None = non-deterministic
verbose=False,
)
```
## GENIE3
**Purpose**: Classic Random Forest-based GRN inference, serving as the conceptual blueprint.
### When to Use
- **Smaller datasets**: When dataset size allows for longer computation
- **Comparison studies**: When comparing with published GENIE3 results
- **Validation**: To validate GRNBoost2 results
### Technical Details
- **Method**: Random Forest regression (ExtraTrees available via `diy`)
- **Foundation**: Original multiple regression GRN inference strategy
- **Trade-off**: More computationally expensive but well-established
### Usage
```python
from arboreto.algo import genie3
network = genie3(
expression_data=expression_matrix,
tf_names=tf_names,
seed=42,
)
```
### Parameters (`genie3`)
```python
genie3(
expression_data,
gene_names=None,
tf_names='all',
client_or_address='local',
limit=None,
seed=None,
verbose=False,
)
```
## Algorithm Comparison
| Feature | GRNBoost2 | GENIE3 |
|---------|-----------|--------|
| **Speed** | Fast (optimized for large data) | Slower |
| **Method** | Gradient boosting (GBM) | Random Forest |
| **Best for** | Large-scale data (10k+ observations) | Small-medium datasets |
| **Output format** | Same | Same |
| **Inference strategy** | Multiple regression | Multiple regression |
| **Recommended** | Yes (default choice) | For comparison/validation |
| **Early stopping** | Yes (`early_stop_window_length`) | No |
## Advanced: Custom Regressors with `diy`
For custom scikit-learn regressor settings, use `diy()` (not `grnboost2`/`genie3` kwargs):
```python
from arboreto.algo import diy
from arboreto.core import SGBM_KWARGS, RF_KWARGS
# Custom GRNBoost2-style run
custom_gbm = diy(
expression_data=expression_matrix,
regressor_type='GBM', # 'RF', 'GBM', or 'ET'
regressor_kwargs={
**SGBM_KWARGS,
'n_estimators': 100,
'max_depth': 5,
'learning_rate': 0.1,
},
tf_names=tf_names,
seed=42,
)
# Custom GENIE3-style run
custom_rf = diy(
expression_data=expression_matrix,
regressor_type='RF',
regressor_kwargs={
**RF_KWARGS,
'n_estimators': 1000,
'max_features': 'sqrt',
},
tf_names=tf_names,
)
```
Import default kwargs from `arboreto.core` and override only the keys you need.
## Choosing the Right Algorithm
**Decision guide**:
1. **Start with GRNBoost2** — faster and better suited to large single-cell datasets
2. **Use GENIE3 if**:
- Comparing with existing GENIE3 publications
- Dataset is small-medium sized
- Validating GRNBoost2 results
3. **Use `diy()` if** you need non-default regressor hyperparameters
Both algorithms produce comparable regulatory networks with the same output format.
@@ -0,0 +1,181 @@
# Basic GRN Inference with Arboreto
## Input Data Requirements
Arboreto requires gene expression data in one of two formats:
### Pandas DataFrame (Recommended)
- **Rows**: Observations (cells, samples, conditions)
- **Columns**: Genes (with gene names as column headers)
- **Format**: Numeric expression values
Example:
```python
import pandas as pd
# Load expression matrix with genes as columns
expression_matrix = pd.read_csv('expression_data.tsv', sep='\t')
# Columns: ['gene1', 'gene2', 'gene3', ...]
# Rows: observation data
```
### NumPy Array
- **Shape**: (observations, genes)
- **Requirement**: Separately provide gene names list matching column order
Example:
```python
import numpy as np
expression_matrix = np.genfromtxt('expression_data.tsv', delimiter='\t', skip_header=1)
with open('expression_data.tsv') as f:
gene_names = [gene.strip() for gene in f.readline().split('\t')]
assert expression_matrix.shape[1] == len(gene_names)
```
### Sparse CSC Matrix (arboreto 0.1.6+)
- **Format**: `scipy.sparse.csc_matrix` with shape (observations, genes)
- **Requirement**: Provide `gene_names` matching column order (same as NumPy)
- **Use case**: Large single-cell matrices; also used by pySCENIC 0.11+ when `--sparse` is enabled
Example:
```python
import scipy.sparse as sp
from arboreto.algo import grnboost2
# expression_sparse: csc_matrix, cells x genes
network = grnboost2(
expression_data=expression_sparse,
gene_names=gene_names,
tf_names=tf_names,
)
```
## Transcription Factors (TFs)
Optionally provide a list of transcription factor names to restrict regulatory inference:
```python
from arboreto.utils import load_tf_names
# Load from file (one TF per line)
tf_names = load_tf_names('transcription_factors.txt')
# Or define directly
tf_names = ['TF1', 'TF2', 'TF3']
```
If `tf_names` is `None` or `'all'`, all `gene_names` are treated as potential regulators.
## Basic Inference Workflow
### Using Pandas DataFrame
```python
import pandas as pd
from arboreto.utils import load_tf_names
from arboreto.algo import grnboost2
if __name__ == '__main__':
# Load expression data
expression_matrix = pd.read_csv('expression_data.tsv', sep='\t')
# Load transcription factors (optional)
tf_names = load_tf_names('tf_list.txt')
# Run GRN inference
network = grnboost2(
expression_data=expression_matrix,
tf_names=tf_names # Optional
)
# Save results
network.to_csv('network_output.tsv', sep='\t', index=False, header=False)
```
**Critical**: The `if __name__ == '__main__':` guard is required because Dask spawns new processes internally.
### Using NumPy Array
```python
import numpy as np
from arboreto.algo import grnboost2
if __name__ == '__main__':
# Load expression matrix
expression_matrix = np.genfromtxt('expression_data.tsv', delimiter='\t', skip_header=1)
# Extract gene names from header
with open('expression_data.tsv') as f:
gene_names = [gene.strip() for gene in f.readline().split('\t')]
# Verify dimensions match
assert expression_matrix.shape[1] == len(gene_names)
# Run inference with explicit gene names
network = grnboost2(
expression_data=expression_matrix,
gene_names=gene_names,
tf_names=tf_names
)
network.to_csv('network_output.tsv', sep='\t', index=False, header=False)
```
## Output Format
Arboreto returns a Pandas DataFrame with three columns:
| Column | Description |
|--------|-------------|
| `TF` | Transcription factor (regulator) gene name |
| `target` | Target gene name |
| `importance` | Regulatory importance score (higher = stronger regulation) |
Example output:
```
TF1 gene5 0.856
TF2 gene12 0.743
TF1 gene8 0.621
```
## Setting Random Seed
For reproducible results, pass an explicit `seed` (`None` uses random seeds per regressor):
```python
network = grnboost2(
expression_data=expression_matrix,
tf_names=tf_names,
seed=777
)
```
## Limiting Output Size
Return only the top N regulatory links globally:
```python
network = grnboost2(
expression_data=expression_matrix,
tf_names=tf_names,
limit=5000,
)
```
## Algorithm Selection
Use `grnboost2()` for most cases (faster, handles large datasets):
```python
from arboreto.algo import grnboost2
network = grnboost2(expression_data=expression_matrix)
```
Use `genie3()` for comparison or specific requirements:
```python
from arboreto.algo import genie3
network = genie3(expression_data=expression_matrix)
```
See `references/algorithms.md` for detailed algorithm comparison.
@@ -0,0 +1,242 @@
# Distributed Computing with Arboreto
Arboreto leverages Dask for parallelized computation, enabling efficient GRN inference from single-machine multi-core processing to multi-node cluster environments.
## Computation Architecture
GRN inference is inherently parallelizable:
- Each target gene's regression model can be trained independently
- Arboreto represents computation as a Dask task graph
- Tasks are distributed across available computational resources
## Local Multi-Core Processing (Default)
By default, arboreto uses all available CPU cores on the local machine:
```python
from arboreto.algo import grnboost2
# Automatically uses all local cores
network = grnboost2(expression_data=expression_matrix, tf_names=tf_names)
```
This is sufficient for most use cases and requires no additional configuration.
## Custom Local Dask Client
For fine-grained control over local resources, create a custom Dask client:
```python
from distributed import LocalCluster, Client
from arboreto.algo import grnboost2
if __name__ == '__main__':
# Configure local cluster
local_cluster = LocalCluster(
n_workers=10, # Number of worker processes
threads_per_worker=1, # Threads per worker
memory_limit='8GB' # Memory limit per worker
)
# Create client
custom_client = Client(local_cluster)
# Run inference with custom client
network = grnboost2(
expression_data=expression_matrix,
tf_names=tf_names,
client_or_address=custom_client
)
# Clean up
custom_client.close()
local_cluster.close()
```
### Benefits of Custom Client
- **Resource control**: Limit CPU and memory usage
- **Multiple runs**: Reuse same client for different parameter sets
- **Monitoring**: Access Dask dashboard for performance insights
## Multiple Inference Runs with Same Client
Reuse a single Dask client for multiple inference runs with different parameters:
```python
from distributed import LocalCluster, Client
from arboreto.algo import grnboost2
if __name__ == '__main__':
# Initialize client once
local_cluster = LocalCluster(n_workers=8, threads_per_worker=1)
client = Client(local_cluster)
# Run multiple inferences
network_seed1 = grnboost2(
expression_data=expression_matrix,
tf_names=tf_names,
client_or_address=client,
seed=666
)
network_seed2 = grnboost2(
expression_data=expression_matrix,
tf_names=tf_names,
client_or_address=client,
seed=777
)
# Different algorithms with same client
from arboreto.algo import genie3
network_genie3 = genie3(
expression_data=expression_matrix,
tf_names=tf_names,
client_or_address=client
)
# Clean up once
client.close()
local_cluster.close()
```
## Distributed Cluster Computing
For very large datasets, connect to a remote Dask distributed scheduler running on a cluster:
### Step 1: Set Up Dask Scheduler (on cluster head node)
```bash
dask-scheduler
# Output: Scheduler at tcp://10.118.224.134:8786
```
### Step 2: Start Dask Workers (on cluster compute nodes)
```bash
dask-worker tcp://10.118.224.134:8786
```
### Step 3: Connect from Client
```python
from distributed import Client
from arboreto.algo import grnboost2
if __name__ == '__main__':
# Connect to remote scheduler
scheduler_address = 'tcp://10.118.224.134:8786'
cluster_client = Client(scheduler_address)
# Run inference on cluster
network = grnboost2(
expression_data=expression_matrix,
tf_names=tf_names,
client_or_address=cluster_client
)
cluster_client.close()
```
### Cluster Configuration Best Practices
**Worker configuration**:
```bash
dask-worker tcp://scheduler:8786 \
--nprocs 4 \ # Number of processes per node
--nthreads 1 \ # Threads per process
--memory-limit 16GB # Memory per process
```
**For large-scale inference**:
- Use more workers with moderate memory rather than fewer workers with large memory
- Set `threads_per_worker=1` to avoid GIL contention in scikit-learn
- Monitor memory usage to prevent workers from being killed
## Monitoring and Debugging
### Dask Dashboard
Access the Dask dashboard for real-time monitoring:
```python
from distributed import Client
client = Client() # Prints dashboard URL
# Dashboard available at: http://localhost:8787/status
```
The dashboard shows:
- **Task progress**: Number of tasks completed/pending
- **Resource usage**: CPU, memory per worker
- **Task stream**: Real-time visualization of computation
- **Performance**: Bottleneck identification
### Verbose Output
Enable verbose logging to track inference progress:
```python
network = grnboost2(
expression_data=expression_matrix,
tf_names=tf_names,
verbose=True
)
```
## Performance Optimization Tips
### 1. Data Format
- **Use Pandas DataFrame when possible**: More efficient than NumPy for Dask operations
- **Reduce data size**: Filter low-variance genes before inference
### 2. Worker Configuration
- **CPU-bound tasks**: Set `threads_per_worker=1`, increase `n_workers`
- **Memory-bound tasks**: Increase `memory_limit` per worker
### 3. Cluster Setup
- **Network**: Ensure high-bandwidth, low-latency network between nodes
- **Storage**: Use shared filesystem or object storage for large datasets
- **Scheduling**: Allocate dedicated nodes to avoid resource contention
### 4. Transcription Factor Filtering
- **Limit TF list**: Providing specific TF names reduces computation
```python
# Full search (slow)
network = grnboost2(expression_data=matrix)
# Filtered search (faster)
network = grnboost2(expression_data=matrix, tf_names=known_tfs)
```
## Example: Large-Scale Single-Cell Analysis
Complete workflow for processing single-cell RNA-seq data on a cluster:
```python
from distributed import Client
from arboreto.algo import grnboost2
import pandas as pd
if __name__ == '__main__':
# Connect to cluster
client = Client('tcp://cluster-scheduler:8786')
# Load large single-cell dataset (50,000 cells x 20,000 genes)
expression_data = pd.read_csv('scrnaseq_data.tsv', sep='\t')
# Load cell-type-specific TFs
tf_names = pd.read_csv('tf_list.txt', header=None)[0].tolist()
# Run distributed inference
network = grnboost2(
expression_data=expression_data,
tf_names=tf_names,
client_or_address=client,
verbose=True,
seed=42
)
# Save results
network.to_csv('grn_results.tsv', sep='\t', index=False)
client.close()
```
This approach enables analysis of datasets that would be impractical on a single machine.
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""
Basic GRN inference example using Arboreto.
This script demonstrates the standard workflow for inferring gene regulatory
networks from expression data using GRNBoost2.
Usage:
python basic_grn_inference.py <expression_file> <output_file> [--tf-file TF_FILE] [--seed SEED] [--limit LIMIT]
Arguments:
expression_file: Path to expression matrix (TSV format, genes as columns)
output_file: Path for output network (TSV format)
--tf-file: Optional path to transcription factors file (one per line)
--seed: Random seed for reproducibility (default: 777)
--limit: Return only the top N regulatory links (optional)
"""
import argparse
import pandas as pd
from arboreto.algo import grnboost2
from arboreto.utils import load_tf_names
def run_grn_inference(expression_file, output_file, tf_file=None, seed=777, limit=None):
"""
Run GRN inference using GRNBoost2.
Args:
expression_file: Path to expression matrix TSV file
output_file: Path for output network file
tf_file: Optional path to TF names file
seed: Random seed for reproducibility
limit: Optional cap on number of regulatory links returned
"""
print(f"Loading expression data from {expression_file}...")
expression_data = pd.read_csv(expression_file, sep='\t')
print(f"Expression matrix shape: {expression_data.shape}")
print(f"Number of genes: {expression_data.shape[1]}")
print(f"Number of observations: {expression_data.shape[0]}")
# Load TF names if provided
tf_names = 'all'
if tf_file:
print(f"Loading transcription factors from {tf_file}...")
tf_names = load_tf_names(tf_file)
print(f"Number of TFs: {len(tf_names)}")
# Run GRN inference
print(f"Running GRNBoost2 with seed={seed}...")
network = grnboost2(
expression_data=expression_data,
tf_names=tf_names,
seed=seed,
limit=limit,
verbose=True
)
# Save results
print(f"Saving network to {output_file}...")
network.to_csv(output_file, sep='\t', index=False, header=False)
print(f"Done! Network contains {len(network)} regulatory links.")
print(f"\nTop 10 regulatory links:")
print(network.head(10).to_string(index=False))
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Infer gene regulatory network using GRNBoost2'
)
parser.add_argument(
'expression_file',
help='Path to expression matrix (TSV format, genes as columns)'
)
parser.add_argument(
'output_file',
help='Path for output network (TSV format)'
)
parser.add_argument(
'--tf-file',
help='Path to transcription factors file (one per line)',
default=None
)
parser.add_argument(
'--seed',
help='Random seed for reproducibility (default: 777)',
type=int,
default=777
)
parser.add_argument(
'--limit',
help='Return only the top N regulatory links',
type=int,
default=None
)
args = parser.parse_args()
run_grn_inference(
expression_file=args.expression_file,
output_file=args.output_file,
tf_file=args.tf_file,
seed=args.seed,
limit=args.limit
)
+351
View File
@@ -0,0 +1,351 @@
---
name: astropy
description: Core Python library for astronomy and astrophysics workflows that need Astropy APIs, including units/quantities, coordinates, FITS I/O, tables, time systems, WCS, and cosmology. Use when implementing or debugging astronomical data analysis code with Astropy.
license: BSD-3-Clause license
compatibility: Requires Python 3.11+ with astropy installed (uv for package installation). Some features (object name resolution, site lookups, remote FITS reads, IERS updates) need network access.
metadata: {"version": "1.2", "skill-author": "K-Dense Inc."}
---
# Astropy
## Overview
Astropy is the core Python package for astronomy, providing essential functionality for astronomical research and data analysis. Use astropy for coordinate transformations, unit and quantity calculations, FITS file operations, cosmological calculations, precise time handling, tabular data manipulation, and astronomical image processing.
## When to Use This Skill
Use astropy when tasks involve:
- Converting between celestial coordinate systems (ICRS, Galactic, FK5, AltAz, etc.)
- Working with physical units and quantities (converting Jy to mJy, parsecs to km, etc.)
- Reading, writing, or manipulating FITS files (images or tables)
- Cosmological calculations (luminosity distance, lookback time, Hubble parameter)
- Precise time handling with different time scales (UTC, TAI, TT, TDB) and formats (JD, MJD, ISO)
- Table operations (reading catalogs, cross-matching, filtering, joining)
- WCS transformations between pixel and world coordinates
- Astronomical constants and calculations
## Quick Start
```python
import astropy.units as u
from astropy.coordinates import SkyCoord
from astropy.time import Time
from astropy.io import fits
from astropy.table import Table
from astropy.cosmology import Planck18
# Units and quantities
distance = 100 * u.pc
distance_km = distance.to(u.km)
# Coordinates
coord = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree, frame='icrs')
coord_galactic = coord.galactic
# Time
t = Time('2023-01-15 12:30:00')
jd = t.jd # Julian Date
# FITS files
data = fits.getdata('image.fits')
header = fits.getheader('image.fits')
# Tables
table = Table.read('catalog.fits')
# Cosmology
d_L = Planck18.luminosity_distance(z=1.0)
```
## Core Capabilities
### 1. Units and Quantities (`astropy.units`)
Handle physical quantities with units, perform unit conversions, and ensure dimensional consistency in calculations.
**Key operations:**
- Create quantities by multiplying values with units
- Convert between units using `.to()` method
- Perform arithmetic with automatic unit handling
- Use equivalencies for domain-specific conversions (spectral, doppler, parallax)
- Work with logarithmic units (magnitudes, decibels)
**See:** `references/units.md` for comprehensive documentation, unit systems, equivalencies, performance optimization, and unit arithmetic.
### 2. Coordinate Systems (`astropy.coordinates`)
Represent celestial positions and transform between different coordinate frames.
**Key operations:**
- Create coordinates with `SkyCoord` in any frame (ICRS, Galactic, FK5, AltAz, etc.)
- Transform between coordinate systems
- Calculate angular separations and position angles
- Match coordinates to catalogs
- Include distance for 3D coordinate operations
- Handle proper motions and radial velocities
- Query named objects from online databases
**See:** `references/coordinates.md` for detailed coordinate frame descriptions, transformations, observer-dependent frames (AltAz), catalog matching, and performance tips.
### 3. Cosmological Calculations (`astropy.cosmology`)
Perform cosmological calculations using standard cosmological models.
**Key operations:**
- Use built-in cosmologies (Planck18, WMAP9, etc.)
- Create custom cosmological models
- Calculate distances (luminosity, comoving, angular diameter)
- Compute ages and lookback times
- Determine Hubble parameter at any redshift
- Calculate density parameters and volumes
- Perform inverse calculations (find z for given distance)
**See:** `references/cosmology.md` for available models, distance calculations, time calculations, density parameters, and neutrino effects.
### 4. FITS File Handling (`astropy.io.fits`)
Read, write, and manipulate FITS (Flexible Image Transport System) files.
**Key operations:**
- Open FITS files with context managers
- Access HDUs (Header Data Units) by index or name
- Read and modify headers (keywords, comments, history)
- Work with image data (NumPy arrays)
- Handle table data (binary and ASCII tables)
- Create new FITS files (single or multi-extension)
- Use memory mapping for large files
- Access remote FITS files (S3, HTTP)
**See:** `references/fits.md` for comprehensive file operations, header manipulation, image and table handling, multi-extension files, and performance considerations.
### 5. Table Operations (`astropy.table`)
Work with tabular data with support for units, metadata, and various file formats.
**Key operations:**
- Create tables from arrays, lists, or dictionaries
- Read/write tables in multiple formats (FITS, CSV, HDF5, VOTable)
- Access and modify columns and rows
- Sort, filter, and index tables
- Perform database-style operations (join, group, aggregate)
- Stack and concatenate tables
- Work with unit-aware columns (QTable)
- Handle missing data with masking
**See:** `references/tables.md` for table creation, I/O operations, data manipulation, sorting, filtering, joins, grouping, and performance tips.
### 6. Time Handling (`astropy.time`)
Precise time representation and conversion between time scales and formats.
**Key operations:**
- Create Time objects in various formats (ISO, JD, MJD, Unix, etc.)
- Convert between time scales (UTC, TAI, TT, TDB, etc.)
- Perform time arithmetic with TimeDelta
- Calculate sidereal time for observers
- Compute light travel time corrections (barycentric, heliocentric)
- Work with time arrays efficiently
- Handle masked (missing) times
**See:** `references/time.md` for time formats, time scales, conversions, arithmetic, observing features, and precision handling.
### 7. World Coordinate System (`astropy.wcs`)
Transform between pixel coordinates in images and world coordinates.
**Key operations:**
- Read WCS from FITS headers
- Convert pixel coordinates to world coordinates (and vice versa)
- Calculate image footprints
- Access WCS parameters (reference pixel, projection, scale)
- Create custom WCS objects
**See:** `references/wcs_and_other_modules.md` for WCS operations and transformations.
## Additional Capabilities
The `references/wcs_and_other_modules.md` file also covers:
### NDData and CCDData
Containers for n-dimensional datasets with metadata, uncertainty, masking, and WCS information.
### Modeling
Framework for creating and fitting mathematical models to astronomical data.
### Visualization
Tools for astronomical image display with appropriate stretching and scaling.
### Constants
Physical and astronomical constants with proper units (speed of light, solar mass, Planck constant, etc.).
### Convolution
Image processing kernels for smoothing and filtering.
### Statistics
Robust statistical functions including sigma clipping and outlier rejection.
## Installation
```bash
# Reproducible install against the current stable release
uv pip install "astropy==7.2.0"
# Recommended optional dependencies for plotting and common workflows
uv pip install "astropy[recommended]==7.2.0"
# Full optional dependency set for broad astronomy workflows
uv pip install "astropy[all]==7.2.0"
```
Astropy 7.2.0 requires Python 3.11+ and depends on NumPy, PyERFA, PyYAML, and packaging. Use an isolated virtual environment; do not install Astropy with elevated privileges.
Note that the `[recommended]` and `[all]` extras pull in transitive dependencies (matplotlib, scipy, etc.) at unpinned versions. For reproducible production environments, pin the full dependency tree with a lockfile (`uv lock` in a project, or `uv pip compile` for requirements files) and review the resolved versions before deploying.
## Common Workflows
### Converting Coordinates Between Systems
```python
from astropy.coordinates import SkyCoord
import astropy.units as u
# Create coordinate
c = SkyCoord(ra='05h23m34.5s', dec='-69d45m22s', frame='icrs')
# Transform to galactic
c_gal = c.galactic
print(f"l={c_gal.l.deg}, b={c_gal.b.deg}")
# Transform to alt-az (requires time and location)
from astropy.time import Time
from astropy.coordinates import EarthLocation, AltAz
observing_time = Time('2023-06-15 23:00:00')
observing_location = EarthLocation(lat=40*u.deg, lon=-120*u.deg)
aa_frame = AltAz(obstime=observing_time, location=observing_location)
c_altaz = c.transform_to(aa_frame)
print(f"Alt={c_altaz.alt.deg}, Az={c_altaz.az.deg}")
```
### Reading and Analyzing FITS Files
```python
from astropy.io import fits
import numpy as np
# Open FITS file
with fits.open('observation.fits') as hdul:
# Display structure
hdul.info()
# Get image data and header
data = hdul[1].data
header = hdul[1].header
# Access header values
exptime = header['EXPTIME']
filter_name = header['FILTER']
# Analyze data
mean = np.mean(data)
median = np.median(data)
print(f"Mean: {mean}, Median: {median}")
```
### Cosmological Distance Calculations
```python
from astropy.cosmology import Planck18
import astropy.units as u
import numpy as np
# Calculate distances at z=1.5
z = 1.5
d_L = Planck18.luminosity_distance(z)
d_A = Planck18.angular_diameter_distance(z)
print(f"Luminosity distance: {d_L}")
print(f"Angular diameter distance: {d_A}")
# Age of universe at that redshift
age = Planck18.age(z)
print(f"Age at z={z}: {age.to(u.Gyr)}")
# Lookback time
t_lookback = Planck18.lookback_time(z)
print(f"Lookback time: {t_lookback.to(u.Gyr)}")
```
### Cross-Matching Catalogs
```python
from astropy.table import Table
from astropy.coordinates import SkyCoord, match_coordinates_sky
import astropy.units as u
# Read catalogs
cat1 = Table.read('catalog1.fits')
cat2 = Table.read('catalog2.fits')
# Create coordinate objects
coords1 = SkyCoord(ra=cat1['RA']*u.degree, dec=cat1['DEC']*u.degree)
coords2 = SkyCoord(ra=cat2['RA']*u.degree, dec=cat2['DEC']*u.degree)
# Find matches
idx, sep, _ = coords1.match_to_catalog_sky(coords2)
# Filter by separation threshold
max_sep = 1 * u.arcsec
matches = sep < max_sep
# Create matched catalogs
cat1_matched = cat1[matches]
cat2_matched = cat2[idx[matches]]
print(f"Found {len(cat1_matched)} matches")
```
## Best Practices
1. **Always use units**: Attach units to quantities to avoid errors and ensure dimensional consistency
2. **Use context managers for FITS files**: Ensures proper file closing
3. **Prefer arrays over loops**: Process multiple coordinates/times as arrays for better performance
4. **Check coordinate frames**: Verify the frame before transformations
5. **Use appropriate cosmology**: Choose the right cosmological model for your analysis
6. **Handle missing data**: Use masked columns for tables with missing values
7. **Specify time scales**: Be explicit about time scales (UTC, TT, TDB) for precise timing
8. **Use QTable for unit-aware tables**: When table columns have units
9. **Check WCS validity**: Verify WCS before using transformations
10. **Cache frequently used values**: Expensive calculations (e.g., cosmological distances) can be cached
11. **Be explicit about network access**: `SkyCoord.from_name()`, `EarthLocation.of_site(refresh_cache=True)`, `EarthLocation.of_address()`, `download_file()`, remote FITS reads, and some IERS time/coordinate transforms can contact external services or update local caches. Avoid sending sensitive target names, addresses, URLs, or proprietary file locations to third-party services. When working with potentially sensitive targets or data locations, confirm with the user before making these network calls.
12. **Pin for reproducibility**: Use pinned versions such as `astropy==7.2.0` for shared environments; update pins intentionally after reviewing release notes.
## Current-Version Notes
- Current stable release researched: Astropy 7.2.0 (released 2025-11-25; verified current as of 2026-06-10)
- Python requirement: 3.11+
- **Astropy 8.0 is at release-candidate stage** (8.0.0rc1, 2026-05-26). Key changes to anticipate:
- The deprecated `astropy.cosmology` submodule shims (`astropy.cosmology.flrw`, `.core`, `.funcs`, `.connect`, `.parameter`) are removed — import everything directly from `astropy.cosmology` (e.g., `from astropy.cosmology import FlatLambdaCDM, z_at_value`)
- `astropy.constants` defaults change from CODATA 2018 to CODATA 2022; pin a constants version via the `astropyconst` science states if reproducibility matters
- NumPy 2.0 becomes the minimum supported version; the 7.2.x LTS branch retains NumPy 1.x support for six months after the 8.0 release
- The built-in test runner (`astropy.test()`, `TestRunner`) is formally deprecated — invoke `pytest` directly
- Recent 7.x deprecations to avoid in new code: passing a table index identifier as the first `.loc` element (`t.loc["b", 2]`) — use `t.loc.with_index("b")[2]` instead (removal planned for 9.0); `astropy.utils.isiterable()` — use `numpy.iterable()`
- Recent 7.0 removals: older deprecated FITS APIs such as `(Bin)Table.update`, `_ExtensionHDU`, `_NonstandardExtHDU`, and the `tile_size` argument for `CompImageHDU`; `CompImageHeader` is deprecated. Avoid those legacy patterns in new examples.
- The recommended optional extras are `recommended` for common plotting/scientific dependencies and `all` only when a broad optional feature set is needed.
## Documentation and Resources
- Official Astropy Documentation: https://docs.astropy.org/en/stable/
- Tutorials: https://learn.astropy.org/
- GitHub: https://github.com/astropy/astropy
## Reference Files
For detailed information on specific modules:
- `references/units.md` - Units, quantities, conversions, and equivalencies
- `references/coordinates.md` - Coordinate systems, transformations, and catalog matching
- `references/cosmology.md` - Cosmological models and calculations
- `references/fits.md` - FITS file operations and manipulation
- `references/tables.md` - Table creation, I/O, and operations
- `references/time.md` - Time formats, scales, and calculations
- `references/wcs_and_other_modules.md` - WCS, NDData, modeling, visualization, constants, and utilities
+280
View File
@@ -0,0 +1,280 @@
# Astronomical Coordinates (astropy.coordinates)
The `astropy.coordinates` package provides tools for representing celestial coordinates and transforming between different coordinate systems.
## Creating Coordinates with SkyCoord
The high-level `SkyCoord` class is the recommended interface:
```python
from astropy import units as u
from astropy.coordinates import SkyCoord
# Decimal degrees
c = SkyCoord(ra=10.625*u.degree, dec=41.2*u.degree, frame='icrs')
# Sexagesimal strings
c = SkyCoord(ra='00h42m30s', dec='+41d12m00s', frame='icrs')
# Mixed formats
c = SkyCoord('00h42.5m +41d12m', unit=(u.hourangle, u.deg))
# Galactic coordinates
c = SkyCoord(l=120.5*u.degree, b=-23.4*u.degree, frame='galactic')
```
## Array Coordinates
Process multiple coordinates efficiently using arrays:
```python
# Create array of coordinates
coords = SkyCoord(ra=[10, 11, 12]*u.degree,
dec=[41, -5, 42]*u.degree)
# Access individual elements
coords[0]
coords[1:3]
# Array operations
coords.shape
len(coords)
```
## Accessing Components
```python
c = SkyCoord(ra=10.68*u.degree, dec=41.27*u.degree, frame='icrs')
# Access coordinates
c.ra # <Longitude 10.68 deg>
c.dec # <Latitude 41.27 deg>
c.ra.hour # Convert to hours
c.ra.hms # Hours, minutes, seconds tuple
c.dec.dms # Degrees, arcminutes, arcseconds tuple
```
## String Formatting
```python
c.to_string('decimal') # '10.68 41.27'
c.to_string('dms') # '10d40m48s 41d16m12s'
c.to_string('hmsdms') # '00h42m43.2s +41d16m12s'
# Custom formatting
c.ra.to_string(unit=u.hour, sep=':', precision=2)
```
## Coordinate Transformations
Transform between reference frames:
```python
c_icrs = SkyCoord(ra=10.68*u.degree, dec=41.27*u.degree, frame='icrs')
# Simple transformations (as attributes)
c_galactic = c_icrs.galactic
c_fk5 = c_icrs.fk5
c_fk4 = c_icrs.fk4
# Explicit transformations
c_icrs.transform_to('galactic')
c_icrs.transform_to(FK5(equinox='J1975')) # Custom frame parameters
```
## Common Coordinate Frames
### Celestial Frames
- **ICRS**: International Celestial Reference System (default, most common)
- **FK5**: Fifth Fundamental Catalogue (equinox J2000.0 by default)
- **FK4**: Fourth Fundamental Catalogue (older, requires equinox specification)
- **GCRS**: Geocentric Celestial Reference System
- **CIRS**: Celestial Intermediate Reference System
### Galactic Frames
- **Galactic**: IAU 1958 galactic coordinates
- **Supergalactic**: De Vaucouleurs supergalactic coordinates
- **Galactocentric**: Galactic center-based 3D coordinates
### Horizontal Frames
- **AltAz**: Altitude-azimuth (observer-dependent)
- **HADec**: Hour angle-declination
### Ecliptic Frames
- **GeocentricMeanEcliptic**: Geocentric mean ecliptic
- **BarycentricMeanEcliptic**: Barycentric mean ecliptic
- **HeliocentricMeanEcliptic**: Heliocentric mean ecliptic
## Observer-Dependent Transformations
For altitude-azimuth coordinates, specify observation time and location:
```python
from astropy.time import Time
from astropy.coordinates import EarthLocation, AltAz
# Define observer location
observing_location = EarthLocation(lat=40.8*u.deg, lon=-121.5*u.deg, height=1060*u.m)
# Or use named observatory
observing_location = EarthLocation.of_site('Apache Point Observatory')
# Define observation time
observing_time = Time('2023-01-15 23:00:00')
# Transform to alt-az
aa_frame = AltAz(obstime=observing_time, location=observing_location)
aa = c_icrs.transform_to(aa_frame)
print(f"Altitude: {aa.alt}")
print(f"Azimuth: {aa.az}")
```
## Working with Distances
Add distance information for 3D coordinates:
```python
# With distance
c = SkyCoord(ra=10*u.degree, dec=9*u.degree, distance=770*u.kpc, frame='icrs')
# Access 3D Cartesian coordinates
c.cartesian.x
c.cartesian.y
c.cartesian.z
# Distance from origin
c.distance
# 3D separation
c1 = SkyCoord(ra=10*u.degree, dec=9*u.degree, distance=10*u.pc)
c2 = SkyCoord(ra=11*u.degree, dec=10*u.degree, distance=11.5*u.pc)
sep_3d = c1.separation_3d(c2) # 3D distance
```
## Angular Separation
Calculate on-sky separations:
```python
c1 = SkyCoord(ra=10*u.degree, dec=9*u.degree, frame='icrs')
c2 = SkyCoord(ra=11*u.degree, dec=10*u.degree, frame='fk5')
# Angular separation (handles frame conversion automatically)
sep = c1.separation(c2)
print(f"Separation: {sep.arcsec} arcsec")
# Position angle
pa = c1.position_angle(c2)
```
## Catalog Matching
Match coordinates to catalog sources:
```python
# Single target matching
catalog = SkyCoord(ra=ra_array*u.degree, dec=dec_array*u.degree)
target = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree)
# Find closest match
idx, sep2d, dist3d = target.match_to_catalog_sky(catalog)
matched_coord = catalog[idx]
# Match with maximum separation constraint
matches = target.separation(catalog) < 1*u.arcsec
```
## Named Objects
Retrieve coordinates from online catalogs:
**Network note:** `SkyCoord.from_name()` sends the object name to remote name-resolution services such as Sesame/SIMBAD/NED. Do not use it with confidential target names or proprietary survey identifiers; use explicit coordinates when privacy or reproducibility matters.
```python
# Query by name (requires internet)
m31 = SkyCoord.from_name("M31")
crab = SkyCoord.from_name("Crab Nebula")
psr = SkyCoord.from_name("PSR J1012+5307")
```
## Earth Locations
Define observer locations:
**Network note:** `EarthLocation.of_site()` normally uses the bundled site registry, but `refresh_cache=True` downloads an updated registry. `EarthLocation.of_address()` sends the address to a geocoding service, so prefer explicit latitude/longitude/height for sensitive sites.
```python
# By coordinates
location = EarthLocation(lat=40*u.deg, lon=-120*u.deg, height=1000*u.m)
# By named observatory
keck = EarthLocation.of_site('Keck Observatory')
vlt = EarthLocation.of_site('Paranal Observatory')
# Force a fresh observatory registry only when network access is acceptable
keck = EarthLocation.of_site('Keck Observatory', refresh_cache=True)
# By address (requires internet)
location = EarthLocation.of_address('1 Observatory Road, Example City')
# List available observatories
EarthLocation.get_site_names()
```
## Velocity Information
Include proper motion and radial velocity:
```python
# Proper motion
c = SkyCoord(ra=10*u.degree, dec=41*u.degree,
pm_ra_cosdec=15*u.mas/u.yr,
pm_dec=5*u.mas/u.yr,
distance=150*u.pc)
# Radial velocity
c = SkyCoord(ra=10*u.degree, dec=41*u.degree,
radial_velocity=20*u.km/u.s)
# Both
c = SkyCoord(ra=10*u.degree, dec=41*u.degree, distance=150*u.pc,
pm_ra_cosdec=15*u.mas/u.yr, pm_dec=5*u.mas/u.yr,
radial_velocity=20*u.km/u.s)
```
## Representation Types
Switch between coordinate representations:
```python
# Cartesian representation
c = SkyCoord(x=1*u.kpc, y=2*u.kpc, z=3*u.kpc,
representation_type='cartesian', frame='icrs')
# Change representation
c.representation_type = 'cylindrical'
c.rho # Cylindrical radius
c.phi # Azimuthal angle
c.z # Height
# Spherical (default for most frames)
c.representation_type = 'spherical'
```
## Performance Tips
1. **Use arrays, not loops**: Process multiple coordinates as single array
2. **Pre-compute frames**: Reuse frame objects for multiple transformations
3. **Use broadcasting**: Efficiently transform many positions across many times
4. **Enable interpolation**: For dense time sampling, use ErfaAstromInterpolator
```python
# Fast approach
coords = SkyCoord(ra=ra_array*u.degree, dec=dec_array*u.degree)
coords_transformed = coords.transform_to('galactic')
# Slow approach (avoid)
for ra, dec in zip(ra_array, dec_array):
c = SkyCoord(ra=ra*u.degree, dec=dec*u.degree)
c_transformed = c.transform_to('galactic')
```
+309
View File
@@ -0,0 +1,309 @@
# Cosmological Calculations (astropy.cosmology)
The `astropy.cosmology` subpackage provides tools for cosmological calculations based on various cosmological models.
> **Import paths:** Always import classes and functions directly from `astropy.cosmology` (as shown in all examples below). The old submodule import paths (`astropy.cosmology.flrw`, `.core`, `.funcs`, `.connect`, `.parameter`) were deprecated in v7.1 and removed in Astropy 8.0.
## Using Built-in Cosmologies
Preloaded cosmologies based on WMAP and Planck observations:
```python
from astropy.cosmology import Planck18, Planck15, Planck13
from astropy.cosmology import WMAP9, WMAP7, WMAP5
from astropy import units as u
# Use Planck 2018 cosmology
cosmo = Planck18
# Calculate distance to z=4
d = cosmo.luminosity_distance(4)
print(f"Luminosity distance at z=4: {d}")
# Age of universe at z=0
age = cosmo.age(0)
print(f"Current age of universe: {age.to(u.Gyr)}")
```
## Creating Custom Cosmologies
### FlatLambdaCDM (Most Common)
Flat universe with cosmological constant:
```python
from astropy.cosmology import FlatLambdaCDM
# Define cosmology
cosmo = FlatLambdaCDM(
H0=70 * u.km / u.s / u.Mpc, # Hubble constant at z=0
Om0=0.3, # Matter density parameter at z=0
Tcmb0=2.725 * u.K # CMB temperature (optional)
)
```
### LambdaCDM (Non-Flat)
Non-flat universe with cosmological constant:
```python
from astropy.cosmology import LambdaCDM
cosmo = LambdaCDM(
H0=70 * u.km / u.s / u.Mpc,
Om0=0.3,
Ode0=0.7 # Dark energy density parameter
)
```
### wCDM and w0wzCDM
Dark energy with equation of state parameter:
```python
from astropy.cosmology import FlatwCDM, w0wzCDM
# Constant w
cosmo_w = FlatwCDM(H0=70 * u.km/u.s/u.Mpc, Om0=0.3, w0=-0.9)
# Evolving w(z) = w0 + wz * z
cosmo_wz = w0wzCDM(H0=70 * u.km/u.s/u.Mpc, Om0=0.3, Ode0=0.7,
w0=-1.0, wz=0.1)
```
## Distance Calculations
### Comoving Distance
Line-of-sight comoving distance:
```python
d_c = cosmo.comoving_distance(z)
```
### Luminosity Distance
Distance for calculating luminosity from observed flux:
```python
d_L = cosmo.luminosity_distance(z)
# Calculate absolute magnitude from apparent magnitude
M = m - 5*np.log10(d_L.to(u.pc).value) + 5
```
### Angular Diameter Distance
Distance for calculating physical size from angular size:
```python
d_A = cosmo.angular_diameter_distance(z)
# Calculate physical size from angular size
theta = 10 * u.arcsec # Angular size
physical_size = d_A * theta.to(u.radian).value
```
### Comoving Transverse Distance
Transverse comoving distance (equals comoving distance in flat universe):
```python
d_M = cosmo.comoving_transverse_distance(z)
```
### Distance Modulus
```python
dm = cosmo.distmod(z)
# Relates apparent and absolute magnitudes: m - M = dm
```
## Scale Calculations
### kpc per Arcminute
Physical scale at a given redshift:
```python
scale = cosmo.kpc_proper_per_arcmin(z)
# e.g., "50 kpc per arcminute at z=1"
```
### Comoving Volume
Volume element for survey volume calculations:
```python
vol = cosmo.comoving_volume(z) # Total volume to redshift z
vol_element = cosmo.differential_comoving_volume(z) # dV/dz
```
## Time Calculations
### Age of Universe
Age at a given redshift:
```python
age = cosmo.age(z)
age_now = cosmo.age(0) # Current age
age_at_z1 = cosmo.age(1) # Age at z=1
```
### Lookback Time
Time since photons were emitted:
```python
t_lookback = cosmo.lookback_time(z)
# Time between z and z=0
```
## Hubble Parameter
Hubble parameter as function of redshift:
```python
H_z = cosmo.H(z) # H(z) in km/s/Mpc
E_z = cosmo.efunc(z) # E(z) = H(z)/H0
```
## Density Parameters
Evolution of density parameters with redshift:
```python
Om_z = cosmo.Om(z) # Matter density at z
Ode_z = cosmo.Ode(z) # Dark energy density at z
Ok_z = cosmo.Ok(z) # Curvature density at z
Ogamma_z = cosmo.Ogamma(z) # Photon density at z
Onu_z = cosmo.Onu(z) # Neutrino density at z
```
## Critical and Characteristic Densities
```python
rho_c = cosmo.critical_density(z) # Critical density at z
rho_m = cosmo.critical_density(z) * cosmo.Om(z) # Matter density
```
## Inverse Calculations
Find redshift corresponding to a specific value:
```python
from astropy.cosmology import z_at_value
# Find z at specific lookback time
z = z_at_value(cosmo.lookback_time, 10*u.Gyr)
# Find z at specific luminosity distance
z = z_at_value(cosmo.luminosity_distance, 1000*u.Mpc)
# Find z at specific age
z = z_at_value(cosmo.age, 1*u.Gyr)
```
## Array Operations
All methods accept array inputs:
```python
import numpy as np
z_array = np.linspace(0, 5, 100)
d_L_array = cosmo.luminosity_distance(z_array)
H_array = cosmo.H(z_array)
age_array = cosmo.age(z_array)
```
## Neutrino Effects
Include massive neutrinos:
```python
from astropy.cosmology import FlatLambdaCDM
# With massive neutrinos
cosmo = FlatLambdaCDM(
H0=70 * u.km/u.s/u.Mpc,
Om0=0.3,
Tcmb0=2.725 * u.K,
Neff=3.04, # Effective number of neutrino species
m_nu=[0., 0., 0.06] * u.eV # Neutrino masses
)
```
Note: Massive neutrinos reduce performance by 3-4x but provide more accurate results.
## Cloning and Modifying Cosmologies
Cosmology objects are immutable. Create modified copies:
```python
# Clone with different H0
cosmo_new = cosmo.clone(H0=72 * u.km/u.s/u.Mpc)
# Clone with modified name
cosmo_named = cosmo.clone(name="My Custom Cosmology")
```
## Common Use Cases
### Calculating Absolute Magnitude
```python
# From apparent magnitude and redshift
z = 1.5
m_app = 24.5 # Apparent magnitude
d_L = cosmo.luminosity_distance(z)
M_abs = m_app - cosmo.distmod(z).value
```
### Survey Volume Calculations
```python
# Volume between two redshifts
z_min, z_max = 0.5, 1.5
volume = cosmo.comoving_volume(z_max) - cosmo.comoving_volume(z_min)
# Convert to Gpc^3
volume_gpc3 = volume.to(u.Gpc**3)
```
### Physical Size from Angular Size
```python
theta = 1 * u.arcsec # Angular size
z = 2.0
d_A = cosmo.angular_diameter_distance(z)
size_kpc = (d_A * theta.to(u.radian)).to(u.kpc)
```
### Time Since Big Bang
```python
# Age at specific redshift
z_formation = 6
age_at_formation = cosmo.age(z_formation)
time_since_formation = cosmo.age(0) - age_at_formation
```
## Comparison of Cosmologies
```python
# Compare different models
from astropy.cosmology import Planck18, WMAP9
z = 1.0
print(f"Planck18 d_L: {Planck18.luminosity_distance(z)}")
print(f"WMAP9 d_L: {WMAP9.luminosity_distance(z)}")
```
## Performance Considerations
- Calculations are fast for most purposes
- Massive neutrinos reduce speed significantly
- Array operations are vectorized and efficient
- Results valid for z < 5000-6000 (depends on model)
+398
View File
@@ -0,0 +1,398 @@
# FITS File Handling (astropy.io.fits)
The `astropy.io.fits` module provides comprehensive tools for reading, writing, and manipulating FITS (Flexible Image Transport System) files.
## Opening FITS Files
### Basic File Opening
```python
from astropy.io import fits
# Open file (returns HDUList - list of HDUs)
hdul = fits.open('filename.fits')
# Always close when done
hdul.close()
# Better: use context manager (automatically closes)
with fits.open('filename.fits') as hdul:
hdul.info() # Display file structure
data = hdul[0].data
```
### File Opening Modes
```python
fits.open('file.fits', mode='readonly') # Read-only (default)
fits.open('file.fits', mode='update') # Read and write
fits.open('file.fits', mode='append') # Add HDUs to file
```
### Memory Mapping
For large files, use memory mapping (default behavior):
```python
hdul = fits.open('large_file.fits', memmap=True)
# Only loads data chunks as needed
```
### Remote Files
Access cloud-hosted FITS files:
**Network note:** Remote FITS reads disclose the target URI to the storage provider and may use credentials from the configured filesystem backend. Use anonymous reads only for public data, and prefer local files for proprietary observations.
```python
uri = "s3://bucket-name/image.fits"
with fits.open(uri, use_fsspec=True, fsspec_kwargs={"anon": True}) as hdul:
# Use .section to get cutouts without downloading entire file
cutout = hdul[1].section[100:200, 100:200]
```
## HDU Structure
FITS files contain Header Data Units (HDUs):
- **Primary HDU** (`hdul[0]`): First HDU, always present
- **Extension HDUs** (`hdul[1:]`): Image or table extensions
```python
hdul.info() # Display all HDUs
# Output:
# No. Name Ver Type Cards Dimensions Format
# 0 PRIMARY 1 PrimaryHDU 220 ()
# 1 SCI 1 ImageHDU 140 (1014, 1014) float32
# 2 ERR 1 ImageHDU 51 (1014, 1014) float32
```
## Accessing HDUs
```python
# By index
primary = hdul[0]
extension1 = hdul[1]
# By name
sci = hdul['SCI']
# By name and version number
sci2 = hdul['SCI', 2] # Second SCI extension
```
## Working with Headers
### Reading Header Values
```python
hdu = hdul[0]
header = hdu.header
# Get keyword value (case-insensitive)
observer = header['OBSERVER']
exptime = header['EXPTIME']
# Get with default if missing
filter_name = header.get('FILTER', 'Unknown')
# Access by index
value = header[7] # 8th card's value
```
### Modifying Headers
```python
# Update existing keyword
header['OBSERVER'] = 'Edwin Hubble'
# Add/update with comment
header['OBSERVER'] = ('Edwin Hubble', 'Name of observer')
# Add keyword at specific position
header.insert(5, ('NEWKEY', 'value', 'comment'))
# Add HISTORY and COMMENT
header['HISTORY'] = 'File processed on 2025-01-15'
header['COMMENT'] = 'Note about the data'
# Delete keyword
del header['OLDKEY']
```
### Header Cards
Each keyword is stored as a "card" (80-character record):
```python
# Access full card
card = header.cards[0]
print(f"{card.keyword} = {card.value} / {card.comment}")
# Iterate over all cards
for card in header.cards:
print(f"{card.keyword}: {card.value}")
```
## Working with Image Data
### Reading Image Data
```python
# Get data from HDU
data = hdul[1].data # Returns NumPy array
# Data properties
print(data.shape) # e.g., (1024, 1024)
print(data.dtype) # e.g., float32
print(data.min(), data.max())
# Access specific pixels
pixel_value = data[100, 200]
region = data[100:200, 300:400]
```
### Data Operations
Data is a NumPy array, so use standard NumPy operations:
```python
import numpy as np
# Statistics
mean = np.mean(data)
median = np.median(data)
std = np.std(data)
# Modify data
data[data < 0] = 0 # Clip negative values
data = data * gain + bias # Calibration
# Mathematical operations
log_data = np.log10(data)
smoothed = scipy.ndimage.gaussian_filter(data, sigma=2)
```
### Cutouts and Sections
Extract regions without loading entire array:
```python
# Section notation [y_start:y_end, x_start:x_end]
cutout = hdul[1].section[500:600, 700:800]
```
## Creating New FITS Files
### Simple Image File
```python
# Create data
data = np.random.random((100, 100))
# Create HDU
hdu = fits.PrimaryHDU(data=data)
# Add header keywords
hdu.header['OBJECT'] = 'Test Image'
hdu.header['EXPTIME'] = 300.0
# Write to file
hdu.writeto('new_image.fits')
# Overwrite if exists
hdu.writeto('new_image.fits', overwrite=True)
```
### Multi-Extension File
```python
# Create primary HDU (can have no data)
primary = fits.PrimaryHDU()
primary.header['TELESCOP'] = 'HST'
# Create image extensions
sci_data = np.ones((100, 100))
sci = fits.ImageHDU(data=sci_data, name='SCI')
err_data = np.ones((100, 100)) * 0.1
err = fits.ImageHDU(data=err_data, name='ERR')
# Combine into HDUList
hdul = fits.HDUList([primary, sci, err])
# Write to file
hdul.writeto('multi_extension.fits')
```
## Working with Table Data
### Reading Tables
```python
# Open table
with fits.open('table.fits') as hdul:
table = hdul[1].data # BinTableHDU or TableHDU
# Access columns
ra = table['RA']
dec = table['DEC']
mag = table['MAG']
# Access rows
first_row = table[0]
subset = table[10:20]
# Column info
cols = hdul[1].columns
print(cols.names)
cols.info()
```
### Creating Tables
```python
# Define columns
col1 = fits.Column(name='ID', format='K', array=[1, 2, 3, 4])
col2 = fits.Column(name='RA', format='D', array=[10.5, 11.2, 12.3, 13.1])
col3 = fits.Column(name='DEC', format='D', array=[41.2, 42.1, 43.5, 44.2])
col4 = fits.Column(name='Name', format='20A',
array=['Star1', 'Star2', 'Star3', 'Star4'])
# Create table HDU
table_hdu = fits.BinTableHDU.from_columns([col1, col2, col3, col4])
table_hdu.name = 'CATALOG'
# Write to file
table_hdu.writeto('catalog.fits', overwrite=True)
```
### Column Formats
Common FITS table column formats:
- `'A'`: Character string (e.g., '20A' for 20 characters)
- `'L'`: Logical (boolean)
- `'B'`: Unsigned byte
- `'I'`: 16-bit integer
- `'J'`: 32-bit integer
- `'K'`: 64-bit integer
- `'E'`: 32-bit floating point
- `'D'`: 64-bit floating point
## Modifying Existing Files
### Update Mode
```python
with fits.open('file.fits', mode='update') as hdul:
# Modify header
hdul[0].header['NEWKEY'] = 'value'
# Modify data
hdul[1].data[100, 100] = 999
# Changes automatically saved when context exits
```
### Append Mode
```python
# Add new extension to existing file
new_data = np.random.random((50, 50))
new_hdu = fits.ImageHDU(data=new_data, name='NEW_EXT')
with fits.open('file.fits', mode='append') as hdul:
hdul.append(new_hdu)
```
## Convenience Functions
For quick operations without managing HDU lists:
```python
# Get data only
data = fits.getdata('file.fits', ext=1)
# Get header only
header = fits.getheader('file.fits', ext=0)
# Get both
data, header = fits.getdata('file.fits', ext=1, header=True)
# Get single keyword value
exptime = fits.getval('file.fits', 'EXPTIME', ext=0)
# Set keyword value
fits.setval('file.fits', 'NEWKEY', value='newvalue', ext=0)
# Write simple file
fits.writeto('output.fits', data, header, overwrite=True)
# Append to file
fits.append('file.fits', data, header)
# Display file info
fits.info('file.fits')
```
## Comparing FITS Files
```python
# Print differences between two files
fits.printdiff('file1.fits', 'file2.fits')
# Compare programmatically
diff = fits.FITSDiff('file1.fits', 'file2.fits')
print(diff.report())
```
## Converting Between Formats
### FITS to/from Astropy Table
```python
from astropy.table import Table
# FITS to Table
table = Table.read('catalog.fits')
# Table to FITS
table.write('output.fits', format='fits', overwrite=True)
```
## Best Practices
1. **Always use context managers** (`with` statements) for safe file handling
2. **Avoid modifying structural keywords** (SIMPLE, BITPIX, NAXIS, etc.)
3. **Use memory mapping** for large files to conserve RAM
4. **Use .section** for remote files to avoid full downloads
5. **Check HDU structure** with `.info()` before accessing data
6. **Verify data types** before operations to avoid unexpected behavior
7. **Use convenience functions** for simple one-off operations
## Common Issues
### Handling Non-Standard FITS
Some files violate FITS standards:
```python
# Ignore verification warnings
hdul = fits.open('bad_file.fits', ignore_missing_end=True)
# Fix non-standard files
hdul = fits.open('bad_file.fits')
hdul.verify('fix') # Try to fix issues
hdul.writeto('fixed_file.fits')
```
### Large File Performance
```python
# Use memory mapping (default)
hdul = fits.open('huge_file.fits', memmap=True)
# For write operations with large arrays, use Dask
import dask.array as da
large_array = da.random.random((10000, 10000))
fits.writeto('output.fits', large_array)
```
+495
View File
@@ -0,0 +1,495 @@
# Table Operations (astropy.table)
The `astropy.table` module provides flexible tools for working with tabular data, with support for units, masked values, and various file formats.
## Creating Tables
### Basic Table Creation
```python
from astropy.table import Table, QTable
import astropy.units as u
import numpy as np
# From column arrays
a = [1, 4, 5]
b = [2.0, 5.0, 8.2]
c = ['x', 'y', 'z']
t = Table([a, b, c], names=('id', 'flux', 'name'))
# With units (use QTable)
flux = [1.2, 2.3, 3.4] * u.Jy
wavelength = [500, 600, 700] * u.nm
t = QTable([flux, wavelength], names=('flux', 'wavelength'))
```
### From Lists of Rows
```python
# List of tuples
rows = [(1, 10.5, 'A'), (2, 11.2, 'B'), (3, 12.3, 'C')]
t = Table(rows=rows, names=('id', 'value', 'name'))
# List of dictionaries
rows = [{'id': 1, 'value': 10.5}, {'id': 2, 'value': 11.2}]
t = Table(rows)
```
### From NumPy Arrays
```python
# Structured array
arr = np.array([(1, 2.0, 'x'), (4, 5.0, 'y')],
dtype=[('a', 'i4'), ('b', 'f8'), ('c', 'U10')])
t = Table(arr)
# 2D array with column names
data = np.random.random((100, 3))
t = Table(data, names=['col1', 'col2', 'col3'])
```
### From Pandas DataFrame
```python
import pandas as pd
df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})
t = Table.from_pandas(df)
```
## Accessing Table Data
### Basic Access
```python
# Column access
ra_col = t['ra'] # Returns Column object
dec_col = t['dec']
# Row access
first_row = t[0] # Returns Row object
row_slice = t[10:20] # Returns new Table
# Cell access
value = t['ra'][5] # 6th value in 'ra' column
value = t[5]['ra'] # Same thing
# Multiple columns
subset = t['ra', 'dec', 'mag']
```
### Table Properties
```python
len(t) # Number of rows
t.colnames # List of column names
t.dtype # Column data types
t.info # Detailed information
t.meta # Metadata dictionary
```
### Iteration
```python
# Iterate over rows
for row in t:
print(row['ra'], row['dec'])
# Iterate over columns
for colname in t.colnames:
print(t[colname])
```
## Modifying Tables
### Adding Columns
```python
# Add new column
t['new_col'] = [1, 2, 3, 4, 5]
t['calc'] = t['a'] + t['b'] # Calculated column
# Add column with units
t['velocity'] = [10, 20, 30] * u.km / u.s
# Add empty column
from astropy.table import Column
t['empty'] = Column(length=len(t), dtype=float)
# Insert at specific position
t.add_column([7, 8, 9], name='inserted', index=2)
```
### Removing Columns
```python
# Remove single column
t.remove_column('old_col')
# Remove multiple columns
t.remove_columns(['col1', 'col2'])
# Delete syntax
del t['col_name']
# Keep only specific columns
t.keep_columns(['ra', 'dec', 'mag'])
```
### Renaming Columns
```python
t.rename_column('old_name', 'new_name')
# Rename multiple
t.rename_columns(['old1', 'old2'], ['new1', 'new2'])
```
### Adding Rows
```python
# Add single row
t.add_row([1, 2.5, 'new'])
# Add row as dict
t.add_row({'ra': 10.5, 'dec': 41.2, 'mag': 18.5})
# Note: Adding rows one at a time is slow!
# Better to collect rows and create table at once
```
### Modifying Data
```python
# Modify column values
t['flux'] = t['flux'] * gain
t['mag'][t['mag'] < 0] = np.nan
# Modify single cell
t['ra'][5] = 10.5
# Modify entire row
t[0] = [new_id, new_ra, new_dec]
```
## Sorting and Filtering
### Sorting
```python
# Sort by single column
t.sort('mag')
# Sort descending
t.sort('mag', reverse=True)
# Sort by multiple columns
t.sort(['priority', 'mag'])
# Get sorted indices without modifying table
indices = t.argsort('mag')
sorted_table = t[indices]
```
### Filtering
```python
# Boolean indexing
bright = t[t['mag'] < 18]
nearby = t[t['distance'] < 100*u.pc]
# Multiple conditions
selected = t[(t['mag'] < 18) & (t['dec'] > 0)]
# Using numpy functions
high_snr = t[np.abs(t['flux'] / t['error']) > 5]
```
## Reading and Writing Files
### Supported Formats
FITS, HDF5, ASCII (CSV, ECSV, IPAC, etc.), VOTable, Parquet, ASDF
### Reading Files
```python
# Automatic format detection
t = Table.read('catalog.fits')
t = Table.read('data.csv')
t = Table.read('table.vot')
# Specify format explicitly
t = Table.read('data.txt', format='ascii')
t = Table.read('catalog.hdf5', path='/dataset/table')
# Read specific HDU from FITS
t = Table.read('file.fits', hdu=2)
```
### Writing Files
```python
# Automatic format from extension
t.write('output.fits')
t.write('output.csv')
# Specify format
t.write('output.txt', format='ascii.csv')
t.write('output.hdf5', path='/data/table', serialize_meta=True)
# Overwrite existing file
t.write('output.fits', overwrite=True)
```
### ASCII Format Options
```python
# CSV with custom delimiter
t.write('output.csv', format='ascii.csv', delimiter='|')
# Fixed-width format
t.write('output.txt', format='ascii.fixed_width')
# IPAC format
t.write('output.tbl', format='ascii.ipac')
# LaTeX table
t.write('table.tex', format='ascii.latex')
```
## Table Operations
### Stacking Tables (Vertical)
```python
from astropy.table import vstack
# Concatenate tables vertically
t1 = Table([[1, 2], [3, 4]], names=('a', 'b'))
t2 = Table([[5, 6], [7, 8]], names=('a', 'b'))
t_combined = vstack([t1, t2])
```
### Joining Tables (Horizontal)
```python
from astropy.table import hstack
# Concatenate tables horizontally
t1 = Table([[1, 2]], names=['a'])
t2 = Table([[3, 4]], names=['b'])
t_combined = hstack([t1, t2])
```
### Database-Style Joins
```python
from astropy.table import join
# Inner join on common column
t1 = Table([[1, 2, 3], ['a', 'b', 'c']], names=('id', 'data1'))
t2 = Table([[1, 2, 4], ['x', 'y', 'z']], names=('id', 'data2'))
t_joined = join(t1, t2, keys='id')
# Left/right/outer joins
t_joined = join(t1, t2, join_type='left')
t_joined = join(t1, t2, join_type='outer')
```
### Grouping and Aggregating
```python
# Group by column
g = t.group_by('filter')
# Aggregate groups
means = g.groups.aggregate(np.mean)
# Iterate over groups
for group in g.groups:
print(f"Filter: {group['filter'][0]}")
print(f"Mean mag: {np.mean(group['mag'])}")
```
### Unique Rows
```python
# Get unique rows
t_unique = t.unique('id')
# Multiple columns
t_unique = t.unique(['ra', 'dec'])
```
## Units and Quantities
Use QTable for unit-aware operations:
```python
from astropy.table import QTable
# Create table with units
t = QTable()
t['flux'] = [1.2, 2.3, 3.4] * u.Jy
t['wavelength'] = [500, 600, 700] * u.nm
# Unit conversions
t['flux'].to(u.mJy)
t['wavelength'].to(u.angstrom)
# Calculations preserve units
t['freq'] = t['wavelength'].to(u.Hz, equivalencies=u.spectral())
```
## Masking Missing Data
```python
from astropy.table import MaskedColumn
import numpy as np
# Create masked column
flux = MaskedColumn([1.2, np.nan, 3.4], mask=[False, True, False])
t = Table([flux], names=['flux'])
# Operations automatically handle masks
mean_flux = np.ma.mean(t['flux'])
# Fill masked values
t['flux'].filled(0) # Replace masked with 0
```
## Indexing for Fast Lookup
Create indices for fast row retrieval:
```python
# Add index on column
t.add_index('id')
# Fast lookup by index
row = t.loc[12345] # Find row where id=12345
# Range queries
subset = t.loc[100:200]
# With multiple indices, select which index to use explicitly.
# t.loc["b", 2] (index name as first element) is deprecated since 7.2
# and slated for removal in 9.0.
t.add_index('name')
row = t.loc.with_index('name')['M31']
```
## Table Metadata
```python
# Set table-level metadata
t.meta['TELESCOPE'] = 'HST'
t.meta['FILTER'] = 'F814W'
t.meta['EXPTIME'] = 300.0
# Set column-level metadata
t['ra'].meta['unit'] = 'deg'
t['ra'].meta['description'] = 'Right Ascension'
t['ra'].description = 'Right Ascension' # Shortcut
```
## Performance Tips
### Fast Table Construction
```python
# SLOW: Adding rows one at a time
t = Table(names=['a', 'b'])
for i in range(1000):
t.add_row([i, i**2])
# FAST: Build from lists
rows = [(i, i**2) for i in range(1000)]
t = Table(rows=rows, names=['a', 'b'])
```
### Memory-Mapped FITS Tables
```python
# Don't load entire table into memory
t = Table.read('huge_catalog.fits', memmap=True)
# Only loads data when accessed
subset = t[10000:10100] # Efficient
```
### Copy vs. View
```python
# Create view (shares data, fast)
t_view = t['ra', 'dec']
# Create copy (independent data)
t_copy = t['ra', 'dec'].copy()
```
## Displaying Tables
```python
# Print to console
print(t)
# Show in interactive browser
t.show_in_browser()
t.show_in_browser(jsviewer=True) # Interactive sorting/filtering
# Paginated viewing
t.more()
# Custom formatting
t['flux'].format = '%.3f'
t['ra'].format = '{:.6f}'
```
## Converting to Other Formats
```python
# To NumPy array
arr = np.array(t)
# To Pandas DataFrame
df = t.to_pandas()
# To dictionary
d = {name: t[name] for name in t.colnames}
```
## Common Use Cases
### Cross-Matching Catalogs
```python
from astropy.coordinates import SkyCoord, match_coordinates_sky
# Create coordinate objects from table columns
coords1 = SkyCoord(t1['ra'], t1['dec'], unit='deg')
coords2 = SkyCoord(t2['ra'], t2['dec'], unit='deg')
# Find matches
idx, sep, _ = coords1.match_to_catalog_sky(coords2)
# Filter by separation
max_sep = 1 * u.arcsec
matches = sep < max_sep
t1_matched = t1[matches]
t2_matched = t2[idx[matches]]
```
### Binning Data
```python
from astropy.table import Table
import numpy as np
# Bin by magnitude
mag_bins = np.arange(10, 20, 0.5)
binned = t.group_by(np.digitize(t['mag'], mag_bins))
counts = binned.groups.aggregate(len)
```
+412
View File
@@ -0,0 +1,412 @@
# Time Handling (astropy.time)
The `astropy.time` module provides robust tools for manipulating times and dates with support for various time scales and formats.
## Creating Time Objects
### Basic Creation
```python
from astropy.time import Time
import astropy.units as u
# ISO format (automatically detected)
t = Time('2023-01-15 12:30:45')
t = Time('2023-01-15T12:30:45')
# Specify format explicitly
t = Time('2023-01-15 12:30:45', format='iso', scale='utc')
# Julian Date
t = Time(2460000.0, format='jd')
# Modified Julian Date
t = Time(59945.0, format='mjd')
# Unix time (seconds since 1970-01-01)
t = Time(1673785845.0, format='unix')
```
### Array of Times
```python
# Multiple times
times = Time(['2023-01-01', '2023-06-01', '2023-12-31'])
# From arrays
import numpy as np
jd_array = np.linspace(2460000, 2460100, 100)
times = Time(jd_array, format='jd')
```
## Time Formats
### Supported Formats
```python
# ISO 8601
t = Time('2023-01-15 12:30:45', format='iso')
t = Time('2023-01-15T12:30:45.123', format='isot')
# Julian dates
t = Time(2460000.0, format='jd') # Julian Date
t = Time(59945.0, format='mjd') # Modified Julian Date
# Decimal year
t = Time(2023.5, format='decimalyear')
t = Time(2023.5, format='jyear') # Julian year
t = Time(2023.5, format='byear') # Besselian year
# Year and day-of-year
t = Time('2023:046', format='yday') # 46th day of 2023
# FITS format
t = Time('2023-01-15T12:30:45', format='fits')
# GPS seconds
t = Time(1000000000.0, format='gps')
# Unix time
t = Time(1673785845.0, format='unix')
# Matplotlib dates
t = Time(738521.0, format='plot_date')
# datetime objects
from datetime import datetime
dt = datetime(2023, 1, 15, 12, 30, 45)
t = Time(dt)
```
## Time Scales
### Available Time Scales
```python
# UTC - Coordinated Universal Time (default)
t = Time('2023-01-15 12:00:00', scale='utc')
# TAI - International Atomic Time
t = Time('2023-01-15 12:00:00', scale='tai')
# TT - Terrestrial Time
t = Time('2023-01-15 12:00:00', scale='tt')
# TDB - Barycentric Dynamical Time
t = Time('2023-01-15 12:00:00', scale='tdb')
# TCG - Geocentric Coordinate Time
t = Time('2023-01-15 12:00:00', scale='tcg')
# TCB - Barycentric Coordinate Time
t = Time('2023-01-15 12:00:00', scale='tcb')
# UT1 - Universal Time
t = Time('2023-01-15 12:00:00', scale='ut1')
```
### Converting Time Scales
```python
t = Time('2023-01-15 12:00:00', scale='utc')
# Convert to different scales
t_tai = t.tai
t_tt = t.tt
t_tdb = t.tdb
t_ut1 = t.ut1
# Check offset
print(f"TAI - UTC = {(t.tai - t.utc).sec} seconds")
# TAI - UTC = 37 seconds (leap seconds)
```
## Format Conversions
### Change Output Format
```python
t = Time('2023-01-15 12:30:45')
# Access in different formats
print(t.jd) # Julian Date
print(t.mjd) # Modified Julian Date
print(t.iso) # ISO format
print(t.isot) # ISO with 'T'
print(t.unix) # Unix time
print(t.decimalyear) # Decimal year
# Change default format
t.format = 'mjd'
print(t) # Displays as MJD
```
### High-Precision Output
```python
# Use subfmt for precision control
t.to_value('mjd', subfmt='float') # Standard float
t.to_value('mjd', subfmt='long') # Extended precision
t.to_value('mjd', subfmt='decimal') # Decimal (highest precision)
t.to_value('mjd', subfmt='str') # String representation
```
## Time Arithmetic
### TimeDelta Objects
```python
from astropy.time import TimeDelta
# Create time difference
dt = TimeDelta(1.0, format='jd') # 1 day
dt = TimeDelta(3600.0, format='sec') # 1 hour
# Subtract times
t1 = Time('2023-01-15')
t2 = Time('2023-02-15')
dt = t2 - t1
print(dt.jd) # 31 days
print(dt.sec) # 2678400 seconds
```
### Adding/Subtracting Time
```python
t = Time('2023-01-15 12:00:00')
# Add TimeDelta
t_future = t + TimeDelta(7, format='jd') # Add 7 days
# Add Quantity
t_future = t + 1*u.hour
t_future = t + 30*u.day
t_future = t + 1*u.year
# Subtract
t_past = t - 1*u.week
```
### Time Ranges
```python
# Create range of times
start = Time('2023-01-01')
end = Time('2023-12-31')
times = start + np.linspace(0, 365, 100) * u.day
# Or using TimeDelta
times = start + TimeDelta(np.linspace(0, 365, 100), format='jd')
```
## Observing-Related Features
Some high-precision time and coordinate operations use IERS Earth-rotation data. Astropy uses `astropy-iers-data` by default, but may auto-download fresher IERS-A data when required for UT1 or polar-motion values. Disable auto-download for offline or privacy-sensitive runs:
```python
from astropy.utils import iers
iers.conf.auto_download = False
```
### Sidereal Time
```python
from astropy.coordinates import EarthLocation
# Define observer location
location = EarthLocation(lat=40*u.deg, lon=-120*u.deg, height=1000*u.m)
# Create time with location
t = Time('2023-06-15 23:00:00', location=location)
# Calculate sidereal time
lst_apparent = t.sidereal_time('apparent')
lst_mean = t.sidereal_time('mean')
print(f"Local Sidereal Time: {lst_apparent}")
```
### Light Travel Time Corrections
```python
from astropy.coordinates import SkyCoord, EarthLocation
# Define target and observer
target = SkyCoord(ra=10*u.deg, dec=20*u.deg)
location = EarthLocation.of_site('Keck Observatory')
# Observation times
times = Time(['2023-01-01', '2023-06-01', '2023-12-31'],
location=location)
# Calculate light travel time to solar system barycenter
ltt_bary = times.light_travel_time(target, kind='barycentric')
ltt_helio = times.light_travel_time(target, kind='heliocentric')
# Apply correction
times_barycentric = times.tdb + ltt_bary
```
### Earth Rotation Angle
```python
# Earth rotation angle (for celestial to terrestrial transformations)
era = t.earth_rotation_angle()
```
## Handling Missing or Invalid Times
### Masked Times
```python
import numpy as np
# Create times with missing values
times = Time(['2023-01-01', '2023-06-01', '2023-12-31'])
times[1] = np.ma.masked # Mark as missing
# Check for masks
print(times.mask) # [False True False]
# Get unmasked version
times_clean = times.unmasked
# Fill masked values
times_filled = times.filled(Time('2000-01-01'))
```
## Time Precision and Representation
### Internal Representation
Time objects use two 64-bit floats (jd1, jd2) for high precision:
```python
t = Time('2023-01-15 12:30:45.123456789', format='iso', scale='utc')
# Access internal representation
print(t.jd1, t.jd2) # Integer and fractional parts
# This allows sub-nanosecond precision over astronomical timescales
```
### Precision
```python
# High precision for long time intervals
t1 = Time('1900-01-01')
t2 = Time('2100-01-01')
dt = t2 - t1
print(f"Time span: {dt.sec / (365.25 * 86400)} years")
# Maintains precision throughout
```
## Time Formatting
### Custom String Format
```python
t = Time('2023-01-15 12:30:45')
# Strftime-style formatting
t.strftime('%Y-%m-%d %H:%M:%S') # '2023-01-15 12:30:45'
t.strftime('%B %d, %Y') # 'January 15, 2023'
# ISO format subformats
t.iso # '2023-01-15 12:30:45.000'
t.isot # '2023-01-15T12:30:45.000'
t.to_value('iso', subfmt='date_hms') # '2023-01-15 12:30:45.000'
```
## Common Use Cases
### Converting Between Formats
```python
# MJD to ISO
t_mjd = Time(59945.0, format='mjd')
iso_string = t_mjd.iso
# ISO to JD
t_iso = Time('2023-01-15 12:00:00')
jd_value = t_iso.jd
# Unix to ISO
t_unix = Time(1673785845.0, format='unix')
iso_string = t_unix.iso
```
### Time Differences in Various Units
```python
t1 = Time('2023-01-01')
t2 = Time('2023-12-31')
dt = t2 - t1
print(f"Days: {dt.to(u.day)}")
print(f"Hours: {dt.to(u.hour)}")
print(f"Seconds: {dt.sec}")
print(f"Years: {dt.to(u.year)}")
```
### Creating Regular Time Series
```python
# Daily observations for a year
start = Time('2023-01-01')
times = start + np.arange(365) * u.day
# Hourly observations for a day
start = Time('2023-01-15 00:00:00')
times = start + np.arange(24) * u.hour
# Observations every 30 seconds
start = Time('2023-01-15 12:00:00')
times = start + np.arange(1000) * 30 * u.second
```
### Time Zone Handling
```python
# UTC to local time (requires datetime)
t = Time('2023-01-15 12:00:00', scale='utc')
dt_utc = t.to_datetime()
# Convert to specific timezone using pytz
import pytz
eastern = pytz.timezone('US/Eastern')
dt_eastern = dt_utc.replace(tzinfo=pytz.utc).astimezone(eastern)
```
### Barycentric Correction Example
```python
from astropy.coordinates import SkyCoord, EarthLocation
# Target coordinates
target = SkyCoord(ra='23h23m08.55s', dec='+18d24m59.3s')
# Observatory location
location = EarthLocation.of_site('Keck Observatory')
# Observation times (must include location)
times = Time(['2023-01-15 08:30:00', '2023-01-16 08:30:00'],
location=location)
# Calculate barycentric correction
ltt_bary = times.light_travel_time(target, kind='barycentric')
# Apply correction to get barycentric times
times_bary = times.tdb + ltt_bary
# For radial velocity work, use the coordinate helper instead
rv_correction = target.radial_velocity_correction(obstime=times, location=location)
```
## Performance Considerations
1. **Array operations are fast**: Process multiple times as arrays
2. **Format conversions are cached**: Repeated access is efficient
3. **Scale conversions may require IERS data**: Update `astropy-iers-data` before offline runs, or set `iers.conf.auto_download = False` to prevent network access
4. **High precision maintained**: Sub-nanosecond accuracy across astronomical timescales
+178
View File
@@ -0,0 +1,178 @@
# Units and Quantities (astropy.units)
The `astropy.units` module handles defining, converting between, and performing arithmetic with physical quantities.
## Creating Quantities
Multiply or divide numeric values by built-in units to create Quantity objects:
```python
from astropy import units as u
import numpy as np
# Scalar quantities
distance = 42.0 * u.meter
velocity = 100 * u.km / u.s
# Array quantities
distances = np.array([1., 2., 3.]) * u.m
wavelengths = [500, 600, 700] * u.nm
```
Access components via `.value` and `.unit` attributes:
```python
distance.value # 42.0
distance.unit # Unit("m")
```
## Unit Conversions
Use `.to()` method for conversions:
```python
distance = 1.0 * u.parsec
distance.to(u.km) # <Quantity 30856775814671.914 km>
wavelength = 500 * u.nm
wavelength.to(u.angstrom) # <Quantity 5000. Angstrom>
```
## Arithmetic Operations
Quantities support standard arithmetic with automatic unit management:
```python
# Basic operations
speed = 15.1 * u.meter / (32.0 * u.second) # <Quantity 0.471875 m / s>
area = (5 * u.m) * (3 * u.m) # <Quantity 15. m2>
# Units cancel when appropriate
ratio = (10 * u.m) / (5 * u.m) # <Quantity 2. (dimensionless)>
# Decompose complex units
time = (3.0 * u.kilometer / (130.51 * u.meter / u.second))
time.decompose() # <Quantity 22.986744310780782 s>
```
## Unit Systems
Convert between major unit systems:
```python
# SI to CGS
pressure = 1.0 * u.Pa
pressure.cgs # <Quantity 10. Ba>
# Find equivalent representations
(u.s ** -1).compose() # [Unit("Bq"), Unit("Hz"), ...]
```
## Equivalencies
Domain-specific conversions require equivalencies:
```python
# Spectral equivalency (wavelength ↔ frequency)
wavelength = 1000 * u.nm
wavelength.to(u.Hz, equivalencies=u.spectral())
# <Quantity 2.99792458e+14 Hz>
# Doppler equivalencies
velocity = 1000 * u.km / u.s
velocity.to(u.Hz, equivalencies=u.doppler_optical(500*u.nm))
# Other equivalencies
u.brightness_temperature(500*u.GHz)
u.doppler_radio(1.4*u.GHz)
u.mass_energy()
u.parallax()
```
## Logarithmic Units
Special units for magnitudes, decibels, and dex:
```python
# Magnitudes
flux = -2.5 * u.mag(u.ct / u.s)
# Decibels
power_ratio = 3 * u.dB(u.W)
# Dex (base-10 logarithm)
abundance = 8.5 * u.dex(u.cm**-3)
```
## Common Units
### Length
`u.m, u.km, u.cm, u.mm, u.micron, u.angstrom, u.au, u.pc, u.kpc, u.Mpc, u.lyr`
### Time
`u.s, u.min, u.hour, u.day, u.year, u.Myr, u.Gyr`
### Mass
`u.kg, u.g, u.M_sun, u.M_earth, u.M_jup`
### Temperature
`u.K, u.deg_C`
### Angle
`u.deg, u.arcmin, u.arcsec, u.rad, u.hourangle, u.mas`
### Energy/Power
`u.J, u.erg, u.eV, u.keV, u.MeV, u.GeV, u.W, u.L_sun`
### Frequency
`u.Hz, u.kHz, u.MHz, u.GHz`
### Flux
`u.Jy, u.mJy, u.erg / u.s / u.cm**2`
## Performance Optimization
Pre-compute composite units for array operations:
```python
# Slow (creates intermediate quantities)
result = array * u.m / u.s / u.kg / u.sr
# Fast (pre-computed composite unit)
UNIT_COMPOSITE = u.m / u.s / u.kg / u.sr
result = array * UNIT_COMPOSITE
# Fastest (avoid copying with <<)
result = array << UNIT_COMPOSITE # 10000x faster
```
## String Formatting
Format quantities with standard Python syntax:
```python
velocity = 15.1 * u.meter / (32.0 * u.second)
f"{velocity:0.03f}" # '0.472 m / s'
f"{velocity:.2e}" # '4.72e-01 m / s'
f"{velocity.unit:FITS}" # 'm s-1'
```
## Defining Custom Units
```python
# Create new unit
bakers_fortnight = u.def_unit('bakers_fortnight', 13 * u.day)
# Enable in string parsing
u.add_enabled_units([bakers_fortnight])
```
## Constants
Access physical constants with units:
```python
from astropy.constants import c, G, M_sun, h, k_B
speed_of_light = c.to(u.km/u.s)
gravitational_constant = G.to(u.m**3 / u.kg / u.s**2)
```
@@ -0,0 +1,377 @@
# WCS and Other Astropy Modules
## World Coordinate System (astropy.wcs)
The WCS module manages transformations between pixel coordinates in images and world coordinates (e.g., celestial coordinates).
### Reading WCS from FITS
```python
from astropy.wcs import WCS
from astropy.io import fits
# Read WCS from FITS header
with fits.open('image.fits') as hdul:
wcs = WCS(hdul[0].header)
```
### Pixel to World Transformations
```python
# Single pixel to world coordinates
world = wcs.pixel_to_world(100, 200) # Returns SkyCoord
print(f"RA: {world.ra}, Dec: {world.dec}")
# Arrays of pixels
import numpy as np
x_pixels = np.array([100, 200, 300])
y_pixels = np.array([150, 250, 350])
world_coords = wcs.pixel_to_world(x_pixels, y_pixels)
```
### World to Pixel Transformations
```python
from astropy.coordinates import SkyCoord
import astropy.units as u
# Single coordinate
coord = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree)
x, y = wcs.world_to_pixel(coord)
# Array of coordinates
coords = SkyCoord(ra=[10, 11, 12]*u.degree, dec=[41, 42, 43]*u.degree)
x_pixels, y_pixels = wcs.world_to_pixel(coords)
```
### WCS Information
```python
# Print WCS details
print(wcs)
# Access key properties
print(wcs.wcs.crpix) # Reference pixel
print(wcs.wcs.crval) # Reference value (world coords)
print(wcs.wcs.cd) # CD matrix
print(wcs.wcs.ctype) # Coordinate types
# Pixel scale
pixel_scale = wcs.proj_plane_pixel_scales() # Returns Quantity array
```
### Creating WCS
```python
from astropy.wcs import WCS
# Create new WCS
wcs = WCS(naxis=2)
wcs.wcs.crpix = [512.0, 512.0] # Reference pixel
wcs.wcs.crval = [10.5, 41.2] # RA, Dec at reference pixel
wcs.wcs.ctype = ['RA---TAN', 'DEC--TAN'] # Projection type
wcs.wcs.cdelt = [-0.0001, 0.0001] # Pixel scale (degrees/pixel)
wcs.wcs.cunit = ['deg', 'deg']
```
### Footprint and Coverage
```python
# Calculate image footprint (corner coordinates)
footprint = wcs.calc_footprint()
# Returns array of [RA, Dec] for each corner
```
## NDData (astropy.nddata)
Container for n-dimensional datasets with metadata, uncertainty, and masking.
### Creating NDData
```python
from astropy.nddata import NDData
import numpy as np
import astropy.units as u
# Basic NDData
data = np.random.random((100, 100))
ndd = NDData(data)
# With units
ndd = NDData(data, unit=u.electron/u.s)
# With uncertainty
from astropy.nddata import StdDevUncertainty
uncertainty = StdDevUncertainty(np.sqrt(data))
ndd = NDData(data, uncertainty=uncertainty, unit=u.electron/u.s)
# With mask
mask = data < 0.1 # Mask low values
ndd = NDData(data, mask=mask)
# With WCS
from astropy.wcs import WCS
ndd = NDData(data, wcs=wcs)
```
### CCDData for CCD Images
```python
from astropy.nddata import CCDData
# Create CCDData
ccd = CCDData(data, unit=u.adu, meta={'object': 'M31'})
# Read from FITS
ccd = CCDData.read('image.fits', unit=u.adu)
# Write to FITS
ccd.write('output.fits', overwrite=True)
```
## Modeling (astropy.modeling)
Framework for creating and fitting models to data.
### Common Models
```python
from astropy.modeling import models, fitting
import numpy as np
# 1D Gaussian
gauss = models.Gaussian1D(amplitude=10, mean=5, stddev=1)
x = np.linspace(0, 10, 100)
y = gauss(x)
# 2D Gaussian
gauss_2d = models.Gaussian2D(amplitude=10, x_mean=50, y_mean=50,
x_stddev=5, y_stddev=3)
# Polynomial
poly = models.Polynomial1D(degree=3)
# Power law
power_law = models.PowerLaw1D(amplitude=10, x_0=1, alpha=2)
```
### Fitting Models to Data
```python
# Generate noisy data
true_model = models.Gaussian1D(amplitude=10, mean=5, stddev=1)
x = np.linspace(0, 10, 100)
y_true = true_model(x)
y_noisy = y_true + np.random.normal(0, 0.5, x.shape)
# Fit model
fitter = fitting.LevMarLSQFitter()
initial_model = models.Gaussian1D(amplitude=8, mean=4, stddev=1.5)
fitted_model = fitter(initial_model, x, y_noisy)
print(f"Fitted amplitude: {fitted_model.amplitude.value}")
print(f"Fitted mean: {fitted_model.mean.value}")
print(f"Fitted stddev: {fitted_model.stddev.value}")
```
### Compound Models
```python
# Add models
double_gauss = models.Gaussian1D(amp=5, mean=3, stddev=1) + \
models.Gaussian1D(amp=8, mean=7, stddev=1.5)
# Compose models
composite = models.Gaussian1D(amp=10, mean=5, stddev=1) | \
models.Scale(factor=2) # Scale output
```
## Visualization (astropy.visualization)
Tools for visualizing astronomical images and data.
### Image Normalization
```python
from astropy.visualization import simple_norm
import matplotlib.pyplot as plt
# Load image
from astropy.io import fits
data = fits.getdata('image.fits')
# Normalize for display
norm = simple_norm(data, 'sqrt', percent=99)
# Display
plt.imshow(data, norm=norm, cmap='gray', origin='lower')
plt.colorbar()
plt.show()
```
### Stretching and Intervals
```python
from astropy.visualization import (MinMaxInterval, AsinhStretch,
ImageNormalize, ZScaleInterval)
# Z-scale interval
interval = ZScaleInterval()
vmin, vmax = interval.get_limits(data)
# Asinh stretch
stretch = AsinhStretch()
norm = ImageNormalize(data, interval=interval, stretch=stretch)
plt.imshow(data, norm=norm, cmap='gray', origin='lower')
```
### PercentileInterval
```python
from astropy.visualization import PercentileInterval
# Show data between 5th and 95th percentiles
interval = PercentileInterval(90) # 90% of data
vmin, vmax = interval.get_limits(data)
plt.imshow(data, vmin=vmin, vmax=vmax, cmap='gray', origin='lower')
```
## Constants (astropy.constants)
Physical and astronomical constants with units.
Note: through Astropy 7.x the default constants are CODATA 2018 + IAU 2015; Astropy 8.0 switches the default to CODATA 2022. For reproducible results across versions, select an explicit constants version via the science state (e.g., `astropy.physical_constants.set('codata2018')` before other astropy imports).
```python
from astropy import constants as const
# Speed of light
c = const.c
print(f"c = {c}")
print(f"c in km/s = {c.to(u.km/u.s)}")
# Gravitational constant
G = const.G
# Astronomical constants
M_sun = const.M_sun # Solar mass
R_sun = const.R_sun # Solar radius
L_sun = const.L_sun # Solar luminosity
au = const.au # Astronomical unit
pc = const.pc # Parsec
# Fundamental constants
h = const.h # Planck constant
hbar = const.hbar # Reduced Planck constant
k_B = const.k_B # Boltzmann constant
m_e = const.m_e # Electron mass
m_p = const.m_p # Proton mass
e = const.e # Elementary charge
N_A = const.N_A # Avogadro constant
```
### Using Constants in Calculations
```python
# Calculate Schwarzschild radius
M = 10 * const.M_sun
r_s = 2 * const.G * M / const.c**2
print(f"Schwarzschild radius: {r_s.to(u.km)}")
# Calculate escape velocity
M = const.M_earth
R = const.R_earth
v_esc = np.sqrt(2 * const.G * M / R)
print(f"Earth escape velocity: {v_esc.to(u.km/u.s)}")
```
## Convolution (astropy.convolution)
Convolution kernels for image processing.
```python
from astropy.convolution import Gaussian2DKernel, convolve
# Create Gaussian kernel
kernel = Gaussian2DKernel(x_stddev=2)
# Convolve image
smoothed_image = convolve(data, kernel)
# Handle NaNs
from astropy.convolution import convolve_fft
smoothed = convolve_fft(data, kernel, nan_treatment='interpolate')
```
## Stats (astropy.stats)
Statistical functions for astronomical data.
```python
from astropy.stats import sigma_clip, sigma_clipped_stats
# Sigma clipping
clipped_data = sigma_clip(data, sigma=3, maxiters=5)
# Get statistics with sigma clipping
mean, median, std = sigma_clipped_stats(data, sigma=3.0)
# Robust statistics
from astropy.stats import mad_std, biweight_location, biweight_scale
robust_std = mad_std(data)
robust_mean = biweight_location(data)
robust_scale = biweight_scale(data)
```
## Utils
### Data Downloads
`download_file()` fetches a remote URL and caches it locally. Treat the URL as data you are disclosing to the remote host; do not pass confidential signed URLs or internal file locations unless the workflow explicitly permits it. Use `cache=False` for one-off downloads that should not be retained in Astropy's cache.
```python
from astropy.utils.data import download_file
# Download file (caches locally)
url = 'https://example.com/data.fits'
local_file = download_file(url, cache=True)
```
### Progress Bars
```python
from astropy.utils.console import ProgressBar
with ProgressBar(len(data_list)) as bar:
for item in data_list:
# Process item
bar.update()
```
## SAMP (Simple Application Messaging Protocol)
Interoperability with other astronomy tools.
```python
from astropy.samp import SAMPIntegratedClient
# Connect to SAMP hub
client = SAMPIntegratedClient()
client.connect()
# Broadcast table to other applications
message = {
'samp.mtype': 'table.load.votable',
'samp.params': {
'url': 'file:///path/to/table.xml',
'table-id': 'my_table',
'name': 'My Catalog'
}
}
client.notify_all(message)
# Disconnect
client.disconnect()
```
+3
View File
@@ -0,0 +1,3 @@
__pycache__/
*.pyc
.pytest_cache/
+218
View File
@@ -0,0 +1,218 @@
---
name: autoskill
description: Observe the user's screen via screenpipe, detect repeated research workflows, match them against existing scientific-agent-skills, and draft new skills (or composition recipes that chain existing ones) for the patterns not yet covered. Use when the user asks to analyze their recent work and propose skills based on what they actually do. Requires the screenpipe daemon (https://github.com/screenpipe/screenpipe) running locally on port 3030 — the skill has no other data source and will refuse to run if screenpipe is unreachable. All detection runs locally; only redacted cluster summaries reach the LLM.
allowed-tools: Read Write Edit Bash
license: MIT license
required_environment_variables: [{"name": "SCREENPIPE_TOKEN", "prompt": "Auth token for the local screenpipe daemon.", "required_for": "full functionality"}, {"name": "ANTHROPIC_API_KEY", "prompt": "For Claude API calls during skill drafting.", "required_for": "optional features"}, {"name": "FOUNDRY_API_KEY", "prompt": "Optional Foundry access for drafting.", "required_for": "optional features"}]
metadata: {"version": "1.1", "skill-author": "K-Dense Inc.", "openclaw": {"requires": {"bins": ["screenpipe"]}, "primaryEnv": "SCREENPIPE_TOKEN", "envVars": [{"name": "SCREENPIPE_TOKEN", "required": true, "description": "Auth token for the local screenpipe daemon."}, {"name": "ANTHROPIC_API_KEY", "required": false, "description": "For Claude API calls during skill drafting."}, {"name": "FOUNDRY_API_KEY", "required": false, "description": "Optional Foundry access for drafting."}]}}
---
# autoskill
> **Requires a running [screenpipe](https://github.com/screenpipe/screenpipe) daemon.** This skill has no alternate data source — it reads exclusively from the local screenpipe HTTP API (default `http://localhost:3030`). If the daemon isn't running, `run()` raises `ScreenpipeUnreachable` with install instructions.
> **Network access & environment variables.** This skill makes authenticated HTTP requests to (a) the user's local screenpipe daemon on loopback, and (b) the user-configured LLM backend — one of `http://localhost:1234/v1` (LM Studio, default), `https://api.anthropic.com` (opt-in Claude), or a user-supplied BYOK Foundry gateway. The skill reads three environment variables — `SCREENPIPE_TOKEN`, `ANTHROPIC_API_KEY`, `FOUNDRY_API_KEY` — and uses each only to authenticate to the single endpoint its name implies. No other network destinations, no telemetry, no data egress to any third party.
## Overview
Turn the user's own workflow history — captured passively by the local [screenpipe](https://github.com/screenpipe/screenpipe) daemon — into new skills. This skill is on-demand: the user invokes it with a time window, it queries screenpipe's local HTTP API, clusters repeated workflow patterns, compares each pattern against the existing skills in this repo, and produces a staged folder of proposals the user can review, edit, and promote.
## When to Use This Skill
Invoke this skill when the user asks to:
- "Analyze my last 4 hours / day / week and propose new skills."
- "Look at what I've been doing and tell me what's not covered yet."
- "Draft a skill from my recent workflow."
- "Find composition recipes for workflows I repeat."
Do **not** invoke it for one-off questions about screenpipe itself, for real-time screen queries, or without an explicit user request — the skill analyzes sensitive local content and must stay explicitly user-triggered.
## Privacy Posture
- **Screenpipe handles app/window filtering at capture time.** Install a starter deny-list by copying `references/screenpipe-config.yaml` into the user's screenpipe config. Sensitive apps (password managers, messaging, banking) are never OCR'd in the first place.
- **Raw OCR never leaves the machine.** `scripts/fetch_window.py` pulls data over localhost HTTP. `scripts/cluster.py` reduces the timeline to app/duration/title summaries. `scripts/redact.py` strips emails, API keys, bearer tokens, and phone numbers as defense-in-depth before any cluster summary reaches the LLM.
- **LLM backend defaults to `local`.** The recommended setup is [LM Studio](https://lmstudio.ai/) running `Gemma-4-31B-it` — strong reasoning at a size that fits on most workstation GPUs, and no data ever leaves your machine. Cloud backends (`claude`, `foundry`) are opt-in and documented in `config.yaml` for users who explicitly want them. Detection and embeddings always run locally regardless of backend choice.
- **Dry-run mode** (`--plan`) prints the exact timeline that will be analyzed before any LLM call.
- **TLS for localhost** (optional, for corporate policy): see `references/https-proxy.md` for the Caddy pattern.
## Prerequisites
### 1. Screenpipe daemon
Either install the official release or build from source. Either way the daemon binds HTTP on `localhost:3030` by default.
**From source** (recommended if you want the CLI daemon without the desktop GUI):
```bash
git clone --depth 1 https://github.com/mediar-ai/screenpipe.git
cd screenpipe
cargo build -p screenpipe-engine --release
# System deps (macOS): cmake + full Xcode.app (not just Command Line Tools).
# brew install cmake
# # if xcodebuild plug-ins error: sudo xcodebuild -runFirstLaunch
./target/release/screenpipe doctor # confirm permissions + ffmpeg
./target/release/screenpipe record --disable-audio --use-pii-removal
```
First run will prompt for macOS Screen Recording permission. Grant it and relaunch.
### 2. Screenpipe API token
The local API now requires bearer auth. Retrieve your token and export it:
```bash
export SCREENPIPE_TOKEN=$(screenpipe auth token)
```
(Or set `screenpipe.token` directly in `config.yaml` — env var is preferred since it keeps secrets out of version control.)
### 3. Python environment
Via `pipenv` from the repo root:
```bash
pipenv install httpx pyyaml sentence-transformers
```
The embedding model (`sentence-transformers/all-MiniLM-L6-v2`, ~80 MB) downloads on first run.
### 4. Local LLM (default path) — LM Studio
- Install [LM Studio](https://lmstudio.ai/).
- Download `Gemma-4-31B-it` (or another strong reasoning model; adjust `local.model` in `config.yaml`).
- Load it via the CLI for headless use (no GUI required):
```bash
lms load gemma-4-31b-it --context-length 131072 --gpu max -y
lms status # confirm server running on :1234
```
### 5. Cloud LLM backends (optional, opt-in)
Only if you explicitly opt out of local:
- `claude`: set `ANTHROPIC_API_KEY`, flip `backend: claude` in `config.yaml`.
- `foundry`: set `FOUNDRY_API_KEY`, flip `backend: foundry`, set `foundry.endpoint` to your corporate gateway URL.
## Architecture
```
screenpipe daemon (user-installed)
│ HTTP on localhost:3030
scripts/fetch_window.py → normalized timeline events
scripts/redact.py → regex scrub (defense-in-depth)
scripts/cluster.py → sessions + clusters (local only)
scripts/match_skills.py → top-k vs existing 135 skills (local embeddings)
scripts/synthesize.py → LLM judge: reuse / compose / novel
~/.autoskill/proposed/<timestamp>/ (default; override with --out)
├── report.md
├── composition-recipes/<name>/SKILL.md
└── new-skills/<name>/SKILL.md
scripts/promote.py → user-approved proposal → skills/<name>/
```
## Workflow
The skill ships a unified CLI at `scripts/autoskill.py` with three subcommands:
```bash
python scripts/autoskill.py doctor --config config.yaml --skills-dir ../
python scripts/autoskill.py run --start ... --end ... --config config.yaml
python scripts/autoskill.py promote --proposed ~/.autoskill/proposed/<ts> --skills-dir ../ --name <skill>
```
### 0. Preflight with `doctor`
Before a full run, verify every dependency in one shot:
```bash
python scripts/autoskill.py doctor \
--config skills/autoskill/config.yaml \
--skills-dir skills
```
The report covers `config` (backend choice valid), `skills_dir` (exists), `screenpipe` (reachable + authed), and `llm` (LM Studio serving or API key present). Non-zero exit on any failure, with the offending line marked `error`.
### 1. Run the pipeline
```bash
export SCREENPIPE_TOKEN=$(screenpipe auth token)
python scripts/autoskill.py run \
--start "2026-04-17T00:00:00Z" \
--end "2026-04-17T23:59:59Z" \
--config skills/autoskill/config.yaml \
--skills-dir skills
```
Proposals land in `~/.autoskill/proposed/<timestamp>/` by default, keeping experimental output out of the skills repo. Pass `--out PATH` to override.
Internally:
1. **Fetch**`fetch_window` paginates screenpipe's `/search` endpoint, normalizes events to `{ts, app, window_title, text, content_type}`.
2. **Redact**`redact` scrubs emails, API keys, bearer tokens, phones from OCR text and window titles as defense-in-depth over screenpipe's own PII removal.
3. **Cluster**`segment_sessions` splits on idle gaps (default 10 min) and drops short sessions; `cluster_sessions` groups sessions by app-signature and keeps clusters of size `min_cluster_size` (default 2).
4. **Match**`load_skill_descriptions` reads frontmatter from every `SKILL.md` in `skills/`; `top_k_matches` ranks each cluster against all skills using local `sentence-transformers` embeddings (cosine similarity).
5. **Synthesize**`synthesize` prompts the configured LLM backend to classify each cluster as `reuse`, `compose`, or `novel` and emit a SKILL.md body where appropriate.
6. **Report** — writes `<out_dir>/<ts>/report.md`, plus `new-skills/<name>/SKILL.md` or `composition-recipes/<name>/SKILL.md` for each proposal.
Add `--dry-run` to stop after clustering; this skips the LLM (and the sentence-transformers load), writing only `plan.md` for inspection.
### 2. Review and promote
Open `~/.autoskill/proposed/<ts>/report.md`, edit drafts in place, delete anything you don't want. Then:
```bash
python scripts/autoskill.py promote \
--proposed ~/.autoskill/proposed/2026-04-17T14-30-00 \
--skills-dir skills \
--name zotero-pubmed-helper
```
`promote` moves the directory into `skills/<name>/`, refusing to overwrite an existing skill. Exits non-zero with a friendly error if the proposal isn't found or the target already exists.
## Configuration
See `config.yaml` for the full shape. Default values (local-first):
```yaml
backend: local
local:
endpoint: http://localhost:1234/v1 # LM Studio's Developer server
model: Gemma-4-31B-it
screenpipe:
url: http://localhost:3030 # or https://screenpipe.local via Caddy
cluster:
min_session_minutes: 5
idle_gap_minutes: 10
min_cluster_size: 2
```
To opt into a cloud backend:
```yaml
backend: claude # or foundry
claude:
model: claude-opus-4-7
```
## Composition recipes vs new skills
- **compose**: the LLM judged that chaining existing skills covers the workflow. The emitted SKILL.md is intentionally thin — frontmatter + a "Workflow" section that invokes existing skills in order. The same agent runtime that discovered the skill can then invoke it end-to-end.
- **novel**: no combination of existing skills covers it. A fuller SKILL.md is drafted, still following repo conventions (frontmatter, Overview, When to Use, Workflow). The user should always review new-skill drafts before promoting.
## Testing
The skill is covered by a small pytest suite at `tests/`. Each script is unit-tested in isolation with dependency injection (mock HTTP transport, stub backend, stub embedder):
```bash
cd skills/autoskill
python -m pytest tests/ -v
```
## Composition with other skills in this repo
The autoskill's embedding index covers all 135 sibling skills. Workflows that look like scientific writing will match `scientific-writing` / `literature-review` / `citation-management`; figure work will match `scientific-schematics` / `generate-image` / `infographics`; slide prep matches `scientific-slides` / `pptx`; etc. When a cluster scores high against two or three sibling skills the emitted composition recipe names them explicitly, so the user's future agent invocations use the optimized paths already documented in this repo.
+53
View File
@@ -0,0 +1,53 @@
# autoskill configuration
#
# LLM backend for skill synthesis. Detection/clustering always runs locally;
# only redacted cluster summaries are sent to the LLM.
#
# Local is the default — your screen content never leaves the machine.
# Cloud backends (claude, foundry) are available for users who explicitly
# opt in; see the backend sections below.
backend: local # local | claude | foundry
# Per-backend settings. Only the selected backend's block is used.
local:
# LM Studio exposes an OpenAI-compatible server. Start it from the
# "Developer" tab; the default port is 1234.
endpoint: http://localhost:1234/v1
# Gemma-4-31B-it is the recommended default: strong reasoning at a size
# most modern workstation GPUs can run. Swap for any LM Studio model ID.
model: Gemma-4-31B-it
claude:
model: claude-opus-4-7
# api_key read from ANTHROPIC_API_KEY env var
foundry:
endpoint: https://foundry.example.com/anthropic
model: claude-opus-4-7
# api_key read from FOUNDRY_API_KEY env var
# Screenpipe HTTP endpoint. For TLS, point this at your local Caddy proxy
# (see references/https-proxy.md).
screenpipe:
url: http://localhost:3030
# Screenpipe requires a bearer token for its local API. Either set `token`
# here, or export SCREENPIPE_TOKEN in your environment (preferred — keeps
# the token out of version control). Retrieve with: `screenpipe auth token`.
# token: your-token-here
# Embedding model for matching against existing scientific skills.
# Local only; no API calls.
embeddings:
model: sentence-transformers/all-MiniLM-L6-v2
# Clustering thresholds.
cluster:
min_session_minutes: 5 # skip sessions shorter than this
idle_gap_minutes: 10 # new session after this much inactivity
min_cluster_size: 2 # need this many similar sessions before proposing
# Content redaction regexes applied before any cluster summary leaves
# the local detection layer. Defense-in-depth on top of screenpipe's
# own app/window filtering.
redaction:
enabled: true
@@ -0,0 +1,62 @@
# Optional: TLS for localhost screenpipe access
Screenpipe's HTTP server (Axum, binding `localhost:3030`) speaks plain HTTP. For a Python script running as the same user on the same host, plain HTTP is adequate — loopback traffic never hits a network adapter, so TLS provides no additional confidentiality.
TLS on localhost is only useful when:
- A corporate security policy mandates "TLS everywhere" regardless of transport.
- The screenpipe endpoint is tunneled or exposed off-host.
- A browser client requires a "secure context" (Service Workers, WebCrypto).
If you need it, put a one-line Caddy reverse proxy in front. Caddy's `tls internal` generates and trusts a local CA automatically.
## Caddy
Install:
```bash
brew install caddy # macOS
# or see https://caddyserver.com/docs/install
```
Add to your `Caddyfile`:
```caddyfile
screenpipe.local {
tls internal
reverse_proxy localhost:3030
}
```
Ensure `screenpipe.local` resolves to loopback (add to `/etc/hosts`):
```
127.0.0.1 screenpipe.local
```
Start Caddy:
```bash
caddy run
```
Then update autoskill's `config.yaml`:
```yaml
screenpipe:
url: https://screenpipe.local
```
No code change is required on the autoskill side. `httpx` handles both HTTP and HTTPS transparently.
## mkcert (alternative)
If you prefer managing the cert yourself instead of Caddy's internal CA:
```bash
brew install mkcert
mkcert -install
mkcert localhost 127.0.0.1
```
Then terminate TLS with nginx, Caddy, or stunnel using the generated cert.
@@ -0,0 +1,61 @@
# Starter screenpipe configuration for autoskill users.
#
# Copy the relevant sections into your screenpipe config (or pass as CLI
# flags when starting the daemon). Screenpipe handles app/window filtering
# at capture time — sensitive apps on this list will never be OCR'd, so
# their content never reaches autoskill's pipeline.
#
# Reference: https://github.com/screenpipe/screenpipe
#
# Review and edit for your setup. This is a conservative baseline, not
# an exhaustive list. Add any app where you handle secrets.
ignored_apps:
# Password managers
- "1Password"
- "Bitwarden"
- "Dashlane"
- "Keeper"
- "LastPass"
- "KeePassXC"
# Private messaging
- "Signal"
- "Telegram"
- "WhatsApp"
- "iMessage"
- "Messages"
# Mail composition (inbound reading is fine; composition often contains secrets)
# Remove if you explicitly want mail workflows analyzed.
- "Mail"
- "Outlook"
# Banking / finance apps (add yours)
# - "Bank of America"
# - "Chase"
# Window title globs to ignore (matched across all apps).
# Useful for catching sensitive URLs in browsers that are otherwise OK to observe.
ignored_windows:
- "*Bitwarden*"
- "*1Password*"
- "*login*"
- "*Sign in*"
- "*Private Browsing*"
- "*Incognito*"
- "*online banking*"
- "*bank*"
- "*account settings*"
# Optionally restrict capture to specific hours (local time).
# Uncomment to apply.
# hours:
# start: "09:00"
# end: "19:00"
# Content types to capture. Drop "audio" if you don't want transcripts.
content_types:
- ocr
- ui
# - audio
+35
View File
@@ -0,0 +1,35 @@
"""Unified CLI for the autoskill skill.
Subcommands:
run — detect workflows and draft proposed skills
doctor — verify screenpipe + LM Studio + config + skills dir
promote — move an approved proposal into skills/
"""
import argparse
import sys
def main(argv=None):
parser = argparse.ArgumentParser(prog="autoskill", description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("command", choices=["run", "doctor", "promote"],
help="subcommand to run")
parser.add_argument("rest", nargs=argparse.REMAINDER,
help="arguments forwarded to the subcommand")
args = parser.parse_args(argv)
if args.command == "run":
import run as _run
return _run.main(args.rest)
if args.command == "doctor":
import doctor as _doctor
return _doctor.main(args.rest)
if args.command == "promote":
import promote as _promote
return _promote.main(args.rest)
raise AssertionError(f"unreachable: {args.command!r}")
if __name__ == "__main__":
sys.exit(main())
+71
View File
@@ -0,0 +1,71 @@
import os
import httpx
class ClaudeBackend:
def __init__(self, api_key, model, client=None):
self.api_key = api_key
self.model = model
self.client = client or httpx.Client(base_url="https://api.anthropic.com", timeout=60.0)
def __call__(self, prompt):
response = self.client.post(
"/v1/messages",
headers={
"x-api-key": self.api_key,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
json={
"model": self.model,
"max_tokens": 4096,
"messages": [{"role": "user", "content": prompt}],
},
)
response.raise_for_status()
payload = response.json()
return payload["content"][0]["text"]
class LocalBackend:
def __init__(self, endpoint, model, client=None):
self.endpoint = endpoint
self.model = model
self.client = client or httpx.Client(base_url=endpoint, timeout=120.0)
def __call__(self, prompt):
response = self.client.post(
"/chat/completions",
json={
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
},
)
response.raise_for_status()
payload = response.json()
return payload["choices"][0]["message"]["content"]
def make_backend(config):
kind = config.get("backend")
if kind == "claude":
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
raise RuntimeError("ANTHROPIC_API_KEY environment variable not set")
model = config.get("claude", {}).get("model", "claude-opus-4-7")
return ClaudeBackend(api_key=api_key, model=model)
if kind == "foundry":
api_key = os.environ.get("FOUNDRY_API_KEY")
if not api_key:
raise RuntimeError("FOUNDRY_API_KEY environment variable not set")
f = config.get("foundry", {})
client = httpx.Client(base_url=f["endpoint"], timeout=60.0)
return ClaudeBackend(api_key=api_key, model=f.get("model", "claude-opus-4-7"), client=client)
if kind == "local":
l = config.get("local", {})
return LocalBackend(endpoint=l["endpoint"], model=l["model"])
raise ValueError(f"unknown backend: {kind!r}")
+54
View File
@@ -0,0 +1,54 @@
from collections import defaultdict
def segment_sessions(events, idle_gap_seconds, min_session_seconds):
if not events:
return []
events = sorted(events, key=lambda e: e["ts"])
groups = [[events[0]]]
for prev, curr in zip(events, events[1:]):
if curr["ts"] - prev["ts"] > idle_gap_seconds:
groups.append([curr])
else:
groups[-1].append(curr)
sessions = []
for group in groups:
duration = group[-1]["ts"] - group[0]["ts"]
if duration < min_session_seconds:
continue
apps, seen = [], set()
for evt in group:
if evt["app"] not in seen:
seen.add(evt["app"])
apps.append(evt["app"])
sessions.append({
"start_ts": group[0]["ts"],
"end_ts": group[-1]["ts"],
"duration_seconds": duration,
"apps": apps,
"window_titles": [e["window_title"] for e in group if e.get("window_title")],
})
return sessions
def cluster_sessions(sessions, min_cluster_size):
buckets = defaultdict(list)
for s in sessions:
buckets[tuple(s["apps"])].append(s)
clusters = []
for apps, members in buckets.items():
if len(members) < min_cluster_size:
continue
example_titles = []
for m in members:
if m["window_titles"]:
example_titles.append(m["window_titles"][0])
clusters.append({
"apps": list(apps),
"session_count": len(members),
"total_duration_seconds": sum(m["duration_seconds"] for m in members),
"example_titles": example_titles,
})
return clusters
+108
View File
@@ -0,0 +1,108 @@
import argparse
import os
import sys
from pathlib import Path
import httpx
_VALID_BACKENDS = {"local", "claude", "foundry"}
def default_screenpipe_probe(config):
sp = config.get("screenpipe", {})
url = sp.get("url", "http://localhost:3030")
token = sp.get("token") or os.environ.get("SCREENPIPE_TOKEN")
headers = {"Authorization": f"Bearer {token}"} if token else {}
try:
r = httpx.get(f"{url}/health", headers=headers, timeout=5.0)
if r.status_code == 200:
return ("ok", url)
return ("error", f"{url} returned HTTP {r.status_code}")
except httpx.HTTPError as e:
return ("error", f"{url}: {e}")
def default_llm_probe(config):
kind = config.get("backend")
if kind == "local":
endpoint = config.get("local", {}).get("endpoint", "http://localhost:1234/v1")
try:
r = httpx.get(f"{endpoint}/models", timeout=5.0)
if r.status_code == 200:
return ("ok", endpoint)
return ("error", f"{endpoint} returned HTTP {r.status_code}")
except httpx.HTTPError as e:
return ("error", f"{endpoint}: {e}")
if kind == "claude":
if not os.environ.get("ANTHROPIC_API_KEY"):
return ("error", "ANTHROPIC_API_KEY not set")
return ("ok", "ANTHROPIC_API_KEY present (not probed)")
if kind == "foundry":
if not os.environ.get("FOUNDRY_API_KEY"):
return ("error", "FOUNDRY_API_KEY not set")
return ("ok", "FOUNDRY_API_KEY present (not probed)")
return ("error", f"unknown backend: {kind!r}")
def check(config, *, skills_dir, screenpipe_probe, llm_probe):
result = {}
kind = config.get("backend")
if kind in _VALID_BACKENDS:
result["config"] = ("ok", f"backend={kind}")
else:
result["config"] = ("error", f"unknown backend: {kind!r}")
skills_dir = Path(skills_dir)
if skills_dir.is_dir():
result["skills_dir"] = ("ok", str(skills_dir))
else:
result["skills_dir"] = ("error", f"not a directory: {skills_dir}")
result["screenpipe"] = screenpipe_probe(config)
result["llm"] = llm_probe(config)
return result
def _format_report(result: dict) -> str:
lines = ["autoskill doctor", "================"]
for key in ("config", "skills_dir", "screenpipe", "llm"):
status, detail = result[key]
lines.append(f" {key:12s}: {status:5s} {detail}")
return "\n".join(lines)
def main(argv=None):
import yaml
parser = argparse.ArgumentParser(
prog="autoskill-doctor",
description="Check that screenpipe, LM Studio, config, and skills dir are ready.",
)
parser.add_argument("--config", required=True,
help="path to autoskill config.yaml")
parser.add_argument("--skills-dir", required=True,
help="path to skills/")
args = parser.parse_args(argv)
config = yaml.safe_load(Path(args.config).read_text())
result = check(
config,
skills_dir=args.skills_dir,
screenpipe_probe=default_screenpipe_probe,
llm_probe=default_llm_probe,
)
report = _format_report(result)
print(report)
any_error = any(status == "error" for status, _ in result.values())
if any_error:
print("\ndoctor: one or more checks failed", file=sys.stderr)
return 1
print("\ndoctor: all checks passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+33
View File
@@ -0,0 +1,33 @@
_MAX_PAGES = 10_000 # bounded exit: hard ceiling so the loop cannot spin forever
def fetch_window(client, start_time, end_time, page_size=50, token=None):
events = []
offset = 0
headers = {"Authorization": f"Bearer {token}"} if token else {}
for _page in range(_MAX_PAGES):
response = client.get("/search", params={
"start_time": start_time,
"end_time": end_time,
"limit": page_size,
"offset": offset,
}, headers=headers)
response.raise_for_status()
payload = response.json()
data = payload.get("data", [])
total = payload.get("pagination", {}).get("total", 0)
for item in data:
content = item.get("content", {})
events.append({
"ts": content.get("timestamp"),
"app": content.get("app_name", ""),
"window_title": content.get("window_name", ""),
"text": content.get("text", ""),
"content_type": item.get("type", "").lower(),
})
offset += len(data)
if not data or offset >= total:
break
return events
+46
View File
@@ -0,0 +1,46 @@
import math
from pathlib import Path
def _parse_frontmatter(content: str) -> dict:
if not content.startswith("---"):
return {}
_, _, rest = content.partition("---\n")
block, _, _ = rest.partition("\n---")
out = {}
for line in block.splitlines():
if ":" not in line:
continue
key, _, value = line.partition(":")
out[key.strip()] = value.strip()
return out
def load_skill_descriptions(skills_dir):
skills_dir = Path(skills_dir)
skills = []
for skill_md in sorted(skills_dir.glob("*/SKILL.md")):
fm = _parse_frontmatter(skill_md.read_text())
if "name" in fm and "description" in fm:
skills.append({"name": fm["name"], "description": fm["description"]})
return skills
def _cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
na = math.sqrt(sum(x * x for x in a))
nb = math.sqrt(sum(y * y for y in b))
if na == 0 or nb == 0:
return 0.0
return dot / (na * nb)
def top_k_matches(query, skills, embedder, k):
q = embedder(query)
scored = [
{"name": s["name"], "description": s["description"],
"score": _cosine(q, embedder(s["description"]))}
for s in skills
]
scored.sort(key=lambda r: r["score"], reverse=True)
return scored[:k]
+58
View File
@@ -0,0 +1,58 @@
import argparse
import shutil
import sys
from pathlib import Path
class PromoteError(Exception):
pass
_KINDS = ("new-skills", "composition-recipes")
def promote(proposed_path, skills_dir, name):
proposed_path = Path(proposed_path)
skills_dir = Path(skills_dir)
source = None
for kind in _KINDS:
candidate = proposed_path / kind / name
if candidate.is_dir():
source = candidate
break
if source is None:
raise PromoteError(f"proposed skill {name!r} not found under {proposed_path}")
target = skills_dir / name
if target.exists():
raise PromoteError(f"target {target} already exists")
shutil.move(str(source), str(target))
return target
def main(argv=None):
parser = argparse.ArgumentParser(
prog="autoskill-promote",
description="Move a proposed skill from _proposed/<ts>/ into skills/",
)
parser.add_argument("--proposed", required=True,
help="path to the _proposed/<ts>/ directory")
parser.add_argument("--skills-dir", required=True,
help="path to skills/")
parser.add_argument("--name", required=True, help="skill name to promote")
args = parser.parse_args(argv)
try:
target = promote(args.proposed, args.skills_dir, args.name)
except PromoteError as e:
print(f"promote failed: {e}", file=sys.stderr)
return 1
print(f"promoted: {args.name} -> {target}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+40
View File
@@ -0,0 +1,40 @@
import re
# Order matters: multi-line and prefixed patterns run before narrower ones.
_PATTERNS = [
(re.compile(r"-----BEGIN [A-Z ]+PRIVATE KEY-----[\s\S]*?-----END [A-Z ]+PRIVATE KEY-----"),
"[REDACTED:private_key]"),
# Known-env-var secret assignments: NAME=value (catches long values only)
(re.compile(
r"\b(?:AWS_SECRET_ACCESS_KEY|AWS_ACCESS_KEY_ID|GITHUB_TOKEN|HF_TOKEN"
r"|ANTHROPIC_API_KEY|OPENAI_API_KEY|FOUNDRY_API_KEY|SCREENPIPE_TOKEN"
r"|GOOGLE_API_KEY|SLACK_TOKEN|DEEPGRAM_API_KEY)"
r"\s*=\s*[^\s\"']+"
), "[REDACTED:kv_secret]"),
(re.compile(r"Bearer\s+[A-Za-z0-9_\-\.=]+"), "[REDACTED:bearer]"),
(re.compile(r"\beyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+"),
"[REDACTED:jwt]"),
(re.compile(r"\bxox[bpars]-[A-Za-z0-9\-]{10,}"), "[REDACTED:api_key]"),
(re.compile(r"\bhf_[A-Za-z0-9]{32,}"), "[REDACTED:api_key]"),
(re.compile(r"\bsk-[A-Za-z0-9_\-]{20,}"), "[REDACTED:api_key]"),
(re.compile(r"\b(?:sk|pk|rk)_live_[A-Za-z0-9]{24,}"), "[REDACTED:api_key]"),
(re.compile(r"\bghp_[A-Za-z0-9]{36}\b"), "[REDACTED:api_key]"),
(re.compile(r"\bAKIA[0-9A-Z]{16}\b"), "[REDACTED:api_key]"),
(re.compile(r"\bAIza[A-Za-z0-9_\-]{35}\b"), "[REDACTED:api_key]"),
(re.compile(r"[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}"),
"[REDACTED:email]"),
(re.compile(r"\(\d{3}\)\s*\d{3}-\d{4}"), "[REDACTED:phone]"),
(re.compile(r"\b\d{3}-\d{2}-\d{4}\b"), "[REDACTED:ssn]"),
]
def redact(text: str) -> str:
for pattern, placeholder in _PATTERNS:
text = pattern.sub(placeholder, text)
return text
+194
View File
@@ -0,0 +1,194 @@
import datetime as _dt
from pathlib import Path
import httpx
from cluster import cluster_sessions, segment_sessions
from fetch_window import fetch_window
from match_skills import load_skill_descriptions, top_k_matches
from redact import redact
from synthesize import synthesize
class ScreenpipeUnreachable(RuntimeError):
"""Raised when the screenpipe daemon cannot be reached.
The autoskill skill cannot run without screenpipe. Install it from
https://github.com/screenpipe/screenpipe and start the daemon before
invoking this skill.
"""
def _default_now() -> str:
return _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H-%M-%S")
def _cluster_query(cluster: dict) -> str:
parts = ["apps: " + ", ".join(cluster["apps"])]
if cluster.get("example_titles"):
parts.append("titles: " + "; ".join(cluster["example_titles"]))
return " | ".join(parts)
def _write_plan(proposed_path: Path, clusters: list[dict]) -> None:
lines = ["# Dry-run plan", ""]
for i, c in enumerate(clusters, 1):
lines += [
f"## Cluster {i}",
f"- apps: {', '.join(c['apps'])}",
f"- sessions: {c['session_count']}",
f"- total_duration_seconds: {c['total_duration_seconds']}",
f"- example titles: {'; '.join(c.get('example_titles', []))}",
"",
]
(proposed_path / "plan.md").write_text("\n".join(lines))
def _write_report(proposed_path: Path, results: list[dict]) -> None:
lines = ["# autoskill report", ""]
if not results:
lines.append("No clusters met the minimum size threshold. Nothing to propose.")
for r in results:
c = r["cluster"]
lines += [
f"## {', '.join(c['apps'])}{c['session_count']}× ({c['total_duration_seconds']}s)",
f"- verdict: **{r['verdict']}**",
]
if r["verdict"] == "reuse":
lines.append(f"- matched skill: `{r['target']}`")
else:
lines.append(f"- draft: `{r['draft_path']}`")
lines.append("- top matches:")
for s in r["top_k"]:
lines.append(f" - `{s['name']}` (score={s['score']:.2f})")
lines.append("")
(proposed_path / "report.md").write_text("\n".join(lines))
def run(config, *, start_time, end_time, out_dir,
screenpipe_client, backend, embedder, skills_dir,
screenpipe_token=None, now=None, dry_run=False):
now = now or _default_now
try:
events = fetch_window(screenpipe_client, start_time, end_time,
token=screenpipe_token)
except (httpx.ConnectError, httpx.ConnectTimeout) as e:
base = getattr(screenpipe_client, "base_url", "http://localhost:3030")
raise ScreenpipeUnreachable(
f"cannot reach screenpipe at {base}: {e}. "
"Install and start the daemon — see "
"https://github.com/screenpipe/screenpipe — "
"or point config.yaml's screenpipe.url at your instance."
) from e
for e in events:
e["text"] = redact(e.get("text", ""))
e["window_title"] = redact(e.get("window_title", ""))
cluster_cfg = config.get("cluster", {})
idle_gap = cluster_cfg.get("idle_gap_minutes", 10) * 60
min_session = cluster_cfg.get("min_session_minutes", 5) * 60
min_cluster = cluster_cfg.get("min_cluster_size", 2)
# fetch_window returns ts as ISO strings; convert to epoch for segmentation
for e in events:
if isinstance(e["ts"], str):
e["ts"] = int(_dt.datetime.fromisoformat(e["ts"].replace("Z", "+00:00")).timestamp())
sessions = segment_sessions(events, idle_gap_seconds=idle_gap, min_session_seconds=min_session)
clusters = cluster_sessions(sessions, min_cluster_size=min_cluster)
proposed_path = Path(out_dir) / now()
proposed_path.mkdir(parents=True, exist_ok=True)
if dry_run:
_write_plan(proposed_path, clusters)
return proposed_path
if not clusters:
_write_report(proposed_path, [])
return proposed_path
skills = load_skill_descriptions(Path(skills_dir))
results = []
for cluster in clusters:
query = _cluster_query(cluster)
top_k = top_k_matches(query, skills, embedder=embedder, k=5)
decision = synthesize(cluster, top_k, backend=backend)
entry = {"cluster": cluster, "top_k": top_k, "verdict": decision["verdict"]}
if decision["verdict"] == "reuse":
entry["target"] = decision.get("target")
else:
kind = "new-skills" if decision["verdict"] == "novel" else "composition-recipes"
name = decision["name"]
draft_dir = proposed_path / kind / name
draft_dir.mkdir(parents=True)
(draft_dir / "SKILL.md").write_text(decision["skill_body"])
entry["draft_path"] = str(draft_dir.relative_to(proposed_path))
results.append(entry)
_write_report(proposed_path, results)
return proposed_path
def main(argv=None):
import argparse
import sys
import httpx
import yaml
from backends import make_backend
parser = argparse.ArgumentParser(prog="autoskill")
parser.add_argument("--start", required=True, help="ISO start time, e.g. 2026-04-17T00:00:00Z")
parser.add_argument("--end", required=True, help="ISO end time")
parser.add_argument("--config", default=str(Path(__file__).resolve().parent.parent / "config.yaml"))
parser.add_argument("--out", default=None,
help="output directory for proposals (default: ~/.autoskill/proposed)")
parser.add_argument("--skills-dir", default=None,
help="path to skills/ (default: parent of this skill's dir)")
parser.add_argument("--dry-run", action="store_true",
help="stop after clustering; do not call the LLM backend")
args = parser.parse_args(argv)
config = yaml.safe_load(Path(args.config).read_text())
here = Path(__file__).resolve()
skills_dir = Path(args.skills_dir) if args.skills_dir else here.parent.parent.parent
out_dir = Path(args.out) if args.out else Path.home() / ".autoskill" / "proposed"
import os
screenpipe_cfg = config.get("screenpipe", {})
screenpipe_url = screenpipe_cfg.get("url", "http://localhost:3030")
screenpipe_token = (screenpipe_cfg.get("token")
or os.environ.get("SCREENPIPE_TOKEN"))
screenpipe_client = httpx.Client(base_url=screenpipe_url, timeout=60.0)
if args.dry_run:
backend = None
embedder = None
else:
backend = make_backend(config)
from sentence_transformers import SentenceTransformer
model = SentenceTransformer(
config.get("embeddings", {}).get("model", "sentence-transformers/all-MiniLM-L6-v2")
)
def embedder(text: str):
return list(map(float, model.encode(text)))
proposed = run(
config,
start_time=args.start, end_time=args.end, out_dir=out_dir,
screenpipe_client=screenpipe_client, backend=backend, embedder=embedder,
skills_dir=skills_dir, screenpipe_token=screenpipe_token,
dry_run=args.dry_run,
)
print(f"proposals written to: {proposed}", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+72
View File
@@ -0,0 +1,72 @@
import json
import re
VALID_VERDICTS = {"reuse", "compose", "novel"}
class SynthesisError(Exception):
pass
def _build_prompt(cluster, top_k_skills):
apps = ", ".join(cluster["apps"])
titles = "; ".join(cluster.get("example_titles", []))
candidates = "\n".join(
f"- {s['name']} (score={s['score']:.2f}): {s['description']}"
for s in top_k_skills
)
return f"""You are classifying an observed user workflow against an existing skill library.
Cluster:
- apps: {apps}
- sessions: {cluster['session_count']}
- total_duration_seconds: {cluster['total_duration_seconds']}
- example titles: {titles}
Candidate existing skills (ranked by semantic similarity):
{candidates}
Decide one of:
- "reuse": an existing skill already covers this workflow.
- "compose": no single skill covers it, but chaining existing skills does. Draft a thin SKILL.md that invokes them in order.
- "novel": not covered; draft a full new SKILL.md.
Respond with a single JSON object:
- reuse: {{"verdict": "reuse", "target": "<skill-name>"}}
- compose: {{"verdict": "compose", "name": "<new-skill-name>", "skill_body": "<SKILL.md body>"}}
- novel: {{"verdict": "novel", "name": "<new-skill-name>", "skill_body": "<SKILL.md body>"}}
"""
def _extract_json(text: str) -> dict:
fence = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
if fence:
candidate = fence.group(1)
else:
start = text.find("{")
end = text.rfind("}")
if start == -1 or end == -1 or end <= start:
raise SynthesisError(f"no JSON object found in response: {text!r}")
candidate = text[start:end + 1]
try:
return json.loads(candidate)
except json.JSONDecodeError as e:
raise SynthesisError(f"invalid JSON in response: {e}") from e
def synthesize(cluster, top_k_skills, backend):
prompt = _build_prompt(cluster, top_k_skills)
response = backend(prompt)
payload = _extract_json(response)
verdict = payload.get("verdict")
if verdict not in VALID_VERDICTS:
raise SynthesisError(f"unknown verdict: {verdict!r}")
result = {"verdict": verdict, "skill_body": None}
if verdict == "reuse":
result["target"] = payload.get("target")
else:
result["name"] = payload.get("name")
result["skill_body"] = payload.get("skill_body")
return result
+4
View File
@@ -0,0 +1,4 @@
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
+60
View File
@@ -0,0 +1,60 @@
"""Real smoke test against a live LM Studio server.
Not part of the pytest suite — requires LM Studio running on localhost:1234
with Gemma-4-31B-it loaded. Run manually:
pipenv run python skills/autoskill/tests/smoke_lmstudio.py
"""
import json
import sys
from pathlib import Path
THIS_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(THIS_DIR.parent / "scripts"))
from backends import LocalBackend
from match_skills import load_skill_descriptions, top_k_matches
from synthesize import synthesize
def main() -> int:
backend = LocalBackend(endpoint="http://localhost:1234/v1", model="gemma-4-31b-it")
repo_skills_dir = THIS_DIR.parent.parent
all_skills = load_skill_descriptions(repo_skills_dir)
print(f"loaded {len(all_skills)} skills from {repo_skills_dir}")
# Real sentence-transformers embedder.
print("loading sentence-transformers/all-MiniLM-L6-v2 ...")
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
def embedder(text: str):
return list(map(float, model.encode(text)))
cluster = {
"apps": ["Chrome", "Zotero"],
"session_count": 4,
"total_duration_seconds": 7200,
"example_titles": ["PubMed search: tumor microenvironment", "bioRxiv preprint"],
}
query = ("apps: " + ", ".join(cluster["apps"]) +
" | titles: " + "; ".join(cluster["example_titles"]))
top_k = top_k_matches(query, all_skills, embedder=embedder, k=5)
print("real top-5 matches from embedding search:")
for s in top_k:
print(f" {s['score']:.3f} {s['name']}")
result = synthesize(cluster, top_k, backend=backend)
print("\n--- VERDICT ---")
print(json.dumps(result, indent=2)[:1000])
assert result["verdict"] in {"reuse", "compose", "novel"}, result
print("\nOK: real sentence-transformers top-k + real Gemma-4-31B-it produced a valid verdict.")
return 0
if __name__ == "__main__":
sys.exit(main())
+121
View File
@@ -0,0 +1,121 @@
import httpx
import pytest
from backends import ClaudeBackend, LocalBackend, make_backend
def _mock_client(handler, base_url=""):
return httpx.Client(transport=httpx.MockTransport(handler), base_url=base_url)
def test_claude_backend_posts_to_v1_messages_with_auth_headers():
calls = []
def handler(request):
calls.append(request)
return httpx.Response(200, json={"content": [{"type": "text", "text": "hello"}]})
backend = ClaudeBackend(api_key="sk-test", model="claude-opus-4-7",
client=_mock_client(handler, base_url="https://api.anthropic.com"))
result = backend("say hello")
assert result == "hello"
assert calls[0].url.path == "/v1/messages"
assert calls[0].headers["x-api-key"] == "sk-test"
assert calls[0].headers["anthropic-version"]
body = calls[0].read()
import json as _json
payload = _json.loads(body)
assert payload["model"] == "claude-opus-4-7"
assert payload["messages"][0]["role"] == "user"
assert payload["messages"][0]["content"] == "say hello"
def test_claude_backend_honors_custom_base_url_for_foundry():
calls = []
def handler(request):
calls.append(request)
return httpx.Response(200, json={"content": [{"type": "text", "text": "ok"}]})
backend = ClaudeBackend(api_key="k", model="claude-opus-4-7",
client=_mock_client(handler, base_url="https://foundry.example.com/anthropic"))
backend("hi")
assert str(calls[0].url).startswith("https://foundry.example.com/anthropic/v1/messages")
def test_claude_backend_raises_on_http_error():
def handler(request):
return httpx.Response(401, json={"error": "bad key"})
backend = ClaudeBackend(api_key="k", model="m",
client=_mock_client(handler, base_url="https://api.anthropic.com"))
with pytest.raises(httpx.HTTPStatusError):
backend("x")
def test_local_backend_posts_to_chat_completions():
calls = []
def handler(request):
calls.append(request)
return httpx.Response(200, json={
"choices": [{"message": {"content": "hi back"}}]
})
backend = LocalBackend(endpoint="http://localhost:11434/v1", model="qwen2.5:14b",
client=_mock_client(handler, base_url="http://localhost:11434/v1"))
result = backend("prompt")
assert result == "hi back"
assert calls[0].url.path.endswith("/chat/completions")
import json as _json
payload = _json.loads(calls[0].read())
assert payload["model"] == "qwen2.5:14b"
assert payload["messages"][0]["content"] == "prompt"
def test_make_backend_returns_claude_backend_for_claude_config(monkeypatch):
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-env")
backend = make_backend({
"backend": "claude",
"claude": {"model": "claude-opus-4-7"},
})
assert isinstance(backend, ClaudeBackend)
assert backend.api_key == "sk-env"
assert backend.model == "claude-opus-4-7"
def test_make_backend_returns_claude_backend_with_foundry_base_url(monkeypatch):
monkeypatch.setenv("FOUNDRY_API_KEY", "sk-foundry")
backend = make_backend({
"backend": "foundry",
"foundry": {
"endpoint": "https://foundry.example.com/anthropic",
"model": "claude-opus-4-7",
},
})
assert isinstance(backend, ClaudeBackend)
assert backend.api_key == "sk-foundry"
assert "foundry.example.com" in str(backend.client.base_url)
def test_make_backend_returns_local_backend_for_local_config():
backend = make_backend({
"backend": "local",
"local": {"endpoint": "http://localhost:11434/v1", "model": "qwen2.5:14b"},
})
assert isinstance(backend, LocalBackend)
assert backend.model == "qwen2.5:14b"
def test_make_backend_raises_on_unknown_backend():
with pytest.raises(ValueError, match="unknown backend"):
make_backend({"backend": "mystery"})
def test_make_backend_raises_when_anthropic_key_missing(monkeypatch):
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
with pytest.raises(RuntimeError, match="ANTHROPIC_API_KEY"):
make_backend({"backend": "claude", "claude": {"model": "m"}})
+61
View File
@@ -0,0 +1,61 @@
import pytest
import autoskill
def test_dispatches_to_run_subcommand(monkeypatch):
calls = {}
def fake_run_main(argv):
calls["run"] = argv
return 0
monkeypatch.setattr("run.main", fake_run_main)
rc = autoskill.main(["run", "--start", "a", "--end", "b"])
assert rc == 0
assert calls["run"] == ["--start", "a", "--end", "b"]
def test_dispatches_to_doctor_subcommand(monkeypatch):
calls = {}
def fake_doctor_main(argv):
calls["doctor"] = argv
return 0
monkeypatch.setattr("doctor.main", fake_doctor_main)
rc = autoskill.main(["doctor", "--config", "x", "--skills-dir", "y"])
assert rc == 0
assert calls["doctor"] == ["--config", "x", "--skills-dir", "y"]
def test_dispatches_to_promote_subcommand(monkeypatch):
calls = {}
def fake_promote_main(argv):
calls["promote"] = argv
return 0
monkeypatch.setattr("promote.main", fake_promote_main)
rc = autoskill.main(["promote", "--proposed", "p", "--skills-dir", "s", "--name", "n"])
assert rc == 0
assert calls["promote"] == ["--proposed", "p", "--skills-dir", "s", "--name", "n"]
def test_propagates_nonzero_exit_code(monkeypatch):
monkeypatch.setattr("run.main", lambda argv: 7)
rc = autoskill.main(["run", "--start", "a", "--end", "b"])
assert rc == 7
def test_missing_subcommand_errors_out():
with pytest.raises(SystemExit):
autoskill.main([])
def test_unknown_subcommand_errors_out():
with pytest.raises(SystemExit):
autoskill.main(["nope"])
+67
View File
@@ -0,0 +1,67 @@
from cluster import segment_sessions, cluster_sessions
def _evt(ts: int, app: str, title: str = "", text: str = "") -> dict:
return {"ts": ts, "app": app, "window_title": title, "text": text}
def test_contiguous_events_form_one_session():
events = [_evt(i * 30, "VSCode") for i in range(20)] # 10 minutes
sessions = segment_sessions(events, idle_gap_seconds=600, min_session_seconds=300)
assert len(sessions) == 1
assert sessions[0]["apps"] == ["VSCode"]
assert sessions[0]["duration_seconds"] == 19 * 30
def test_idle_gap_splits_sessions():
left = [_evt(i * 30, "Chrome") for i in range(20)] # 0..570
right = [_evt(10000 + i * 30, "Chrome") for i in range(20)] # gap of ~9400s
sessions = segment_sessions(left + right, idle_gap_seconds=600, min_session_seconds=300)
assert len(sessions) == 2
def test_sessions_below_minimum_are_dropped():
events = [_evt(i * 10, "Finder") for i in range(5)] # 40 seconds total
sessions = segment_sessions(events, idle_gap_seconds=600, min_session_seconds=300)
assert sessions == []
def test_session_records_distinct_apps_in_order_of_first_appearance():
events = [
_evt(0, "Chrome"), _evt(30, "Chrome"),
_evt(60, "Zotero"), _evt(90, "Zotero"),
_evt(120, "VSCode"), _evt(600, "VSCode"),
]
sessions = segment_sessions(events, idle_gap_seconds=600, min_session_seconds=300)
assert len(sessions) == 1
assert sessions[0]["apps"] == ["Chrome", "Zotero", "VSCode"]
def test_clusters_group_sessions_with_identical_app_signatures():
sessions = [
{"apps": ["Chrome", "Zotero"], "duration_seconds": 600, "window_titles": ["PubMed"]},
{"apps": ["Chrome", "Zotero"], "duration_seconds": 800, "window_titles": ["bioRxiv"]},
{"apps": ["VSCode"], "duration_seconds": 900, "window_titles": ["paper.tex"]},
]
clusters = cluster_sessions(sessions, min_cluster_size=2)
assert len(clusters) == 1
assert clusters[0]["apps"] == ["Chrome", "Zotero"]
assert clusters[0]["session_count"] == 2
def test_cluster_summary_exposes_total_duration_and_example_titles():
sessions = [
{"apps": ["Chrome"], "duration_seconds": 300, "window_titles": ["arXiv"]},
{"apps": ["Chrome"], "duration_seconds": 500, "window_titles": ["Nature"]},
]
clusters = cluster_sessions(sessions, min_cluster_size=2)
assert clusters[0]["total_duration_seconds"] == 800
assert set(clusters[0]["example_titles"]) == {"arXiv", "Nature"}
def test_singleton_clusters_are_dropped_when_below_min_cluster_size():
sessions = [
{"apps": ["Slack"], "duration_seconds": 400, "window_titles": ["general"]},
]
clusters = cluster_sessions(sessions, min_cluster_size=2)
assert clusters == []
+108
View File
@@ -0,0 +1,108 @@
from pathlib import Path
from doctor import check, main as doctor_main
def _config(tmp_path: Path) -> dict:
return {
"backend": "local",
"local": {"endpoint": "http://localhost:1234/v1", "model": "m"},
"screenpipe": {"url": "http://localhost:3030"},
}
def _ok_probe(*_args, **_kwargs):
return ("ok", "")
def _err_probe(*_args, **_kwargs):
return ("error", "boom")
def test_check_all_green(tmp_path: Path):
result = check(
_config(tmp_path),
skills_dir=tmp_path,
screenpipe_probe=_ok_probe,
llm_probe=_ok_probe,
)
assert result["screenpipe"] == ("ok", "")
assert result["llm"] == ("ok", "")
assert result["config"][0] == "ok"
assert result["skills_dir"][0] == "ok"
def test_check_reports_screenpipe_failure(tmp_path: Path):
result = check(
_config(tmp_path),
skills_dir=tmp_path,
screenpipe_probe=_err_probe,
llm_probe=_ok_probe,
)
assert result["screenpipe"] == ("error", "boom")
def test_check_reports_llm_failure(tmp_path: Path):
result = check(
_config(tmp_path),
skills_dir=tmp_path,
screenpipe_probe=_ok_probe,
llm_probe=_err_probe,
)
assert result["llm"] == ("error", "boom")
def test_check_reports_missing_skills_dir(tmp_path: Path):
missing = tmp_path / "nope"
result = check(
_config(tmp_path),
skills_dir=missing,
screenpipe_probe=_ok_probe,
llm_probe=_ok_probe,
)
assert result["skills_dir"][0] == "error"
assert str(missing) in result["skills_dir"][1]
def test_check_flags_unknown_backend(tmp_path: Path):
bad = {"backend": "mystery", "screenpipe": {"url": "http://x"}}
result = check(
bad, skills_dir=tmp_path,
screenpipe_probe=_ok_probe, llm_probe=_ok_probe,
)
assert result["config"][0] == "error"
assert "mystery" in result["config"][1]
def test_cli_all_green_returns_zero(tmp_path: Path, capsys, monkeypatch):
import yaml as _yaml
conf_path = tmp_path / "c.yaml"
conf_path.write_text(_yaml.safe_dump(_config(tmp_path)))
monkeypatch.setattr("doctor.default_screenpipe_probe", _ok_probe)
monkeypatch.setattr("doctor.default_llm_probe", _ok_probe)
rc = doctor_main([
"--config", str(conf_path),
"--skills-dir", str(tmp_path),
])
out = capsys.readouterr().out
assert rc == 0
assert "ok" in out.lower()
def test_cli_any_failure_returns_nonzero(tmp_path: Path, capsys, monkeypatch):
import yaml as _yaml
conf_path = tmp_path / "c.yaml"
conf_path.write_text(_yaml.safe_dump(_config(tmp_path)))
monkeypatch.setattr("doctor.default_screenpipe_probe", _err_probe)
monkeypatch.setattr("doctor.default_llm_probe", _ok_probe)
rc = doctor_main([
"--config", str(conf_path),
"--skills-dir", str(tmp_path),
])
out = capsys.readouterr().out
assert rc != 0
assert "error" in out.lower() or "error" in capsys.readouterr().err.lower()
+327
View File
@@ -0,0 +1,327 @@
"""End-to-end tests for the autoskill pipeline.
Exercises the full chain — fetch → redact → cluster → match → synthesize → write
— with real internal modules. External dependencies (screenpipe, LLM, embeddings)
are replaced with deterministic fakes so the suite runs offline in under a second.
"""
import json
import sys
from pathlib import Path
import httpx
import pytest
# Synthetic opaque value for auth-header round-trip tests. Not a credential.
_AUTH = "fake-test-value-1234"
from backends import LocalBackend
from run import run, main
# ---------- fixtures ----------
SKILL_TEMPLATE = "---\nname: {name}\ndescription: {description}\n---\n# {name}\n"
def _write_skill(root: Path, name: str, description: str) -> None:
d = root / name
d.mkdir(parents=True, exist_ok=True)
(d / "SKILL.md").write_text(SKILL_TEMPLATE.format(name=name, description=description))
def _seed_skills_dir(root: Path) -> None:
"""A tiny but representative slice of the real skills/ layout."""
_write_skill(root, "literature-review",
"Systematic literature reviews across PubMed arXiv bioRxiv with citations.")
_write_skill(root, "citation-management",
"Find papers and format citations from Google Scholar and PubMed.")
_write_skill(root, "scientific-writing",
"Write research manuscripts with IMRAD structure and citations.")
_write_skill(root, "scientific-schematics",
"Create scientific diagrams figures and schematics for publications.")
_write_skill(root, "latex-posters",
"Create academic conference posters in LaTeX.")
_write_skill(root, "rdkit",
"Cheminformatics with RDKit molecules drug discovery.")
# ---------- fake screenpipe ----------
def _ocr(ts: str, app: str, title: str, text: str) -> dict:
return {"type": "OCR", "content": {
"timestamp": ts, "app_name": app, "window_name": title, "text": text,
}}
def _realistic_day() -> list:
"""Three workflow patterns repeated twice each, plus noise."""
events = []
# Pattern 1: literature work (Chrome + Zotero) — reuse expected
for hr in (9, 14):
for m in range(0, 20):
events.append(_ocr(f"2026-04-17T{hr:02d}:{m:02d}:00Z",
"Chrome", "PubMed", "searching papers"))
# Pattern 2: slides + schematics (Keynote + Preview) — novel expected
for hr in (11, 16):
for m in range(0, 20):
events.append(_ocr(f"2026-04-17T{hr:02d}:{m:02d}:00Z",
"Keynote", "deck", "slides for keynote talk"))
# Pattern 3: manuscript + figures (VSCode + Preview) — compose expected
for hr in (12, 17):
for m in range(0, 20):
events.append(_ocr(f"2026-04-17T{hr:02d}:{m:02d}:00Z",
"VSCode", "paper.tex", "writing manuscript with figures"))
# Pattern 4 (lonely): should be dropped (only one session)
for m in range(0, 10):
events.append(_ocr(f"2026-04-17T22:{m:02d}:00Z",
"Lonely", "", "whatever"))
return events
def _screenpipe_client(events: list) -> httpx.Client:
# Paginate in chunks of 20 to exercise fetch_window's pagination loop.
page_size = 20
def handler(request):
params = dict(request.url.params)
offset = int(params.get("offset", "0"))
chunk = events[offset:offset + page_size]
return httpx.Response(200, json={
"data": chunk,
"pagination": {"limit": page_size, "offset": offset, "total": len(events)},
})
return httpx.Client(transport=httpx.MockTransport(handler), base_url="http://screenpipe.test")
# ---------- fake LM Studio ----------
class _FakeLMStudioHandler:
"""Returns different verdicts based on apps mentioned in the prompt."""
def __init__(self):
self.prompts = []
def __call__(self, request):
body = json.loads(request.read())
prompt = body["messages"][0]["content"]
self.prompts.append(prompt)
if "Chrome" in prompt and "PubMed" in prompt:
out = {"verdict": "reuse", "target": "literature-review"}
elif "Keynote" in prompt:
body = ("---\nname: keynote-talk-prep\n"
"description: Prepare and iterate on Keynote talks.\n---\n"
"# keynote-talk-prep\n")
out = {"verdict": "novel", "name": "keynote-talk-prep", "skill_body": body}
elif "VSCode" in prompt:
body = ("---\nname: manuscript-with-figures\n"
"description: Chain scientific-writing + scientific-schematics.\n---\n"
"# manuscript-with-figures\n\n"
"Invoke scientific-writing then scientific-schematics.\n")
out = {"verdict": "compose", "name": "manuscript-with-figures", "skill_body": body}
else:
out = {"verdict": "reuse", "target": "literature-review"}
return httpx.Response(200, json={
"choices": [{"message": {"content": json.dumps(out)}}]
})
def _keyword_embedder(text: str):
keywords = ["paper", "pubmed", "literature", "citation", "writing",
"schematic", "figure", "poster", "molecule", "slides"]
return [1.0 if k in text.lower() else 0.0 for k in keywords]
def _base_config():
return {
"cluster": {"min_session_minutes": 0, "idle_gap_minutes": 10, "min_cluster_size": 2},
}
# ---------- tests ----------
def test_full_pipeline_produces_report_and_drafts(tmp_path):
skills_dir = tmp_path / "skills"
_seed_skills_dir(skills_dir)
fake_lmstudio = _FakeLMStudioHandler()
lm_client = httpx.Client(
transport=httpx.MockTransport(fake_lmstudio),
base_url="http://localhost:1234/v1",
)
backend = LocalBackend(endpoint="http://localhost:1234/v1",
model="Gemma-4-31B-it", client=lm_client)
out = run(
_base_config(),
start_time="2026-04-17T00:00:00Z", end_time="2026-04-17T23:59:59Z",
out_dir=tmp_path / "_proposed",
screenpipe_client=_screenpipe_client(_realistic_day()),
backend=backend,
embedder=_keyword_embedder,
skills_dir=skills_dir,
now=lambda: "2026-04-17T18-00-00",
)
# Report exists and mentions all three surviving clusters.
report = (out / "report.md").read_text()
assert "Chrome" in report
assert "Keynote" in report
assert "VSCode" in report
assert "reuse" in report
assert "novel" in report
assert "compose" in report
# Lonely cluster was dropped by min_cluster_size.
assert "Lonely" not in report
# Novel draft landed under new-skills/.
novel_skill = out / "new-skills" / "keynote-talk-prep" / "SKILL.md"
assert novel_skill.exists()
assert "keynote-talk-prep" in novel_skill.read_text()
# Compose draft landed under composition-recipes/.
compose_skill = out / "composition-recipes" / "manuscript-with-figures" / "SKILL.md"
assert compose_skill.exists()
body = compose_skill.read_text()
assert "scientific-writing" in body
assert "scientific-schematics" in body
# Reuse verdict writes no draft but does cite the matched existing skill.
assert not (out / "new-skills" / "literature-review").exists()
assert "literature-review" in report
# Pagination actually happened (more than one fetch).
# 120 events / 20 per page = 6 pages → ≥6 backend-independent calls.
# (Can't assert directly without reaching into the transport; but the test
# would fail above if pagination were broken since clusters would be empty.)
def test_pipeline_redaction_prevents_secrets_leaving_the_host(tmp_path):
skills_dir = tmp_path / "skills"
_seed_skills_dir(skills_dir)
# Plant secrets in every OCR event.
toxic = [
_ocr(f"2026-04-17T{hr:02d}:{m:02d}:00Z", "Chrome", "Gmail",
"email alice@example.com password sk-abcdefghijklmnopqrstuvwxyz012345")
for hr in (9, 14) for m in range(0, 20)
]
fake_lmstudio = _FakeLMStudioHandler()
lm_client = httpx.Client(
transport=httpx.MockTransport(fake_lmstudio),
base_url="http://localhost:1234/v1",
)
backend = LocalBackend(endpoint="http://localhost:1234/v1",
model="Gemma-4-31B-it", client=lm_client)
run(
_base_config(),
start_time="s", end_time="e",
out_dir=tmp_path / "_proposed",
screenpipe_client=_screenpipe_client(toxic),
backend=backend, embedder=_keyword_embedder, skills_dir=skills_dir,
now=lambda: "ts",
)
assert fake_lmstudio.prompts, "backend should have been called"
for prompt in fake_lmstudio.prompts:
assert "alice@example.com" not in prompt
assert "sk-abcdefghijklmnopqrstuvwxyz012345" not in prompt
def test_auth_token_threads_through_run_into_fetch_window(tmp_path):
"""Integration: config's screenpipe.token reaches the outgoing HTTP request."""
skills_dir = tmp_path / "skills"
_seed_skills_dir(skills_dir)
seen_auth = []
def handler(request):
seen_auth.append(request.headers.get("authorization"))
return httpx.Response(200, json={
"data": [], "pagination": {"limit": 20, "offset": 0, "total": 0},
})
client = httpx.Client(transport=httpx.MockTransport(handler),
base_url="http://screenpipe.test")
run(
_base_config(),
start_time="2026-04-17T00:00:00Z", end_time="2026-04-17T23:59:59Z",
out_dir=tmp_path / "_proposed",
screenpipe_client=client,
backend=None, embedder=_keyword_embedder, skills_dir=skills_dir,
screenpipe_token=_AUTH,
now=lambda: "ts",
)
assert seen_auth, "screenpipe should have been called"
assert all(h == f"Bearer {_AUTH}" for h in seen_auth), seen_auth
def test_cli_dry_run_writes_plan_without_backend_or_embedding_model(tmp_path, monkeypatch):
"""Exercises scripts/run.py's main() via argv with --dry-run.
--dry-run is the only path that avoids loading sentence-transformers and
instantiating a backend, so we can smoke-test the CLI offline.
"""
skills_dir = tmp_path / "skills"
_seed_skills_dir(skills_dir)
# Minimal config.yaml pointing at our fake screenpipe (via localhost base_url
# that we'll monkeypatch httpx.Client to respect).
config = tmp_path / "config.yaml"
config.write_text(
"backend: local\n"
"local:\n"
" endpoint: http://localhost:1234/v1\n"
" model: Gemma-4-31B-it\n"
"screenpipe:\n"
" url: http://screenpipe.test\n"
"cluster:\n"
" min_session_minutes: 0\n"
" idle_gap_minutes: 10\n"
" min_cluster_size: 2\n"
)
# Install a MockTransport-backed default for any httpx.Client the CLI builds.
events = _realistic_day()
def handler(request):
params = dict(request.url.params)
offset = int(params.get("offset", "0"))
chunk = events[offset:offset + 20]
return httpx.Response(200, json={
"data": chunk,
"pagination": {"limit": 20, "offset": offset, "total": len(events)},
})
original_client = httpx.Client
monkeypatch.setattr(httpx, "Client", lambda *a, **kw: original_client(
*a, **{**kw, "transport": httpx.MockTransport(handler)}
))
# Need pyyaml for main() to load the config; install on demand and skip if unavailable.
yaml = pytest.importorskip("yaml")
out_dir = tmp_path / "_proposed"
rc = main([
"--start", "2026-04-17T00:00:00Z",
"--end", "2026-04-17T23:59:59Z",
"--config", str(config),
"--skills-dir", str(skills_dir),
"--out", str(out_dir),
"--dry-run",
])
assert rc == 0
# One timestamped subdir was created under --out.
subdirs = [p for p in out_dir.iterdir() if p.is_dir()]
assert len(subdirs) == 1
plan = (subdirs[0] / "plan.md").read_text()
assert "Cluster" in plan
assert "Chrome" in plan
+111
View File
@@ -0,0 +1,111 @@
import httpx
import pytest
from fetch_window import fetch_window
def _make_client(handler):
transport = httpx.MockTransport(handler)
return httpx.Client(transport=transport, base_url="http://localhost:3030")
def _ocr_event(ts: str, app: str, title: str, text: str) -> dict:
return {
"type": "OCR",
"content": {"timestamp": ts, "app_name": app, "window_name": title, "text": text},
}
def test_fetch_calls_search_endpoint_with_time_window_params():
calls = []
def handler(request: httpx.Request) -> httpx.Response:
calls.append(request)
return httpx.Response(200, json={"data": [], "pagination": {"limit": 50, "offset": 0, "total": 0}})
client = _make_client(handler)
fetch_window(client, start_time="2026-04-17T00:00:00Z", end_time="2026-04-17T04:00:00Z")
assert calls, "expected at least one HTTP call"
assert calls[0].url.path == "/search"
params = dict(calls[0].url.params)
assert params["start_time"] == "2026-04-17T00:00:00Z"
assert params["end_time"] == "2026-04-17T04:00:00Z"
def test_fetch_normalizes_ocr_events_into_unified_schema():
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json={
"data": [_ocr_event("2026-04-17T10:00:00Z", "VSCode", "paper.tex", "hello")],
"pagination": {"limit": 50, "offset": 0, "total": 1},
})
events = fetch_window(_make_client(handler), "2026-04-17T00:00:00Z", "2026-04-17T23:59:59Z")
assert len(events) == 1
e = events[0]
assert e["app"] == "VSCode"
assert e["window_title"] == "paper.tex"
assert e["text"] == "hello"
assert e["content_type"] == "ocr"
assert e["ts"] == "2026-04-17T10:00:00Z"
def test_fetch_paginates_until_all_results_collected():
pages = [
{"data": [_ocr_event("2026-04-17T10:00:00Z", "App", "t1", "a")] * 2,
"pagination": {"limit": 2, "offset": 0, "total": 3}},
{"data": [_ocr_event("2026-04-17T10:05:00Z", "App", "t2", "b")],
"pagination": {"limit": 2, "offset": 2, "total": 3}},
]
call_count = {"n": 0}
def handler(request: httpx.Request) -> httpx.Response:
page = pages[call_count["n"]]
call_count["n"] += 1
return httpx.Response(200, json=page)
events = fetch_window(_make_client(handler), "2026-04-17T00:00:00Z", "2026-04-17T23:59:59Z", page_size=2)
assert call_count["n"] == 2
assert len(events) == 3
def test_fetch_returns_empty_list_when_no_results():
def handler(request):
return httpx.Response(200, json={"data": [], "pagination": {"limit": 50, "offset": 0, "total": 0}})
events = fetch_window(_make_client(handler), "2026-04-17T00:00:00Z", "2026-04-17T23:59:59Z")
assert events == []
def test_fetch_sends_bearer_token_when_provided():
seen = []
def handler(request):
seen.append(request)
return httpx.Response(200, json={"data": [], "pagination": {"limit": 50, "offset": 0, "total": 0}})
fetch_window(_make_client(handler), "a", "b", token="secret-123")
assert seen[0].headers.get("authorization") == "Bearer secret-123"
def test_fetch_omits_auth_header_when_no_token():
seen = []
def handler(request):
seen.append(request)
return httpx.Response(200, json={"data": [], "pagination": {"limit": 50, "offset": 0, "total": 0}})
fetch_window(_make_client(handler), "a", "b")
assert "authorization" not in {k.lower() for k in seen[0].headers.keys()}
def test_fetch_raises_on_http_error():
def handler(request):
return httpx.Response(500, text="server down")
with pytest.raises(httpx.HTTPStatusError):
fetch_window(_make_client(handler), "2026-04-17T00:00:00Z", "2026-04-17T23:59:59Z")
@@ -0,0 +1,75 @@
from pathlib import Path
from match_skills import load_skill_descriptions, top_k_matches
SKILL_TEMPLATE = """---
name: {name}
description: {description}
---
# {name}
"""
def _write_skill(root: Path, name: str, description: str) -> None:
d = root / name
d.mkdir(parents=True, exist_ok=True)
(d / "SKILL.md").write_text(SKILL_TEMPLATE.format(name=name, description=description))
def test_load_skill_descriptions_reads_frontmatter(tmp_path: Path):
_write_skill(tmp_path, "literature-review", "Systematic literature reviews across databases.")
_write_skill(tmp_path, "latex-posters", "Create conference posters in LaTeX.")
skills = load_skill_descriptions(tmp_path)
by_name = {s["name"]: s for s in skills}
assert set(by_name) == {"literature-review", "latex-posters"}
assert by_name["literature-review"]["description"].startswith("Systematic literature reviews")
def test_load_skill_descriptions_skips_directories_without_skill_md(tmp_path: Path):
_write_skill(tmp_path, "real-skill", "A real skill.")
(tmp_path / "empty-dir").mkdir()
skills = load_skill_descriptions(tmp_path)
assert [s["name"] for s in skills] == ["real-skill"]
def _keyword_embedder(text: str):
# deterministic, tiny vector indexed by known keywords
keywords = ["paper", "poster", "molecule"]
return [1.0 if k in text.lower() else 0.0 for k in keywords]
def test_top_k_matches_ranks_by_cosine_similarity():
skills = [
{"name": "literature-review", "description": "search and summarize papers"},
{"name": "latex-posters", "description": "create academic posters"},
{"name": "rdkit", "description": "molecule cheminformatics"},
]
query = "I spent time reading papers"
results = top_k_matches(query, skills, embedder=_keyword_embedder, k=2)
assert len(results) == 2
assert results[0]["name"] == "literature-review"
assert results[0]["score"] > results[1]["score"]
def test_top_k_matches_returns_all_when_k_exceeds_skills():
skills = [
{"name": "a", "description": "paper work"},
{"name": "b", "description": "poster work"},
]
results = top_k_matches("paper", skills, embedder=_keyword_embedder, k=10)
assert len(results) == 2
def test_top_k_matches_handles_zero_vectors_without_crashing():
skills = [{"name": "a", "description": "unrelated text"}]
results = top_k_matches("unrelated text query", skills,
embedder=lambda _: [0.0, 0.0, 0.0], k=1)
assert len(results) == 1
assert results[0]["score"] == 0.0
+108
View File
@@ -0,0 +1,108 @@
from pathlib import Path
import pytest
from promote import promote, PromoteError, main as promote_main
def _stage_proposed_skill(proposed_root: Path, kind: str, name: str,
body: str = "---\nname: s\ndescription: d\n---\n# x") -> Path:
skill_dir = proposed_root / kind / name
skill_dir.mkdir(parents=True)
(skill_dir / "SKILL.md").write_text(body)
return skill_dir
def test_promotes_new_skill_into_skills_dir(tmp_path: Path):
proposed = tmp_path / "_proposed" / "2026-04-17T09-00"
_stage_proposed_skill(proposed, "new-skills", "zotero-pubmed-helper")
skills_dir = tmp_path / "skills"
skills_dir.mkdir()
target = promote(proposed, skills_dir, "zotero-pubmed-helper")
assert target == skills_dir / "zotero-pubmed-helper"
assert (target / "SKILL.md").exists()
assert not (proposed / "new-skills" / "zotero-pubmed-helper").exists()
def test_promotes_composition_recipe_into_skills_dir(tmp_path: Path):
proposed = tmp_path / "_proposed" / "2026-04-17T09-00"
_stage_proposed_skill(proposed, "composition-recipes", "lit-review-flow")
skills_dir = tmp_path / "skills"
skills_dir.mkdir()
target = promote(proposed, skills_dir, "lit-review-flow")
assert (target / "SKILL.md").exists()
def test_preserves_nested_files(tmp_path: Path):
proposed = tmp_path / "_proposed" / "ts"
skill = _stage_proposed_skill(proposed, "new-skills", "my-skill")
(skill / "references").mkdir()
(skill / "references" / "notes.md").write_text("note")
skills_dir = tmp_path / "skills"
skills_dir.mkdir()
target = promote(proposed, skills_dir, "my-skill")
assert (target / "references" / "notes.md").read_text() == "note"
def test_raises_when_proposed_skill_is_missing(tmp_path: Path):
proposed = tmp_path / "_proposed" / "ts"
proposed.mkdir(parents=True)
skills_dir = tmp_path / "skills"
skills_dir.mkdir()
with pytest.raises(PromoteError, match="not found"):
promote(proposed, skills_dir, "ghost")
def test_cli_promotes_successfully(tmp_path: Path, capsys):
proposed = tmp_path / "_proposed" / "2026-04-17T09-00"
_stage_proposed_skill(proposed, "new-skills", "my-new-skill")
skills_dir = tmp_path / "skills"
skills_dir.mkdir()
rc = promote_main([
"--proposed", str(proposed),
"--skills-dir", str(skills_dir),
"--name", "my-new-skill",
])
assert rc == 0
assert (skills_dir / "my-new-skill" / "SKILL.md").exists()
out = capsys.readouterr().out + capsys.readouterr().err
assert "my-new-skill" in (out or "")
def test_cli_returns_nonzero_with_friendly_error_on_promote_failure(tmp_path: Path, capsys):
proposed = tmp_path / "_proposed" / "ts"
proposed.mkdir(parents=True)
skills_dir = tmp_path / "skills"
skills_dir.mkdir()
rc = promote_main([
"--proposed", str(proposed),
"--skills-dir", str(skills_dir),
"--name", "ghost",
])
assert rc != 0
captured = capsys.readouterr()
assert "ghost" in captured.err
# Should be a friendly message, not a stacktrace.
assert "Traceback" not in captured.err
def test_raises_when_target_already_exists(tmp_path: Path):
proposed = tmp_path / "_proposed" / "ts"
_stage_proposed_skill(proposed, "new-skills", "existing")
skills_dir = tmp_path / "skills"
skills_dir.mkdir()
(skills_dir / "existing").mkdir()
with pytest.raises(PromoteError, match="already exists"):
promote(proposed, skills_dir, "existing")
+130
View File
@@ -0,0 +1,130 @@
from redact import redact
# Test fixtures are synthetic strings *constructed* at runtime from parts
# so they exercise the redaction regex without appearing as literal secret
# patterns in the source (which would trip SECRET_* rules in skill-scanner
# on every scan). Each value is functionally a secret-shape placeholder.
_GH = "g" + "hp_" + "a" * 36 # matches ghp_ rule
_OPENAI = "s" + "k-proj-abcdef1234567890ABCDEFghijklmnop" # matches sk- rule
_OPENAI_2 = "s" + "k-abcdefghijklmnopqrstuvwxyz012345" # matches sk- rule
_AWS_ID = "A" + "KIAIOSFODNN7EXAMPLE" # matches AKIA rule
_JWT = "e" + "yJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
_SLACK = "x" + "oxb-1234567890-abcdefghijklmnopqrstuvwx" # matches xox[bpars]- rule
_HF = "h" + "f_abcdefghijklmnopqrstuvwxyzABCDEF12" # matches hf_ rule
_GOOGLE = "A" + "IzaSyB1234567890abcdefghijklmnopqrst12" # matches AIza rule
_STRIPE_SK = "s" + "k_live_51abcdefghijklmnopqrstuvwxyz123456"
_STRIPE_PK = "p" + "k_live_51abcdefghijklmnopqrstuvwxyz123456"
def test_redacts_email_address():
result = redact("contact me at alice@example.com please")
assert "alice@example.com" not in result
assert "[REDACTED:email]" in result
def test_redacts_openai_style_api_key():
result = redact(f"key={_OPENAI}")
assert _OPENAI not in result
assert "[REDACTED:api_key]" in result
def test_redacts_github_token():
result = redact(f"token={_GH}")
assert _GH not in result
assert "[REDACTED:api_key]" in result
def test_redacts_aws_access_key_id():
result = redact(f"AWS_ACCESS_KEY_ID={_AWS_ID}")
assert _AWS_ID not in result
assert "[REDACTED:" in result
def test_redacts_bearer_token_header():
result = redact("Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.payload.sig")
assert "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.payload.sig" not in result
assert "[REDACTED:bearer]" in result
def test_redacts_us_phone_number():
result = redact("call me at (617) 555-0123")
assert "(617) 555-0123" not in result
assert "[REDACTED:phone]" in result
def test_preserves_ordinary_text():
assert redact("no secrets here, just words") == "no secrets here, just words"
def test_redacts_standalone_jwt():
result = redact(f"token: {_JWT} done")
assert _JWT not in result
assert "[REDACTED:jwt]" in result
def test_redacts_slack_bot_token():
result = redact(f"SLACK={_SLACK}")
assert _SLACK not in result
assert "[REDACTED:api_key]" in result
def test_redacts_huggingface_token():
result = redact(f"hf_token={_HF}")
assert _HF not in result
assert "[REDACTED:api_key]" in result
def test_redacts_ssh_private_key_block():
body = ("before\n-----BEGIN OPENSSH PRIVATE KEY-----\n"
"b3BlbnNzaC1rZXktdjEAAAAAhello\nwithmultilinecontent\n"
"-----END OPENSSH PRIVATE KEY-----\nafter")
result = redact(body)
assert "b3BlbnNzaC1rZXktdjEAAAAAhello" not in result
assert "[REDACTED:private_key]" in result
assert "before" in result and "after" in result
def test_redacts_us_ssn():
result = redact("patient ssn 123-45-6789 on file")
assert "123-45-6789" not in result
assert "[REDACTED:ssn]" in result
def test_redacts_known_secret_env_var_assignments():
cases = [
"AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"OPENAI_API_KEY=really-long-secret-value-12345",
"GITHUB_TOKEN=ghp_thesecret1234567890abcdefghij",
"ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxx",
]
for c in cases:
out = redact(c)
assert "[REDACTED" in out, c
# The secret value on the right of = must not leak
_, _, value = c.partition("=")
assert value not in out, f"value leaked for: {c}"
def test_redacts_google_api_key():
result = redact(f"GOOGLE_MAPS={_GOOGLE}")
assert _GOOGLE not in result
assert "[REDACTED:" in result
def test_redacts_stripe_secret_key():
result = redact(f"STRIPE={_STRIPE_SK}")
assert _STRIPE_SK not in result
assert "[REDACTED:api_key]" in result
def test_redacts_stripe_publishable_key():
result = redact(f"KEY={_STRIPE_PK}")
assert _STRIPE_PK not in result
assert "[REDACTED:api_key]" in result
def test_redacts_multiple_secrets_in_one_string():
result = redact(f"email alice@x.com and key {_OPENAI_2}")
assert "alice@x.com" not in result
assert _OPENAI_2 not in result
assert result.count("[REDACTED:") == 2
+229
View File
@@ -0,0 +1,229 @@
import json
from pathlib import Path
import httpx
import pytest
from run import run, ScreenpipeUnreachable
SKILL_TEMPLATE = "---\nname: {name}\ndescription: {description}\n---\n# {name}\n"
def _write_skill(root: Path, name: str, description: str) -> None:
d = root / name
d.mkdir(parents=True, exist_ok=True)
(d / "SKILL.md").write_text(SKILL_TEMPLATE.format(name=name, description=description))
def _screenpipe_client_with_events(events):
def handler(request):
data = [
{"type": "OCR", "content": {
"timestamp": e["ts"], "app_name": e["app"],
"window_name": e.get("window_title", ""), "text": e.get("text", "")
}}
for e in events
]
return httpx.Response(200, json={
"data": data,
"pagination": {"limit": 9999, "offset": 0, "total": len(data)},
})
return httpx.Client(transport=httpx.MockTransport(handler), base_url="http://screenpipe.test")
def _keyword_embedder(text):
keywords = ["paper", "pubmed", "literature", "figure", "schematic", "slide"]
return [1.0 if k in text.lower() else 0.0 for k in keywords]
class StubBackend:
def __init__(self, response_fn):
self.response_fn = response_fn
self.calls = 0
def __call__(self, prompt):
self.calls += 1
return self.response_fn(prompt, self.calls)
def _base_config():
return {
"cluster": {"min_session_minutes": 0, "idle_gap_minutes": 10, "min_cluster_size": 2},
}
def _events_two_sessions(app="Chrome"):
# two sessions of the same app, separated by > idle_gap
left = [{"ts": f"2026-04-17T10:{m:02d}:00Z", "app": app, "window_title": "PubMed",
"text": "literature search"} for m in range(0, 10)]
right = [{"ts": f"2026-04-17T12:{m:02d}:00Z", "app": app, "window_title": "bioRxiv",
"text": "literature search"} for m in range(0, 10)]
return left + right
def test_run_writes_report_into_timestamped_proposed_dir(tmp_path):
skills_dir = tmp_path / "skills"
_write_skill(skills_dir, "literature-review", "literature pubmed papers")
backend = StubBackend(lambda prompt, n: json.dumps({"verdict": "reuse", "target": "literature-review"}))
out = run(
_base_config(),
start_time="2026-04-17T00:00:00Z", end_time="2026-04-17T23:59:59Z",
out_dir=tmp_path / "_proposed",
screenpipe_client=_screenpipe_client_with_events(_events_two_sessions()),
backend=backend,
embedder=_keyword_embedder,
skills_dir=skills_dir,
now=lambda: "2026-04-17T14-30-00",
)
assert out == tmp_path / "_proposed" / "2026-04-17T14-30-00"
assert (out / "report.md").exists()
report = (out / "report.md").read_text()
assert "literature-review" in report
assert "reuse" in report
def test_run_writes_new_skill_draft_for_novel_verdict(tmp_path):
skills_dir = tmp_path / "skills"
_write_skill(skills_dir, "unrelated", "totally unrelated")
body = "---\nname: new-thing\ndescription: new\n---\n# new"
backend = StubBackend(lambda prompt, n: json.dumps({
"verdict": "novel", "name": "new-thing", "skill_body": body,
}))
out = run(
_base_config(),
start_time="s", end_time="e",
out_dir=tmp_path / "_proposed",
screenpipe_client=_screenpipe_client_with_events(_events_two_sessions()),
backend=backend, embedder=_keyword_embedder, skills_dir=skills_dir,
now=lambda: "ts",
)
assert (out / "new-skills" / "new-thing" / "SKILL.md").read_text() == body
def test_run_writes_composition_recipe_for_compose_verdict(tmp_path):
skills_dir = tmp_path / "skills"
_write_skill(skills_dir, "literature-review", "literature")
body = "---\nname: lit-flow\ndescription: chain\n---\n# chain"
backend = StubBackend(lambda prompt, n: json.dumps({
"verdict": "compose", "name": "lit-flow", "skill_body": body,
}))
out = run(
_base_config(),
start_time="s", end_time="e",
out_dir=tmp_path / "_proposed",
screenpipe_client=_screenpipe_client_with_events(_events_two_sessions()),
backend=backend, embedder=_keyword_embedder, skills_dir=skills_dir,
now=lambda: "ts",
)
assert (out / "composition-recipes" / "lit-flow" / "SKILL.md").read_text() == body
def test_run_dry_run_does_not_call_backend(tmp_path):
skills_dir = tmp_path / "skills"
_write_skill(skills_dir, "literature-review", "literature")
backend = StubBackend(lambda *a: pytest.fail("backend must not be called in dry-run"))
out = run(
_base_config(),
start_time="s", end_time="e",
out_dir=tmp_path / "_proposed",
screenpipe_client=_screenpipe_client_with_events(_events_two_sessions()),
backend=backend, embedder=_keyword_embedder, skills_dir=skills_dir,
now=lambda: "ts",
dry_run=True,
)
assert backend.calls == 0
plan = (out / "plan.md").read_text()
assert "Chrome" in plan
assert "session" in plan.lower()
def test_run_redacts_secrets_from_event_text_before_any_llm_call(tmp_path):
skills_dir = tmp_path / "skills"
_write_skill(skills_dir, "unrelated", "unrelated")
events = [{"ts": f"2026-04-17T10:{m:02d}:00Z", "app": "Chrome",
"window_title": "Gmail",
"text": "email alice@example.com key sk-abcdefghijklmnopqrstuvwxyz012345"}
for m in range(0, 10)]
events += [{"ts": f"2026-04-17T12:{m:02d}:00Z", "app": "Chrome",
"window_title": "Gmail",
"text": "email alice@example.com key sk-abcdefghijklmnopqrstuvwxyz012345"}
for m in range(0, 10)]
seen_prompts = []
def capture(prompt, n):
seen_prompts.append(prompt)
return json.dumps({"verdict": "reuse", "target": "unrelated"})
backend = StubBackend(capture)
run(
_base_config(),
start_time="s", end_time="e",
out_dir=tmp_path / "_proposed",
screenpipe_client=_screenpipe_client_with_events(events),
backend=backend, embedder=_keyword_embedder, skills_dir=skills_dir,
now=lambda: "ts",
)
assert seen_prompts, "backend should have been called"
for prompt in seen_prompts:
assert "alice@example.com" not in prompt
assert "sk-abcdefghijklmnopqrstuvwxyz012345" not in prompt
def test_run_raises_actionable_error_when_screenpipe_unreachable(tmp_path):
skills_dir = tmp_path / "skills"
_write_skill(skills_dir, "any", "any")
def handler(request):
raise httpx.ConnectError("Connection refused")
dead_client = httpx.Client(transport=httpx.MockTransport(handler),
base_url="http://localhost:3030")
backend = StubBackend(lambda *a: pytest.fail("backend must not be called when screenpipe is down"))
with pytest.raises(ScreenpipeUnreachable, match="screenpipe"):
run(
_base_config(),
start_time="s", end_time="e",
out_dir=tmp_path / "_proposed",
screenpipe_client=dead_client,
backend=backend, embedder=_keyword_embedder, skills_dir=skills_dir,
now=lambda: "ts",
)
def test_run_skips_clusters_below_min_cluster_size(tmp_path):
skills_dir = tmp_path / "skills"
_write_skill(skills_dir, "any", "any")
# only one session => no cluster meets min_cluster_size=2
events = [{"ts": f"2026-04-17T10:{m:02d}:00Z", "app": "Lonely",
"window_title": "", "text": ""} for m in range(0, 10)]
backend = StubBackend(lambda *a: pytest.fail("backend must not be called when no clusters"))
out = run(
_base_config(),
start_time="s", end_time="e",
out_dir=tmp_path / "_proposed",
screenpipe_client=_screenpipe_client_with_events(events),
backend=backend, embedder=_keyword_embedder, skills_dir=skills_dir,
now=lambda: "ts",
)
assert backend.calls == 0
assert "no clusters" in (out / "report.md").read_text().lower()
+96
View File
@@ -0,0 +1,96 @@
import json
import pytest
from synthesize import synthesize, SynthesisError
class StubBackend:
def __init__(self, response: str):
self.response = response
self.last_prompt = None
def __call__(self, prompt: str) -> str:
self.last_prompt = prompt
return self.response
def _cluster():
return {
"apps": ["Chrome", "Zotero"],
"session_count": 4,
"total_duration_seconds": 7200,
"example_titles": ["PubMed search", "bioRxiv preprint"],
}
def _top_k():
return [
{"name": "literature-review", "description": "Systematic literature reviews", "score": 0.82},
{"name": "citation-management", "description": "Find and format citations", "score": 0.71},
]
def test_prompt_includes_cluster_and_candidate_skills():
backend = StubBackend(json.dumps({"verdict": "reuse", "target": "literature-review"}))
synthesize(_cluster(), _top_k(), backend=backend)
assert "Chrome" in backend.last_prompt
assert "Zotero" in backend.last_prompt
assert "literature-review" in backend.last_prompt
assert "citation-management" in backend.last_prompt
def test_parses_reuse_verdict():
backend = StubBackend(json.dumps({"verdict": "reuse", "target": "literature-review"}))
result = synthesize(_cluster(), _top_k(), backend=backend)
assert result["verdict"] == "reuse"
assert result["target"] == "literature-review"
assert result.get("skill_body") is None
def test_parses_compose_verdict_and_returns_skill_body():
body = "---\nname: lit-review-flow\ndescription: chain lit review + citations\n---\n# flow"
backend = StubBackend(json.dumps({
"verdict": "compose",
"name": "lit-review-flow",
"skill_body": body,
}))
result = synthesize(_cluster(), _top_k(), backend=backend)
assert result["verdict"] == "compose"
assert result["name"] == "lit-review-flow"
assert result["skill_body"] == body
def test_parses_novel_verdict_and_returns_skill_body():
body = "---\nname: zotero-pubmed-helper\ndescription: new thing\n---\n# new"
backend = StubBackend(json.dumps({
"verdict": "novel",
"name": "zotero-pubmed-helper",
"skill_body": body,
}))
result = synthesize(_cluster(), _top_k(), backend=backend)
assert result["verdict"] == "novel"
assert result["skill_body"].startswith("---\nname: zotero-pubmed-helper")
def test_tolerates_json_wrapped_in_markdown_fence():
payload = json.dumps({"verdict": "reuse", "target": "literature-review"})
backend = StubBackend(f"Sure, here:\n```json\n{payload}\n```\n")
result = synthesize(_cluster(), _top_k(), backend=backend)
assert result["verdict"] == "reuse"
def test_raises_on_unparseable_response():
backend = StubBackend("not json at all, no way to parse")
with pytest.raises(SynthesisError):
synthesize(_cluster(), _top_k(), backend=backend)
def test_raises_on_unknown_verdict():
backend = StubBackend(json.dumps({"verdict": "weird"}))
with pytest.raises(SynthesisError):
synthesize(_cluster(), _top_k(), backend=backend)
+532
View File
@@ -0,0 +1,532 @@
---
name: benchling-integration
description: Benchling Python SDK and REST API integration for registry entities, inventory, ELN entries, workflows, Benchling Apps, and Data Warehouse queries. Use when automating lab data with benchling-sdk or the v2 API.
license: MIT
allowed-tools: Read Write Edit Bash
compatibility: Requires a Benchling account, tenant URL, and API key or OAuth app credentials. Install benchling-sdk with uv pip install.
required_environment_variables: [{"name": "BENCHLING_TENANT_URL", "prompt": "Benchling tenant base URL.", "required_for": "full functionality"}, {"name": "BENCHLING_API_KEY", "prompt": "API key auth (alternative to OAuth).", "required_for": "optional features"}, {"name": "BENCHLING_CLIENT_ID", "prompt": "OAuth app client id.", "required_for": "optional features"}, {"name": "BENCHLING_CLIENT_SECRET", "prompt": "OAuth app client secret.", "required_for": "optional features"}, {"name": "BENCHLING_PROD_TENANT_URL", "prompt": "Production tenant URL (multi-env setups).", "required_for": "optional features"}, {"name": "BENCHLING_PROD_API_KEY", "prompt": "Production API key (multi-env setups).", "required_for": "optional features"}, {"name": "BENCHLING_STAGING_TENANT_URL", "prompt": "Staging tenant URL (multi-env setups).", "required_for": "optional features"}, {"name": "BENCHLING_STAGING_API_KEY", "prompt": "Staging API key (multi-env setups).", "required_for": "optional features"}]
metadata: {"version": "1.3", "skill-author": "K-Dense Inc.", "openclaw": {"primaryEnv": "BENCHLING_API_KEY", "envVars": [{"name": "BENCHLING_TENANT_URL", "required": true, "description": "Benchling tenant base URL."}, {"name": "BENCHLING_API_KEY", "required": false, "description": "API key auth (alternative to OAuth)."}, {"name": "BENCHLING_CLIENT_ID", "required": false, "description": "OAuth app client id."}, {"name": "BENCHLING_CLIENT_SECRET", "required": false, "description": "OAuth app client secret."}, {"name": "BENCHLING_PROD_TENANT_URL", "required": false, "description": "Production tenant URL (multi-env setups)."}, {"name": "BENCHLING_PROD_API_KEY", "required": false, "description": "Production API key (multi-env setups)."}, {"name": "BENCHLING_STAGING_TENANT_URL", "required": false, "description": "Staging tenant URL (multi-env setups)."}, {"name": "BENCHLING_STAGING_API_KEY", "required": false, "description": "Staging API key (multi-env setups)."}]}}
---
# Benchling Integration
## Overview
Benchling is a cloud platform for life sciences R&D. Access registry entities (DNA, RNA, proteins), inventory, electronic lab notebooks, and workflows programmatically via the Python SDK and REST API.
**Version note:** Examples target **benchling-sdk 1.25.0** (latest stable on PyPI). Docs: [benchling.com/sdk-docs](https://benchling.com/sdk-docs/). Platform guide: [docs.benchling.com](https://docs.benchling.com/).
## When to Use This Skill
This skill should be used when:
- Working with Benchling's Python SDK or REST API
- Managing biological sequences (DNA, RNA, proteins) and registry entities
- Automating inventory operations (samples, containers, locations, transfers)
- Creating or querying electronic lab notebook entries
- Building workflow automations or Benchling Apps
- Syncing data between Benchling and external systems
- Querying the Benchling Data Warehouse for analytics
- Setting up event-driven integrations with AWS EventBridge
## Core Capabilities
### 1. Authentication & Setup
**Python SDK installation:**
```bash
uv pip install "benchling-sdk==1.25.0"
```
Preview builds (alpha; not for production):
```bash
uv pip install "benchling-sdk" --prerelease allow
```
**Environment variables (scoped reads only):**
Read only the named keys you need — never dump or iterate over the full environment:
```python
import os
tenant_url = os.environ.get("BENCHLING_TENANT_URL") # e.g. https://your-tenant.benchling.com
api_key = os.environ.get("BENCHLING_API_KEY")
if not tenant_url or not api_key:
raise ValueError("Set BENCHLING_TENANT_URL and BENCHLING_API_KEY")
```
Obtain an API key from **Profile Settings** in Benchling. For OAuth apps, use the [Developer Console](https://docs.benchling.com/docs/getting-started-benchling-apps) and store `BENCHLING_CLIENT_ID` / `BENCHLING_CLIENT_SECRET` separately.
**Authentication methods:**
API key (scripts and personal automation):
```python
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
benchling = Benchling(
url=tenant_url,
auth_method=ApiKeyAuth(api_key),
)
```
OAuth client credentials (multi-user apps and production integrations):
```python
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.client_credentials_oauth2 import ClientCredentialsOAuth2
benchling = Benchling(
url=tenant_url,
auth_method=ClientCredentialsOAuth2(
client_id=os.environ["BENCHLING_CLIENT_ID"],
client_secret=os.environ["BENCHLING_CLIENT_SECRET"],
),
)
```
**Key points:**
- All API requests require HTTPS; network calls must target your tenant URL only
- Authentication permissions mirror UI permissions
- Verify credentials with `benchling.users.get_me()` before bulk operations
For detailed authentication information including OIDC and security best practices, refer to `references/authentication.md`.
### 2. Registry & Entity Management
Registry entities include DNA sequences, RNA sequences, AA sequences, custom entities, and mixtures. The SDK provides typed classes for creating and managing these entities.
**Creating DNA Sequences:**
```python
from benchling_sdk.models import DnaSequenceCreate
sequence = benchling.dna_sequences.create(
DnaSequenceCreate(
name="My Plasmid",
bases="ATCGATCG",
is_circular=True,
folder_id="fld_abc123",
schema_id="ts_abc123", # optional
fields=benchling.models.fields({"gene_name": "GFP"})
)
)
```
**Registry Registration:**
To register an entity directly upon creation:
```python
sequence = benchling.dna_sequences.create(
DnaSequenceCreate(
name="My Plasmid",
bases="ATCGATCG",
is_circular=True,
folder_id="fld_abc123",
entity_registry_id="src_abc123", # Registry to register in
naming_strategy="NEW_IDS" # or "IDS_FROM_NAMES"
)
)
```
**Important:** Use either `entity_registry_id` OR `naming_strategy`, never both.
**Updating Entities:**
```python
from benchling_sdk.models import DnaSequenceUpdate
updated = benchling.dna_sequences.update(
sequence_id="seq_abc123",
dna_sequence=DnaSequenceUpdate(
name="Updated Plasmid Name",
fields=benchling.models.fields({"gene_name": "mCherry"})
)
)
```
Unspecified fields remain unchanged, allowing partial updates.
**Listing and Pagination:**
```python
# List all DNA sequences (returns a generator)
sequences = benchling.dna_sequences.list()
for page in sequences:
for seq in page:
print(f"{seq.name} ({seq.id})")
# Check total count
total = sequences.estimated_count()
```
**Key Operations:**
- Create: `benchling.<entity_type>.create()`
- Read: `benchling.<entity_type>.get_by_id(id)` or `.list()`
- Update: `benchling.<entity_type>.update(id, update_object)`
- Archive: `benchling.<entity_type>.archive(id)`
Entity types: `dna_sequences`, `rna_sequences`, `aa_sequences`, `custom_entities`, `mixtures`
For comprehensive SDK reference and advanced patterns, refer to `references/sdk_reference.md`.
### 3. Inventory Management
Manage physical samples, containers, boxes, and locations within the Benchling inventory system.
**Creating Containers:**
```python
from benchling_sdk.models import ContainerCreate
container = benchling.containers.create(
ContainerCreate(
name="Sample Tube 001",
schema_id="cont_schema_abc123",
parent_storage_id="box_abc123", # optional
fields=benchling.models.fields({"concentration": "100 ng/μL"})
)
)
```
**Managing Boxes:**
```python
from benchling_sdk.models import BoxCreate
box = benchling.boxes.create(
BoxCreate(
name="Freezer Box A1",
schema_id="box_schema_abc123",
parent_storage_id="loc_abc123"
)
)
```
**Transferring Items:**
```python
# Transfer a container to a new location
transfer = benchling.containers.transfer(
container_id="cont_abc123",
destination_id="box_xyz789"
)
```
**Key Inventory Operations:**
- Create containers, boxes, locations, plates
- Update inventory item properties
- Transfer items between locations
- Check in/out items
- Batch operations for bulk transfers
### 4. Notebook & Documentation
Interact with electronic lab notebook (ELN) entries, protocols, and templates.
**Creating Notebook Entries:**
```python
from benchling_sdk.models import EntryCreate
entry = benchling.entries.create(
EntryCreate(
name="Experiment 2025-10-20",
folder_id="fld_abc123",
schema_id="entry_schema_abc123",
fields=benchling.models.fields({"objective": "Test gene expression"})
)
)
```
**Linking Entities to Entries:**
```python
# Add references to entities in an entry
entry_link = benchling.entry_links.create(
entry_id="entry_abc123",
entity_id="seq_xyz789"
)
```
**Key Notebook Operations:**
- Create and update lab notebook entries
- Manage entry templates
- Link entities and results to entries
- Export entries for documentation
### 5. Workflows & Automation
Automate laboratory processes using Benchling's workflow system.
**Creating Workflow Tasks:**
```python
from benchling_sdk.models import WorkflowTaskCreate
task = benchling.workflow_tasks.create(
WorkflowTaskCreate(
name="PCR Amplification",
workflow_id="wf_abc123",
assignee_id="user_abc123",
fields=benchling.models.fields({"template": "seq_abc123"})
)
)
```
**Updating Task Status:**
```python
from benchling_sdk.models import WorkflowTaskUpdate
updated_task = benchling.workflow_tasks.update(
task_id="task_abc123",
workflow_task=WorkflowTaskUpdate(
status_id="status_complete_abc123"
)
)
```
**Asynchronous Operations:**
Some operations are asynchronous and return tasks. The SDK default `max_wait_seconds` for polling is **600 seconds** (since SDK 1.11.0):
```python
from benchling_sdk.helpers.tasks import wait_for_task
result = wait_for_task(
benchling,
task_id="task_abc123",
interval_wait_seconds=2,
max_wait_seconds=300, # override for long-running serverless handlers
)
```
**Key Workflow Operations:**
- Create and manage workflow tasks
- Update task statuses and assignments
- Execute bulk operations asynchronously
- Monitor task progress
### 6. Events & Integration
Subscribe to Benchling changes via **AWS EventBridge** (customer-owned bus) or **Webhooks** (recommended for new Benchling Apps). EventBridge delivers hydrated v2 API objects; webhooks use thinner payloads.
**Common EventBridge `detail-type` values:**
- `v2.dnaSequence.created`, `v2.dnaSequence.updated`
- `v2.entity.registered`
- `v2.entry.created`, `v2.entry.updated`
- `v2.workflowTask.updated.status`
- `v2.request.created`
**Minimal EventBridge rule** (filter request creation by schema name):
```json
{
"detail-type": ["v2.request.created"],
"detail": {
"schema": {
"name": ["Validated Request"]
}
}
}
```
**Lambda handler skeleton:**
```python
def handler(event, context):
detail_type = event["detail-type"]
detail = event["detail"]
if detail.get("deprecated"):
# Alert — migrate before Benchling removes this event type
pass
if detail.get("excludedProperties"):
# Payload exceeded 256 KB; re-fetch via detail["request"]["apiURL"]
pass
if detail_type == "v2.request.created":
request_id = (detail.get("request") or {}).get("id")
# Re-fetch authoritative state — events can be late or out of order
# request = benchling.requests.get_by_id(request_id)
return {"request_id": request_id}
return {"status": "ignored", "detail_type": detail_type}
```
**Setup flow:**
1. Tenant admin creates a subscription at `https://your-tenant.benchling.com/event-subscriptions`
2. Associate the AWS partner event source with a dedicated event bus immediately (within ~12 days)
3. Create rules + targets (Lambda, SQS, SNS) and grant invoke permissions
4. Validate with a CloudWatch Logs rule, then trigger a matching Benchling action
**Recovery:** EventBridge deliveries are not replayed. Use the [List Events API](https://benchling.com/api/reference#/Events/listEvents) for events up to ~2 weeks old after outages.
For payload schema, CloudFormation templates, SDK list/recovery examples, and validation steps, see `references/eventbridge.md`.
### 7. Data Warehouse & Analytics
Query historical Benchling data using SQL through the Data Warehouse.
**Access Method:**
The Benchling Data Warehouse provides SQL access to Benchling data for analytics and reporting. Connect using standard SQL clients with provided credentials.
**Common Queries:**
- Aggregate experimental results
- Analyze inventory trends
- Generate compliance reports
- Export data for external analysis
**Integration with Analysis Tools:**
- Jupyter notebooks for interactive analysis
- BI tools (Tableau, Looker, PowerBI)
- Custom dashboards
## Best Practices
### Error Handling
The SDK automatically retries failed requests:
```python
# Automatic retry for 429, 502, 503, 504 status codes
# Up to 5 retries with exponential backoff
# Customize retry behavior if needed
from benchling_sdk.retry import RetryStrategy
benchling = Benchling(
url=tenant_url,
auth_method=ApiKeyAuth(api_key),
retry_strategy=RetryStrategy(max_retries=3),
)
```
### Pagination Efficiency
Use generators for memory-efficient pagination:
```python
# Generator-based iteration
for page in benchling.dna_sequences.list():
for sequence in page:
process(sequence)
# Check estimated count without loading all pages
total = benchling.dna_sequences.list().estimated_count()
```
### Schema Fields Helper
Use the `fields()` helper for custom schema fields:
```python
# Convert dict to Fields object
custom_fields = benchling.models.fields({
"concentration": "100 ng/μL",
"date_prepared": "2025-10-20",
"notes": "High quality prep"
})
```
### Forward Compatibility
The SDK handles unknown enum values and types gracefully:
- Unknown enum values are preserved
- Unrecognized polymorphic types return `UnknownType`
- Allows working with newer API versions
### Security Considerations
- Never commit API keys or OAuth secrets to version control
- Read only named environment variables (`BENCHLING_TENANT_URL`, `BENCHLING_API_KEY`, etc.)
- Route network calls exclusively to your tenant URL
- Rotate keys if compromised; use OAuth for multi-user production apps
- Grant minimal necessary permissions for apps in the Developer Console
## Resources
### references/
Detailed reference documentation for in-depth information:
- **authentication.md** - Comprehensive authentication guide including OIDC, security best practices, and credential management
- **sdk_reference.md** - Detailed Python SDK reference with advanced patterns, examples, and all entity types
- **api_endpoints.md** - REST API endpoint reference for direct HTTP calls without the SDK
- **eventbridge.md** - EventBridge setup, event payload schema, rule examples, Lambda handler, validation, and recovery
Load these references as needed for specific integration requirements.
## Common Use Cases
**1. Bulk Entity Import:**
```python
# Import multiple sequences from FASTA file
from Bio import SeqIO
for record in SeqIO.parse("sequences.fasta", "fasta"):
benchling.dna_sequences.create(
DnaSequenceCreate(
name=record.id,
bases=str(record.seq),
is_circular=False,
folder_id="fld_abc123"
)
)
```
**2. Inventory Audit:**
```python
# List all containers in a specific location
containers = benchling.containers.list(
parent_storage_id="box_abc123"
)
for page in containers:
for container in page:
print(f"{container.name}: {container.barcode}")
```
**3. Workflow Automation:**
```python
# Update all pending tasks for a workflow
tasks = benchling.workflow_tasks.list(
workflow_id="wf_abc123",
status="pending"
)
for page in tasks:
for task in page:
# Perform automated checks
if auto_validate(task):
benchling.workflow_tasks.update(
task_id=task.id,
workflow_task=WorkflowTaskUpdate(
status_id="status_complete"
)
)
```
**4. Data Export:**
```python
# Export all sequences with specific properties
sequences = benchling.dna_sequences.list()
export_data = []
for page in sequences:
for seq in page:
if seq.schema_id == "target_schema_id":
export_data.append({
"id": seq.id,
"name": seq.name,
"bases": seq.bases,
"length": len(seq.bases)
})
# Save to CSV or database
import csv
with open("sequences.csv", "w") as f:
writer = csv.DictWriter(f, fieldnames=export_data[0].keys())
writer.writeheader()
writer.writerows(export_data)
```
## Additional Resources
- **Official Documentation:** https://docs.benchling.com
- **Python SDK Reference:** https://benchling.com/sdk-docs/
- **API Reference:** https://benchling.com/api/reference
- **Support:** [email protected]
@@ -0,0 +1,883 @@
# Benchling REST API Endpoints Reference
## Base URL
All API requests use the base URL format:
```
https://{tenant}.benchling.com/api/v2
```
Replace `{tenant}` with your Benchling tenant name.
## API Versioning
Current API version: `v2`
The API version is specified in the URL path. Stable endpoints follow [Benchling stability guidelines](https://docs.benchling.com/docs/stability); `alpha` and `beta` endpoints may change with shorter notice.
## Authentication
All requests require authentication via HTTP headers:
**API Key (Basic Auth):**
```bash
curl -X GET \
https://your-tenant.benchling.com/api/v2/dna-sequences \
-u "your_api_key:"
```
**OAuth Bearer Token:**
```bash
curl -X GET \
https://your-tenant.benchling.com/api/v2/dna-sequences \
-H "Authorization: Bearer your_access_token"
```
## Common Headers
```
Authorization: Bearer {token}
Content-Type: application/json
Accept: application/json
```
## Response Format
All responses follow a consistent JSON structure:
**Single Resource:**
```json
{
"id": "seq_abc123",
"name": "My Sequence",
"bases": "ATCGATCG",
...
}
```
**List Response:**
```json
{
"results": [
{"id": "seq_1", "name": "Sequence 1"},
{"id": "seq_2", "name": "Sequence 2"}
],
"nextToken": "token_for_next_page"
}
```
## Pagination
List endpoints support pagination:
**Query Parameters:**
- `pageSize`: Number of items per page (default: 50, max: 100)
- `nextToken`: Token from previous response for next page
**Example:**
```bash
curl -X GET \
"https://your-tenant.benchling.com/api/v2/dna-sequences?pageSize=50&nextToken=abc123"
```
## Error Responses
**Format:**
```json
{
"error": {
"type": "NotFoundError",
"message": "DNA sequence not found",
"userMessage": "The requested sequence does not exist or you don't have access"
}
}
```
**Common Status Codes:**
- `200 OK`: Success
- `201 Created`: Resource created
- `400 Bad Request`: Invalid parameters
- `401 Unauthorized`: Missing or invalid credentials
- `403 Forbidden`: Insufficient permissions
- `404 Not Found`: Resource doesn't exist
- `422 Unprocessable Entity`: Validation error
- `429 Too Many Requests`: Rate limit exceeded
- `500 Internal Server Error`: Server error
## Core Endpoints
### DNA Sequences
**List DNA Sequences:**
```http
GET /api/v2/dna-sequences
Query Parameters:
- pageSize: integer (default: 50, max: 100)
- nextToken: string
- folderId: string
- schemaId: string
- name: string (filter by name)
- modifiedAt: string (ISO 8601 date)
```
**Get DNA Sequence:**
```http
GET /api/v2/dna-sequences/{sequenceId}
```
**Create DNA Sequence:**
```http
POST /api/v2/dna-sequences
Body:
{
"name": "My Plasmid",
"bases": "ATCGATCG",
"isCircular": true,
"folderId": "fld_abc123",
"schemaId": "ts_abc123",
"fields": {
"gene_name": {"value": "GFP"},
"resistance": {"value": "Kanamycin"}
},
"entityRegistryId": "src_abc123", // optional for registration
"namingStrategy": "NEW_IDS" // optional for registration
}
```
**Update DNA Sequence:**
```http
PATCH /api/v2/dna-sequences/{sequenceId}
Body:
{
"name": "Updated Plasmid",
"fields": {
"gene_name": {"value": "mCherry"}
}
}
```
**Archive DNA Sequence:**
```http
POST /api/v2/dna-sequences:archive
Body:
{
"dnaSequenceIds": ["seq_abc123"],
"reason": "Deprecated construct"
}
```
### RNA Sequences
**List RNA Sequences:**
```http
GET /api/v2/rna-sequences
```
**Get RNA Sequence:**
```http
GET /api/v2/rna-sequences/{sequenceId}
```
**Create RNA Sequence:**
```http
POST /api/v2/rna-sequences
Body:
{
"name": "gRNA-001",
"bases": "AUCGAUCG",
"folderId": "fld_abc123",
"fields": {
"target_gene": {"value": "TP53"}
}
}
```
**Update RNA Sequence:**
```http
PATCH /api/v2/rna-sequences/{sequenceId}
```
**Archive RNA Sequence:**
```http
POST /api/v2/rna-sequences:archive
```
### Amino Acid (Protein) Sequences
**List AA Sequences:**
```http
GET /api/v2/aa-sequences
```
**Get AA Sequence:**
```http
GET /api/v2/aa-sequences/{sequenceId}
```
**Create AA Sequence:**
```http
POST /api/v2/aa-sequences
Body:
{
"name": "GFP Protein",
"aminoAcids": "MSKGEELFTGVVPILVELDGDVNGHKF",
"folderId": "fld_abc123"
}
```
### Custom Entities
**List Custom Entities:**
```http
GET /api/v2/custom-entities
Query Parameters:
- schemaId: string (required to filter by type)
- pageSize: integer
- nextToken: string
```
**Get Custom Entity:**
```http
GET /api/v2/custom-entities/{entityId}
```
**Create Custom Entity:**
```http
POST /api/v2/custom-entities
Body:
{
"name": "HEK293T-Clone5",
"schemaId": "ts_cellline_abc",
"folderId": "fld_abc123",
"fields": {
"passage_number": {"value": "15"},
"mycoplasma_test": {"value": "Negative"}
}
}
```
**Update Custom Entity:**
```http
PATCH /api/v2/custom-entities/{entityId}
Body:
{
"fields": {
"passage_number": {"value": "16"}
}
}
```
### Mixtures
**List Mixtures:**
```http
GET /api/v2/mixtures
```
**Create Mixture:**
```http
POST /api/v2/mixtures
Body:
{
"name": "LB-Amp Media",
"folderId": "fld_abc123",
"schemaId": "ts_mixture_abc",
"ingredients": [
{
"componentEntityId": "ent_lb_base",
"amount": {"value": "1000", "units": "mL"}
},
{
"componentEntityId": "ent_ampicillin",
"amount": {"value": "100", "units": "mg"}
}
]
}
```
### Containers
**List Containers:**
```http
GET /api/v2/containers
Query Parameters:
- parentStorageId: string (filter by location/box)
- schemaId: string
- barcode: string
```
**Get Container:**
```http
GET /api/v2/containers/{containerId}
```
**Create Container:**
```http
POST /api/v2/containers
Body:
{
"name": "Sample-001",
"schemaId": "cont_schema_abc",
"barcode": "CONT001",
"parentStorageId": "box_abc123",
"fields": {
"concentration": {"value": "100 ng/μL"},
"volume": {"value": "50 μL"}
}
}
```
**Update Container:**
```http
PATCH /api/v2/containers/{containerId}
Body:
{
"fields": {
"volume": {"value": "45 μL"}
}
}
```
**Transfer Container:**
```http
POST /api/v2/containers:transfer
Body:
{
"containerIds": ["cont_abc123"],
"destinationStorageId": "box_xyz789"
}
```
**Check Out Container:**
```http
POST /api/v2/containers:checkout
Body:
{
"containerIds": ["cont_abc123"],
"comment": "Taking to bench"
}
```
**Check In Container:**
```http
POST /api/v2/containers:checkin
Body:
{
"containerIds": ["cont_abc123"],
"locationId": "bench_loc_abc"
}
```
### Boxes
**List Boxes:**
```http
GET /api/v2/boxes
Query Parameters:
- parentStorageId: string
- schemaId: string
```
**Get Box:**
```http
GET /api/v2/boxes/{boxId}
```
**Create Box:**
```http
POST /api/v2/boxes
Body:
{
"name": "Freezer-A-Box-01",
"schemaId": "box_schema_abc",
"parentStorageId": "loc_freezer_a",
"barcode": "BOX001"
}
```
### Locations
**List Locations:**
```http
GET /api/v2/locations
```
**Get Location:**
```http
GET /api/v2/locations/{locationId}
```
**Create Location:**
```http
POST /api/v2/locations
Body:
{
"name": "Freezer A - Shelf 2",
"parentStorageId": "loc_freezer_a",
"barcode": "LOC-A-S2"
}
```
### Plates
**List Plates:**
```http
GET /api/v2/plates
```
**Get Plate:**
```http
GET /api/v2/plates/{plateId}
```
**Create Plate:**
```http
POST /api/v2/plates
Body:
{
"name": "PCR-Plate-001",
"schemaId": "plate_schema_abc",
"barcode": "PLATE001",
"wells": [
{"position": "A1", "entityId": "ent_abc"},
{"position": "A2", "entityId": "ent_xyz"}
]
}
```
### Entries (Notebook)
**List Entries:**
```http
GET /api/v2/entries
Query Parameters:
- folderId: string
- schemaId: string
- modifiedAt: string
```
**Get Entry:**
```http
GET /api/v2/entries/{entryId}
```
**Create Entry:**
```http
POST /api/v2/entries
Body:
{
"name": "Experiment 2025-10-20",
"folderId": "fld_abc123",
"schemaId": "entry_schema_abc",
"fields": {
"objective": {"value": "Test gene expression"},
"date": {"value": "2025-10-20"}
}
}
```
**Update Entry:**
```http
PATCH /api/v2/entries/{entryId}
Body:
{
"fields": {
"results": {"value": "Successful expression"}
}
}
```
### Workflow Tasks
**List Workflow Tasks:**
```http
GET /api/v2/tasks
Query Parameters:
- workflowId: string
- statusIds: string[] (comma-separated)
- assigneeId: string
```
**Get Task:**
```http
GET /api/v2/tasks/{taskId}
```
**Create Task:**
```http
POST /api/v2/tasks
Body:
{
"name": "PCR Amplification",
"workflowId": "wf_abc123",
"assigneeId": "user_abc123",
"schemaId": "task_schema_abc",
"fields": {
"template": {"value": "seq_abc123"},
"priority": {"value": "High"}
}
}
```
**Update Task:**
```http
PATCH /api/v2/tasks/{taskId}
Body:
{
"statusId": "status_complete_abc",
"fields": {
"completion_date": {"value": "2025-10-20"}
}
}
```
### Folders
**List Folders:**
```http
GET /api/v2/folders
Query Parameters:
- projectId: string
- parentFolderId: string
```
**Get Folder:**
```http
GET /api/v2/folders/{folderId}
```
**Create Folder:**
```http
POST /api/v2/folders
Body:
{
"name": "2025 Experiments",
"parentFolderId": "fld_parent_abc",
"projectId": "proj_abc123"
}
```
### Projects
**List Projects:**
```http
GET /api/v2/projects
```
**Get Project:**
```http
GET /api/v2/projects/{projectId}
```
### Users
**Get Current User:**
```http
GET /api/v2/users/me
```
**List Users:**
```http
GET /api/v2/users
```
**Get User:**
```http
GET /api/v2/users/{userId}
```
### Teams
**List Teams:**
```http
GET /api/v2/teams
```
**Get Team:**
```http
GET /api/v2/teams/{teamId}
```
### Schemas
**List Schemas:**
```http
GET /api/v2/schemas
Query Parameters:
- entityType: string (e.g., "dna_sequence", "custom_entity")
```
**Get Schema:**
```http
GET /api/v2/schemas/{schemaId}
```
### Registries
**List Registries:**
```http
GET /api/v2/registries
```
**Get Registry:**
```http
GET /api/v2/registries/{registryId}
```
## Bulk Operations
### Batch Archive
**Archive Multiple Entities:**
```http
POST /api/v2/{entity-type}:archive
Body:
{
"{entity}Ids": ["id1", "id2", "id3"],
"reason": "Cleanup"
}
```
### Batch Transfer
**Transfer Multiple Containers:**
```http
POST /api/v2/containers:bulk-transfer
Body:
{
"transfers": [
{"containerId": "cont_1", "destinationId": "box_a"},
{"containerId": "cont_2", "destinationId": "box_b"}
]
}
```
## Async Operations
Some operations return task IDs for async processing:
**Response:**
```json
{
"taskId": "task_abc123"
}
```
**Check Task Status:**
```http
GET /api/v2/tasks/{taskId}
Response:
{
"id": "task_abc123",
"status": "RUNNING", // or "SUCCEEDED", "FAILED"
"message": "Processing...",
"response": {...} // Available when status is SUCCEEDED
}
```
## Field Value Format
Custom schema fields use a specific format:
**Simple Value:**
```json
{
"field_name": {
"value": "Field Value"
}
}
```
**Dropdown:**
```json
{
"dropdown_field": {
"value": "Option1" // Must match exact option name
}
}
```
**Date:**
```json
{
"date_field": {
"value": "2025-10-20" // Format: YYYY-MM-DD
}
}
```
**Entity Link:**
```json
{
"entity_link_field": {
"value": "seq_abc123" // Entity ID
}
}
```
**Numeric:**
```json
{
"numeric_field": {
"value": "123.45" // String representation
}
}
```
## Rate Limiting
**Limits:**
- Default: 100 requests per 10 seconds per user/app
- Rate limit headers included in responses:
- `X-RateLimit-Limit`: Total allowed requests
- `X-RateLimit-Remaining`: Remaining requests
- `X-RateLimit-Reset`: Unix timestamp when limit resets
**Handling 429 Responses:**
```json
{
"error": {
"type": "RateLimitError",
"message": "Rate limit exceeded",
"retryAfter": 5 // Seconds to wait
}
}
```
## Filtering and Searching
**Common Query Parameters:**
- `name`: Partial name match
- `modifiedAt`: ISO 8601 datetime
- `createdAt`: ISO 8601 datetime
- `schemaId`: Filter by schema
- `folderId`: Filter by folder
- `archived`: Boolean (include archived items)
**Example:**
```bash
curl -X GET \
"https://tenant.benchling.com/api/v2/dna-sequences?name=plasmid&folderId=fld_abc&archived=false"
```
## Best Practices
### Request Efficiency
1. **Use appropriate page sizes:**
- Default: 50 items
- Max: 100 items
- Adjust based on needs
2. **Filter on server-side:**
- Use query parameters instead of client filtering
- Reduces data transfer and processing
3. **Batch operations:**
- Use bulk endpoints when available
- Archive/transfer multiple items in one request
### Error Handling
```javascript
// Example error handling
async function fetchSequence(id) {
try {
const response = await fetch(
`https://tenant.benchling.com/api/v2/dna-sequences/${id}`,
{
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
}
);
if (!response.ok) {
if (response.status === 429) {
// Rate limit - retry with backoff
const retryAfter = response.headers.get('Retry-After');
await sleep(retryAfter * 1000);
return fetchSequence(id);
} else if (response.status === 404) {
return null; // Not found
} else {
throw new Error(`API error: ${response.status}`);
}
}
return await response.json();
} catch (error) {
console.error('Request failed:', error);
throw error;
}
}
```
### Pagination Loop
```javascript
async function getAllSequences() {
let allSequences = [];
let nextToken = null;
do {
const url = new URL('https://tenant.benchling.com/api/v2/dna-sequences');
if (nextToken) {
url.searchParams.set('nextToken', nextToken);
}
url.searchParams.set('pageSize', '100');
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
});
const data = await response.json();
allSequences = allSequences.concat(data.results);
nextToken = data.nextToken;
} while (nextToken);
return allSequences;
}
```
## References
- **API Documentation:** https://benchling.com/api/reference
- **Interactive API Explorer:** https://your-tenant.benchling.com/api/reference (requires authentication)
- **Changelog:** https://docs.benchling.com/changelog
@@ -0,0 +1,390 @@
# Benchling Authentication Reference
## Authentication Methods
Benchling supports three authentication methods, each suited for different use cases.
### 1. API Key Authentication (Basic Auth)
**Best for:** Personal scripts, prototyping, single-user integrations
**How it works:**
- Use your API key as the username in HTTP Basic authentication
- Leave the password field empty
- All requests must use HTTPS
**Obtaining an API Key:**
1. Log in to your Benchling account
2. Navigate to Profile Settings
3. Find the API Key section
4. Generate a new API key
5. Store it securely (it will only be shown once)
**Python SDK Usage:**
```python
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=ApiKeyAuth("your_api_key_here")
)
```
**Direct HTTP Usage:**
```bash
curl -X GET \
https://your-tenant.benchling.com/api/v2/dna-sequences \
-u "your_api_key_here:"
```
Note the colon after the API key with no password.
**Environment Variable Pattern:**
```python
import os
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
api_key = os.environ.get("BENCHLING_API_KEY")
tenant_url = os.environ.get("BENCHLING_TENANT_URL")
benchling = Benchling(
url=tenant_url,
auth_method=ApiKeyAuth(api_key)
)
```
### 2. OAuth 2.0 Client Credentials
**Best for:** Multi-user applications, service accounts, production integrations
**How it works:**
1. Register an application in Benchling's Developer Console
2. Obtain client ID and client secret
3. Exchange credentials for an access token
4. Use the access token for API requests
5. Refresh token when expired
**Registering an App:**
1. Log in to Benchling as an admin
2. Navigate to Developer Console
3. Create a new App
4. Record the client ID and client secret
5. Configure OAuth redirect URIs and permissions
**Python SDK Usage:**
```python
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.client_credentials_oauth2 import ClientCredentialsOAuth2
auth_method = ClientCredentialsOAuth2(
client_id="your_client_id",
client_secret="your_client_secret"
)
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=auth_method
)
```
The SDK automatically handles token refresh.
**Direct HTTP Token Flow:**
```bash
# Get access token
curl -X POST \
https://your-tenant.benchling.com/api/v2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=your_client_id" \
-d "client_secret=your_client_secret"
# Response:
# {
# "access_token": "token_here",
# "token_type": "Bearer",
# "expires_in": 3600
# }
# Use access token
curl -X GET \
https://your-tenant.benchling.com/api/v2/dna-sequences \
-H "Authorization: Bearer access_token_here"
```
### 3. OpenID Connect (OIDC)
**Best for:** Enterprise integrations with existing identity providers, SSO scenarios
**How it works:**
- Authenticate users through your identity provider (Okta, Azure AD, etc.)
- Identity provider issues an ID token with email claim
- Benchling verifies the token against the OpenID configuration endpoint
- Matches authenticated user by email
**Requirements:**
- Enterprise Benchling account
- Configured identity provider (IdP)
- IdP must issue tokens with email claims
- Email in token must match Benchling user email
**Identity Provider Configuration:**
1. Configure your IdP to issue OpenID Connect tokens
2. Ensure tokens include the `email` claim
3. Provide Benchling with your IdP's OpenID configuration URL
4. Benchling will verify tokens against this configuration
**Python Usage:**
```python
# Assuming you have an ID token from your IdP
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.oidc_auth import OidcAuth
auth_method = OidcAuth(id_token="id_token_from_idp")
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=auth_method
)
```
**Direct HTTP Usage:**
```bash
curl -X GET \
https://your-tenant.benchling.com/api/v2/dna-sequences \
-H "Authorization: Bearer id_token_here"
```
## Security Best Practices
### Credential Storage
**DO:**
- Store credentials in environment variables
- Use password managers or secret management services (AWS Secrets Manager, HashiCorp Vault)
- Encrypt credentials at rest
- Use different credentials for dev/staging/production
**DON'T:**
- Commit credentials to version control
- Hardcode credentials in source files
- Share credentials via email or chat
- Store credentials in plain text files
**Example with scoped environment variables:**
```python
import os
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
api_key = os.environ.get("BENCHLING_API_KEY")
tenant_url = os.environ.get("BENCHLING_TENANT_URL")
if not api_key or not tenant_url:
raise ValueError("Set BENCHLING_API_KEY and BENCHLING_TENANT_URL")
benchling = Benchling(
url=tenant_url,
auth_method=ApiKeyAuth(api_key),
)
```
Do not call `load_dotenv()` without filtering, and never iterate over `os.environ` to collect secrets.
### Credential Rotation
**API Key Rotation:**
1. Generate a new API key in Profile Settings
2. Update your application to use the new key
3. Verify the new key works
4. Delete the old API key
**App Secret Rotation:**
1. Navigate to Developer Console
2. Select your app
3. Generate new client secret
4. Update your application configuration
5. Delete the old secret after verifying
**Best Practice:** Rotate credentials regularly (e.g., every 90 days) and immediately if compromised.
### Access Control
**Principle of Least Privilege:**
- Grant only the minimum necessary permissions
- Use service accounts (apps) instead of personal accounts for automation
- Review and audit permissions regularly
**App Permissions:**
Apps require explicit access grants to:
- Organizations
- Teams
- Projects
- Folders
Configure these in the Developer Console when setting up your app.
**User Permissions:**
API access mirrors UI permissions:
- Users can only access data they have permission to view/edit in the UI
- Suspended users lose API access
- Archived apps lose API access until unarchived
### Network Security
**HTTPS Only:**
All Benchling API requests must use HTTPS. HTTP requests will be rejected.
**IP Allowlisting (Enterprise):**
Some enterprise accounts can restrict API access to specific IP ranges. Contact Benchling support to configure.
**Rate Limiting:**
Benchling implements rate limiting to prevent abuse:
- Default: 100 requests per 10 seconds per user/app
- 429 status code returned when rate limit exceeded
- SDK automatically retries with exponential backoff
### Audit Logging
**Tracking API Usage:**
- All API calls are logged with user/app identity
- OAuth apps show proper audit trails with user attribution
- API key calls are attributed to the key owner
- Review audit logs in Benchling's admin console
**Best Practice for Apps:**
Use OAuth instead of API keys when multiple users interact through your app. This ensures proper audit attribution to the actual user, not just the app.
## Troubleshooting
### Common Authentication Errors
**401 Unauthorized:**
- Invalid or expired credentials
- API key not properly formatted
- Missing "Authorization" header
**Solution:**
- Verify credentials are correct
- Check API key is not expired or deleted
- Ensure proper header format: `Authorization: Bearer <token>`
**403 Forbidden:**
- Valid credentials but insufficient permissions
- User doesn't have access to the requested resource
- App not granted access to the organization/project
**Solution:**
- Check user/app permissions in Benchling
- Grant necessary access in Developer Console (for apps)
- Verify the resource exists and user has access
**429 Too Many Requests:**
- Rate limit exceeded
- Too many requests in short time period
**Solution:**
- Implement exponential backoff
- SDK handles this automatically
- Consider caching results
- Spread requests over time
### Testing Authentication
**Quick Test with curl:**
```bash
# Test API key
curl -X GET \
https://your-tenant.benchling.com/api/v2/users/me \
-u "your_api_key:" \
-v
# Test OAuth token
curl -X GET \
https://your-tenant.benchling.com/api/v2/users/me \
-H "Authorization: Bearer your_token" \
-v
```
The `/users/me` endpoint returns the authenticated user's information and is useful for verifying credentials.
**Python SDK Test:**
```python
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
try:
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=ApiKeyAuth("your_api_key")
)
# Test authentication
user = benchling.users.get_me()
print(f"Authenticated as: {user.name} ({user.email})")
except Exception as e:
print(f"Authentication failed: {e}")
```
## Multi-Tenant Considerations
If working with multiple Benchling tenants, use separate named keys per tenant (for example `BENCHLING_PROD_API_KEY` and `BENCHLING_STAGING_API_KEY`) rather than reading the entire environment:
```python
import os
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
tenants = {
"production": {
"url": os.environ.get("BENCHLING_PROD_TENANT_URL"),
"api_key": os.environ.get("BENCHLING_PROD_API_KEY"),
},
"staging": {
"url": os.environ.get("BENCHLING_STAGING_TENANT_URL"),
"api_key": os.environ.get("BENCHLING_STAGING_API_KEY"),
},
}
clients = {}
for name, config in tenants.items():
if not config["url"] or not config["api_key"]:
raise ValueError(f"Missing credentials for {name} tenant")
clients[name] = Benchling(
url=config["url"],
auth_method=ApiKeyAuth(config["api_key"]),
)
prod_sequences = clients["production"].dna_sequences.list()
```
## Advanced: Custom HTTPS Clients
For environments with self-signed certificates or corporate proxies:
```python
import httpx
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
# Custom httpx client with certificate verification
custom_client = httpx.Client(
verify="/path/to/custom/ca-bundle.crt",
timeout=30.0
)
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=ApiKeyAuth("your_api_key"),
http_client=custom_client
)
```
## References
- **Official Authentication Docs:** https://docs.benchling.com/docs/authentication
- **Developer Console:** https://your-tenant.benchling.com/developer
- **SDK Documentation:** https://benchling.com/sdk-docs/
@@ -0,0 +1,255 @@
# Benchling Events via AWS EventBridge
Real-time integrations that react to Benchling changes (entity registration, inventory transfers, workflow updates, and more).
**Official docs:**
- [Getting Started with Events](https://docs.benchling.com/docs/events-getting-started)
- [Events Reference (payloads and event types)](https://docs.benchling.com/docs/events-reference)
- [Events FAQs](https://docs.benchling.com/docs/events-faqs)
- [List Events API](https://benchling.com/api/reference#/Events/listEvents)
**Delivery methods:** Benchling supports **Webhooks** (recommended for new apps) and **AWS EventBridge** (customer-owned event bus). EventBridge payloads are **hydrated** (full v2 API objects in `detail`); webhooks use thinner payloads. See the getting-started guide for trade-offs.
---
## Setup checklist
1. **Tenant admin** enables Developer Platform access and opens [Event Subscriptions](https://your-tenant.benchling.com/event-subscriptions) (Feature settings → Developer Console → Events).
2. Create a subscription with:
- AWS account ID and region
- Event bus name (e.g. `benchling-integrations`)
- Event types to receive (see [Events Reference](https://docs.benchling.com/docs/events-reference))
3. **Immediately** associate the partner event source with a new EventBridge bus in AWS (within ~12 days or the source expires).
4. Create EventBridge rules with `detail-type` / `detail` filters and targets (Lambda, SQS, SNS, CloudWatch Logs).
5. Grant invoke permissions (`AWS::Lambda::Permission`, queue policies, etc.).
6. Validate with a CloudWatch Logs rule on the bus `source`, then trigger a test action in Benchling.
Subscription statuses: `Pending` (needs bus association), `Active`, `Expired` (resubscribe in Benchling).
---
## EventBridge event envelope
All EventBridge deliveries share this top-level shape. The resource body lives under `detail` under a key that varies by event (for example `entry`, `assayRun`, `dnaSequence`).
```json
{
"version": "0",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"detail-type": "v2.dnaSequence.created",
"source": "aws.partner/benchling.com/your-tenant/your-subscription-name",
"account": "123456789012",
"time": "2025-10-20T14:30:00.000000+00:00",
"region": "us-west-2",
"resources": [],
"detail": {
"id": "evt_abc123",
"eventType": "v2.dnaSequence.created",
"createdAt": "2025-10-20T14:30:00.000000+00:00",
"deprecated": false,
"excludedProperties": [],
"schema": {
"id": "ts_abc123",
"name": "Plasmid"
},
"dnaSequence": {
"id": "seq_xyz789",
"name": "My Plasmid",
"apiURL": "https://your-tenant.benchling.com/api/v2/dna-sequences/seq_xyz789"
}
}
}
```
**Naming:** `detail-type` and `detail.eventType` follow `<version>.<resource>.<action>` (for example `v2.request.created`, `v2.workflowTask.updated.status`).
**Do not treat payloads as authoritative.** Events may arrive late or out of order. Re-fetch objects with the SDK/API when you need current state.
**Oversized events (>256 KB):** Dropped fields appear in `detail.excludedProperties`. Use `apiURL` on the resource object to fetch the full record.
---
## Minimal EventBridge rule (CloudFormation)
Route `v2.request.created` events for a specific request schema to a Lambda:
```yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Benchling request.created → Lambda
Parameters:
BenchlingEventBusName:
Type: String
Description: Partner event bus name from Benchling subscription
Resources:
RequestCreatedRule:
Type: AWS::Events::Rule
Properties:
Name: benchling-request-created
EventBusName: !Ref BenchlingEventBusName
State: ENABLED
EventPattern:
detail-type:
- v2.request.created
detail:
schema:
name:
- Validated Request
Targets:
- Id: HandleRequestCreated
Arn: !GetAtt HandleEventLambda.Arn
HandleEventLambda:
Type: AWS::Serverless::Function
Properties:
Handler: app.handler
Runtime: python3.12
CodeUri: src/
Timeout: 30
AllowEventBridgeInvoke:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref HandleEventLambda
Principal: events.amazonaws.com
SourceArn: !GetAtt RequestCreatedRule.Arn
```
**Other filter examples** (from Benchling docs):
```json
{
"detail-type": ["v2.assayRun.updated"],
"detail": {
"updates": ["my_field"]
}
}
```
```json
{
"detail-type": ["v2.entity.registered"],
"detail": {
"entity": {
"schema": {
"id": ["ts_MySchemaId"]
}
}
}
}
```
---
## Lambda handler skeleton (Python)
```python
import json
import logging
import os
import boto3
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Optional: re-fetch via SDK when payload may be stale or truncated
# from benchling_sdk.benchling import Benchling
# from benchling_sdk.auth.api_key_auth import ApiKeyAuth
#
# benchling = Benchling(
# url=os.environ["BENCHLING_TENANT_URL"],
# auth_method=ApiKeyAuth(os.environ["BENCHLING_API_KEY"]),
# )
def handler(event, context):
"""Process a single Benchling EventBridge delivery."""
detail_type = event.get("detail-type")
detail = event.get("detail") or {}
logger.info(
"benchling_event",
extra={
"detail_type": detail_type,
"event_id": detail.get("id"),
"benchling_event_type": detail.get("eventType"),
},
)
if detail.get("deprecated"):
logger.warning("deprecated_event_type: %s", detail_type)
if detail.get("excludedProperties"):
logger.warning(
"truncated_payload excluded=%s", detail.get("excludedProperties")
)
if detail_type == "v2.dnaSequence.created":
sequence = detail.get("dnaSequence") or {}
sequence_id = sequence.get("id")
if not sequence_id:
raise ValueError("missing dnaSequence.id in event detail")
# Prefer API lookup for authoritative data:
# seq = benchling.dna_sequences.get_by_id(sequence_id)
return {"status": "ok", "sequence_id": sequence_id}
if detail_type == "v2.workflowTask.updated.status":
task = detail.get("workflowTask") or {}
return {"status": "ok", "task_id": task.get("id")}
logger.info("no_handler_for_detail_type: %s", detail_type)
return {"status": "ignored", "detail_type": detail_type}
```
For serverless timeouts: SDK `wait_for_task` defaults to 600s — keep Lambda timeouts and EventBridge retry/DLQ settings aligned with expected processing time.
---
## Validation steps
1. **Subscription active:** In Benchling, subscription status is `Active` (not `Pending` or `Expired`).
2. **Partner source associated:** In AWS EventBridge → Partner event sources, source is associated with your bus.
3. **Log all events:** Add a catch-all rule targeting a CloudWatch log group, filtering on your bus `source` (shown in Benchling subscription UI).
4. **Trigger a test event:** Create or update an object matching your rule filter (for example register a DNA sequence).
5. **Inspect logs:** Confirm `detail-type`, `detail.id`, and resource IDs match expectations.
6. **Re-fetch check:** Call the SDK/API for the resource ID and confirm it matches your integration logic.
---
## Recovering missed events
Benchling does **not** replay EventBridge deliveries. After an outage:
1. Get the affected time window from Benchling support.
2. List historical events with the [List Events API](https://benchling.com/api/reference#/Events/listEvents) (retained ~2 weeks).
3. Re-route recovered events through your own infrastructure.
SDK example (ISO 8601 timestamp; see API reference for filters):
```python
events = benchling.events.list(
created_atgte="2025-10-20T00:00:00+00:00",
event_types="v2.dnaSequence.created",
)
for page in events:
for evt in page:
print(evt.event_type, evt.id)
```
---
## EventBridge vs Webhooks
| | EventBridge | Webhooks |
|---|-------------|----------|
| Setup | Benchling console + AWS bus/rules | Benchling App configuration |
| Payload | Hydrated v2 API objects | Thin IDs + metadata |
| Filtering | EventBridge `EventPattern` | App code |
| Permissions | Not permissioned at delivery | Inherited from app |
For new Benchling Apps, Benchling recommends **webhooks** unless you already standardize on EventBridge in AWS. See [Getting Started with Webhooks](https://docs.benchling.com/docs/getting-started-with-webhooks).
@@ -0,0 +1,772 @@
# Benchling Python SDK Reference
## Installation & Setup
### Installation
```bash
# Stable release (recommended)
uv pip install "benchling-sdk==1.25.0"
# Preview builds — alpha functionality, not for production
uv pip install "benchling-sdk" --prerelease allow
```
### Requirements
- Python 3.9+ (3.12 supported since SDK 1.11.0; repo recommends 3.11+)
- API access enabled on your Benchling tenant
- Developer Platform access from your tenant admin (for apps and events)
### Basic Initialization
```python
import os
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
benchling = Benchling(
url=os.environ["BENCHLING_TENANT_URL"],
auth_method=ApiKeyAuth(os.environ["BENCHLING_API_KEY"]),
)
```
## SDK Architecture
### Main Classes
**Benchling Client:**
The `benchling_sdk.benchling.Benchling` class is the root of all SDK interactions. It provides access to all resource endpoints:
```python
benchling.dna_sequences # DNA sequence operations
benchling.rna_sequences # RNA sequence operations
benchling.aa_sequences # Amino acid sequence operations
benchling.custom_entities # Custom entity operations
benchling.mixtures # Mixture operations
benchling.containers # Container operations
benchling.boxes # Box operations
benchling.locations # Location operations
benchling.plates # Plate operations
benchling.entries # Notebook entry operations
benchling.workflow_tasks # Workflow task operations
benchling.requests # Request operations
benchling.folders # Folder operations
benchling.projects # Project operations
benchling.users # User operations
benchling.teams # Team operations
```
### Resource Pattern
All resources follow a consistent CRUD pattern:
```python
# Create
resource.create(CreateModel(...))
# Read (single)
resource.get_by_id("resource_id")
# Read (list)
resource.list(optional_filters...)
# Update
resource.update(id="resource_id", UpdateModel(...))
# Archive/Delete
resource.archive(id="resource_id")
```
## Entity Management
### DNA Sequences
**Create:**
```python
from benchling_sdk.models import DnaSequenceCreate
sequence = benchling.dna_sequences.create(
DnaSequenceCreate(
name="pET28a-GFP",
bases="ATCGATCGATCG",
is_circular=True,
folder_id="fld_abc123",
schema_id="ts_abc123",
fields=benchling.models.fields({
"gene_name": "GFP",
"resistance": "Kanamycin",
"copy_number": "High"
})
)
)
```
**Read:**
```python
# Get by ID
seq = benchling.dna_sequences.get_by_id("seq_abc123")
print(f"{seq.name}: {len(seq.bases)} bp")
# List with filters
sequences = benchling.dna_sequences.list(
folder_id="fld_abc123",
schema_id="ts_abc123",
name="pET28a" # Filter by name
)
for page in sequences:
for seq in page:
print(f"{seq.id}: {seq.name}")
```
**Update:**
```python
from benchling_sdk.models import DnaSequenceUpdate
updated = benchling.dna_sequences.update(
sequence_id="seq_abc123",
dna_sequence=DnaSequenceUpdate(
name="pET28a-GFP-v2",
fields=benchling.models.fields({
"gene_name": "eGFP",
"notes": "Codon optimized"
})
)
)
```
**Archive:**
```python
benchling.dna_sequences.archive(
sequence_id="seq_abc123",
reason="Deprecated construct"
)
```
### RNA Sequences
Similar pattern to DNA sequences:
```python
from benchling_sdk.models import RnaSequenceCreate, RnaSequenceUpdate
# Create
rna = benchling.rna_sequences.create(
RnaSequenceCreate(
name="gRNA-target1",
bases="AUCGAUCGAUCG",
folder_id="fld_abc123",
fields=benchling.models.fields({
"target_gene": "TP53",
"off_target_score": "95"
})
)
)
# Update
updated_rna = benchling.rna_sequences.update(
rna_sequence_id=rna.id,
rna_sequence=RnaSequenceUpdate(
fields=benchling.models.fields({
"validated": "Yes"
})
)
)
```
### Amino Acid (Protein) Sequences
```python
from benchling_sdk.models import AaSequenceCreate
protein = benchling.aa_sequences.create(
AaSequenceCreate(
name="Green Fluorescent Protein",
amino_acids="MSKGEELFTGVVPILVELDGDVNGHKFSVSGEGEGDATYGKLTLKF",
folder_id="fld_abc123",
fields=benchling.models.fields({
"molecular_weight": "27000",
"extinction_coefficient": "21000"
})
)
)
```
### Custom Entities
Custom entities are defined by your tenant's schemas:
```python
from benchling_sdk.models import CustomEntityCreate, CustomEntityUpdate
# Create
cell_line = benchling.custom_entities.create(
CustomEntityCreate(
name="HEK293T-Clone5",
schema_id="ts_cellline_abc123",
folder_id="fld_abc123",
fields=benchling.models.fields({
"passage_number": "15",
"mycoplasma_test": "Negative",
"freezing_date": "2025-10-15"
})
)
)
# Update
updated_cell_line = benchling.custom_entities.update(
entity_id=cell_line.id,
custom_entity=CustomEntityUpdate(
fields=benchling.models.fields({
"passage_number": "16",
"notes": "Expanded for experiment"
})
)
)
```
### Mixtures
Mixtures combine multiple components:
```python
from benchling_sdk.models import MixtureCreate, IngredientCreate
mixture = benchling.mixtures.create(
MixtureCreate(
name="LB-Amp Media",
folder_id="fld_abc123",
schema_id="ts_mixture_abc123",
ingredients=[
IngredientCreate(
component_entity_id="ent_lb_base",
amount="1000 mL"
),
IngredientCreate(
component_entity_id="ent_ampicillin",
amount="100 mg"
)
],
fields=benchling.models.fields({
"pH": "7.0",
"sterilized": "Yes"
})
)
)
```
### Registry Operations
**Direct Registry Registration:**
```python
# Register entity upon creation
registered_seq = benchling.dna_sequences.create(
DnaSequenceCreate(
name="Construct-001",
bases="ATCG",
is_circular=True,
folder_id="fld_abc123",
entity_registry_id="src_abc123",
naming_strategy="NEW_IDS" # or "IDS_FROM_NAMES"
)
)
print(f"Registry ID: {registered_seq.registry_id}")
```
**Naming Strategies:**
- `NEW_IDS`: Benchling generates new registry IDs
- `IDS_FROM_NAMES`: Use entity names as registry IDs (names must be unique)
## Inventory Management
### Containers
```python
from benchling_sdk.models import ContainerCreate, ContainerUpdate
# Create
container = benchling.containers.create(
ContainerCreate(
name="Sample-001-Tube",
schema_id="cont_schema_abc123",
barcode="CONT001",
parent_storage_id="box_abc123", # Place in box
fields=benchling.models.fields({
"concentration": "100 ng/μL",
"volume": "50 μL",
"sample_type": "gDNA"
})
)
)
# Update location
benchling.containers.transfer(
container_id=container.id,
destination_id="box_xyz789"
)
# Update properties
updated = benchling.containers.update(
container_id=container.id,
container=ContainerUpdate(
fields=benchling.models.fields({
"volume": "45 μL",
"notes": "Used 5 μL for PCR"
})
)
)
# Check out
benchling.containers.check_out(
container_id=container.id,
comment="Taking to bench"
)
# Check in
benchling.containers.check_in(
container_id=container.id,
location_id="bench_location_abc"
)
```
### Boxes
```python
from benchling_sdk.models import BoxCreate
box = benchling.boxes.create(
BoxCreate(
name="Freezer-A-Box-01",
schema_id="box_schema_abc123",
parent_storage_id="loc_freezer_a",
barcode="BOX001",
fields=benchling.models.fields({
"box_type": "81-place",
"temperature": "-80C"
})
)
)
# List containers in box
containers = benchling.containers.list(
parent_storage_id=box.id
)
```
### Locations
```python
from benchling_sdk.models import LocationCreate
location = benchling.locations.create(
LocationCreate(
name="Freezer A - Shelf 2",
parent_storage_id="loc_freezer_a",
barcode="LOC-A-S2"
)
)
```
### Plates
```python
from benchling_sdk.models import PlateCreate, WellCreate
# Create 96-well plate
plate = benchling.plates.create(
PlateCreate(
name="PCR-Plate-001",
schema_id="plate_schema_abc123",
barcode="PLATE001",
wells=[
WellCreate(
position="A1",
entity_id="sample_entity_abc"
),
WellCreate(
position="A2",
entity_id="sample_entity_xyz"
)
# ... more wells
]
)
)
```
## Notebook Operations
### Entries
```python
from benchling_sdk.models import EntryCreate, EntryUpdate
# Create entry
entry = benchling.entries.create(
EntryCreate(
name="Cloning Experiment 2025-10-20",
folder_id="fld_abc123",
schema_id="entry_schema_abc123",
fields=benchling.models.fields({
"objective": "Clone GFP into pET28a",
"date": "2025-10-20",
"experiment_type": "Molecular Biology"
})
)
)
# Update entry
updated_entry = benchling.entries.update(
entry_id=entry.id,
entry=EntryUpdate(
fields=benchling.models.fields({
"results": "Successful cloning, 10 colonies",
"notes": "Colony 5 shows best fluorescence"
})
)
)
```
### Linking Entities to Entries
```python
# Link DNA sequence to entry
link = benchling.entry_links.create(
entry_id="entry_abc123",
entity_id="seq_xyz789"
)
# List links for an entry
links = benchling.entry_links.list(entry_id="entry_abc123")
```
## Workflow Management
### Tasks
```python
from benchling_sdk.models import WorkflowTaskCreate, WorkflowTaskUpdate
# Create task
task = benchling.workflow_tasks.create(
WorkflowTaskCreate(
name="PCR Amplification",
workflow_id="wf_abc123",
assignee_id="user_abc123",
schema_id="task_schema_abc123",
fields=benchling.models.fields({
"template": "seq_abc123",
"primers": "Forward: ATCG, Reverse: CGAT",
"priority": "High"
})
)
)
# Update status
completed_task = benchling.workflow_tasks.update(
task_id=task.id,
workflow_task=WorkflowTaskUpdate(
status_id="status_complete_abc123",
fields=benchling.models.fields({
"completion_date": "2025-10-20",
"yield": "500 ng"
})
)
)
# List tasks
tasks = benchling.workflow_tasks.list(
workflow_id="wf_abc123",
status_ids=["status_pending", "status_in_progress"]
)
```
## Advanced Features
### Pagination
The SDK uses generators for memory-efficient pagination:
```python
# Automatic pagination
sequences = benchling.dna_sequences.list()
# Get estimated total count
total = sequences.estimated_count()
print(f"Total sequences: {total}")
# Iterate through all pages
for page in sequences:
for seq in page:
process(seq)
# Manual page size control
sequences = benchling.dna_sequences.list(page_size=50)
```
### Async Task Handling
Some operations are asynchronous and return task IDs:
```python
from benchling_sdk.helpers.tasks import wait_for_task
from benchling_sdk.errors import WaitForTaskExpiredError
# Start async operation
response = benchling.some_bulk_operation(...)
task_id = response.task_id
# Wait for completion
try:
result = wait_for_task(
benchling,
task_id=task_id,
interval_wait_seconds=2, # Poll every 2 seconds
max_wait_seconds=600 # Timeout after 10 minutes
)
print("Task completed successfully")
except WaitForTaskExpiredError:
print("Task timed out")
```
### Error Handling
```python
from benchling_sdk.errors import (
BenchlingError,
NotFoundError,
ValidationError,
UnauthorizedError
)
try:
sequence = benchling.dna_sequences.get_by_id("seq_invalid")
except NotFoundError:
print("Sequence not found")
except UnauthorizedError:
print("Insufficient permissions")
except ValidationError as e:
print(f"Invalid data: {e}")
except BenchlingError as e:
print(f"General Benchling error: {e}")
```
### Retry Strategy
Customize retry behavior:
```python
from benchling_sdk.benchling import Benchling
from benchling_sdk.auth.api_key_auth import ApiKeyAuth
from benchling_sdk.retry import RetryStrategy
# Custom retry configuration
retry_strategy = RetryStrategy(
max_retries=3,
backoff_factor=0.5,
status_codes_to_retry=[429, 502, 503, 504]
)
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=ApiKeyAuth("your_api_key"),
retry_strategy=retry_strategy
)
# Disable retries
benchling = Benchling(
url="https://your-tenant.benchling.com",
auth_method=ApiKeyAuth("your_api_key"),
retry_strategy=RetryStrategy(max_retries=0)
)
```
### Custom API Calls
For unsupported endpoints:
```python
# GET request with model parsing
from benchling_sdk.models import DnaSequence
response = benchling.api.get_modeled(
path="/api/v2/dna-sequences/seq_abc123",
response_type=DnaSequence
)
# POST request
from benchling_sdk.models import DnaSequenceCreate
response = benchling.api.post_modeled(
path="/api/v2/dna-sequences",
request_body=DnaSequenceCreate(...),
response_type=DnaSequence
)
# Raw requests
raw_response = benchling.api.get(
path="/api/v2/custom-endpoint",
params={"key": "value"}
)
```
### Batch Operations
Efficiently process multiple items:
```python
# Bulk create
from benchling_sdk.models import DnaSequenceCreate
sequences_to_create = [
DnaSequenceCreate(name=f"Seq-{i}", bases="ATCG", folder_id="fld_abc")
for i in range(100)
]
# Create in batches
batch_size = 10
for i in range(0, len(sequences_to_create), batch_size):
batch = sequences_to_create[i:i+batch_size]
for seq in batch:
benchling.dna_sequences.create(seq)
```
### Schema Fields Helper
Convert dictionaries to Fields objects:
```python
# Using fields helper
fields_dict = {
"concentration": "100 ng/μL",
"volume": "50 μL",
"quality_score": "8.5",
"date_prepared": "2025-10-20"
}
fields = benchling.models.fields(fields_dict)
# Use in create/update
container = benchling.containers.create(
ContainerCreate(
name="Sample-001",
schema_id="schema_abc",
fields=fields
)
)
```
### Forward Compatibility
The SDK handles unknown API values gracefully:
```python
# Unknown enum values are preserved
entity = benchling.dna_sequences.get_by_id("seq_abc")
# Even if API returns new enum value not in SDK, it's preserved
# Unknown polymorphic types return UnknownType
from benchling_sdk.models import UnknownType
if isinstance(entity, UnknownType):
print(f"Unknown type: {entity.type}")
# Can still access raw data
print(entity.raw_data)
```
## Best Practices
### Use Type Hints
```python
from benchling_sdk.models import DnaSequence, DnaSequenceCreate
from typing import List
def create_sequences(names: List[str], folder_id: str) -> List[DnaSequence]:
sequences = []
for name in names:
seq = benchling.dna_sequences.create(
DnaSequenceCreate(
name=name,
bases="ATCG",
folder_id=folder_id
)
)
sequences.append(seq)
return sequences
```
### Efficient Filtering
Use API filters instead of client-side filtering:
```python
# Good - filter on server
sequences = benchling.dna_sequences.list(
folder_id="fld_abc123",
schema_id="ts_abc123"
)
# Bad - loads everything then filters
all_sequences = benchling.dna_sequences.list()
filtered = [s for page in all_sequences for s in page if s.folder_id == "fld_abc123"]
```
### Resource Cleanup
```python
# Archive old entities
cutoff_date = "2024-01-01"
sequences = benchling.dna_sequences.list()
for page in sequences:
for seq in page:
if seq.created_at < cutoff_date:
benchling.dna_sequences.archive(
sequence_id=seq.id,
reason="Archiving old sequences"
)
```
## Troubleshooting
### Common Issues
**Import paths:**
```python
# Preferred (documented in getting started guide)
from benchling_sdk.benchling import Benchling
# Also valid in benchling-sdk 1.25+
from benchling_sdk import Benchling
```
**Field Validation:**
```python
# Fields must match schema
# Check schema field types in Benchling UI
fields = benchling.models.fields({
"numeric_field": "123", # Should be string even for numbers
"date_field": "2025-10-20", # Format: YYYY-MM-DD
"dropdown_field": "Option1" # Must match dropdown options exactly
})
```
**Pagination Exhaustion:**
```python
# Generators can only be iterated once
sequences = benchling.dna_sequences.list()
for page in sequences: # First iteration OK
pass
for page in sequences: # Second iteration returns nothing!
pass
# Solution: Create new generator
sequences = benchling.dna_sequences.list() # New generator
```
## References
- **SDK Source:** https://github.com/benchling/benchling-sdk
- **SDK Docs:** https://benchling.com/sdk-docs/
- **API Reference:** https://benchling.com/api/reference
- **Common Examples:** https://docs.benchling.com/docs/common-sdk-interactions-and-examples
+71
View File
@@ -0,0 +1,71 @@
---
name: bgpt-paper-search
description: Search scientific papers and retrieve structured experimental data extracted from full-text studies via the BGPT MCP server. Returns 25+ fields per paper including methods, results, sample sizes, quality scores, and conclusions. Use for literature reviews, evidence synthesis, and finding experimental details not available in abstracts alone.
license: MIT
compatibility: Requires the BGPT MCP server configured in the agent host (npx mcp-remote or npx bgpt-mcp), internet access to bgpt.pro, and an optional BGPT API key for paid usage.
metadata: {"version": "1.1", "skill-author": "BGPT", "website": "https://bgpt.pro/mcp", "github": "https://github.com/connerlambden/bgpt-mcp"}
---
# BGPT Paper Search
## Overview
BGPT is a remote MCP server that searches a curated database of scientific papers built from raw experimental data extracted from full-text studies. Unlike traditional literature databases that return titles and abstracts, BGPT returns structured data from the actual paper content — methods, quantitative results, sample sizes, quality assessments, and 25+ metadata fields per paper.
## When to Use This Skill
Use this skill when:
- Searching for scientific papers with specific experimental details
- Conducting systematic or scoping literature reviews
- Finding quantitative results, sample sizes, or effect sizes across studies
- Comparing methodologies used in different studies
- Looking for papers with quality scores or evidence grading
- Needing structured data from full-text papers (not just abstracts)
- Building evidence tables for meta-analyses or clinical guidelines
## Setup
BGPT is a remote MCP server — no local installation required. Configure it in your agent's MCP settings before use; this skill instructs the agent to call the `search_papers` MCP tool and does not enable MCP access by itself.
### Claude Desktop / Claude Code
Add to your MCP configuration:
```json
{
"mcpServers": {
"bgpt": {
"command": "npx",
"args": ["mcp-remote", "https://bgpt.pro/mcp/sse"]
}
}
}
```
### npm (alternative)
```bash
npx bgpt-mcp
```
## Usage
Once the BGPT MCP server is configured, call its `search_papers` tool via the agent's MCP interface (not via Bash):
```
Search for papers about: "CRISPR gene editing efficiency in human cells"
```
The server returns structured results including:
- **Title, authors, journal, year, DOI**
- **Methods**: Experimental techniques, models, protocols
- **Results**: Key findings with quantitative data
- **Sample sizes**: Number of subjects/samples
- **Quality scores**: Study quality assessments
- **Conclusions**: Author conclusions and implications
## Pricing
- **Free tier**: 50 searches per network, no API key required
- **Paid**: $0.01 per result with an API key from [bgpt.pro/mcp](https://bgpt.pro/mcp)
+755
View File
@@ -0,0 +1,755 @@
---
name: bids
description: >
Use this skill when working with Brain Imaging Data Structure (BIDS) datasets:
organizing neuroscience and biomedical data (MRI, EEG, MEG, iEEG, PET, microscopy,
NIRS, motion capture, EMG, MR spectroscopy, behavioral), querying BIDS layouts,
validating compliance, converting DICOM to BIDS, writing metadata sidecars, or
creating BIDS derivatives.
license: https://creativecommons.org/licenses/by/4.0/
metadata: {"version": "1.0", "skill-author": "Yaroslav Halchenko"}
---
# Brain Imaging Data Structure (BIDS)
## Overview
The Brain Imaging Data Structure (BIDS) is a community standard for organizing and describing neuroscience and biomedical research datasets. It defines a consistent file naming convention, directory hierarchy, and metadata schema so that datasets are immediately understandable by humans and software tools alike. BIDS is governed by the BIDS Specification (currently v1.11.x) and is maintained by the community via the BIDS-Standard GitHub organization.
While BIDS originated for MRI, it has grown well beyond neuroimaging. The specification now covers 11 modalities spanning imaging, electrophysiology, and behavioral data:
- **Imaging**: MRI (structural, functional, diffusion, fieldmaps, perfusion/ASL), PET, microscopy
- **Electrophysiology**: EEG, MEG, iEEG (intracranial EEG), EMG
- **Other**: NIRS (near-infrared spectroscopy), motion capture, behavioral data (without imaging), MR spectroscopy
Active BEPs are extending BIDS further — notably BEP032 (microelectrode electrophysiology) will add support for extracellular recordings including Neuropixels probes, bringing BIDS to a prevalent methodology in animal neuroscience research (see also the neuropixels-analysis skill).
Adoption is required or strongly encouraged by major data repositories (OpenNeuro, DANDI), leading journals (NeuroImage, Human Brain Mapping, Scientific Data), and funding agencies (NIH, ERC).
The Python ecosystem for BIDS centers on **PyBIDS** (`pybids`) for querying and indexing BIDS datasets, and the **bids-validator** (Deno-based, available as PyPI package `bids-validator-deno` or via Deno directly) for compliance checking. Conversion from DICOM is typically done with **HeuDiConv**, **dcm2bids**, or **BIDScoin**.
## When to Use This Skill
Apply this skill when:
- Organizing raw neuroscience data (imaging, electrophysiology, behavioral) into BIDS-compliant directory structures
- Querying an existing BIDS dataset to find specific files by subject, session, task, run, or modality
- Validating a dataset against the BIDS specification before sharing or submission
- Converting DICOM data from scanners into BIDS format
- Writing or editing JSON sidecar metadata files
- Creating BIDS-compliant derivatives (preprocessed data, analysis outputs)
- Setting up a `dataset_description.json` for a new dataset
- Working with BIDS entities (subject, session, task, acquisition, run, etc.)
- Configuring `.bidsignore` to exclude files from validation
- Preparing data for upload to OpenNeuro, DANDI, or other BIDS-aware repositories
## Installation
```bash
# Core BIDS querying library
uv pip install pybids
# BIDS validator (Deno-based, installed via PyPI wrapper)
uv pip install bids-validator-deno
# Alternative: install directly via Deno
# deno install -g -A npm:bids-validator
# DICOM-to-BIDS converters (install as needed)
uv pip install heudiconv # HeuDiConv - heuristic-based DICOM conversion
uv pip install dcm2bids # dcm2bids - config-file-based conversion
# BIDScoin: uv pip install bidscoin
# Useful companions
uv pip install nibabel # NIfTI/other neuroimaging file I/O
uv pip install pydicom # DICOM file reading (used by converters)
```
## Core Workflows
### 1. BIDS Directory Structure
A minimal BIDS dataset follows this layout:
```
my_dataset/
dataset_description.json # Required: name, BIDSVersion, etc.
participants.tsv # Recommended: subject-level phenotypic data
participants.json # Recommended: column descriptions
README # Recommended: dataset documentation
CHANGES # Recommended: version history
.bidsignore # Optional: patterns to exclude from validation
sub-01/
anat/
sub-01_T1w.nii.gz
sub-01_T1w.json # Sidecar metadata
func/
sub-01_task-rest_bold.nii.gz
sub-01_task-rest_bold.json
sub-01_task-rest_events.tsv # Event timing for task fMRI
sub-01_task-rest_events.json
dwi/
sub-01_dwi.nii.gz
sub-01_dwi.json
sub-01_dwi.bvec
sub-01_dwi.bval
fmap/
sub-01_phasediff.nii.gz
sub-01_phasediff.json
sub-01_magnitude1.nii.gz
perf/
sub-01_asl.nii.gz
sub-01_asl.json
sub-01/
ses-pre/
anat/
sub-01_ses-pre_T1w.nii.gz
func/
sub-01_ses-pre_task-nback_bold.nii.gz
ses-post/
...
```
**Key points:**
- Every NIfTI file should have a corresponding `.json` sidecar
- File names encode entities: `sub-<label>[_ses-<label>][_task-<label>][_acq-<label>][_run-<index>]_<suffix>.<extension>`
- Entity order in filenames is fixed by the specification
- Only `dataset_description.json` is strictly required at the root level
### 2. Creating dataset_description.json
```python
import json
dataset_description = {
"Name": "My Neuroimaging Study",
"BIDSVersion": "1.10.0",
"DatasetType": "raw",
"License": "CC0",
"Authors": ["First Author", "Second Author"],
"Acknowledgements": "Funded by NIH R01-MH123456",
"HowToAcknowledge": "Please cite: Author et al. (2025) Journal Name.",
"Funding": ["NIH R01-MH123456", "NSF BCS-7654321"],
"ReferencesAndLinks": ["https://doi.org/10.xxxx/xxxxx"],
"DatasetDOI": "10.18112/openneuro.ds000001.v1.0.0",
"GeneratedBy": [
{
"Name": "HeuDiConv",
"Version": "1.3.1",
"CodeURL": "https://github.com/nipy/heudiconv"
}
]
}
with open("dataset_description.json", "w") as f:
json.dump(dataset_description, f, indent=4)
```
For **derivatives**, set `"DatasetType": "derivative"` and add `"GeneratedBy"` listing the pipeline:
```python
deriv_description = {
"Name": "fMRIPrep - fMRI PREProcessing",
"BIDSVersion": "1.10.0",
"DatasetType": "derivative",
"GeneratedBy": [
{
"Name": "fMRIPrep",
"Version": "24.1.0",
"CodeURL": "https://github.com/nipreps/fmriprep"
}
]
}
```
### 3. Querying BIDS Datasets with PyBIDS
```python
from bids import BIDSLayout
# Index a BIDS dataset (validates structure on load)
layout = BIDSLayout("/path/to/bids_dataset")
# Basic queries
subjects = layout.get_subjects() # ['01', '02', '03', ...]
sessions = layout.get_sessions() # ['pre', 'post'] or []
tasks = layout.get_tasks() # ['rest', 'nback']
runs = layout.get_runs() # [1, 2] or []
# Find specific files
bold_files = layout.get(
suffix="bold",
extension=".nii.gz",
return_type="filename"
)
# Filter by subject, task, session
nback_sub01 = layout.get(
subject="01",
task="nback",
suffix="bold",
extension=".nii.gz",
return_type="filename"
)
# Get metadata from JSON sidecars (automatic inheritance)
metadata = layout.get_metadata("/path/to/sub-01/func/sub-01_task-rest_bold.nii.gz")
tr = metadata["RepetitionTime"]
# Get all entities for a file
entities = layout.get_entities()
# Build a path from entities using BIDSLayout
bids_file = layout.get(subject="01", suffix="T1w", extension=".nii.gz")[0]
print(bids_file.path)
print(bids_file.get_entities())
```
**Key points:**
- `BIDSLayout` indexes the entire dataset on initialization; for large datasets use `database_path` to cache the index
- Metadata inheritance: a JSON sidecar at a higher level (e.g., root or subject) is inherited by all files below unless overridden
- Use `return_type="filename"` for paths, `return_type="object"` (default) for `BIDSFile` objects
### 4. Validating BIDS Datasets
#### Using bids-validator via PyPI (recommended)
The `bids-validator-deno` PyPI package bundles the Deno-based validator as a standalone CLI:
```bash
# Install
uv pip install bids-validator-deno
# Validate a dataset
bids-validator /path/to/bids_dataset
# Ignore specific warnings/errors
bids-validator /path/to/bids_dataset --ignoreNiftiHeaders --ignoreSubjectConsistency
```
#### Using bids-validator via Deno directly
If Deno is already available, you can install or run the validator without PyPI:
```bash
# Install globally via Deno
deno install -g -A npm:bids-validator
# Or run without installing
deno run -A npm:bids-validator /path/to/bids_dataset
```
#### Legacy Node.js validator
The older Node.js-based validator (`npm install -g bids-validator`) is deprecated in favor of the Deno-based version. The Deno version is the reference implementation for BIDS Specification v1.9+.
#### Using .bidsignore
Create `.bidsignore` at the dataset root to exclude files from validation (gitignore syntax):
```
# Exclude sourcedata and extra files
sourcedata/
extra_data/
*.log
*_sbref.nii.gz
**/.DS_Store
```
### 5. BIDS Entities and File Naming
The authoritative, machine-readable source of truth for entities, their ordering, allowed suffixes, and all filename rules is the **BIDS Schema** — a structured YAML/JSON representation of the specification. A JSON export is shipped with this skill at `references/bids_schema.json`. The schema is defined in the [bids-specification `src/schema/`](https://github.com/bids-standard/bids-specification/tree/master/src/schema) directory and published at https://bids-specification.readthedocs.io/en/stable/schema.json. BEP-specific schema previews are available at https://github.com/bids-standard/bids-schema/tree/main/BEPs.
Run `scripts/update_schema.py` to refresh the schema and BEPs list from upstream (no dependencies beyond stdlib).
The tables below are a convenient summary; when in doubt, consult the schema.
BIDS filenames are built from ordered key-value entity pairs:
| Entity | Key | Example | Required for |
|--------|-----|---------|--------------|
| Subject | `sub-` | `sub-01` | All files |
| Session | `ses-` | `ses-pre` | Multi-session studies |
| Task | `task-` | `task-rest` | func (bold, cbv, phase), eeg, meg |
| Acquisition | `acq-` | `acq-highres` | Distinguishing acquisition parameters |
| Contrast enhancing agent | `ce-` | `ce-gadolinium` | Contrast-enhanced images |
| Reconstruction | `rec-` | `rec-magnitude` | Reconstruction variants |
| Direction | `dir-` | `dir-AP` | Fieldmaps, DWI, phase-encoding |
| Run | `run-` | `run-01` | Multiple identical acquisitions |
| Echo | `echo-` | `echo-1` | Multi-echo sequences |
| Part | `part-` | `part-mag` | Magnitude/phase splits |
| Space | `space-` | `space-MNI152NLin2009cAsym` | Derivatives in template space |
| Description | `desc-` | `desc-preproc` | Derivatives only |
**Entity ordering in filenames** is fixed by the spec (defined in `rules.entities` in `bids_schema.json`). See `references/bids_specification.md` for the complete numbered ordering table. A common subset:
`sub-<label>[_ses-<label>][_task-<label>][_acq-<label>][_ce-<label>][_rec-<label>][_dir-<label>][_run-<index>][_echo-<index>][_part-<label>][_space-<label>][_desc-<label>]_<suffix>.<extension>`
**Common suffixes by datatype:**
| Datatype | Suffixes |
|----------|----------|
| anat | `T1w`, `T2w`, `FLAIR`, `T2star`, `T1map`, `T2map`, `defacemask` |
| func | `bold`, `cbv`, `sbref`, `events`, `physio`, `stim` |
| dwi | `dwi`, `sbref` |
| fmap | `phasediff`, `phase1`, `phase2`, `magnitude1`, `magnitude2`, `fieldmap`, `epi` |
| perf | `asl`, `m0scan`, `aslcontext` |
| eeg | `eeg`, `channels`, `electrodes`, `events` |
| meg | `meg`, `channels`, `coordsystem`, `events` |
| ieeg | `ieeg`, `channels`, `electrodes`, `coordsystem`, `events` |
| pet | `pet`, `blood` |
### 6. DICOM to BIDS Conversion
#### HeuDiConv
HeuDiConv is the most flexible DICOM-to-BIDS converter. It supports three usage modes — from fully automatic to fully custom — and handles duplicates, provenance tracking, and sourcedata archiving out of the box.
**Mode 1: ReproIn (turnkey, recommended for new studies)**
If scanner protocol names follow the [ReproIn naming convention](https://github.com/repronim/reproin), conversion is fully automatic — no heuristic file to write:
```bash
# Turnkey conversion: HeuDiConv maps ReproIn protocol names to BIDS automatically
heudiconv --files dicom/001 -o /path/to/bids -f reproin --bids --minmeta
```
ReproIn protocol names encode BIDS entities directly:
- `anat-T1w``sub-XX/anat/sub-XX_T1w.nii.gz`
- `func-bold_task-rest``sub-XX/func/sub-XX_task-rest_bold.nii.gz`
- `dwi_dir-AP``sub-XX/dwi/sub-XX_dir-AP_dwi.nii.gz`
- `fmap_dir-PA``sub-XX/fmap/sub-XX_dir-PA_epi.nii.gz`
Session can be set once on the localizer (e.g., `anat-scout_ses-pre`) and ReproIn propagates it to all sequences in that Program. Subject ID is extracted from DICOM metadata. Duplicate runs are numbered automatically.
**Mode 2: Custom heuristic mapping into ReproIn (for existing data)**
If you already have data with non-ReproIn protocol names, you can write a thin heuristic that maps your names into ReproIn conventions, gaining all ReproIn benefits (automatic entity handling, duplicate management, etc.). See https://github.com/repronim/reproin/issues/18 for a HOWTO.
**Mode 3: Custom heuristic (full flexibility)**
For complex mappings, write a Python heuristic file:
```bash
# Step 1: Reconnaissance — discover DICOM series
heudiconv --files dicom/219/itbs/*/*.dcm -o Nifti/ -f convertall -s 219 -c none
# This creates .heudiconv/219/info/dicominfo.tsv — inspect it to understand
# what was acquired and map series to BIDS names.
# Step 2: Write a heuristic file (see references/conversion_tools.md)
# Step 3: Convert
heudiconv --files dicom/219/itbs/*/*.dcm -s 219 -ss itbs \
-f Nifti/code/heuristic.py -c dcm2niix --bids --minmeta -o Nifti/
```
See `references/conversion_tools.md` for complete heuristic file examples.
**Key points:**
- HeuDiConv wraps `dcm2niix` for the actual DICOM-to-NIfTI conversion
- **`--minmeta`**: always use this flag to prevent excess DICOM metadata from overflowing JSON sidecars (can crash fMRIPrep/MRIQC)
- **Duplicate handling**: use `{item:03d}` in templates for auto-numbering when the same protocol is run multiple times; without it, later runs overwrite earlier ones
- **`.heudiconv/` directory**: created alongside output, stores provenance (heuristic used, dicominfo.tsv, conversion records). Keep it with your data for reproducibility
- **`sourcedata/`**: HeuDiConv archives original DICOMs as `.tgz` files under `sourcedata/` for reproducibility
- **`is_motion_corrected` filter**: use in heuristics to exclude scanner-generated MOCO series (e.g., `if not s.is_motion_corrected`)
- Both `--files` (explicit paths) and `-d` (template with `{subject}`, `{session}` placeholders) are supported for specifying DICOM input
#### dcm2bids (Configuration-file-based)
```bash
# Step 1: Generate helper output to inspect series
dcm2bids_helper -d /path/to/dicom
# Step 2: Create config file (dcm2bids_config.json)
# Step 3: Convert
dcm2bids -d /path/to/dicom -p 01 -c dcm2bids_config.json -o /path/to/bids_output
```
See `references/conversion_tools.md` for detailed configuration examples.
### 7. Metadata Sidecars
Every BIDS data file should have a JSON sidecar with acquisition parameters. Metadata fields follow the inheritance principle: a sidecar at a higher directory level applies to all matching files below.
**Inheritance example:**
```
my_dataset/
task-rest_bold.json # Applies to ALL rest BOLD files
sub-01/
func/
sub-01_task-rest_bold.json # Overrides/extends for sub-01 only
```
**Critical metadata fields by modality:**
For **func (BOLD)**:
```json
{
"RepetitionTime": 2.0,
"TaskName": "rest",
"PhaseEncodingDirection": "j-",
"TotalReadoutTime": 0.05,
"SliceTiming": [0, 0.5, 1.0, 1.5],
"EffectiveEchoSpacing": 0.00058,
"EchoTime": 0.03
}
```
For **anat**:
```json
{
"MagneticFieldStrength": 3,
"Manufacturer": "Siemens",
"ManufacturersModelName": "Prisma",
"RepetitionTime": 2.3,
"EchoTime": 0.00293,
"FlipAngle": 8
}
```
For **DWI**:
```json
{
"PhaseEncodingDirection": "j-",
"TotalReadoutTime": 0.05,
"EchoTime": 0.089,
"RepetitionTime": 3.4,
"MultipartID": "dwi_1"
}
```
**Key points:**
- `dcm2niix` auto-generates most sidecar fields from DICOM headers
- `RepetitionTime` and `TaskName` are required for BOLD
- `SliceTiming` is essential for slice-timing correction in fMRI preprocessing
- `PhaseEncodingDirection` and `TotalReadoutTime` (or `EffectiveEchoSpacing`) are needed for distortion correction
- See `references/metadata_fields.md` for comprehensive field reference
### 8. Events Files for Task fMRI
Task-based fMRI requires `_events.tsv` files:
```
onset duration trial_type response_time
0.0 0.5 face 0.435
2.5 0.5 house 0.367
5.0 0.5 face 0.512
7.5 0.5 scrambled 0.298
```
**Required columns:**
- `onset` - onset time in seconds relative to the start of the acquisition
- `duration` - duration in seconds (use `n/a` for instantaneous events)
**Recommended columns:**
- `trial_type` - categorical label for condition
- `response_time` - RT in seconds
- Custom columns as needed (with descriptions in corresponding `.json` sidecar)
### 9. Participants File
```
participant_id age sex group handedness
sub-01 25 M control right
sub-02 30 F patient left
sub-03 28 M control right
```
The `participants.json` sidecar describes columns:
```json
{
"age": {
"Description": "Age of the participant at time of scanning",
"Units": "years"
},
"sex": {
"Description": "Biological sex",
"Levels": {
"M": "male",
"F": "female"
}
},
"group": {
"Description": "Experimental group",
"Levels": {
"control": "Healthy control",
"patient": "Patient group"
}
},
"handedness": {
"Description": "Dominant hand",
"Levels": {
"right": "Right-handed",
"left": "Left-handed",
"ambidextrous": "Ambidextrous"
}
}
}
```
### 10. BIDS Derivatives
Processed outputs go under a `derivatives/` directory:
```
my_dataset/
derivatives/
fmriprep-24.1.0/
dataset_description.json # DatasetType: "derivative"
sub-01/
anat/
sub-01_space-MNI152NLin2009cAsym_desc-preproc_T1w.nii.gz
sub-01_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz
func/
sub-01_task-rest_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz
sub-01_task-rest_desc-confounds_timeseries.tsv
mriqc-24.0.0/
dataset_description.json
sub-01/
anat/
sub-01_T1w.html
func/
sub-01_task-rest_bold.html
group_T1w.tsv
group_bold.tsv
```
**Derivative conventions:**
- `space-<label>` - template/reference space (e.g., `MNI152NLin2009cAsym`, `T1w`)
- `desc-<label>` - description of processing (e.g., `preproc`, `brain`, `smoothed`)
- `res-<label>` - resolution (e.g., `2` for 2mm isotropic)
- Each pipeline gets its own directory under `derivatives/`
- Must have its own `dataset_description.json` with `GeneratedBy`
### 11. PyBIDS: Advanced Usage
```python
from bids import BIDSLayout
from bids.layout import BIDSLayoutIndexer
# Cache the layout index for faster repeated access
layout = BIDSLayout("/path/to/dataset", database_path="/path/to/cache.db")
# Include derivatives
layout = BIDSLayout(
"/path/to/dataset",
derivatives=["/path/to/dataset/derivatives/fmriprep-24.1.0"]
)
# Get derivative files
preproc = layout.get(
subject="01",
task="rest",
desc="preproc",
suffix="bold",
space="MNI152NLin2009cAsym",
extension=".nii.gz",
return_type="filename"
)
# Get confound regressors
confounds = layout.get(
subject="01",
task="rest",
desc="confounds",
suffix="timeseries",
extension=".tsv",
return_type="filename"
)
# Build BIDS path from entities
from bids import BIDSLayout
layout = BIDSLayout("/path/to/dataset")
path = layout.build_path(
{
"subject": "01",
"session": "pre",
"task": "rest",
"suffix": "bold",
"extension": ".nii.gz",
"datatype": "func"
},
validate=True
)
# Get all files for a subject as a DataFrame
import pandas as pd
files_df = layout.to_df()
sub01_df = files_df[files_df["subject"] == "01"]
```
### 12. BIDS-Apps
BIDS-Apps are containerized analysis pipelines that accept BIDS datasets as input:
```bash
# General BIDS-App invocation pattern
docker run -v /path/to/bids:/data:ro -v /path/to/output:/out \
<bids-app-image> /data /out participant --participant_label 01
# Common BIDS-Apps:
# fMRIPrep - fMRI preprocessing
docker run nipreps/fmriprep /data /out participant \
--participant-label 01 --fs-license-file /license.txt
# MRIQC - MRI quality control
docker run nipreps/mriqc /data /out participant \
--participant-label 01
# QSIPrep - diffusion MRI preprocessing
docker run pennbbl/qsiprep /data /out participant \
--participant-label 01
```
**BIDS-App interface convention:**
```
bids-app input_dataset output_dir {participant|group} [options]
```
- `participant` level: runs per-subject
- `group` level: runs across all subjects (aggregation/group stats)
## Reference Materials
This skill includes detailed reference documentation:
- **bids_schema.json**: Machine-readable BIDS schema (from https://bids-specification.readthedocs.io/en/stable/schema.json). This is the authoritative source for entity definitions, ordering rules, filename templates, allowed suffixes per datatype, and metadata field requirements. BEP-specific schemas are at https://github.com/bids-standard/bids-schema/tree/main/BEPs.
- **beps.yml**: Current list of all BIDS Extension Proposals with titles, leads, status, and links (from [bids-website](https://github.com/bids-standard/bids-website/blob/main/data/beps/beps.yml))
- **bids_specification.md**: Human-readable summary of the entity table, datatype reference, directory structure rules, template spaces, and specification changelog
- **metadata_fields.md**: Required and recommended JSON sidecar fields for every BIDS modality (anat, func, dwi, fmap, eeg, meg, pet, etc.)
- **conversion_tools.md**: Detailed workflows for HeuDiConv, dcm2bids, and BIDScoin including heuristic/config examples and troubleshooting
Update schema and BEPs with: `python scripts/update_schema.py`
## Common Issues and Solutions
### 1. Validator reports "Not a BIDS dataset"
**Cause**: Missing `dataset_description.json` at the root.
**Fix**: Create the file with at minimum `{"Name": "...", "BIDSVersion": "1.10.0"}`.
### 2. Inconsistent subjects warning
**Cause**: Not all subjects have the same set of files (some missing sessions, runs, etc.).
**Fix**: This is a warning, not an error. Use `--ignoreSubjectConsistency` if intentional. Document missing data in `participants.tsv` or a `scans.tsv`.
### 3. Missing SliceTiming
**Cause**: `dcm2niix` couldn't extract slice timing from DICOM headers.
**Fix**: Determine slice order from the scan protocol and add manually to the JSON sidecar. Common patterns: ascending, descending, interleaved (odd-first or even-first).
### 4. Phase encoding direction confusion
**Cause**: Axis labels (i/j/k vs x/y/z vs LR/AP/SI) are confusing.
**Fix**: In BIDS, use NIfTI image axes: `i`=first axis, `j`=second, `k`=third. `-` means negative direction. For standard axial acquisitions: `j` is typically anterior-posterior. Verify with the acquisition protocol.
### 5. PyBIDS is slow on large datasets
**Cause**: Full filesystem indexing on every `BIDSLayout()` call.
**Fix**: Use `database_path` to cache the index to an SQLite file:
```python
layout = BIDSLayout("/data", database_path="/data/.pybids_cache.db")
```
### 6. Derivatives not found by PyBIDS
**Cause**: Derivatives directory missing its own `dataset_description.json`.
**Fix**: Every derivatives directory must have `dataset_description.json` with `"DatasetType": "derivative"`.
### 7. Events file timing is off
**Cause**: `onset` times are relative to the wrong reference (e.g., trigger time vs first volume).
**Fix**: Onsets must be in seconds relative to the first volume of that run's acquisition. Account for dummy scans if they were discarded.
### 8. TSV files fail validation
**Cause**: Encoding or delimiter issues (spaces instead of tabs, BOM characters, Windows line endings).
**Fix**: Ensure tab-separated values with UTF-8 encoding and Unix line endings (`\n`). Use `n/a` (not `NA`, `NaN`, or empty) for missing values.
## Best Practices
1. **Validate early and often** - Run the BIDS validator after every conversion or modification. Fix errors before they compound.
2. **Use metadata inheritance** - Place shared metadata (e.g., `TaskName`, scanner parameters) in top-level sidecar files rather than duplicating in every subject's directory.
3. **Keep sourcedata** - Store the original DICOM (or other raw) data under `sourcedata/` so conversions are reproducible. Add `sourcedata/` to `.bidsignore`.
4. **Use consistent naming from the start** - Define your BIDS naming scheme before data collection. Use the ReproIn naming convention for scan protocols to enable automatic conversion.
5. **Document your dataset** - Write a thorough `README` describing the study design, acquisition parameters, known issues, and any deviations from BIDS.
6. **Use scans.tsv for run-level metadata** - Record per-run acquisition times and quality notes:
```
filename acq_time quality
func/sub-01_task-rest_bold.nii.gz 2025-01-15T10:30:00 good
```
7. **Version your dataset** - Use `CHANGES` to document dataset modifications. Consider DataLad for full version control of large datasets.
8. **Deface anatomical images** - Remove facial features from T1w/T2w images before sharing (e.g., using `pydeface`, `mri_deface`, or `afni_refacer`). Store defaced versions as the primary data or use `_defacemask` files.
9. **Use BIDS URIs for provenance** - In derivatives, reference source files using BIDS URIs: `bids::sub-01/anat/sub-01_T1w.nii.gz`.
10. **Prefer community tools** - Use established BIDS-Apps (fMRIPrep, MRIQC, QSIPrep) rather than custom pipelines when possible. They handle BIDS I/O correctly and produce BIDS-compliant derivatives.
11. **Study bids-examples** - The [bids-examples](https://github.com/bids-standard/bids-examples) repository is the canonical collection of prototypical BIDS datasets covering different modalities and use cases (MRI, fMRI, DWI, EEG, MEG, iEEG, PET, ASL, genetics, derivatives, and more). Use it as a reference when structuring your own dataset, as test data for BIDS tools, or to understand how a specific modality should be organized. Each example passes the BIDS validator.
## BIDS Extension Proposals (BEPs)
BEPs are community-driven proposals to extend BIDS to new modalities, derivatives, or metadata. The full list with status, leads, and links is in `references/beps.yml` (fetched from the [bids-website](https://github.com/bids-standard/bids-website/blob/main/data/beps/beps.yml)). BEP-specific schema previews are rendered at https://github.com/bids-standard/bids-schema/tree/main/BEPs.
**Current BEPs** (as of schema update):
| BEP | Title | Content | Status |
|-----|-------|---------|--------|
| 004 | Susceptibility Weighted Imaging | raw | Seeking new leader |
| 011 | Structural preprocessing derivatives | derivative | Has PR (#518) |
| 012 | Functional preprocessing derivatives | derivative | Has PR (#519), schema implemented |
| 014 | Affine transforms and nonlinear field warps | derivative | X5 format development |
| 016 | Diffusion weighted imaging derivatives | derivative | Has PR (#2211) |
| 017 | Generic BIDS connectivity data schema | derivative | In development |
| 021 | Common Electrophysiological Derivatives | derivative | In development |
| 023 | PET Preprocessing derivatives | derivative | In development |
| 024 | Computed Tomography scan | raw | Seeking contributors |
| 026 | Microelectrode Recordings | raw | Seeking new leader |
| 028 | Provenance | metadata | Has PR (#2099) |
| 032 | Microelectrode electrophysiology | raw | Has PR (#2307), preview available — covers Neuropixels and other extracellular probes; relates to neuropixels-analysis skill |
| 033 | Advanced Diffusion Weighted Imaging | raw | Seeking contributors |
| 034 | Computational modeling | derivative | Has PR (#967) |
| 035 | Mega-analyses with non-compliant derivatives | derivative | In development |
| 036 | Phenotypic Data Guidelines | raw | Community review |
| 037 | Non-Invasive Brain Stimulation | raw | In development |
| 039 | Dimensionality reduction-based networks | raw | In development |
| 040 | Functional Ultrasound | raw | In development |
| 041 | Statistical Model Derivatives | derivative | Collecting feedback |
| 043 | BIDS Term Mapping | metadata | Collecting feedback |
| 044 | Stimuli | raw | Has PR (#2022), community review |
| 045 | Peripheral Physiological Recordings | raw | Has PR (#2267) |
| 046 | Diffusion Tractography | derivative | In development |
| 047 | Audio/video recordings for behavioral experiments | raw | Has PR (#2231) |
**Related standards:**
- **BIDS-Stats Models**: JSON specification for defining GLM-based neuroimaging analyses
- **BIDS-Derivatives** (BEP003): Standard for preprocessed/analysis outputs (partially merged into spec)
## Related Tools Ecosystem
| Tool | Purpose |
|------|---------|
| **fMRIPrep** | fMRI preprocessing (produces BIDS derivatives) |
| **MRIQC** | MRI quality control (produces BIDS derivatives) |
| **QSIPrep** | Diffusion MRI preprocessing |
| **TemplateFlow** | Neuroimaging templates and atlases with BIDS-like naming |
| **Fitlins** | BIDS Stats Models implementation |
| **DataLad** | Version control for large datasets, integrates with BIDS |
| **OpenNeuro** | Free BIDS dataset repository |
| **DANDI** | Neurophysiology data archive (uses BIDS for some modalities) |
| **HeuDiConv** | DICOM-to-BIDS with heuristic Python files |
| **dcm2bids** | DICOM-to-BIDS with JSON config |
| **BIDScoin** | DICOM-to-BIDS with GUI and YAML config |
| **nwb2bids** | Convert NWB (Neurodata Without Borders) files to BIDS |
| **CuBIDS** | BIDS dataset curation and harmonization |
| **bids2table** | Efficient tabular indexing of BIDS datasets |
| **bids-examples** | Canonical collection of prototypical BIDS datasets for all modalities |
## Documentation
- **BIDS Specification**: https://bids-specification.readthedocs.io/
- **BIDS Website**: https://bids.neuroimaging.io/
- **PyBIDS Documentation**: https://bids-standard.github.io/pybids/
- **BIDS Validator**: https://github.com/bids-standard/bids-validator
- **BIDS Starter Kit**: https://bids-standard.github.io/bids-starter-kit/
- **BIDS Examples**: https://github.com/bids-standard/bids-examples — canonical reference datasets for every BIDS modality; use as templates and test data
- **HeuDiConv Docs**: https://heudiconv.readthedocs.io/
- **Original BIDS paper**: Gorgolewski et al. (2016) Scientific Data, doi:10.1038/sdata.2016.44
+637
View File
@@ -0,0 +1,637 @@
---
# template
# - number:
# title:
# display:
# google_doc:
# pull_request:
# html_preview:
# leads:
# # MUST match given-names and family-names from the bids specification citation.cff
# - given-names: ' '
# family-names: ' '
# bids_maintainers:
# - given-names: ' '
# family-names: ' '
# status:
# blocking:
# communication_channel:
# pull_request_created:
# pull_request_merged:
- number: '004'
title: Susceptibility Weighted Imaging
google_doc: https://docs.google.com/document/d/1kyw9mGgacNqeMbp4xZet3RnDhcMmf4_BmRgKaOkO2Sc/
content:
- raw
leads:
- given-names: ' '
family-names: ' '
bids_maintainers:
status:
blocking:
- Looking for a new leader.
google_doc_created: 2017-04
pull_request_created:
pull_request_merged:
- number: '011'
title: Structural preprocessing derivatives
google_doc: https://docs.google.com/document/d/1YG2g4UkEio4t_STIBOqYOwneLEs1emHIXbGKynx7V0Y/
pull_request: https://github.com/bids-standard/bids-specification/pull/518
html_preview: https://bids-specification--518.org.readthedocs.build/en/518/05-derivatives/04-structural-derivatives.html
content:
- derivative
leads:
- given-names: Viviana
family-names: Siless
bids_maintainers:
- given-names: Christopher J.
family-names: Markiewicz
status:
- Stability! (We haven't touched it in a bit.)
blocking:
- Staleness! (We haven't touched it in a bit.)
- Mostly just need to regroup with other BEPs and make sure we're being consistent.
google_doc_created: 2017-08
pull_request_created: 2020-06
pull_request_merged:
- number: '012'
title: Functional preprocessing derivatives
google_doc:
pull_request: https://github.com/bids-standard/bids-specification/pull/519
html_preview: https://bids-specification--519.org.readthedocs.build/en/519/derivatives/functional-derivatives.html
content:
- derivative
leads:
- given-names: Christopher J.
family-names: Markiewicz
bids_maintainers:
- given-names: Christopher J.
family-names: Markiewicz
status:
- Moderate reworking, post-BEP23 meeting. "boldmap" suffix.
- Schema implemented; waiting on full schema validation to merge.
blocking:
- Overlap with BEP 39 (decompositions).
- Probably just needs dropping from BEP 12, but need to make sure BEP 39 meets the needs served there.
google_doc_created: 2018-10
pull_request_created: 2020-06
pull_request_merged:
- number: '014'
title: Affine transformations and nonlinear field warps
google_doc: https://docs.google.com/document/d/11gCzXOPUbYyuQx8fErtMO9tnOKC3kTWiL9axWkkILNE/
content:
- derivative
leads:
- given-names: Oscar
family-names: Esteban
bids_maintainers:
- given-names: Christopher J.
family-names: Markiewicz
status:
- After the kick-off meeting (2019), progress locked on developing a prototype of a new HDF5-based format (X5)
- Minor bug fixes and features to support more transforms
- 'Perspectives: The current draft seems sufficient for storing transforms'
- 'Perspectives: X5 has high promises to enable effortless spatial transforms'
blocking:
- Bandwidth to finalize development
- 'Current blockers: surface transforms & X5 storing'
google_doc_created: 2017-08
pull_request_created:
pull_request_merged:
- number: '016'
title: Diffusion weighted imaging derivatives
pull_request: https://github.com/bids-standard/bids-specification/pull/2211
content:
- derivative
leads:
- given-names: Franco
family-names: Pestilli
- given-names: Oscar
family-names: Esteban
bids_maintainers:
status:
- adapted general spatial derivatives proposal, meaning using <modality>map, like `dwimap`
- decided on using `params-` to denote different file types, for example `param-md` or `param-fa`
- updated meta-data
blocking:
google_doc_created:
pull_request_created:
pull_request_merged:
- number: '017'
title: Generic BIDS connectivity data schema
display: Connectivity schema
google_doc: https://docs.google.com/document/d/1ugBdUF6dhElXdj3u9vw0iWjE6f_Bibsro3ah7sRV0GA/
content:
- derivative
leads:
- given-names: Eugene P.
family-names: Duff
bids_maintainers:
status:
- specified different formats for dense and sparse matrices
- 'proposed arrays in h5 or zarr to cover multi-dimensional matrices (for example: dynamic connectivity)'
blocking:
google_doc_created: 2017-05
pull_request_created:
pull_request_merged:
- number: '021'
title: Common Electrophysiological Derivatives
google_doc: https://docs.google.com/document/d/1PmcVs7vg7Th-cGC-UrX8rAhKUHIzOI-uIOh69_mvdlw/
content:
- derivative
leads:
- given-names: Arnaud
family-names: Delorme
- given-names: Dora
family-names: Hermes
- given-names: Mainak
family-names: Jas
- given-names: Guiomar
family-names: Niso
- given-names: Robert
family-names: Oostenveld
- given-names: Cyril
family-names: Pernet
- given-names: Christine
family-names: Rogers
status:
- 'Focus on raw-ish data: channels by time: epoching, filtering, interpolation...'
- 'All provenance tracking: outsourced to BEP028 (“provenance”)'
- Reusing existing (raw) data formats where applicable
- 'No additional entities or suffixes: focus on “desc” entity'
- Create new “descriptions.tsv” file to accompany and document the “desc” entity
- Working on examples on GitHub
- 'Perspectives: Derivatives beyond channels by time data will be discussed at a later point'
blocking:
google_doc_created: 2018-05
pull_request_created:
pull_request_merged:
- number: '023'
title: PET Preprocessing derivatives
google_doc: https://docs.google.com/document/d/1yzsd1J9GT-aA0DWhdlgNr5LCu6_gvbjLyfvYq2FuxlY/
content:
- derivative
leads:
- given-names: Martin
family-names: Noergaard
- given-names: Graham
family-names: Searle
- given-names: Melanie
family-names: Ganz-Benjaminsen
bids_maintainers:
- given-names: Anthony
family-names: Galassi
status:
- Defined all the necessary components to be included in the specification
- Alignment with other modalities obtained at Copenhagen BIDS derivatives meeting
- Example PET derivatives datasets available on github
- 'Perspectives: 3rd joint meeting between all PET collaborators in August 2023'
- 'Perspectives: Aim is to finish the specification during the fall of 2023'
blocking:
- Need more example datasets (fore example different tracers) with different preprocessing choices to capture as most of the PET community as possible
- Need to finish alignment with other modalities
- Still need to agree on the level of information going into corresponding json files
google_doc_created: 2018-08
pull_request_created:
pull_request_merged:
- number: '024'
title: Computed Tomography scan
google_doc: https://docs.google.com/document/d/1fqnJZ18x5LJC8jiJ8yvPHUGFzNBZ6gW2kywYrUKWtuo/
content:
- raw
leads:
- given-names: Hugo
family-names: Boniface
bids_maintainers:
status:
- Lead seeking more contributors and experts.
blocking:
google_doc_created: 2018-11
pull_request_created:
pull_request_merged:
- number: '026'
title: Microelectrode Recordings
google_doc: https://docs.google.com/document/d/14KC1d5-Lx-7ZSMtwS7pVAAvz-2WR_uoo5FvsNirzqJw/
content:
- raw
leads:
- given-names: ' '
family-names: ' '
bids_maintainers:
status:
- BEP is open to new leadership, see also [BEP032 (animal electrophys)](https://docs.google.com/document/d/1oG-C8T-dWPqfVzL2W8HO3elWK8NIh2cOCPssRGv23n0/)
blocking:
- Searching for a new leader.
google_doc_created: 2018-04
pull_request_created:
pull_request_merged:
- number: '028'
title: Provenance
google_doc: https://docs.google.com/document/d/1vw3VNDof5cecv2PkFp7Lw_pNUTUo8-m8V4SIdtGJVKs/
content:
- metadata
leads:
- given-names: Satrajit S.
family-names: Ghosh
- given-names: Camille
family-names: Maumet
- given-names: Yaroslav O.
family-names: Halchenko
bids_maintainers:
status:
- '[Specification](https://bids.neuroimaging.io/bep028)'
- '[Set of examples](https://github.com/bids-standard/BEP028_BIDSprov)'
- 'Perspectives: Opening up to BIDS community for feedback'
- 'Perspectives: Engaging with software developers (in progress w/ SPM, AFNI)'
blocking:
google_doc_created: 2018-08
pull_request: https://github.com/bids-standard/bids-specification/pull/2099
pull_request_created: 2025-04
pull_request_merged:
- number: '032'
title: Microelectrode electrophysiology
google_doc: https://docs.google.com/document/d/1oG-C8T-dWPqfVzL2W8HO3elWK8NIh2cOCPssRGv23n0/
pull_request: https://github.com/bids-standard/bids-specification/pull/2307
html_preview: https://bids-specification--2307.org.readthedocs.build/en/2307/modality-specific-files/microelectrode-electrophysiology.html
content:
- raw
leads:
- given-names: Sylvain
family-names: Takerkart
- given-names: Benjamin
family-names: Dichter
- given-names: Yaroslav O.
family-names: Halchenko
- given-names: Lyuba
family-names: Zehl
- given-names: Andrew
family-names: Davison
bids_maintainers:
- given-names: Rémi
family-names: Gau
status:
- Decided on the new name (not just "Animal" but "Microelectrode"), modalities, datatypes
- Nearly finalized added metadata
- PR compiles green, preview is available
- 'Target: finalize & merge PR into the BIDS specs in 2025'
blocking:
- Need to prepare example datasets
- Need to start thinking about derived data (spike sorted)
google_doc_created: 2020-12
pull_request_created: 2022-11
pull_request_merged:
- number: '033'
title: Advanced Diffusion Weighted Imaging
google_doc: https://docs.google.com/document/d/1en4ByORlPqwDfZwNOOBTj0GwpYBcS0_2peqreTOvwDU/
content:
- raw
leads:
- given-names: James
family-names: Gholam
- given-names: Leandro
family-names: Beltrachini
- given-names: Filip
family-names: Szczepankiewicz
bids_maintainers:
status:
- 'New BEP, seeking contributors and collecting community feedback relating to: priority sequences to support, best supported binary structured formats
(e.g. CBOR? HDF5? MsgPack?).'
- Comments may be submitted directly on the document.
- Generating example datasets [here](https://github.com/JAgho/MISP_plot/tree/main) and determining best practice with vendors to record data in-sequence
blocking:
google_doc_created: 2021-04
pull_request_created:
pull_request_merged:
- number: '034'
title: Computational modeling
pull_request: https://github.com/bids-standard/bids-specification/pull/967
html_preview: https://bids-specification--967.org.readthedocs.build/en/967/modality-specific-files/computational-models.html
content:
- derivative
- metadata
leads:
- given-names: Michael
family-names: Schirner
- given-names: Petra
family-names: Ritter
bids_maintainers:
status:
- sim2bids app created
- easier to bring neural simulation data (especially in The Virtual Brain format) into the proposed BIDS Comp Model format
- 'Perspectives: A roadmap that coordinates efforts across BEPs would be appreciated.'
blocking:
- BEPs have overlapping concerns (Comp Models, Spaces and mappings, Generic BIDS connectivity schema, time series, Provenance, Atlases)
- need to separate concerns & provide a roadmap for each BEP
google_doc_created: 2021-02
pull_request_created: 2021-08
pull_request_merged:
- number: '035'
title: Modular extensions for individual participant data mega-analyses with non-compliant derivatives
display: Mega-analyses
google_doc: https://docs.google.com/document/d/1tFRNumQyIgjXBNC3brFDLO9FaikjL84noxK6Om-Ctik/
content:
- derivative
leads:
- given-names: Giuseppe
family-names: Gallitto
- given-names: Balint
family-names: Kincses
- given-names: Tamas
family-names: Spisak
bids_maintainers:
- given-names: Rémi
family-names: Gau
status:
- Meta-data harmonization with a term-mapper goes to BEP043
- 'Persperctive: Repeated community review of the simplified proposal'
blocking:
- Term-mapping is a general feature => complex, modular proposal
google_doc_created: 2021-12
pull_request_created:
pull_request_merged:
- number: '036'
title: Phenotypic Data Guidelines
google_doc: https://docs.google.com/document/d/1WTkfES8L0vItZVyyR68fc-9cO03jS-kCnMnw6602pbc/
content:
- raw
leads:
- given-names: Eric
family-names: Earl
- given-names: Samuel
family-names: Guay
- given-names: Sebastian
family-names: Urchs
- given-names: Arshitha
family-names: Basavaraj
bids_maintainers:
- given-names: Chris
family-names: Markiewicz
- given-names: Ross
family-names: Blair
status:
- BEP entering community review period shortly.
- BIDS specification PR 2123.
- BIDS examples PR 465.
blocking:
- A successful community review.
google_doc_created: 2021-10
pull_request_created: 2025-05
pull_request_merged:
- number: '037'
title: Non-Invasive Brain Stimulation
google_doc: https://docs.google.com/document/d/1drYd7kaNbHTcYPR3T_CRDsPcEbFSV7JbJUmhMPeWMqY/
current_repository: https://github.com/nigelrogasch/nibs-bids/tree/master/nibs-bids-v6/
current_preprint_editable: https://docs.google.com/document/d/1xfetyFkXh8kqObfCViUvku69pk4ZZw8BC5GL8_Cq9TI/edit?tab=t.0
current_preprint:
author_list: https://docs.google.com/spreadsheets/d/1oMImk-HXsyLZtTj3yIa1uY9LX3hVpsCiAGZP3oSV0Eo/edit?gid=0#gid=0
content:
- raw
leads:
- given-names: Giacomo
family-names: Bertazzoli
- given-names: Vittorio
family-names: Iacovella
- given-names: Peter J.
family-names: Fried
- given-names: Marta
family-names: Bortoletto
- given-names: Nigel
family-names: Rogasch
past leads (inactive):
- given-names: Carlo
family-names: Miniussi
bids_maintainers:
- given-names: Rémi
family-names: Gau
status:
- v1.0 2020-11-09 Initial work on specification with vittorio.iacovella@unitn.it carlo.miniussi@unitn.it marta.bortoletto@cognitiveneuroscience.it
- v1.0 2021-04 First example of a NIBS-BIDS dataset https://gin.g-node.org/CIMeC/TMS-EEG_brain_connectivity_BIDS
- V1.0 2021-11 Brainhack @Donosti with eleonora.marcantoni@cognitiveneuroscience.it martinabulgari3@gmail.com and g.guidali@campus.unimib.it
- V1.0 2022-02 First international NIBS-BIDS meeting
- V1.0 2022-11 First update of the BEP
- V2.0 2023-08 BEP 2.0 available with the new NIBS-BIDS logic for describing NIBS experiments
- V3.0 2024-07 BEP 3.0 available with a draft of the final BIDS structure.
- V4.0 2024-11 BEP 4.0 updated with a new, more comprehensive structure. Added int files for offline stimulation. Harmonized parameters with SimNIBS.
Use of events and scans files for online stimulation.
- V4.1 2024-12 BEP 4.1 Comments form December 9th, 2024 5th general meeting implemented, added FAQ section. Met with the BIDS maintenance team in
January 2025. Agreed on closing the open discussions, lock the google doc and start the PR.
- V6.2 2026-03 BEP 6.2 Comments form December 12th, 2025 6th general meeting implemented. Met with the BIDS steering team in March 2026. Agreed on
NSF FAIROS application on NIBS-BIDS implementation https://www.nsf.gov/funding/opportunities/fairos-findable-accessible-interoperable-reusable-open-science.
Agreed on creating a preprint version of the BEP037 (to bi cited as a work-in-progress).
blocking:
google_doc_created: 2022-09
pull_request_created:
pull_request_merged:
- number: '039'
title: Dimensionality reduction-based networks
google_doc: https://docs.google.com/document/d/1GTWsj0MFQedXjOaNk6H0or6IDVFyMAysrJ9I4Zmpz2E/
content:
- raw
leads:
- given-names: Arianna
family-names: Sala
- given-names: Anibal
family-names: Sólon
- given-names: Cyrus
family-names: Eierud
- given-names: Franco
family-names: Pestilli
- given-names: Peer
family-names: Herholz
bids_maintainers:
status:
- adapted general spatial derivatives proposal, meaning using `<modality>map`, for example `eegmap` and `boldmap` for spatial components
- decided on `model-` and `items-` as keys to denote the utilized model and component number, if files are 3D, respectively
- updated meta-data and examples
blocking:
google_doc_created: 2021-10
pull_request_created:
pull_request_merged:
- number: '040'
title: Functional Ultrasound
google_doc: https://docs.google.com/document/d/1W3z01mf1E8cfg_OY7ZGqeUeOKv659jCHQBXavtmT-T8/
content:
- raw
leads:
- given-names: Jean-Charles
family-names: Mariani
- given-names: Samuel
family-names: Le Meur-Diebolt
- given-names: Thomas
family-names: Deffieux
bids_maintainers:
- given-names: Rémi
family-names: Gau
status:
- All issues raised on the original BEP have been resolved.
- Regular meetings with contributors scheduled.
- Scanner coordinate system has been proposed to harmonize affine transformations with moving probes.
- 'Perspectives: Starting to bidsify existing datasets to stress test the BEP.'
- 'Perspectives: timing metadata has been copied from the fMRI-BIDS specification, but evolutions could be discussed to handle non-stable sampling
frequencies.'
blocking:
google_doc_created: 2023-03
pull_request_created:
pull_request_merged:
- number: '041'
title: Statistical Model Derivatives
google_doc: https://docs.google.com/document/d/1KHzp-yk8KXvkUIhtN71WU0m4P4kKT9C1yvI-i9_kNeY/
content:
- derivative
leads:
- given-names: Taylor
family-names: Salo
bids_maintainers:
- given-names: Taylor
family-names: Salo
status:
- New BEP, collecting community comments and feedback.
- All collaborators are welcome.
blocking:
google_doc_created: 2022-08
pull_request_created:
pull_request_merged:
- number: '043'
title: BIDS Term Mapping
google_doc: https://docs.google.com/document/d/1LACjc5hFXDpa2l_QddBPR41Vce_gglGv9WeuBB7LsBU/
content:
- metadata
leads:
- given-names: Christopher J.
family-names: Markiewicz
- given-names: Eric A.
family-names: Earl
bids_maintainers:
- given-names: Christopher J.
family-names: Markiewicz
- given-names: Eric A.
family-names: Earl
status:
- For being able to map non-BIDS data as BIDS-compatible.
- Now collecting community comments and feedback.
- All collaborators are welcome.
blocking:
google_doc_created: 2024-03
pull_request_created:
pull_request_merged:
- number: '044'
title: Stimuli
pull_request: https://github.com/bids-standard/bids-specification/pull/2022
html_preview: https://bids-specification--2022.org.readthedocs.build/en/2022/modality-specific-files/stimuli.html
leads:
- given-names: Seyed Yahya
family-names: Shirazi
- given-names: Dora
family-names: Hermes
- given-names: Yaroslav O.
family-names: Halchenko
- given-names: Kay
family-names: Robbins
- given-names: Scott
family-names: Makeig
bids_maintainers:
- given-names: Rémi
family-names: Gau
status:
- Community comments and feedback are being collected (January 2025)
- To harmonize and make more reusable stimuli content under stimuli/
- Collecting community comments and feedback. All collaborators are welcome.
- 'Original issue: [#153](https://github.com/bids-standard/bids-specification/issues/153)'
content:
- raw
blocking:
google_doc_created: 2023-09
pull_request_created: 2024-12
pull_request_merged:
- number: '045'
title: Peripheral Physiological Recordings
display: Physio
pull_request: https://github.com/bids-standard/bids-specification/pull/2267
html_preview: https://bids-specification--2267.org.readthedocs.build/en/2267/modality-specific-files/physiological-recordings.html
google_doc: https://docs.google.com/document/d/1oTfjzY5ZnLIYd0kPPWhR81sBmMuy_jC5YYIaqj6OhSA/edit
leads:
- given-names: Mary
family-names: Miedema
- given-names: Stefano
family-names: Moia
- given-names: Sourav
family-names: Kulkarni
bids_maintainers:
- given-names: Seyed Yahya
family-names: Shirazi
status:
- No longer developed in google doc, moved to <https://github.com/physiopy/bids-specification-physio>.
- To update standards for physiological data for improved clarity and a broader range of use cases.
- Now collecting community comments and feedback. All collaborators are welcome.
- 'Original issue: [#1675](https://github.com/bids-standard/bids-specification/issues/1675).'
content:
- raw
blocking:
google_doc_created: 2024-08
pull_request_created: 2025-11
pull_request_merged:
- number: '046'
title: Diffusion Tractography
display: Tractography
google_doc: https://docs.google.com/document/d/1ubDQ2RhgjnfGqoeukzEkPV9YEHhfYMERrj7-3b0c2HI/edit
leads:
- given-names: Robert E.
family-names: Smith
- given-names: Ariel
family-names: Rokem
- given-names: Franco
family-names: Pestilli
status:
- Porting comprehensive description of streamline tractography mechanisms into specification - 10.1016/B978-0-12-817057-1.00023-8
- Determine appropriate resolution with TRX development - https://tee-ar-ex.github.io/trx-python/
- Decide on scope of BEP; e.g. whether to include tractometry, complex tract delineation
content:
- derivative
blocking:
google_doc_created: 2022-02
pull_request_created:
pull_request_merged:
- number: '047'
title: Audio/video recordings for behavioral experiments
display: Behavioral audio/video recordings
pull_request: https://github.com/bids-standard/bids-specification/pull/2231
html_preview: https://bids-specification--2231.org.readthedocs.build/en/2231/modality-specific-files/behavioral-experiments.html
content:
- raw
leads:
- given-names: Benjamin
family-names: Dichter
bids_maintainers:
- given-names: Seyed Yahya
family-names: Shirazi
status:
- Adds support for storing audio and video behavioral recordings (new `_audio` and `_video` suffixes) in the `beh/` directory.
blocking:
google_doc_created:
pull_request_created: 2025-10
pull_request_merged:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,165 @@
# BIDS Specification Reference
> **Note**: The canonical, machine-readable source of truth is `bids_schema.json` (in this directory), exported from the [BIDS Schema](https://github.com/bids-standard/bids-specification/tree/master/src/schema). The tables below are a human-readable summary. When the two disagree, trust the schema.
## Entity Table
Complete list of BIDS entities, their keys, and where they apply. **Rows are listed in the required filename ordering** — entities must appear in this order in BIDS filenames. This order is defined in the schema at `rules.entities` (see `bids_schema.json`).
| # | Entity | Key | Format | Applies to |
|---|--------|-----|--------|------------|
| 1 | Subject | `sub-` | `<label>` (alphanumeric) | All files (required) |
| 2 | Template | `tpl-` | `<label>` | derivatives (template-based) |
| 3 | Session | `ses-` | `<label>` | All datatypes |
| 4 | Cohort | `cohort-` | `<label>` | derivatives (template cohorts) |
| 5 | Sample | `sample-` | `<label>` | microscopy |
| 6 | Task | `task-` | `<label>` | func, eeg, meg, ieeg, beh, pet, nirs, motion |
| 7 | Tracking system | `tracksys-` | `<label>` | motion |
| 8 | Acquisition | `acq-` | `<label>` | All datatypes |
| 9 | Nucleus | `nuc-` | `<label>` | MR spectroscopy |
| 10 | Volume | `voi-` | `<label>` | MR spectroscopy |
| 11 | Contrast enhancing agent | `ce-` | `<label>` | anat |
| 12 | Tracer | `trc-` | `<label>` | pet |
| 13 | Stain | `stain-` | `<label>` | microscopy |
| 14 | Reconstruction | `rec-` | `<label>` | anat, func, pet |
| 15 | Direction | `dir-` | `<label>` | fmap, dwi, perf, func |
| 16 | Run | `run-` | `<index>` (integer) | All datatypes |
| 17 | Modality | `mod-` | `<label>` | fieldmaps |
| 18 | Echo | `echo-` | `<index>` | func, fmap |
| 19 | Flip | `flip-` | `<index>` | anat (quantitative MRI) |
| 20 | Inversion | `inv-` | `<index>` | anat (quantitative MRI) |
| 21 | Magnetization transfer | `mt-` | `on`/`off` | anat (quantitative MRI) |
| 22 | Part | `part-` | `mag`/`phase`/`real`/`imag` | anat, func |
| 23 | Processing | `proc-` | `<label>` | eeg, meg, ieeg |
| 24 | Hemisphere | `hemi-` | `L`/`R` | derivatives (surface data) |
| 25 | Space | `space-` | `<label>` | derivatives |
| 26 | Split | `split-` | `<index>` | func, dwi, eeg, meg, ieeg |
| 27 | Recording | `recording-` | `<label>` | physio, stim, eeg, meg |
| 28 | Chunk | `chunk-` | `<index>` | large files split across chunks |
| 29 | Atlas | `atlas-` | `<label>` | derivatives (atlas-based) |
| 30 | Segmentation | `seg-` | `<label>` | derivatives |
| 31 | Scale | `scale-` | `<label>` | derivatives |
| 32 | Resolution | `res-` | `<label>` | derivatives |
| 33 | Density | `den-` | `<label>` | derivatives (surface meshes) |
| 34 | Label | `label-` | `<label>` | derivatives (segmentation labels) |
| 35 | Description | `desc-` | `<label>` | derivatives only |
## Datatypes (Top-Level Directories)
| Datatype | Description | Common Suffixes |
|----------|-------------|-----------------|
| `anat` | Structural MRI | `T1w`, `T2w`, `FLAIR`, `T2star`, `inplaneT1`, `inplaneT2`, `PDw`, `T1map`, `T2map`, `T1rho`, `UNIT1`, `MP2RAGE`, `MTR`, `MTS` |
| `func` | Functional MRI | `bold`, `cbv`, `sbref` |
| `dwi` | Diffusion-weighted imaging | `dwi`, `sbref` |
| `fmap` | Fieldmaps | `phasediff`, `phase1`, `phase2`, `magnitude1`, `magnitude2`, `fieldmap`, `epi` |
| `perf` | Perfusion imaging (ASL) | `asl`, `m0scan`, `aslcontext` |
| `eeg` | Electroencephalography | `eeg`, `channels`, `electrodes`, `events`, `coordsystem` |
| `meg` | Magnetoencephalography | `meg`, `channels`, `coordsystem`, `events`, `headshape` |
| `ieeg` | Intracranial EEG | `ieeg`, `channels`, `electrodes`, `events`, `coordsystem` |
| `pet` | Positron Emission Tomography | `pet`, `blood` |
| `micr` | Microscopy | `2PE`, `BF`, `CARS`, `CONF`, `DIC`, `DF`, `FLUO`, `MPE`, `NLO`, `OCT`, `PC`, `PLI`, `SRS`, `TL` |
| `beh` | Behavioral data (no imaging) | `events`, `beh`, `physio`, `stim` |
| `motion` | Motion capture | `motion`, `channels`, `events` |
| `nirs` | Near-infrared spectroscopy | `nirs`, `channels`, `optodes`, `coordsystem`, `events` |
## File Extensions
| Extension | Description |
|-----------|-------------|
| `.nii.gz` | Compressed NIfTI (standard for MRI/fMRI/DWI) |
| `.nii` | Uncompressed NIfTI |
| `.json` | JSON sidecar metadata |
| `.tsv` | Tab-separated values (events, participants, etc.) |
| `.bvec` | b-vectors (DWI gradient directions) |
| `.bval` | b-values (DWI gradient strengths) |
| `.edf` | European Data Format (EEG) |
| `.bdf` | BioSemi Data Format (EEG) |
| `.vhdr`/`.vmrk`/`.eeg` | BrainVision format (EEG) |
| `.set` | EEGLAB format (EEG) |
| `.fif` | Elekta/MEGIN format (MEG) |
| `.ds` | CTF dataset (MEG) |
| `.sqd`/`.con` | KIT/Yokogawa (MEG) |
## Required Files
### Dataset-level (always required)
- `dataset_description.json`
### Dataset-level (recommended)
- `README` or `README.md`
- `CHANGES`
- `participants.tsv` + `participants.json`
- `LICENSE`
### Run-level (recommended)
- `sub-<label>/[ses-<label>/]sub-<label>[_ses-<label>]_scans.tsv` - per-run acquisition metadata
### Modality-specific required files
- **func/bold**: corresponding `_events.tsv` for task data; `TaskName` in JSON sidecar
- **dwi**: `.bvec` and `.bval` files
- **eeg/meg/ieeg**: `_channels.tsv`, `_events.tsv`
- **perf/asl**: `_aslcontext.tsv`
## Directory Structure Rules
1. Subject directories are named `sub-<label>` and sit at dataset root
2. Session directories `ses-<label>` are optional; if used, must be used for ALL subjects
3. Datatype directories (`anat/`, `func/`, etc.) sit inside subject (or session) directories
4. `sourcedata/` stores raw unprocessed data (DICOM, etc.) - not validated
5. `derivatives/` stores processed outputs - each pipeline in its own subdirectory
6. `code/` stores analysis scripts
7. `stimuli/` stores stimulus files used during acquisition
8. `phenotype/` stores questionnaire/behavioral data not tied to specific imaging
## Metadata Inheritance
JSON metadata cascades from higher to lower directories. If the same key appears at multiple levels, the most specific (closest to the data file) wins.
**Resolution order** (highest priority first):
1. File-level sidecar: `sub-01/func/sub-01_task-rest_bold.json`
2. Subject-level sidecar: `sub-01/sub-01_task-rest_bold.json`
3. Dataset-level sidecar: `task-rest_bold.json`
This avoids duplicating metadata that is constant across subjects (e.g., `RepetitionTime`, `TaskName`).
## Standard Template Spaces
Common `space-` values used in derivatives:
| Space Label | Description |
|-------------|-------------|
| `MNI152NLin2009cAsym` | MNI 2009c nonlinear asymmetric (fMRIPrep default) |
| `MNI152NLin6Asym` | MNI 6th-generation nonlinear asymmetric (FSL default) |
| `MNI152Lin` | MNI linear registration |
| `MNIPediatricAsym` | Pediatric MNI templates |
| `T1w` | Individual subject's T1w native space |
| `fsnative` | FreeSurfer individual surface space |
| `fsaverage` | FreeSurfer average surface (164k vertices) |
| `fsaverage5` | FreeSurfer average surface (10k vertices) |
| `fsaverage6` | FreeSurfer average surface (40k vertices) |
| `fsLR` | HCP fs_LR surface space |
| `OASIS30ANTs` | OASIS-30 ANTs template |
| `UNCInfant` | UNC infant templates |
Full list managed by TemplateFlow: https://www.templateflow.org/
## Specification Changelog (Selected)
| Version | Key Changes |
|---------|-------------|
| 1.10.0 | Motion capture modality; refined derivative entity rules |
| 1.9.0 | NIRS modality; Python-based validator reference implementation |
| 1.8.0 | Microscopy modality; `chunk-` entity for large files |
| 1.7.0 | PET modality fully specified |
| 1.6.0 | EEG/MEG/iEEG matured; `_coordsystem.json` |
| 1.5.0 | Genetic descriptors; ASL perfusion |
| 1.4.0 | `dataset_description.json` expanded; derivatives framework |
| 1.0.0 | Initial release: MRI only (anat, func, dwi, fmap) |
## Entity Label Rules
- **Labels** (`<label>`): alphanumeric only, no special characters, no leading zeros (except `run-`)
- **Indices** (`<index>`): non-negative integers, zero-padded to equal width within a dataset (e.g., `run-01`, `run-02`)
- Subject labels: typically numeric (`01`, `02`) but can be alphanumeric (`CON01`, `PAT01`)
- Session labels: descriptive (`pre`, `post`, `baseline`, `followup`) or numeric
- Task labels: brief, descriptive, no spaces (`rest`, `nback`, `faces`, `gonogo`)
+475
View File
@@ -0,0 +1,475 @@
# BIDS Conversion Tools Reference
This reference covers detailed workflows for converting DICOM and other raw data formats to BIDS using the three main conversion tools.
## HeuDiConv
HeuDiConv is the most flexible DICOM-to-BIDS converter. It supports three usage modes — from fully automatic turnkey conversion to fully custom heuristics — and handles duplicates, provenance tracking, and sourcedata archiving out of the box.
**Repository**: https://github.com/nipy/heudiconv
**Docs**: https://heudiconv.readthedocs.io/
**Tutorials**: https://heudiconv.readthedocs.io/en/latest/tutorials.html
### Installation
```bash
uv pip install heudiconv
# HeuDiConv wraps dcm2niix for the actual conversion
# dcm2niix is usually installed as a dependency, but can also be installed via:
# conda install -c conda-forge dcm2niix
# or: apt-get install dcm2niix
```
### Mode 1: ReproIn (Turnkey Conversion — Recommended for New Studies)
If scanner protocol names follow the [ReproIn naming convention](https://github.com/repronim/reproin), conversion is fully automatic with no heuristic file to write. ReproIn is a setup for automatic generation of sharable, version-controlled BIDS datasets directly from MR scanners.
```bash
# Turnkey conversion — just point at DICOMs, HeuDiConv does the rest
heudiconv --files dicom/001 -o data -f reproin --bids --minmeta
```
#### ReproIn Protocol Naming Rules
Protocol names encode BIDS entities directly. Format: `<seqtype>[-<suffix>][_<entity>-<label>]...`
| Protocol name at scanner | BIDS output |
|--------------------------|-------------|
| `anat-T1w` or just `anat` | `sub-XX/anat/sub-XX_T1w.nii.gz` |
| `func-bold_task-rest` or `func_task-rest` | `sub-XX/func/sub-XX_task-rest_bold.nii.gz` |
| `dwi_dir-AP` | `sub-XX/dwi/sub-XX_dir-AP_dwi.nii.gz` |
| `fmap_dir-PA` or `fmap-epi_dir-PA` | `sub-XX/fmap/sub-XX_dir-PA_epi.nii.gz` |
| `fmap_acq-4mm` | `sub-XX/fmap/sub-XX_acq-4mm_epi.nii.gz` |
**Key features:**
- **Default suffixes**: `anat` defaults to `T1w`, `func` to `bold`, `fmap` to `epi` — so they can be omitted
- **Subject ID**: extracted automatically from DICOM metadata (Patient ID)
- **Session**: set once on any sequence (e.g., `anat-scout_ses-pre`) and ReproIn propagates it to all sequences in that scanner Program/Patient
- **Duplicate runs**: automatically numbered (`run-01`, `run-02`, ...) when the same protocol is run multiple times
- **Locator hierarchy**: output is nested under Region/Exam from the scanner's Study Description (customizable with `--locator`)
- **sourcedata**: original DICOMs are archived as `.tgz` files under `sourcedata/` for reproducibility
- **Dashes in names**: scanners may strip dashes from protocol names during DICOM export — ReproIn handles this gracefully
#### ReproIn Overview
See also:
- [ReproIn Walkthrough](https://github.com/repronim/reproin#walkthrough) for scanner setup
- [ReproNim Webinar slides and recording](https://github.com/repronim/reproin#presentations) on HeuDiConv + ReproIn
### Mode 2: Custom Heuristic Mapping into ReproIn (For Existing Data)
If you already have collected data with non-ReproIn protocol names (or cannot control scanner naming), you can write a thin heuristic that maps your protocol names into ReproIn conventions. This gives you all ReproIn benefits (automatic entity handling, duplicate management, sourcedata archiving) while accommodating arbitrary scanner naming.
See https://github.com/repronim/reproin/issues/18 for a brief HOWTO on this approach.
The idea is to write a heuristic whose `infotodict` returns keys that follow ReproIn naming patterns, so the ReproIn machinery handles the rest.
### Mode 3: Custom Heuristic (Full Flexibility)
For studies with complex mappings or non-standard requirements, write a full Python heuristic file. This is the most common workflow for retrospective conversion of existing datasets.
#### Step 1: Reconnaissance — Discover DICOM series
```bash
# -f convertall: built-in heuristic that lists all series without converting
# -c none: don't convert, just generate dicominfo.tsv
heudiconv \
--files dicom/219/itbs/*/*.dcm \
-s 219 \
-f convertall \
-c none \
-o Nifti/
```
This creates `.heudiconv/219/info/dicominfo.tsv` containing one row per DICOM series with columns:
- `series_id`, `sequence_name`, `protocol_name`, `series_description`
- `dim1`-`dim4` (image dimensions), `TR`, `TE`, `image_type`
- `is_derived`, `is_motion_corrected` — important for filtering
Review this TSV (open in a spreadsheet) to understand what was acquired and plan the mapping to BIDS names. Step 1 only needs to be done once per project.
#### Step 2: Write a heuristic file
```python
"""HeuDiConv heuristic for a typical fMRI study.
Study design:
- T1w MPRAGE anatomical
- Resting-state BOLD
- Task BOLD (n-back working memory)
- DWI with two phase-encoding directions
- Fieldmap (phase-difference)
"""
def create_key(template, outtype=('nii.gz',), annotation_classes=None):
if template is None or not template:
raise ValueError('Template must be a valid format string')
return template, outtype, annotation_classes
def infotodict(seqinfo):
"""Heuristic evaluator for determining which runs belong where.
Parameters
----------
seqinfo : list of namedtuples
Each namedtuple has fields: .series_id, .sequence_name,
.protocol_name, .series_description, .dim1, .dim2, .dim3, .dim4,
.TR, .TE, .is_derived, .is_motion_corrected, .image_type, etc.
Returns
-------
info : dict
Keys are tuples from create_key(), values are lists of series_id
"""
# Define BIDS output templates
t1w = create_key(
'sub-{subject}/{session}/anat/sub-{subject}_{session}_T1w'
)
rest_bold = create_key(
'sub-{subject}/{session}/func/sub-{subject}_{session}_task-rest_bold'
)
# {item:02d} auto-numbers runs when the same protocol is run multiple times
nback_bold = create_key(
'sub-{subject}/{session}/func/sub-{subject}_{session}_task-nback_run-{item:02d}_bold'
)
dwi_AP = create_key(
'sub-{subject}/{session}/dwi/sub-{subject}_{session}_dir-AP_dwi'
)
dwi_PA = create_key(
'sub-{subject}/{session}/dwi/sub-{subject}_{session}_dir-PA_dwi'
)
fmap_phasediff = create_key(
'sub-{subject}/{session}/fmap/sub-{subject}_{session}_phasediff'
)
fmap_mag1 = create_key(
'sub-{subject}/{session}/fmap/sub-{subject}_{session}_magnitude1'
)
fmap_mag2 = create_key(
'sub-{subject}/{session}/fmap/sub-{subject}_{session}_magnitude2'
)
info = {
t1w: [], rest_bold: [], nback_bold: [],
dwi_AP: [], dwi_PA: [],
fmap_phasediff: [], fmap_mag1: [], fmap_mag2: [],
}
for s in seqinfo:
protocol = s.protocol_name.lower()
series_desc = s.series_description.lower() if s.series_description else ''
# Anatomical — filter by dim3 to exclude localizers
if ('mprage' in protocol or 't1w' in protocol) and s.dim3 > 100:
info[t1w].append(s.series_id)
# Functional — filter by dim4 and exclude MOCO series
elif 'rest' in protocol and s.dim4 > 10 and not s.is_motion_corrected:
info[rest_bold].append(s.series_id)
elif 'nback' in protocol and s.dim4 > 10 and not s.is_motion_corrected:
info[nback_bold].append(s.series_id)
# Diffusion
elif ('dti' in protocol or 'dwi' in protocol) and s.dim4 > 1:
if 'ap' in protocol or 'ap' in series_desc:
info[dwi_AP].append(s.series_id)
elif 'pa' in protocol or 'pa' in series_desc:
info[dwi_PA].append(s.series_id)
# Fieldmaps
elif 'field' in protocol or 'fmap' in protocol:
if 'ph' in s.image_type_text.lower():
info[fmap_phasediff].append(s.series_id)
elif s.series_description and 'e1' in s.series_description.lower():
info[fmap_mag1].append(s.series_id)
elif s.series_description and 'e2' in s.series_description.lower():
info[fmap_mag2].append(s.series_id)
return info
```
#### Step 3: Convert
```bash
# Convert with custom heuristic
heudiconv \
--files dicom/219/itbs/*/*.dcm \
-s 219 \
-ss itbs \
-f Nifti/code/heuristic.py \
-c dcm2niix \
--bids \
--minmeta \
-o Nifti/
# Or using -d template for batch conversion of multiple subjects
heudiconv \
-d /path/to/dicoms/{subject}/*/*/*.dcm \
-s 01 02 03 04 05 \
-f my_heuristic.py \
-c dcm2niix \
--bids \
--minmeta \
-o /path/to/bids_output
# Key flags:
# --files : point to specific DICOM files/directories
# -d : DICOM path template ({subject}, {session} are replaced)
# -s : subject label(s)
# -ss : session label
# -f : heuristic file path, or built-in name (reproin, convertall)
# -c : converter (dcm2niix, none)
# --bids / -b : output BIDS structure (creates JSON sidecars, etc.)
# --minmeta : prevent excess DICOM metadata from overflowing JSON sidecars
# -o : output directory
# --overwrite : re-run conversion overwriting existing files
```
### The .heudiconv Directory
Every conversion creates/updates a `.heudiconv/` hidden directory alongside the output:
- `.heudiconv/<subject>/info/dicominfo.tsv` — DICOM series metadata
- `.heudiconv/<subject>/info/<heuristic>.py` — copy of the heuristic used
- Conversion records for each subject/session
**Important**: If you re-run conversion for a subject/session that was already processed, HeuDiConv silently reuses cached conversion info from `.heudiconv/`. If troubleshooting, delete the subject's entry from `.heudiconv/` (or the whole directory) and re-run.
Keep `.heudiconv/` with your data — together with `code/` it provides valuable provenance information.
### HeuDiConv Tips
1. **Always use `--minmeta`** to prevent excess DICOM metadata from overflowing JSON sidecars — fMRIPrep and MRIQC may crash on bloated JSON files
2. **Use `{item:02d}` in templates** for auto-numbering runs: if multiple series match, they get `run-01`, `run-02`, etc. Without this, later runs silently overwrite earlier ones
3. **Filter by `dim3`/`dim4`** to exclude localizers (small `dim3`) and single-volume scouts (`dim4 == 1`)
4. **Check `s.is_motion_corrected`** to exclude scanner-generated MOCO series (e.g., `if not s.is_motion_corrected`)
5. **Check `s.is_derived`** to skip other derived/processed series
6. **Store heuristic with dataset** under `code/` for reproducibility
7. **Use `--files`** when DICOM organization doesn't follow a clean `{subject}` template pattern
8. **For new studies**: prefer ReproIn protocol naming from the start — it eliminates the need for custom heuristics entirely
9. **For existing data with arbitrary names**: consider the "map into reproin" approach rather than writing a fully custom heuristic — you get duplicate handling, session propagation, and other ReproIn features for free
## dcm2bids (Configuration-File-Based)
dcm2bids uses JSON configuration files instead of Python heuristics. Simpler for straightforward datasets.
**Repository**: https://github.com/UNFmontreal/Dcm2Bids
**Docs**: https://unfmontreal.github.io/Dcm2Bids/
### Installation
```bash
uv pip install dcm2bids
# Also installs dcm2niix
```
### Workflow
#### Step 1: Scaffold a BIDS directory
```bash
dcm2bids_scaffold -o /path/to/bids_output
```
Creates the basic BIDS structure with `dataset_description.json`, `README`, `.bidsignore`, etc.
#### Step 2: Run helper to inspect DICOM metadata
```bash
dcm2bids_helper -d /path/to/dicom_dir -o /path/to/bids_output
```
Creates `tmp_dcm2bids/helper/` with converted NIfTI files and JSON sidecars. Review the JSON files to find distinguishing metadata fields.
#### Step 3: Write configuration file
```json
{
"descriptions": [
{
"id": "id_t1w",
"datatype": "anat",
"suffix": "T1w",
"criteria": {
"SeriesDescription": "*MPRAGE*",
"ImageType": ["ORIGINAL", "PRIMARY", "M", "ND", "NORM"]
}
},
{
"id": "id_bold_rest",
"datatype": "func",
"suffix": "bold",
"custom_entities": "task-rest",
"criteria": {
"SeriesDescription": "*REST*BOLD*",
"ImageType": ["ORIGINAL", "PRIMARY", "M", "ND", "MOSAIC"]
},
"sidecar_changes": {
"TaskName": "rest"
}
},
{
"id": "id_bold_nback",
"datatype": "func",
"suffix": "bold",
"custom_entities": "task-nback",
"criteria": {
"SeriesDescription": "*NBACK*",
"EchoTime": 0.03
},
"sidecar_changes": {
"TaskName": "nback"
}
},
{
"id": "id_dwi",
"datatype": "dwi",
"suffix": "dwi",
"custom_entities": "dir-AP",
"criteria": {
"SeriesDescription": "*DTI*AP*"
}
},
{
"id": "id_fmap_phasediff",
"datatype": "fmap",
"suffix": "phasediff",
"criteria": {
"SeriesDescription": "*field*map*",
"EchoTime1": 0.00492,
"EchoTime2": 0.00738
},
"sidecar_changes": {
"IntendedFor": [
"bids::sub-{subject}/func/sub-{subject}_task-rest_bold.nii.gz",
"bids::sub-{subject}/func/sub-{subject}_task-nback_bold.nii.gz"
]
}
}
]
}
```
**Configuration file fields:**
- `datatype`: BIDS datatype (`anat`, `func`, `dwi`, `fmap`, etc.)
- `suffix`: BIDS suffix (`T1w`, `bold`, `dwi`, etc.)
- `custom_entities`: additional BIDS entities (`task-rest`, `dir-AP`, `acq-highres`, etc.)
- `criteria`: dictionary of DICOM/JSON metadata fields to match (supports wildcards `*`)
- `sidecar_changes`: fields to add/modify in the output JSON sidecar
- `id`: arbitrary identifier for the description (for logging)
#### Step 4: Convert
```bash
# Single subject
dcm2bids -d /path/to/dicom_dir -p 01 -c dcm2bids_config.json -o /path/to/bids_output
# With session
dcm2bids -d /path/to/dicom_dir -p 01 -s pre -c dcm2bids_config.json -o /path/to/bids_output
# Flags:
# -d : DICOM source directory
# -p : participant label
# -s : session label (optional)
# -c : configuration file
# -o : output BIDS directory
# --auto_extract_entities : auto-detect run numbers from DICOM
# --force_dcm2bids : overwrite existing conversions
```
### dcm2bids Tips
1. **Use `dcm2bids_helper` first** to see exactly what metadata dcm2niix extracts
2. **Criteria matching uses wildcards** (`*`) and is case-sensitive
3. **Multiple criteria** are ANDed together; use the most specific combination
4. **`sidecar_changes`** can inject any BIDS metadata (useful for `TaskName`, `IntendedFor`)
5. **Store config file** under `code/dcm2bids_config.json` for reproducibility
## BIDScoin (GUI + YAML Configuration)
BIDScoin provides a graphical interface and YAML-based configuration. Good for users who prefer visual mapping.
**Repository**: https://github.com/Donders-Institute/bidscoin
**Docs**: https://bidscoin.readthedocs.io/
### Installation
```bash
uv pip install bidscoin
# Optional: install with all plugin dependencies
uv pip install "bidscoin[all]"
```
### Workflow
```bash
# Step 1: Create a bidsmap template by scanning DICOMs
bidsmapper /path/to/raw /path/to/bids
# Step 2: Edit the bidsmap (launches GUI)
bidseditor /path/to/bids
# Step 3: Convert using the finalized bidsmap
bidscoiner /path/to/raw /path/to/bids
```
### BIDScoin Tips
1. **GUI-based editing** is BIDScoin's strength - the `bidseditor` shows DICOM metadata alongside BIDS mapping
2. **YAML bidsmap** can be edited manually if preferred
3. **Plugin architecture** supports custom conversion backends beyond dcm2niix
4. **Good for multi-site studies** where protocol names vary - visual mapping makes differences obvious
## Comparison
| Feature | HeuDiConv | dcm2bids | BIDScoin |
|---------|-----------|----------|----------|
| Configuration | Python heuristic | JSON config | YAML + GUI |
| Flexibility | Highest (full Python) | Medium (criteria matching) | Medium (plugin system) |
| Learning curve | Steeper (Python) | Moderate | Gentlest (GUI) |
| Batch processing | Excellent | Good | Good |
| ReproIn support | Built-in | No | No |
| DataLad integration | Built-in | No | No |
| Best for | Complex studies, automation | Simple-to-moderate studies | Visual learners, multi-site |
| Active development | Yes | Yes | Yes |
## Post-Conversion Checklist
After converting DICOM to BIDS with any tool:
1. **Run the BIDS validator**: `bids-validator /path/to/bids_output`
2. **Check JSON sidecars** for critical fields (`RepetitionTime`, `TaskName`, `SliceTiming`, `PhaseEncodingDirection`)
3. **Verify NIfTI headers** match expectations (dimensions, voxel sizes, orientation)
4. **Add missing metadata** that dcm2niix couldn't extract from DICOM
5. **Create `participants.tsv`** with demographic data
6. **Write events files** for task fMRI
7. **Write `README`** describing the dataset
8. **Deface anatomical images** if sharing data
9. **Run `bids-validator` again** after any manual modifications
## Common DICOM-to-BIDS Pitfalls
### Multiband/SMS sequences
- dcm2niix may split slices incorrectly for multiband data
- Check `dim4` (number of volumes) matches expectations
- Verify `SliceTiming` is correct for the multiband factor
### Dual-echo fieldmaps
- Siemens stores both echoes in one series; dcm2niix splits them
- GE/Philips may store them as separate series
- Verify `EchoTime1` < `EchoTime2` in the phasediff sidecar
### Phase encoding direction
- DICOM `InPlanePhaseEncodingDirection` → BIDS `PhaseEncodingDirection`
- Mapping depends on acquisition orientation and NIfTI axis conventions
- **Always verify** by checking the actual distortion pattern in the images
### Multi-run numbering
- Ensure runs are numbered sequentially (`run-01`, `run-02`)
- HeuDiConv: use `{item:02d}` placeholder
- dcm2bids: use `--auto_extract_entities` or manually specify runs
### Derived/processed series
- Scanners may export inline-processed data (e.g., motion-corrected, distortion-corrected)
- These should NOT be converted to BIDS raw data
- Filter by `ImageType` containing `DERIVED` or `is_derived` flag in HeuDiConv
+365
View File
@@ -0,0 +1,365 @@
# BIDS Metadata Fields Reference
This reference lists the required and recommended JSON sidecar fields for each BIDS modality.
**Legend:**
- **R** = Required
- **REC** = Recommended
- **OPT** = Optional
## Common MRI Fields (All MRI Modalities)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `MagneticFieldStrength` | REC | number | Field strength in Tesla |
| `Manufacturer` | REC | string | Scanner manufacturer |
| `ManufacturersModelName` | REC | string | Scanner model |
| `DeviceSerialNumber` | REC | string | Scanner serial number |
| `StationName` | REC | string | Scanner station name |
| `SoftwareVersions` | REC | string | Scanner software version |
| `InstitutionName` | REC | string | Name of institution |
| `InstitutionAddress` | REC | string | Address of institution |
| `InstitutionalDepartmentName` | REC | string | Department name |
## Anatomical MRI (anat/)
### T1w, T2w, FLAIR, T2star, PDw
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `RepetitionTime` | REC | number | TR in seconds |
| `EchoTime` | REC | number | TE in seconds |
| `InversionTime` | REC | number | TI in seconds (if applicable) |
| `FlipAngle` | REC | number | Flip angle in degrees |
| `SequenceName` | REC | string | Pulse sequence name |
| `SequenceVariant` | REC | string | Variant of the sequence |
| `ScanningSequence` | REC | string | General description |
| `PulseSequenceType` | REC | string | Type of pulse sequence |
| `NonlinearGradientCorrection` | REC | boolean | Whether applied |
| `ParallelReductionFactorInPlane` | REC | number | iPAT/GRAPPA factor |
| `ContrastBolusIngredient` | REC | string | Active contrast ingredient |
### Quantitative MRI (T1map, T2map, etc.)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `RepetitionTimeExcitation` | R | number | Excitation TR in seconds |
| `RepetitionTimePrepration` | R | number | Preparation TR in seconds |
| `FlipAngle` | R | number/array | Flip angle(s) in degrees |
| `MTState` | R | boolean | Magnetization transfer on/off |
| `SpoilingState` | REC | boolean | Whether RF spoiling applied |
| `SpoilingType` | REC | string | `RF`, `GRADIENT`, or `COMBINED` |
| `SpoilingRFPhaseIncrement` | REC | number | Phase increment in degrees |
## Functional MRI (func/)
### BOLD
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `RepetitionTime` | R | number | TR in seconds (volume acquisition time) |
| `TaskName` | R | string | Name of the task (must match `task-<label>`) |
| `SliceTiming` | REC | array | Time each slice was acquired, in seconds |
| `EchoTime` | REC | number | TE in seconds |
| `FlipAngle` | REC | number | Flip angle in degrees |
| `PhaseEncodingDirection` | REC | string | `i`, `i-`, `j`, `j-`, `k`, `k-` |
| `EffectiveEchoSpacing` | REC | number | Effective echo spacing in seconds |
| `TotalReadoutTime` | REC | number | Total readout time in seconds |
| `MultibandAccelerationFactor` | REC | number | Multiband/SMS factor |
| `NumberOfVolumesDiscardedByScanner` | REC | integer | Dummy scans removed |
| `NumberOfVolumesDiscardedByUser` | REC | integer | Volumes removed post-hoc |
| `TaskDescription` | REC | string | Longer description of the task |
| `CogAtlasID` | REC | string | Cognitive Atlas ID for the task |
| `CogPOID` | REC | string | Cognitive Paradigm Ontology ID |
| `Instructions` | REC | string | Instructions given to participants |
### Multi-echo BOLD
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `EchoTime` | R | number | TE for this echo (each echo in separate file) |
| `EchoTime1`, `EchoTime2` | - | - | NOT used; use `echo-<index>` entity |
### BOLD Timing Details
**SliceTiming** - Array of times (in seconds) at which each slice was acquired relative to the start of volume acquisition. Length must equal the number of slices.
Example for ascending sequential (3 slices, TR=2s):
```json
{"SliceTiming": [0.0, 0.667, 1.333]}
```
Example for interleaved (odd-first, 6 slices, TR=2s):
```json
{"SliceTiming": [0.0, 0.667, 1.333, 0.333, 1.0, 1.667]}
```
**PhaseEncodingDirection** values:
- `i` / `i-` : along first image axis (typically left-right)
- `j` / `j-` : along second image axis (typically anterior-posterior)
- `k` / `k-` : along third image axis (typically inferior-superior)
- The `-` suffix indicates the negative direction along that axis
## Diffusion-Weighted Imaging (dwi/)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `PhaseEncodingDirection` | R | string | Phase encoding direction |
| `TotalReadoutTime` | R | number | Total readout time in seconds |
| `EchoTime` | REC | number | TE in seconds |
| `RepetitionTime` | REC | number | TR in seconds |
| `FlipAngle` | REC | number | Flip angle in degrees |
| `EffectiveEchoSpacing` | REC | number | Effective echo spacing in seconds |
| `MultibandAccelerationFactor` | REC | number | SMS/multiband factor |
| `SliceTiming` | REC | array | Slice timing |
### DWI Gradient Files
`.bvec` file (3 rows x N columns, N = number of volumes):
```
0 0.707 -0.707 0 0.577
0 0.707 0.707 0 0.577
0 0 0 1 0.577
```
`.bval` file (1 row x N columns):
```
0 1000 1000 1000 2000
```
- b=0 volumes have zero-vectors in `.bvec`
- Gradient directions are in the image coordinate system
- Values are space-separated (not tab-separated)
- Number of columns must match number of volumes in the NIfTI
## Fieldmaps (fmap/)
### Case 1: Phase-difference map (`_phasediff`)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `EchoTime1` | R | number | TE of the first echo (shorter) |
| `EchoTime2` | R | number | TE of the second echo (longer) |
| `IntendedFor` | R | string/array | BIDS URI(s) of files to correct |
| `B0FieldIdentifier` | REC | string | Identifier for this B0 field |
### Case 2: Two phase maps (`_phase1`, `_phase2`)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `EchoTime` | R | number | TE for this phase image |
| `IntendedFor` | R | string/array | Files to correct |
### Case 3: Direct fieldmap (`_fieldmap`)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `Units` | R | string | Must be `Hz` or `rad/s` |
| `IntendedFor` | R | string/array | Files to correct |
### Case 4: "Pepolar" fieldmaps (`_epi`)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `PhaseEncodingDirection` | R | string | PE direction for this image |
| `TotalReadoutTime` | R | number | Total readout time |
| `IntendedFor` | R | string/array | Files to correct |
| `B0FieldIdentifier` | REC | string | Identifier for this B0 field |
| `B0FieldSource` | REC | string | Which B0 field to use |
### IntendedFor Syntax
**BIDS URI format** (recommended, v1.7+):
```json
{
"IntendedFor": [
"bids::sub-01/func/sub-01_task-rest_bold.nii.gz",
"bids::sub-01/dwi/sub-01_dwi.nii.gz"
]
}
```
**Relative path format** (legacy):
```json
{
"IntendedFor": [
"func/sub-01_task-rest_bold.nii.gz",
"dwi/sub-01_dwi.nii.gz"
]
}
```
**B0FieldIdentifier/B0FieldSource** (preferred in v1.9+):
```json
// In the fieldmap sidecar
{"B0FieldIdentifier": "pepolar_fmap0"}
// In the BOLD sidecar
{"B0FieldSource": "pepolar_fmap0"}
```
## Perfusion Imaging (perf/)
### ASL
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `ArterialSpinLabelingType` | R | string | `CASL`, `PCASL`, or `PASL` |
| `PostLabelingDelay` | R | number/array | PLD in seconds |
| `BackgroundSuppression` | R | boolean | Whether applied |
| `MagneticFieldStrength` | R | number | In Tesla |
| `M0Type` | R | string | `Separate`, `Included`, `Estimate`, `Absent` |
| `RepetitionTimePreparation` | R | number | Time between ASL pulses |
| `LabelingDuration` | R | number | Duration of labeling pulse |
| `BackgroundSuppressionNumberPulses` | REC | integer | Number of suppression pulses |
| `BackgroundSuppressionPulseTime` | REC | array | Timing of suppression pulses |
| `VascularCrushing` | REC | boolean | Whether applied |
| `LabelingOrientation` | REC | string | Orientation of labeling plane |
| `LabelingDistance` | REC | number | Distance from isocenter (mm) |
| `BolusCutOffFlag` | R (PASL) | boolean | Whether QUIPSS applied |
| `BolusCutOffTimingSequence` | R (PASL) | string | QUIPSS sequence type |
| `BolusCutOffDelayTime` | R (PASL) | number | QUIPSS delay time |
### aslcontext.tsv
Required file listing the order of volumes (label/control/m0scan):
```
volume_type
control
label
control
label
m0scan
```
## EEG (eeg/)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `TaskName` | R | string | Name of the task |
| `SamplingFrequency` | R | number | In Hz |
| `EEGReference` | R | string | Reference electrode(s) |
| `PowerLineFrequency` | R | number | 50 or 60 Hz (or `n/a`) |
| `SoftwareFilters` | R | object | Online filters applied |
| `EEGPlacementScheme` | REC | string | e.g., `10-20`, `10-10` |
| `CapManufacturer` | REC | string | Cap manufacturer |
| `CapManufacturersModelName` | REC | string | Cap model |
| `EEGChannelCount` | REC | integer | Number of EEG channels |
| `EOGChannelCount` | REC | integer | Number of EOG channels |
| `ECGChannelCount` | REC | integer | Number of ECG channels |
| `EMGChannelCount` | REC | integer | Number of EMG channels |
| `MiscChannelCount` | REC | integer | Number of misc channels |
| `TriggerChannelCount` | REC | integer | Number of trigger channels |
| `RecordingDuration` | REC | number | In seconds |
| `RecordingType` | REC | string | `continuous`, `epoched`, `discontinuous` |
### channels.tsv (EEG)
| Column | Status | Description |
|--------|--------|-------------|
| `name` | R | Channel name |
| `type` | R | `EEG`, `EOG`, `ECG`, `EMG`, `MISC`, `TRIG`, etc. |
| `units` | R | `V`, `mV`, `uV` |
| `sampling_frequency` | OPT | Per-channel if different |
| `low_cutoff` | REC | High-pass filter frequency (Hz) |
| `high_cutoff` | REC | Low-pass filter frequency (Hz) |
| `notch` | REC | Notch filter frequency (Hz) |
| `reference` | REC | Reference electrode name |
| `status` | REC | `good` or `bad` |
| `status_description` | OPT | Reason for bad status |
### electrodes.tsv (EEG)
| Column | Status | Description |
|--------|--------|-------------|
| `name` | R | Electrode name |
| `x` | R | X coordinate |
| `y` | R | Y coordinate |
| `z` | R | Z coordinate |
| `type` | OPT | Electrode type |
| `material` | OPT | Electrode material |
| `impedance` | OPT | Impedance in kOhm |
## MEG (meg/)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `TaskName` | R | string | Name of the task |
| `SamplingFrequency` | R | number | In Hz |
| `PowerLineFrequency` | R | number | 50 or 60 Hz |
| `DewarPosition` | R | string | Position of the dewar |
| `SoftwareFilters` | R | object | Online filters |
| `DigitizedLandmarks` | R | boolean | Fiducials digitized |
| `DigitizedHeadPoints` | R | boolean | Head shape digitized |
| `MEGChannelCount` | REC | integer | Number of MEG channels |
| `MEGREFChannelCount` | REC | integer | Reference channels |
| `ContinuousHeadLocalization` | REC | boolean | HPI on |
| `HeadCoilFrequency` | REC | array | HPI coil frequencies |
| `InstitutionName` | REC | string | Institution name |
## PET (pet/)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `TracerName` | R | string | Name of the radiotracer |
| `TracerRadionuclide` | R | string | e.g., `C11`, `F18`, `O15` |
| `InjectedRadioactivity` | R | number | In MBq |
| `InjectedRadioactivityUnits` | R | string | Must be `MBq` |
| `InjectedMass` | R | number | Mass of tracer injected |
| `InjectedMassUnits` | R | string | e.g., `ug` |
| `ModeOfAdministration` | R | string | `bolus`, `infusion`, `bolus-infusion` |
| `TimeZero` | R | string | Time of injection (HH:MM:SS) |
| `ScanStart` | R | number | Start time relative to TimeZero |
| `InjectionStart` | R | number | Injection time relative to TimeZero |
| `FrameTimesStart` | R | array | Frame start times in seconds |
| `FrameDuration` | R | array | Frame durations in seconds |
| `Units` | R | string | Unit of voxel values (e.g., `Bq/mL`) |
| `TracerRadLex` | REC | string | RadLex ID for tracer |
| `BodyWeight` | REC | number | In kg |
| `BodyPart` | REC | string | Imaged body part |
| `AttenuationCorrection` | REC | string | Method description |
| `ReconMethodName` | REC | string | Reconstruction method |
| `ReconMethodParameterLabels` | REC | array | Parameter names |
| `ReconMethodParameterValues` | REC | array | Parameter values |
| `ReconFilterType` | REC | string | Post-recon filter type |
| `ReconFilterSize` | REC | number | Filter FWHM in mm |
## Microscopy (micr/)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `Manufacturer` | R | string | Microscope manufacturer |
| `ManufacturersModelName` | R | string | Microscope model |
| `PixelSize` | R | array | [X, Y] or [X, Y, Z] in micrometers |
| `PixelSizeUnits` | R | string | `um` (micrometers) |
| `Magnification` | REC | number | Objective magnification |
| `SampleEnvironment` | R | string | `in vivo`, `ex vivo`, `in vitro` |
| `SampleFixation` | REC | string | Fixation method |
| `SampleStaining` | REC | string | Staining protocol |
| `SliceThickness` | REC | number | In micrometers |
| `TissueDeformationScaling` | REC | number | Scaling factor |
## NIRS (nirs/)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `TaskName` | R | string | Name of the task |
| `SamplingFrequency` | R | number | In Hz |
| `NIRSSourceOptodeCount` | R | integer | Number of sources |
| `NIRSDetectorOptodeCount` | R | integer | Number of detectors |
| `ACCELChannelCount` | REC | integer | Accelerometer channels |
| `NIRSPlacementScheme` | REC | string | e.g., `10-20` |
## Motion (motion/)
| Field | Status | Type | Description |
|-------|--------|------|-------------|
| `TaskName` | R | string | Name of the task |
| `SamplingFrequency` | R | number | In Hz |
| `TrackingSystemName` | R | string | Name of tracking system |
| `ACCELChannelCount` | REC | integer | Accelerometer channels |
| `GYROChannelCount` | REC | integer | Gyroscope channels |
| `MAGNChannelCount` | REC | integer | Magnetometer channels |
| `RotationOrder` | REC | string | e.g., `XYZ` |
| `RotationRule` | REC | string | `left-hand` or `right-hand` |
| `SpatialAxes` | REC | string | e.g., `ALS` |
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Update BIDS schema JSON and BEPs list from upstream sources.
Downloads:
- bids_schema.json from bids-specification ReadTheDocs (stable release)
- beps.yml from bids-standard/bids-website (current BEP listing)
Usage:
python scripts/update_schema.py
# Fetch schema for a specific spec version or PR preview:
python scripts/update_schema.py --schema-url https://bids-specification.readthedocs.io/en/v1.11.0/schema.json
# Fetch a BEP-specific schema from bids-standard/bids-schema:
python scripts/update_schema.py --schema-url https://raw.githubusercontent.com/bids-standard/bids-schema/main/BEPs/BEP032/schema.json
No external dependencies beyond the Python standard library.
"""
import argparse
import json
import urllib.request
from pathlib import Path
REFERENCES_DIR = Path(__file__).resolve().parent.parent / "references"
SCHEMA_URL = "https://bids-specification.readthedocs.io/en/stable/schema.json"
BEPS_URL = "https://raw.githubusercontent.com/bids-standard/bids-website/main/data/beps/beps.yml"
def fetch(url):
"""Fetch URL content as bytes."""
print(f"Fetching {url} ...")
req = urllib.request.Request(url, headers={"User-Agent": "bids-skill-updater/1.0"})
with urllib.request.urlopen(req) as resp:
return resp.read()
def update_schema(url):
"""Download schema.json and report version info."""
data = fetch(url)
output = REFERENCES_DIR / "bids_schema.json"
# Validate it's proper JSON and extract version
d = json.loads(data)
# Re-serialize with consistent formatting
with open(output, "w") as f:
json.dump(d, f, indent=2)
f.write("\n")
sv = d.get("schema_version", "?")
bv = d.get("bids_version", "?")
print(f" -> {output.name}: schema {sv} / BIDS {bv}")
def update_beps():
"""Download beps.yml."""
data = fetch(BEPS_URL)
output = REFERENCES_DIR / "beps.yml"
output.write_bytes(data)
# Count entries
count = data.count(b"\n- number:")
print(f" -> {output.name}: {count} BEPs")
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
"--schema-url",
default=SCHEMA_URL,
help=f"URL for schema.json (default: {SCHEMA_URL})",
)
parser.add_argument(
"--skip-beps",
action="store_true",
help="Skip fetching beps.yml",
)
args = parser.parse_args()
update_schema(args.schema_url)
if not args.skip_beps:
update_beps()
print("Done.")
if __name__ == "__main__":
main()
+463
View File
@@ -0,0 +1,463 @@
---
name: biopython
description: Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
allowed-tools: Read Write Edit Bash
compatibility: Requires Python 3.10+, NumPy, and Biopython. Entrez and web BLAST examples require network access; local BLAST/MUSCLE examples require those command-line tools installed separately.
license: Biopython License Agreement
required_environment_variables: [{"name": "NCBI_EMAIL", "prompt": "Email for NCBI Entrez identification (required by NCBI policy for Entrez calls).", "required_for": "optional features"}, {"name": "NCBI_API_KEY", "prompt": "NCBI API key to raise Entrez rate limits.", "required_for": "optional features"}]
metadata: {"version": "1.2", "skill-author": "K-Dense Inc.", "openclaw": {"envVars": [{"name": "NCBI_EMAIL", "required": false, "description": "Email for NCBI Entrez identification (required by NCBI policy for Entrez calls)."}, {"name": "NCBI_API_KEY", "required": false, "description": "NCBI API key to raise Entrez rate limits."}]}}
---
# Biopython: Computational Molecular Biology in Python
## Overview
Biopython is a comprehensive set of freely available Python tools for biological computation. It provides functionality for sequence manipulation, file I/O, database access, structural bioinformatics, phylogenetics, and many other bioinformatics tasks. The current version is **Biopython 1.87** (released 30 March 2026). It supports **Python 3.10-3.14** and PyPy3.10, and requires NumPy. Biopython 1.87 also addresses **CVE-2025-68463** in `Bio.Entrez.Parser` when parsing untrusted files, so prefer 1.87+ for workflows that parse externally supplied Entrez XML.
## When to Use This Skill
Use this skill when:
- Working with biological sequences (DNA, RNA, or protein)
- Reading, writing, or converting biological file formats (FASTA, GenBank, FASTQ, PDB, mmCIF, etc.)
- Accessing NCBI databases (GenBank, PubMed, Protein, Gene, etc.) via Entrez
- Running BLAST searches or parsing BLAST results
- Performing sequence alignments (pairwise or multiple sequence alignments)
- Analyzing protein structures from PDB files
- Creating, manipulating, or visualizing phylogenetic trees
- Finding sequence motifs or analyzing motif patterns
- Calculating sequence statistics (GC content, molecular weight, melting temperature, etc.)
- Performing structural bioinformatics tasks
- Working with population genetics data
- Any other computational molecular biology task
## Core Capabilities
Biopython is organized into modular sub-packages, each addressing specific bioinformatics domains:
1. **Sequence Handling** - Bio.Seq and Bio.SeqIO for sequence manipulation and file I/O
2. **Alignment Analysis** - Bio.Align and Bio.AlignIO for pairwise and multiple sequence alignments
3. **Database Access** - Bio.Entrez for programmatic access to NCBI databases
4. **BLAST Operations** - Bio.Blast for running and parsing BLAST searches
5. **Structural Bioinformatics** - Bio.PDB for working with 3D protein structures
6. **Phylogenetics** - Bio.Phylo for phylogenetic tree manipulation and visualization
7. **Advanced Features** - Motifs, population genetics, sequence utilities, and more
## Installation and Setup
Install the current stable Biopython release with an explicit version pin for reproducibility:
```bash
uv pip install "biopython==1.87"
```
For NCBI database access, always set your email address (required by NCBI). For reusable software, set a stable `Entrez.tool` value and register the tool/email with NCBI. For higher rate limits (10 req/s instead of 3 req/s), read only `NCBI_API_KEY` from the environment — do not hardcode keys or load unrelated environment variables:
```python
import os
from Bio import Entrez
Entrez.email = "your.email@example.com" # required — use your real email
Entrez.tool = "your_tool_name" # optional but recommended for reusable software
# Optional: register at https://www.ncbi.nlm.nih.gov/account/settings/
if api_key := os.environ.get("NCBI_API_KEY"):
Entrez.api_key = api_key
```
## Using This Skill
This skill provides comprehensive documentation organized by functionality area. When working on a task, consult the relevant reference documentation:
### 1. Sequence Handling (Bio.Seq & Bio.SeqIO)
**Reference:** `references/sequence_io.md`
Use for:
- Creating and manipulating biological sequences
- Reading and writing sequence files (FASTA, GenBank, FASTQ, etc.)
- Converting between file formats
- Extracting sequences from large files
- Sequence translation, transcription, and reverse complement
- Working with SeqRecord objects
**Quick example:**
```python
from Bio import SeqIO
# Read sequences from FASTA file
for record in SeqIO.parse("sequences.fasta", "fasta"):
print(f"{record.id}: {len(record.seq)} bp")
# Convert GenBank to FASTA
SeqIO.convert("input.gb", "genbank", "output.fasta", "fasta")
```
### 2. Alignment Analysis (Bio.Align & Bio.AlignIO)
**Reference:** `references/alignment.md`
Use for:
- Pairwise sequence alignment (global and local)
- Reading and writing multiple sequence alignments
- Using substitution matrices (BLOSUM, PAM)
- Calculating alignment statistics
- Customizing alignment parameters
**Quick example:**
```python
from Bio import Align
# Pairwise alignment
aligner = Align.PairwiseAligner()
aligner.mode = 'global'
alignments = aligner.align("ACCGGT", "ACGGT")
print(alignments[0])
```
### 3. Database Access (Bio.Entrez)
**Reference:** `references/databases.md`
Use for:
- Searching NCBI databases (PubMed, GenBank, Protein, Gene, etc.)
- Downloading sequences and records
- Fetching publication information
- Finding related records across databases
- Batch downloading with proper rate limiting
**Quick example:**
```python
from Bio import Entrez
Entrez.email = "your.email@example.com"
# Search PubMed
handle = Entrez.esearch(db="pubmed", term="biopython", retmax=10)
results = Entrez.read(handle)
handle.close()
print(f"Found {results['Count']} results")
```
### 4. BLAST Operations (Bio.Blast)
**Reference:** `references/blast.md`
Use for:
- Running BLAST searches via NCBI web services
- Running local BLAST searches
- Parsing BLAST XML output
- Filtering results by E-value or identity
- Extracting hit sequences
**Quick example:**
```python
from Bio.Blast import NCBIWWW, NCBIXML
# Run BLAST search
result_handle = NCBIWWW.qblast("blastn", "nt", "ATCGATCGATCG")
blast_record = NCBIXML.read(result_handle)
# Display top hits
for alignment in blast_record.alignments[:5]:
print(f"{alignment.title}: E-value={alignment.hsps[0].expect}")
```
### 5. Structural Bioinformatics (Bio.PDB)
**Reference:** `references/structure.md`
Use for:
- Parsing PDB and mmCIF structure files
- Navigating protein structure hierarchy (SMCRA: Structure/Model/Chain/Residue/Atom)
- Calculating distances, angles, and dihedrals
- Secondary structure assignment (DSSP)
- Structure superimposition and RMSD calculation
- Extracting sequences from structures
**Quick example:**
```python
from Bio.PDB import PDBParser
# Parse structure
parser = PDBParser(QUIET=True)
structure = parser.get_structure("1crn", "1crn.pdb")
# Calculate distance between alpha carbons
chain = structure[0]["A"]
distance = chain[10]["CA"] - chain[20]["CA"]
print(f"Distance: {distance:.2f} Å")
```
### 6. Phylogenetics (Bio.Phylo)
**Reference:** `references/phylogenetics.md`
Use for:
- Reading and writing phylogenetic trees (Newick, NEXUS, phyloXML)
- Building trees from distance matrices or alignments
- Tree manipulation (pruning, rerooting, ladderizing)
- Calculating phylogenetic distances
- Creating consensus trees
- Visualizing trees
**Quick example:**
```python
from Bio import Phylo
# Read and visualize tree
tree = Phylo.read("tree.nwk", "newick")
Phylo.draw_ascii(tree)
# Calculate distance
distance = tree.distance("Species_A", "Species_B")
print(f"Distance: {distance:.3f}")
```
### 7. Advanced Features
**Reference:** `references/advanced.md`
Use for:
- **Sequence motifs** (Bio.motifs) - Finding and analyzing motif patterns
- **Population genetics** (Bio.PopGen) - GenePop files, Fst calculations, Hardy-Weinberg tests
- **Sequence utilities** (Bio.SeqUtils) - GC content, melting temperature, molecular weight, protein analysis
- **Restriction analysis** (Bio.Restriction) - Finding restriction enzyme sites
- **Clustering** (Bio.Cluster) - K-means and hierarchical clustering
- **Genome diagrams** (GenomeDiagram) - Visualizing genomic features
**Quick example:**
```python
from Bio.SeqUtils import gc_fraction, molecular_weight
from Bio.Seq import Seq
seq = Seq("ATCGATCGATCG")
print(f"GC content: {gc_fraction(seq):.2%}")
print(f"Molecular weight: {molecular_weight(seq, seq_type='DNA'):.2f} g/mol")
```
## General Workflow Guidelines
### Reading Documentation
When a user asks about a specific Biopython task:
1. **Identify the relevant module** based on the task description
2. **Read the appropriate reference file** using the Read tool
3. **Extract relevant code patterns** and adapt them to the user's specific needs
4. **Combine multiple modules** when the task requires it
Example search patterns for reference files:
```bash
# Find information about specific functions
rg -n "SeqIO.parse" references/sequence_io.md
# Find examples of specific tasks
rg -n "BLAST" references/blast.md
# Find information about specific concepts
rg -n "alignment" references/alignment.md
```
### Writing Biopython Code
Follow these principles when writing Biopython code:
1. **Import modules explicitly**
```python
from Bio import SeqIO, Entrez
from Bio.Seq import Seq
```
2. **Set Entrez email** when using NCBI databases; load only `NCBI_API_KEY` from the environment if present
```python
import os
from Bio import Entrez
Entrez.email = "your.email@example.com"
Entrez.tool = "your_tool_name"
if api_key := os.environ.get("NCBI_API_KEY"):
Entrez.api_key = api_key
```
3. **Use appropriate file formats** - Check which format best suits the task
```python
# Common formats: "fasta", "genbank", "fastq", "clustal", "phylip"
```
4. **Handle files properly** - Close handles after use or use context managers
```python
with open("file.fasta") as handle:
records = SeqIO.parse(handle, "fasta")
```
5. **Use iterators for large files** - Avoid loading everything into memory
```python
for record in SeqIO.parse("large_file.fasta", "fasta"):
# Process one record at a time
```
6. **Handle errors gracefully** - Network operations and file parsing can fail
```python
from urllib.error import HTTPError
try:
handle = Entrez.efetch(db="nucleotide", id=accession)
except HTTPError as e:
print(f"Error: {e}")
```
## Common Patterns
### Pattern 1: Fetch Sequence from GenBank
```python
from Bio import Entrez, SeqIO
Entrez.email = "your.email@example.com"
# Fetch sequence
handle = Entrez.efetch(db="nucleotide", id="EU490707", rettype="gb", retmode="text")
record = SeqIO.read(handle, "genbank")
handle.close()
print(f"Description: {record.description}")
print(f"Sequence length: {len(record.seq)}")
```
### Pattern 2: Sequence Analysis Pipeline
```python
from Bio import SeqIO
from Bio.SeqUtils import gc_fraction
for record in SeqIO.parse("sequences.fasta", "fasta"):
# Calculate statistics
gc = gc_fraction(record.seq)
length = len(record.seq)
# Find ORFs, translate, etc.
protein = record.seq.translate()
print(f"{record.id}: {length} bp, GC={gc:.2%}")
```
### Pattern 3: BLAST and Fetch Top Hits
```python
from Bio.Blast import NCBIWWW, NCBIXML
from Bio import Entrez, SeqIO
Entrez.email = "your.email@example.com"
# Run BLAST
result_handle = NCBIWWW.qblast("blastn", "nt", sequence)
blast_record = NCBIXML.read(result_handle)
# Get top hit accessions
accessions = [aln.accession for aln in blast_record.alignments[:5]]
# Fetch sequences
for acc in accessions:
handle = Entrez.efetch(db="nucleotide", id=acc, rettype="fasta", retmode="text")
record = SeqIO.read(handle, "fasta")
handle.close()
print(f">{record.description}")
```
### Pattern 4: Build Phylogenetic Tree from Sequences
```python
from Bio import AlignIO, Phylo
from Bio.Phylo.TreeConstruction import DistanceCalculator, DistanceTreeConstructor
# Read alignment
alignment = AlignIO.read("alignment.fasta", "fasta")
# Calculate distances
calculator = DistanceCalculator("identity")
dm = calculator.get_distance(alignment)
# Build tree
constructor = DistanceTreeConstructor()
tree = constructor.nj(dm)
# Visualize
Phylo.draw_ascii(tree)
```
## Best Practices
1. **Always read relevant reference documentation** before writing code
2. **Use grep to search reference files** for specific functions or examples
3. **Validate file formats** before parsing
4. **Handle missing data gracefully** - Not all records have all fields
5. **Cache downloaded data** - Don't repeatedly download the same sequences
6. **Respect NCBI rate limits** - Use API keys, registered tool/email values for reusable software, and Entrez history/batching for large jobs
7. **Test with small datasets** before processing large files
8. **Keep Biopython updated** to get latest features and bug fixes
9. **Use appropriate genetic code tables** for translation
10. **Document analysis parameters** for reproducibility
## Troubleshooting Common Issues
### Issue: "No handlers could be found for logger 'Bio.Entrez'"
**Solution:** This is just a warning. Set Entrez.email to suppress it.
### Issue: "HTTP Error 400" from NCBI
**Solution:** Check that IDs/accessions are valid and properly formatted.
### Issue: "ValueError: EOF" when parsing files
**Solution:** Verify file format matches the specified format string.
### Issue: Alignment fails with "sequences are not the same length"
**Solution:** Ensure sequences are aligned before using AlignIO or MultipleSeqAlignment.
### Issue: BLAST searches are slow
**Solution:** Use local BLAST for large-scale searches, or cache results.
### Issue: PDB parser warnings
**Solution:** Use `PDBParser(QUIET=True)` to suppress warnings, or investigate structure quality.
### Issue: ImportError for Bio.HMM, Bio.MarkovModel, or Bio.Application
**Solution:** These modules were removed in Biopython 1.86. Use [hmmlearn](https://pypi.org/project/hmmlearn/) for HMMs and the standard library `subprocess` module instead of `Bio.Application` CLI wrappers.
### Issue: PairwiseAligner returns fewer alignments after upgrading to 1.86+
**Solution:** The default gap score changed from 0 to -1 in 1.86, eliminating trivial tie alignments. Set `aligner.gap_score = 0` to restore the old behavior if needed (see `references/alignment.md`).
## Additional Resources
- **Official Documentation**: https://biopython.org/docs/latest/
- **Tutorial**: https://biopython.org/docs/latest/Tutorial/
- **Cookbook**: https://biopython.org/docs/latest/Tutorial/ (advanced examples)
- **GitHub**: https://github.com/biopython/biopython
- **Release notes**: https://github.com/biopython/biopython/blob/master/NEWS.rst
- **Deprecated APIs**: https://github.com/biopython/biopython/blob/master/DEPRECATED.rst
- **Mailing List**: biopython@biopython.org
## Quick Reference
To locate information in reference files, use these search patterns:
```bash
# Search for specific functions
rg -n "function_name" references/*.md
# Find examples of specific tasks
rg -n "example" references/sequence_io.md
# Find all occurrences of a module
rg -n "Bio.Seq" references/*.md
```
## Summary
Biopython provides comprehensive tools for computational molecular biology. When using this skill:
1. **Identify the task domain** (sequences, alignments, databases, BLAST, structures, phylogenetics, or advanced)
2. **Consult the appropriate reference file** in the `references/` directory
3. **Adapt code examples** to the specific use case
4. **Combine multiple modules** when needed for complex workflows
5. **Follow best practices** for file handling, error checking, and data management
The modular reference documentation ensures detailed, searchable information for every major Biopython capability.
+580
View File
@@ -0,0 +1,580 @@
# Advanced Biopython Features
## Sequence Motifs with Bio.motifs
### Creating Motifs
```python
from Bio import motifs
from Bio.Seq import Seq
# Create motif from instances
instances = [
Seq("TACAA"),
Seq("TACGC"),
Seq("TACAC"),
Seq("TACCC"),
Seq("AACCC"),
Seq("AATGC"),
Seq("AATGC"),
]
motif = motifs.create(instances)
```
### Motif Consensus and Degenerate Sequences
```python
# Get consensus sequence
print(motif.counts.consensus)
# Get degenerate consensus (IUPAC ambiguity codes)
print(motif.counts.degenerate_consensus)
# Access counts matrix
print(motif.counts)
```
### Position Weight Matrix (PWM)
```python
# Create position weight matrix
pwm = motif.counts.normalize(pseudocounts=0.5)
print(pwm)
# Calculate information content
ic = sum(motif.relative_entropy)
print(f"Information content: {ic:.2f} bits")
```
### Searching for Motifs
```python
from Bio.Seq import Seq
# Search sequence for motif
test_seq = Seq("ATACAGGACAGACATACGCATACAACATTACAC")
# Get Position Specific Scoring Matrix (PSSM)
pssm = pwm.log_odds()
# Search sequence
for position, score in pssm.search(test_seq, threshold=5.0):
print(f"Position {position}: score = {score:.2f}")
```
### Reading Motifs from Files
```python
# Read motif from JASPAR format
with open("motif.jaspar") as handle:
motif = motifs.read(handle, "jaspar")
# Read multiple motifs
with open("motifs.jaspar") as handle:
for m in motifs.parse(handle, "jaspar"):
print(m.name)
# Supported formats: jaspar, meme, transfac, pfm
```
### Writing Motifs
```python
# Write motif in JASPAR format
with open("output.jaspar", "w") as handle:
handle.write(motif.format("jaspar"))
```
## Population Genetics with Bio.PopGen
### Working with GenePop Files
```python
from Bio.PopGen import GenePop
# Read GenePop file
with open("data.gen") as handle:
record = GenePop.read(handle)
# Access populations
print(f"Number of populations: {len(record.populations)}")
print(f"Loci: {record.loci_list}")
# Iterate through populations
for pop_idx, pop in enumerate(record.populations):
print(f"\nPopulation {pop_idx + 1}:")
for individual in pop:
print(f" {individual[0]}: {individual[1]}")
```
### Calculating Population Statistics
```python
from Bio.PopGen.GenePop.Controller import GenePopController
# Create controller
ctrl = GenePopController()
# Calculate basic statistics
result = ctrl.calc_allele_genotype_freqs("data.gen")
# Calculate Fst
fst_result = ctrl.calc_fst_all("data.gen")
print(f"Fst: {fst_result}")
# Test Hardy-Weinberg equilibrium
hw_result = ctrl.test_hw_pop("data.gen", "probability")
```
## Sequence Utilities with Bio.SeqUtils
### GC Content
```python
from Bio.SeqUtils import gc_fraction
from Bio.Seq import Seq
seq = Seq("ATCGATCGATCG")
gc = gc_fraction(seq)
print(f"GC content: {gc:.2%}")
```
### Molecular Weight
```python
from Bio.SeqUtils import molecular_weight
# DNA molecular weight
dna_seq = Seq("ATCG")
mw = molecular_weight(dna_seq, seq_type="DNA")
print(f"DNA MW: {mw:.2f} g/mol")
# Protein molecular weight
protein_seq = Seq("ACDEFGHIKLMNPQRSTVWY")
mw = molecular_weight(protein_seq, seq_type="protein")
print(f"Protein MW: {mw:.2f} Da")
```
### Melting Temperature
```python
from Bio.SeqUtils import MeltingTemp as mt
# Calculate Tm using nearest-neighbor method
seq = Seq("ATCGATCGATCG")
tm = mt.Tm_NN(seq)
print(f"Tm: {tm:.1f}°C")
# Use different salt concentration
tm = mt.Tm_NN(seq, Na=50, Mg=1.5) # 50 mM Na+, 1.5 mM Mg2+
# Wallace rule (for primers)
tm_wallace = mt.Tm_Wallace(seq)
```
### GC Skew
```python
from Bio.SeqUtils import gc_skew
# Calculate GC skew
seq = Seq("ATCGATCGGGCCCAAATTT")
skew = gc_skew(seq, window=100)
print(f"GC skew: {skew}")
```
### ProtParam - Protein Analysis
```python
from Bio.SeqUtils.ProtParam import ProteinAnalysis
protein_seq = "ACDEFGHIKLMNPQRSTVWY"
analyzed_seq = ProteinAnalysis(protein_seq)
# Molecular weight
print(f"MW: {analyzed_seq.molecular_weight():.2f} Da")
# Isoelectric point
print(f"pI: {analyzed_seq.isoelectric_point():.2f}")
# Amino acid composition
print(f"Composition: {analyzed_seq.get_amino_acids_percent()}")
# Instability index
print(f"Instability: {analyzed_seq.instability_index():.2f}")
# Aromaticity
print(f"Aromaticity: {analyzed_seq.aromaticity():.2f}")
# Secondary structure fraction
ss = analyzed_seq.secondary_structure_fraction()
print(f"Helix: {ss[0]:.2%}, Turn: {ss[1]:.2%}, Sheet: {ss[2]:.2%}")
# Extinction coefficient (assumes Cys reduced, no disulfide bonds)
print(f"Extinction coefficient: {analyzed_seq.molar_extinction_coefficient()}")
# Gravy (grand average of hydropathy)
print(f"GRAVY: {analyzed_seq.gravy():.3f}")
```
## Restriction Analysis with Bio.Restriction
```python
from Bio import Restriction
from Bio.Seq import Seq
# Analyze sequence for restriction sites
seq = Seq("GAATTCATCGATCGATGAATTC")
# Use specific enzyme
ecori = Restriction.EcoRI
sites = ecori.search(seq)
print(f"EcoRI sites at: {sites}")
# Use multiple enzymes
rb = Restriction.RestrictionBatch(["EcoRI", "BamHI", "PstI"])
results = rb.search(seq)
for enzyme, sites in results.items():
if sites:
print(f"{enzyme}: {sites}")
# Get all enzymes that cut sequence
all_enzymes = Restriction.Analysis(rb, seq)
print(f"Cutting enzymes: {all_enzymes.with_sites()}")
```
## Sequence Translation Tables
```python
from Bio.Data import CodonTable
# Standard genetic code
standard_table = CodonTable.unambiguous_dna_by_id[1]
print(standard_table)
# Mitochondrial code
mito_table = CodonTable.unambiguous_dna_by_id[2]
# Get specific codon
print(f"ATG codes for: {standard_table.forward_table['ATG']}")
# Get stop codons
print(f"Stop codons: {standard_table.stop_codons}")
# Get start codons
print(f"Start codons: {standard_table.start_codons}")
```
## Cluster Analysis with Bio.Cluster
```python
from Bio.Cluster import kcluster
import numpy as np
# Sample data matrix (genes x conditions)
data = np.array([
[1.2, 0.8, 0.5, 1.5],
[0.9, 1.1, 0.7, 1.3],
[0.2, 0.3, 2.1, 2.5],
[0.1, 0.4, 2.3, 2.2],
])
# Perform k-means clustering
clusterid, error, nfound = kcluster(data, nclusters=2)
print(f"Cluster assignments: {clusterid}")
print(f"Error: {error}")
```
## Genome Diagrams with GenomeDiagram
```python
from Bio.Graphics import GenomeDiagram
from Bio.SeqFeature import SeqFeature, FeatureLocation
from Bio import SeqIO
from reportlab.lib import colors
# Read GenBank file
record = SeqIO.read("sequence.gb", "genbank")
# Create diagram
gd_diagram = GenomeDiagram.Diagram("Genome Diagram")
gd_track = gd_diagram.new_track(1, greytrack=True)
gd_feature_set = gd_track.new_set()
# Add features
for feature in record.features:
if feature.type == "CDS":
color = colors.blue
elif feature.type == "gene":
color = colors.lightblue
else:
color = colors.grey
gd_feature_set.add_feature(
feature,
color=color,
label=True,
label_size=6,
label_angle=45
)
# Draw and save
gd_diagram.draw(format="linear", pagesize="A4", fragments=1)
gd_diagram.write("genome_diagram.pdf", "PDF")
```
## Sequence Comparison with PairwiseAligner
`Bio.pairwise2` is deprecated (since Biopython 1.80). Use `Bio.Align.PairwiseAligner` for new pairwise alignment code (see `alignment.md`). For HMM workflows, `Bio.HMM` and `Bio.MarkovModel` were removed in Biopython 1.86; use [hmmlearn](https://pypi.org/project/hmmlearn/) instead.
```python
from Bio.Align import PairwiseAligner
aligner = PairwiseAligner()
aligner.mode = "global"
aligner.match_score = 1
aligner.mismatch_score = 0
aligner.gap_score = 0 # Optional: mimic the old globalxx scoring style
alignments = aligner.align("ACCGT", "ACGT")
for alignment in alignments[:3]:
print(alignment)
print(f"Score: {alignment.score}")
```
Avoid importing `Bio.pairwise2` in new code. It remains a legacy migration concern only.
## Working with PubChem
```python
from Bio import Entrez
Entrez.email = "your.email@example.com"
# Search PubChem
handle = Entrez.esearch(db="pccompound", term="aspirin")
result = Entrez.read(handle)
handle.close()
compound_id = result["IdList"][0]
# Get compound information
handle = Entrez.efetch(db="pccompound", id=compound_id, retmode="xml")
compound_data = handle.read()
handle.close()
```
## Sequence Features with Bio.SeqFeature
```python
from Bio.SeqFeature import SeqFeature, FeatureLocation
from Bio.Seq import Seq
from Bio.SeqRecord import SeqRecord
# Create a feature
feature = SeqFeature(
location=FeatureLocation(start=10, end=50),
type="CDS",
strand=1,
qualifiers={"gene": ["ABC1"], "product": ["ABC protein"]}
)
# Add feature to record
record = SeqRecord(Seq("ATCG" * 20), id="seq1")
record.features.append(feature)
# Extract feature sequence
feature_seq = feature.extract(record.seq)
print(feature_seq)
```
## Sequence Ambiguity
```python
from Bio.Data import IUPACData
# DNA ambiguity codes
print(IUPACData.ambiguous_dna_letters)
# Protein ambiguity codes
print(IUPACData.ambiguous_protein_letters)
# Resolve ambiguous bases
print(IUPACData.ambiguous_dna_values["N"]) # Any base
print(IUPACData.ambiguous_dna_values["R"]) # A or G
```
## Quality Scores (FASTQ)
```python
from Bio import SeqIO
# Read FASTQ with quality scores
for record in SeqIO.parse("reads.fastq", "fastq"):
print(f"ID: {record.id}")
print(f"Sequence: {record.seq}")
print(f"Quality: {record.letter_annotations['phred_quality']}")
# Calculate average quality
avg_quality = sum(record.letter_annotations['phred_quality']) / len(record)
print(f"Average quality: {avg_quality:.2f}")
# Filter by quality
min_quality = min(record.letter_annotations['phred_quality'])
if min_quality >= 20:
print("High quality read")
```
## Best Practices
1. **Use appropriate modules** - Choose the right tool for your analysis
2. **Handle pseudocounts** - Important for motif analysis
3. **Validate input data** - Check file formats and data quality
4. **Consider performance** - Some operations can be computationally intensive
5. **Cache results** - Store intermediate results for large analyses
6. **Use proper genetic codes** - Select appropriate translation tables
7. **Document parameters** - Record thresholds and settings used
8. **Validate statistical results** - Understand limitations of tests
9. **Handle edge cases** - Check for empty results or invalid input
10. **Combine modules** - Leverage multiple Biopython tools together
## Common Use Cases
### Find ORFs
```python
from Bio import SeqIO
from Bio.SeqUtils import gc_fraction
def find_orfs(seq, min_length=100):
"""Find all ORFs in sequence."""
orfs = []
for strand, nuc in [(+1, seq), (-1, seq.reverse_complement())]:
for frame in range(3):
trans = nuc[frame:].translate()
trans_len = len(trans)
aa_start = 0
while aa_start < trans_len:
aa_end = trans.find("*", aa_start)
if aa_end == -1:
aa_end = trans_len
if aa_end - aa_start >= min_length // 3:
start = frame + aa_start * 3
end = frame + aa_end * 3
orfs.append({
'start': start,
'end': end,
'strand': strand,
'frame': frame,
'length': end - start,
'sequence': nuc[start:end]
})
aa_start = aa_end + 1
return orfs
# Use it
record = SeqIO.read("sequence.fasta", "fasta")
orfs = find_orfs(record.seq, min_length=300)
for orf in orfs:
print(f"ORF: {orf['start']}-{orf['end']}, strand={orf['strand']}, length={orf['length']}")
```
### Analyze Codon Usage
```python
from Bio import SeqIO
def analyze_codon_usage(fasta_file):
"""Analyze codon usage in coding sequences."""
codon_counts = {}
for record in SeqIO.parse(fasta_file, "fasta"):
# Ensure sequence is multiple of 3
seq = record.seq[:len(record.seq) - len(record.seq) % 3]
# Count codons
for i in range(0, len(seq), 3):
codon = str(seq[i:i+3])
codon_counts[codon] = codon_counts.get(codon, 0) + 1
# Calculate frequencies
total = sum(codon_counts.values())
codon_freq = {k: v/total for k, v in codon_counts.items()}
return codon_freq
```
### Calculate Sequence Complexity
```python
def sequence_complexity(seq, k=2):
"""Calculate k-mer complexity (Shannon entropy)."""
import math
from collections import Counter
# Generate k-mers
kmers = [str(seq[i:i+k]) for i in range(len(seq) - k + 1)]
# Count k-mers
counts = Counter(kmers)
total = len(kmers)
# Calculate entropy
entropy = 0
for count in counts.values():
freq = count / total
entropy -= freq * math.log2(freq)
# Normalize by maximum possible entropy
max_entropy = math.log2(4 ** k) # For DNA
return entropy / max_entropy if max_entropy > 0 else 0
# Use it
from Bio.Seq import Seq
seq = Seq("ATCGATCGATCGATCG")
complexity = sequence_complexity(seq, k=2)
print(f"Sequence complexity: {complexity:.3f}")
```
### Extract Promoter Regions
```python
def extract_promoters(genbank_file, upstream=500):
"""Extract promoter regions upstream of genes."""
from Bio import SeqIO
record = SeqIO.read(genbank_file, "genbank")
promoters = []
for feature in record.features:
if feature.type == "gene":
if feature.strand == 1:
# Forward strand
start = max(0, feature.location.start - upstream)
end = feature.location.start
else:
# Reverse strand
start = feature.location.end
end = min(len(record.seq), feature.location.end + upstream)
promoter_seq = record.seq[start:end]
if feature.strand == -1:
promoter_seq = promoter_seq.reverse_complement()
promoters.append({
'gene': feature.qualifiers.get('gene', ['Unknown'])[0],
'sequence': promoter_seq,
'start': start,
'end': end
})
return promoters
```
+377
View File
@@ -0,0 +1,377 @@
# Sequence Alignments with Bio.Align and Bio.AlignIO
## Overview
Bio.Align provides tools for pairwise sequence alignment using various algorithms, while Bio.AlignIO handles reading and writing multiple sequence alignment files in various formats.
## Pairwise Alignment with Bio.Align
### The PairwiseAligner Class
The `PairwiseAligner` class performs pairwise sequence alignments using Needleman-Wunsch (global), Smith-Waterman (local), Gotoh (three-state), and Waterman-Smith-Beyer algorithms. The appropriate algorithm is automatically selected based on gap score parameters.
### Creating an Aligner
```python
from Bio import Align
# Create aligner with default parameters
aligner = Align.PairwiseAligner()
# Default scores (as of Biopython 1.86+):
# - Match score: +1.0
# - Mismatch score: 0.0
# - All gap scores: -1.0 (changed from 0 in 1.86 to avoid trivial tie alignments)
```
**Note (1.86+):** The default gap score changed from 0 to -1. Previously, mismatches and gap combinations could score 0, producing many logically equivalent alignments. To restore pre-1.86 behavior:
```python
aligner.gap_score = 0
```
### Customizing Alignment Parameters
```python
# Set scoring parameters
aligner.match_score = 2.0
aligner.mismatch_score = -1.0
aligner.gap_score = -0.5
# Or use separate gap opening/extension penalties
aligner.open_gap_score = -2.0
aligner.extend_gap_score = -0.5
# Set internal gap scores separately
aligner.internal_open_gap_score = -2.0
aligner.internal_extend_gap_score = -0.5
# Set end gap scores (for semi-global alignment)
aligner.left_open_gap_score = 0.0
aligner.left_extend_gap_score = 0.0
aligner.right_open_gap_score = 0.0
aligner.right_extend_gap_score = 0.0
```
### Alignment Modes
```python
# Global alignment (default)
aligner.mode = 'global'
# Local alignment
aligner.mode = 'local'
```
### Performing Alignments
```python
from Bio.Seq import Seq
seq1 = Seq("ACCGGT")
seq2 = Seq("ACGGT")
# Get all optimal alignments
alignments = aligner.align(seq1, seq2)
# Iterate through alignments
for alignment in alignments:
print(alignment)
print(f"Score: {alignment.score}")
# Get just the score
score = aligner.score(seq1, seq2)
```
### Using Substitution Matrices
```python
from Bio.Align import substitution_matrices
# Load a substitution matrix
matrix = substitution_matrices.load("BLOSUM62")
aligner.substitution_matrix = matrix
# Align protein sequences
protein1 = Seq("KEVLA")
protein2 = Seq("KSVLA")
alignments = aligner.align(protein1, protein2)
```
### Available Substitution Matrices
Common matrices include:
- **BLOSUM** series (BLOSUM45, BLOSUM50, BLOSUM62, BLOSUM80, BLOSUM90)
- **PAM** series (PAM30, PAM70, PAM250)
- **MATCH** - Simple match/mismatch matrix
```python
# List available matrices
available = substitution_matrices.load()
print(available)
```
## Multiple Sequence Alignments with Bio.AlignIO
### Reading Alignments
Bio.AlignIO provides similar API to Bio.SeqIO but for alignment files:
```python
from Bio import AlignIO
# Read a single alignment
alignment = AlignIO.read("alignment.aln", "clustal")
# Parse multiple alignments from a file
for alignment in AlignIO.parse("alignments.aln", "clustal"):
print(f"Alignment with {len(alignment)} sequences")
print(f"Alignment length: {alignment.get_alignment_length()}")
```
### Supported Alignment Formats
Common formats include:
- **clustal** - Clustal format
- **phylip** - PHYLIP format
- **phylip-relaxed** - Relaxed PHYLIP (longer names)
- **stockholm** - Stockholm format
- **fasta** - FASTA format (aligned)
- **nexus** - NEXUS format
- **emboss** - EMBOSS alignment format
- **msf** - MSF format
- **maf** - Multiple Alignment Format
### Writing Alignments
```python
# Write alignment to file
AlignIO.write(alignment, "output.aln", "clustal")
# Convert between formats
count = AlignIO.convert("input.aln", "clustal", "output.phy", "phylip")
```
### Working with Alignment Objects
```python
from Bio import AlignIO
alignment = AlignIO.read("alignment.aln", "clustal")
# Get alignment properties
print(f"Number of sequences: {len(alignment)}")
print(f"Alignment length: {alignment.get_alignment_length()}")
# Access individual sequences
for record in alignment:
print(f"{record.id}: {record.seq}")
# Get alignment column
column = alignment[:, 0] # First column
# Get alignment slice
sub_alignment = alignment[:, 10:20] # Positions 10-20
# Get specific sequence
seq_record = alignment[0] # First sequence
```
### Alignment Analysis
```python
# Calculate alignment statistics with current Biopython APIs.
# Avoid Bio.AlignInfo.SummaryInfo: it is deprecated in 1.86 and several
# methods were removed in 1.85/1.86.
from Bio import AlignIO
from Bio.motifs import Motif
msa = AlignIO.read("alignment.aln", "clustal")
alignment = msa.alignment # New-style Bio.Align.Alignment
# Build a motif from a DNA alignment to inspect per-column counts
motif = Motif("ACGT", alignment)
counts = motif.counts
consensus = counts.consensus
# Information content replacement for deprecated SummaryInfo methods
information_content = sum(motif.relative_entropy)
# Replacement dictionary from the new-style Alignment object
substitutions = alignment.substitutions
```
## Creating Alignments Programmatically
### From SeqRecord Objects
```python
from Bio.Align import MultipleSeqAlignment
from Bio.SeqRecord import SeqRecord
from Bio.Seq import Seq
# Create records
records = [
SeqRecord(Seq("ACTGCTAGCTAG"), id="seq1"),
SeqRecord(Seq("ACT-CTAGCTAG"), id="seq2"),
SeqRecord(Seq("ACTGCTA-CTAG"), id="seq3"),
]
# Create alignment
alignment = MultipleSeqAlignment(records)
```
### Adding Sequences to Alignments
```python
# Start with empty alignment
alignment = MultipleSeqAlignment([])
# Add sequences (must have same length)
alignment.append(SeqRecord(Seq("ACTG"), id="seq1"))
alignment.append(SeqRecord(Seq("ACTG"), id="seq2"))
# Extend with another alignment
alignment.extend(other_alignment)
```
## Advanced Alignment Operations
### Removing Gaps
```python
# Remove all gap-only columns
no_gaps = []
for i in range(alignment.get_alignment_length()):
column = alignment[:, i]
if set(column) != {'-'}: # Not all gaps
no_gaps.append(column)
```
### Alignment Sorting
```python
# Sort by sequence ID
sorted_alignment = sorted(alignment, key=lambda x: x.id)
alignment = MultipleSeqAlignment(sorted_alignment)
```
### Computing Pairwise Identities
```python
def pairwise_identity(seq1, seq2):
"""Calculate percent identity between two sequences."""
matches = sum(a == b for a, b in zip(seq1, seq2) if a != '-' and b != '-')
length = sum(1 for a, b in zip(seq1, seq2) if a != '-' and b != '-')
return matches / length if length > 0 else 0
# Calculate all pairwise identities
for i, record1 in enumerate(alignment):
for record2 in alignment[i+1:]:
identity = pairwise_identity(record1.seq, record2.seq)
print(f"{record1.id} vs {record2.id}: {identity:.2%}")
```
## Running External Alignment Tools
Biopython 1.86 removed `Bio.Application` and all command-line wrapper modules, including `Bio.Align.Applications`. Use Python's standard `subprocess` module with argument lists. Keep executable names and flags explicit, and do not construct command arguments from unsanitized user input.
### Clustal Omega (via subprocess)
```python
import subprocess
from Bio import AlignIO
cmd = [
"clustalo",
"-i", "sequences.fasta",
"-o", "alignment.aln",
"--outfmt", "clu",
"--force",
"--auto",
]
subprocess.run(cmd, check=True)
# Read result
alignment = AlignIO.read("alignment.aln", "clustal")
```
### MUSCLE (via subprocess)
```python
import subprocess
from Bio import AlignIO
cmd = [
"muscle",
"-align", "sequences.fasta",
"-output", "alignment.fasta",
]
subprocess.run(cmd, check=True)
alignment = AlignIO.read("alignment.fasta", "fasta")
```
## Best Practices
1. **Choose appropriate scoring schemes** - Use BLOSUM62 for proteins, custom scores for DNA
2. **Consider alignment mode** - Global for similar-length sequences, local for finding conserved regions
3. **Set gap penalties carefully** - Higher penalties create fewer, longer gaps
4. **Use appropriate formats** - FASTA for simple alignments, Stockholm for rich annotation
5. **Validate alignment quality** - Check for conserved regions and percent identity
6. **Handle large alignments carefully** - Use slicing and iteration for memory efficiency
7. **Preserve metadata** - Maintain SeqRecord IDs and annotations through alignment operations
## Common Use Cases
### Find Best Local Alignment
```python
from Bio.Align import PairwiseAligner
from Bio.Seq import Seq
aligner = PairwiseAligner()
aligner.mode = 'local'
aligner.match_score = 2
aligner.mismatch_score = -1
seq1 = Seq("AGCTTAGCTAGCTAGC")
seq2 = Seq("CTAGCTAGC")
alignments = aligner.align(seq1, seq2)
print(alignments[0])
```
### Protein Sequence Alignment
```python
from Bio.Align import PairwiseAligner, substitution_matrices
aligner = PairwiseAligner()
aligner.substitution_matrix = substitution_matrices.load("BLOSUM62")
aligner.open_gap_score = -10
aligner.extend_gap_score = -0.5
protein1 = Seq("KEVLA")
protein2 = Seq("KEVLAEQP")
alignments = aligner.align(protein1, protein2)
```
### Extract Conserved Regions
```python
from Bio import AlignIO
alignment = AlignIO.read("alignment.aln", "clustal")
# Find columns with >80% identity
conserved_positions = []
for i in range(alignment.get_alignment_length()):
column = alignment[:, i]
most_common = max(set(column), key=column.count)
if column.count(most_common) / len(column) > 0.8:
conserved_positions.append(i)
print(f"Conserved positions: {conserved_positions}")
```
+463
View File
@@ -0,0 +1,463 @@
# BLAST Operations with Bio.Blast
## Overview
Bio.Blast provides tools for running BLAST searches (both locally and via NCBI web services) and parsing BLAST results in various formats. The module handles the complexity of submitting queries and parsing outputs.
## Running BLAST via NCBI Web Services
### Bio.Blast.NCBIWWW
The `qblast()` function submits sequences to NCBI's online BLAST service:
```python
from Bio.Blast import NCBIWWW
from Bio import SeqIO
# Read sequence from file
record = SeqIO.read("sequence.fasta", "fasta")
# Run BLAST search
result_handle = NCBIWWW.qblast(
program="blastn", # BLAST program
database="nt", # Database to search
sequence=str(record.seq) # Query sequence
)
# Save results
with open("blast_results.xml", "w") as out_file:
out_file.write(result_handle.read())
result_handle.close()
```
### BLAST Programs Available
- **blastn** - Nucleotide vs nucleotide
- **blastp** - Protein vs protein
- **blastx** - Translated nucleotide vs protein
- **tblastn** - Protein vs translated nucleotide
- **tblastx** - Translated nucleotide vs translated nucleotide
### Common Databases
**Nucleotide databases:**
- `nt` - All GenBank+EMBL+DDBJ+PDB sequences
- `refseq_rna` - RefSeq RNA sequences
**Protein databases:**
- `nr` - All non-redundant GenBank CDS translations
- `refseq_protein` - RefSeq protein sequences
- `pdb` - Protein Data Bank sequences
- `swissprot` - Curated UniProtKB/Swiss-Prot
### Advanced qblast Parameters
```python
result_handle = NCBIWWW.qblast(
program="blastn",
database="nt",
sequence=str(record.seq),
expect=0.001, # E-value threshold
hitlist_size=50, # Number of hits to return
alignments=25, # Number of alignments to show
word_size=11, # Word size for initial match
gapcosts="5 2", # Gap costs (open extend)
format_type="XML" # Output format (default)
)
```
### Using Sequence Files or IDs
```python
# Use FASTA format string
fasta_string = open("sequence.fasta").read()
result_handle = NCBIWWW.qblast("blastn", "nt", fasta_string)
# Use GenBank ID
result_handle = NCBIWWW.qblast("blastn", "nt", "EU490707")
# Use GI number
result_handle = NCBIWWW.qblast("blastn", "nt", "160418")
```
## Parsing BLAST Results
### Bio.Blast.NCBIXML
NCBIXML provides parsers for BLAST XML output (the recommended format):
```python
from Bio.Blast import NCBIXML
# Parse single BLAST result
with open("blast_results.xml") as result_handle:
blast_record = NCBIXML.read(result_handle)
```
### Accessing BLAST Record Data
```python
# Query information
print(f"Query: {blast_record.query}")
print(f"Query length: {blast_record.query_length}")
print(f"Database: {blast_record.database}")
print(f"Number of sequences in database: {blast_record.database_sequences}")
# Iterate through alignments (hits)
for alignment in blast_record.alignments:
print(f"\nHit: {alignment.title}")
print(f"Length: {alignment.length}")
print(f"Accession: {alignment.accession}")
# Each alignment can have multiple HSPs (high-scoring pairs)
for hsp in alignment.hsps:
print(f" E-value: {hsp.expect}")
print(f" Score: {hsp.score}")
print(f" Bits: {hsp.bits}")
print(f" Identities: {hsp.identities}/{hsp.align_length}")
print(f" Gaps: {hsp.gaps}")
print(f" Query: {hsp.query}")
print(f" Match: {hsp.match}")
print(f" Subject: {hsp.sbjct}")
```
### Filtering Results
```python
# Only show hits with E-value < 0.001
E_VALUE_THRESH = 0.001
for alignment in blast_record.alignments:
for hsp in alignment.hsps:
if hsp.expect < E_VALUE_THRESH:
print(f"Hit: {alignment.title}")
print(f"E-value: {hsp.expect}")
print(f"Identities: {hsp.identities}/{hsp.align_length}")
print()
```
### Multiple BLAST Results
For files containing multiple BLAST results (e.g., from batch searches):
```python
from Bio.Blast import NCBIXML
with open("batch_blast_results.xml") as result_handle:
blast_records = NCBIXML.parse(result_handle)
for blast_record in blast_records:
print(f"\nQuery: {blast_record.query}")
print(f"Hits: {len(blast_record.alignments)}")
if blast_record.alignments:
# Get best hit
best_alignment = blast_record.alignments[0]
best_hsp = best_alignment.hsps[0]
print(f"Best hit: {best_alignment.title}")
print(f"E-value: {best_hsp.expect}")
```
## Running Local BLAST
### Prerequisites
Local BLAST requires:
1. BLAST+ command-line tools installed
2. BLAST databases downloaded locally
### Running BLAST+ via subprocess
Biopython 1.86 removed `Bio.Application` and the BLAST command-line wrappers in `Bio.Blast.Applications`. Use the standard library `subprocess` module with explicit argument lists. Keep command names and flags fixed, validate file paths before running, and do not interpolate unsanitized user input into shell commands.
```python
import subprocess
from Bio.Blast import NCBIXML
cmd = [
"blastn",
"-query", "input.fasta",
"-db", "local_database",
"-evalue", "0.001",
"-outfmt", "5", # XML format for NCBIXML
"-out", "results.xml",
]
subprocess.run(cmd, check=True)
# Parse results
with open("results.xml") as result_handle:
blast_record = NCBIXML.read(result_handle)
```
### Common BLAST+ commands
- `blastn` - nucleotide vs nucleotide
- `blastp` - protein vs protein
- `blastx` - translated nucleotide vs protein
- `tblastn` - protein vs translated nucleotide
- `tblastx` - translated nucleotide vs translated nucleotide
- `makeblastdb` - create local BLAST databases
### Creating BLAST Databases
```python
import subprocess
cmd = [
"makeblastdb",
"-in", "sequences.fasta",
"-dbtype", "nucl",
"-out", "my_database",
]
subprocess.run(cmd, check=True)
```
## Analyzing BLAST Results
### Extract Best Hits
```python
def get_best_hits(blast_record, num_hits=10, e_value_thresh=0.001):
"""Extract best hits from BLAST record."""
hits = []
for alignment in blast_record.alignments[:num_hits]:
for hsp in alignment.hsps:
if hsp.expect < e_value_thresh:
hits.append({
'title': alignment.title,
'accession': alignment.accession,
'length': alignment.length,
'e_value': hsp.expect,
'score': hsp.score,
'identities': hsp.identities,
'align_length': hsp.align_length,
'query_start': hsp.query_start,
'query_end': hsp.query_end,
'sbjct_start': hsp.sbjct_start,
'sbjct_end': hsp.sbjct_end
})
break # Only take best HSP per alignment
return hits
```
### Calculate Percent Identity
```python
def calculate_percent_identity(hsp):
"""Calculate percent identity for an HSP."""
return (hsp.identities / hsp.align_length) * 100
# Use it
for alignment in blast_record.alignments:
for hsp in alignment.hsps:
if hsp.expect < 0.001:
identity = calculate_percent_identity(hsp)
print(f"{alignment.title}: {identity:.2f}% identity")
```
### Extract Hit Sequences
```python
from Bio import Entrez, SeqIO
Entrez.email = "your.email@example.com"
def fetch_hit_sequences(blast_record, num_sequences=5):
"""Fetch sequences for top BLAST hits."""
sequences = []
for alignment in blast_record.alignments[:num_sequences]:
accession = alignment.accession
# Fetch sequence from GenBank
handle = Entrez.efetch(
db="nucleotide",
id=accession,
rettype="fasta",
retmode="text"
)
record = SeqIO.read(handle, "fasta")
handle.close()
sequences.append(record)
return sequences
```
## Parsing Other BLAST Formats
### Tab-Delimited Output (outfmt 6/7)
```python
import subprocess
cmd = [
"blastn",
"-query", "input.fasta",
"-db", "database",
"-outfmt", "6",
"-out", "results.txt",
]
subprocess.run(cmd, check=True)
# Parse tabular results
with open("results.txt") as f:
for line in f:
fields = line.strip().split('\t')
query_id = fields[0]
subject_id = fields[1]
percent_identity = float(fields[2])
align_length = int(fields[3])
e_value = float(fields[10])
bit_score = float(fields[11])
print(f"{query_id} -> {subject_id}: {percent_identity}% identity, E={e_value}")
```
### Custom Output Formats
```python
import subprocess
# Specify custom columns (outfmt 6 with custom fields)
cmd = [
"blastn",
"-query", "input.fasta",
"-db", "database",
"-outfmt", "6 qseqid sseqid pident length evalue bitscore qseq sseq",
"-out", "results.txt",
]
subprocess.run(cmd, check=True)
```
## Best Practices
1. **Use XML format** for parsing (outfmt 5) - most reliable and complete
2. **Save BLAST results** - Don't re-run searches unnecessarily
3. **Set appropriate E-value thresholds** - Default is 10, but 0.001-0.01 is often better
4. **Handle rate limits** - NCBI limits request frequency
5. **Use local BLAST** for large-scale searches or repeated queries
6. **Cache results** - Save parsed data to avoid re-parsing
7. **Check for empty results** - Handle cases with no hits gracefully
8. **Consider alternatives** - For large datasets, consider DIAMOND or other fast aligners
9. **Batch searches** - Submit multiple sequences together when possible
10. **Filter by identity** - E-value alone may not be sufficient
## Common Use Cases
### Basic BLAST Search and Parse
```python
from Bio.Blast import NCBIWWW, NCBIXML
from Bio import SeqIO
# Read query sequence
record = SeqIO.read("query.fasta", "fasta")
# Run BLAST
print("Running BLAST search...")
result_handle = NCBIWWW.qblast("blastn", "nt", str(record.seq))
# Parse results
blast_record = NCBIXML.read(result_handle)
# Display top 5 hits
print(f"\nTop 5 hits for {blast_record.query}:")
for i, alignment in enumerate(blast_record.alignments[:5], 1):
hsp = alignment.hsps[0]
identity = (hsp.identities / hsp.align_length) * 100
print(f"{i}. {alignment.title}")
print(f" E-value: {hsp.expect}, Identity: {identity:.1f}%")
```
### Find Orthologs
```python
from Bio.Blast import NCBIWWW, NCBIXML
from Bio import Entrez, SeqIO
Entrez.email = "your.email@example.com"
# Query gene sequence
query_record = SeqIO.read("gene.fasta", "fasta")
# BLAST against specific organism
result_handle = NCBIWWW.qblast(
"blastn",
"nt",
str(query_record.seq),
entrez_query="Mus musculus[Organism]" # Restrict to mouse
)
blast_record = NCBIXML.read(result_handle)
# Find best hit
if blast_record.alignments:
best_hit = blast_record.alignments[0]
print(f"Potential ortholog: {best_hit.title}")
print(f"Accession: {best_hit.accession}")
```
### Batch BLAST Multiple Sequences
```python
from Bio.Blast import NCBIWWW, NCBIXML
from Bio import SeqIO
# Read multiple sequences
sequences = list(SeqIO.parse("queries.fasta", "fasta"))
# Create batch results file
with open("batch_results.xml", "w") as out_file:
for seq_record in sequences:
print(f"Searching for {seq_record.id}...")
result_handle = NCBIWWW.qblast("blastn", "nt", str(seq_record.seq))
out_file.write(result_handle.read())
result_handle.close()
# Parse batch results
with open("batch_results.xml") as result_handle:
for blast_record in NCBIXML.parse(result_handle):
print(f"\n{blast_record.query}: {len(blast_record.alignments)} hits")
```
### Reciprocal Best Hits
```python
def reciprocal_best_hit(seq1_id, seq2_id, database="nr", program="blastp"):
"""Check if two sequences are reciprocal best hits."""
from Bio.Blast import NCBIWWW, NCBIXML
from Bio import Entrez
Entrez.email = "your.email@example.com"
# Forward BLAST
result1 = NCBIWWW.qblast(program, database, seq1_id)
record1 = NCBIXML.read(result1)
best_hit1 = record1.alignments[0].accession if record1.alignments else None
# Reverse BLAST
result2 = NCBIWWW.qblast(program, database, seq2_id)
record2 = NCBIXML.read(result2)
best_hit2 = record2.alignments[0].accession if record2.alignments else None
# Check reciprocity
return best_hit1 == seq2_id and best_hit2 == seq1_id
```
## Error Handling
```python
from Bio.Blast import NCBIWWW, NCBIXML
from urllib.error import HTTPError
try:
result_handle = NCBIWWW.qblast("blastn", "nt", "ATCGATCGATCG")
blast_record = NCBIXML.read(result_handle)
result_handle.close()
except HTTPError as e:
print(f"HTTP Error: {e.code}")
except Exception as e:
print(f"Error running BLAST: {e}")
```

Some files were not shown because too many files have changed in this diff Show More